web/user: add language selection

closes #2041

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-01-01 15:03:27 +01:00
parent a6373ebb33
commit 0ef8edc9f1
9 changed files with 487 additions and 307 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,21 @@
import { CoreApi, SessionUser } from "@goauthentik/api"; import { CoreApi, SessionUser } from "@goauthentik/api";
import { i18n } from "@lingui/core";
import { DEFAULT_CONFIG } from "./Config"; import { DEFAULT_CONFIG } from "./Config";
let globalMePromise: Promise<SessionUser>; let globalMePromise: Promise<SessionUser>;
export function me(): Promise<SessionUser> { export function me(): Promise<SessionUser> {
if (!globalMePromise) { if (!globalMePromise) {
globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().catch((ex) => { globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().then((user) => {
if (!user.user.settings || !("locale" in user.user.settings)) {
return user;
}
const locale = user.user.settings.locale;
if (locale && locale !== "") {
console.debug(`authentik/locale: Activating user's configured locale '${locale}'`);
i18n.activate(locale);
}
return user;
}).catch((ex) => {
const defaultUser: SessionUser = { const defaultUser: SessionUser = {
user: { user: {
pk: -1, pk: -1,

View File

@ -1,3 +1,5 @@
import { UserSelf } from "@goauthentik/api";
import { me } from "../api/Users"; import { me } from "../api/Users";
export enum UserDisplay { export enum UserDisplay {
@ -29,6 +31,7 @@ export interface UIConfig {
pagination: { pagination: {
perPage: number; perPage: number;
}; };
locale: string;
} }
export class DefaultUIConfig implements UIConfig { export class DefaultUIConfig implements UIConfig {
@ -49,22 +52,25 @@ export class DefaultUIConfig implements UIConfig {
pagination = { pagination = {
perPage: 20, perPage: 20,
}; };
locale = "";
} }
let globalUiConfig: Promise<UIConfig>; let globalUiConfig: Promise<UIConfig>;
export function uiConfig(): Promise<UIConfig> { export function getConfigForUser(user: UserSelf): UIConfig {
if (!globalUiConfig) { const settings = user.settings;
globalUiConfig = me().then((user) => {
const settings = user.user.settings;
let config = new DefaultUIConfig(); let config = new DefaultUIConfig();
if (!settings) { if (!settings) {
return config; return config;
} }
if ("userInterface" in settings) { config = Object.assign(new DefaultUIConfig(), settings);
config = Object.assign(new DefaultUIConfig(), settings.userInterface);
}
return config; return config;
}
export function uiConfig(): Promise<UIConfig> {
if (!globalUiConfig) {
globalUiConfig = me().then((user) => {
return getConfigForUser(user.user);
}); });
} }
return globalUiConfig; return globalUiConfig;

View File

@ -1,21 +1,51 @@
import { en, fr, tr } from "make-plural/plurals"; import { en, fr, tr } from "make-plural/plurals";
import { i18n } from "@lingui/core"; import { Messages, i18n } from "@lingui/core";
import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale"; import { detect, fromNavigator, fromStorage, fromUrl } from "@lingui/detect-locale";
import { t } from "@lingui/macro";
import { messages as localeEN } from "../locales/en"; import { messages as localeEN } from "../locales/en";
import { messages as localeFR_FR } from "../locales/fr_FR"; import { messages as localeFR_FR } from "../locales/fr_FR";
import { messages as localeDEBUG } from "../locales/pseudo-LOCALE"; import { messages as localeDEBUG } from "../locales/pseudo-LOCALE";
import { messages as localeTR } from "../locales/tr"; import { messages as localeTR } from "../locales/tr";
i18n.loadLocaleData("en", { plurals: en }); export const LOCALES: {
i18n.loadLocaleData("debug", { plurals: en }); code: string;
i18n.loadLocaleData("tr", { plurals: tr }); label: string;
i18n.loadLocaleData("fr_FR", { plurals: fr }); // eslint-disable-next-line @typescript-eslint/ban-types
i18n.load("en", localeEN); plurals: Function;
i18n.load("tr", localeTR); locale: Messages;
i18n.load("fr_FR", localeFR_FR); }[] = [
i18n.load("debug", localeDEBUG); {
code: "en",
plurals: en,
label: t`English`,
locale: localeEN,
},
{
code: "debug",
plurals: en,
label: t`Debug`,
locale: localeDEBUG,
},
{
code: "fr_FR",
plurals: fr,
label: t`French`,
locale: localeFR_FR,
},
{
code: "tr",
plurals: tr,
label: t`Turkish`,
locale: localeTR,
},
];
LOCALES.forEach((locale) => {
i18n.loadLocaleData(locale.code, { plurals: locale.plurals });
i18n.load(locale.code, locale.locale);
});
const DEFAULT_FALLBACK = () => "en"; const DEFAULT_FALLBACK = () => "en";

View File

@ -516,6 +516,10 @@ msgstr "Authorize URL"
msgid "Authorized application:" msgid "Authorized application:"
msgstr "Authorized application:" msgstr "Authorized application:"
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Auto-detect (based on your browser)"
msgstr "Auto-detect (based on your browser)"
#: src/interfaces/UserInterface.ts #: src/interfaces/UserInterface.ts
msgid "Avatar image" msgid "Avatar image"
msgstr "Avatar image" msgstr "Avatar image"
@ -1317,6 +1321,10 @@ msgstr "Date Time"
msgid "Deactivate" msgid "Deactivate"
msgstr "Deactivate" msgstr "Deactivate"
#: src/interfaces/locale.ts
msgid "Debug"
msgstr "Debug"
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
msgstr "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgstr "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
@ -1715,6 +1723,10 @@ msgstr "Enabled"
msgid "Enabling this toggle will create a group named after the user, with the user as member." msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr "Enabling this toggle will create a group named after the user, with the user as member." msgstr "Enabling this toggle will create a group named after the user, with the user as member."
#: src/interfaces/locale.ts
msgid "English"
msgstr "English"
#: src/user/user-settings/mfa/MFADevicesPage.ts #: src/user/user-settings/mfa/MFADevicesPage.ts
msgid "Enroll" msgid "Enroll"
msgstr "Enroll" msgstr "Enroll"
@ -2122,6 +2134,10 @@ msgstr "Forward auth (domain-level)"
msgid "Forward auth (single application)" msgid "Forward auth (single application)"
msgstr "Forward auth (single application)" msgstr "Forward auth (single application)"
#: src/interfaces/locale.ts
msgid "French"
msgstr "French"
#: src/pages/property-mappings/PropertyMappingSAMLForm.ts #: src/pages/property-mappings/PropertyMappingSAMLForm.ts
msgid "Friendly Name" msgid "Friendly Name"
msgstr "Friendly Name" msgstr "Friendly Name"
@ -2742,6 +2758,10 @@ msgstr "Loading..."
msgid "Local" msgid "Local"
msgstr "Local" msgstr "Local"
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Locale"
msgstr "Locale"
#: src/pages/stages/user_login/UserLoginStageForm.ts #: src/pages/stages/user_login/UserLoginStageForm.ts
msgid "Log the currently pending user in." msgid "Log the currently pending user in."
msgstr "Log the currently pending user in." msgstr "Log the currently pending user in."
@ -5233,6 +5253,10 @@ msgstr "Transient"
msgid "Transports" msgid "Transports"
msgstr "Transports" msgstr "Transports"
#: src/interfaces/locale.ts
msgid "Turkish"
msgstr "Turkish"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio" msgid "Twilio"
msgstr "Twilio" msgstr "Twilio"

View File

@ -520,6 +520,10 @@ msgstr "URL d'authorisation"
msgid "Authorized application:" msgid "Authorized application:"
msgstr "Application autorisée :" msgstr "Application autorisée :"
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Auto-detect (based on your browser)"
msgstr ""
#: src/interfaces/UserInterface.ts #: src/interfaces/UserInterface.ts
msgid "Avatar image" msgid "Avatar image"
msgstr "Image d'avatar" msgstr "Image d'avatar"
@ -1316,6 +1320,10 @@ msgstr "Date et heure"
msgid "Deactivate" msgid "Deactivate"
msgstr "Désactiver" msgstr "Désactiver"
#: src/interfaces/locale.ts
msgid "Debug"
msgstr ""
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
msgstr "Détermine l'usage de ce flux. Par exemple, un flux d'authentification est la destination d'un visiteur d'authentik non authentifié." msgstr "Détermine l'usage de ce flux. Par exemple, un flux d'authentification est la destination d'un visiteur d'authentik non authentifié."
@ -1702,6 +1710,10 @@ msgstr "Activé"
msgid "Enabling this toggle will create a group named after the user, with the user as member." msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr "Activer cette option va créer un groupe du même nom que l'utilisateur dont il sera membre." msgstr "Activer cette option va créer un groupe du même nom que l'utilisateur dont il sera membre."
#: src/interfaces/locale.ts
msgid "English"
msgstr ""
#: src/user/user-settings/mfa/MFADevicesPage.ts #: src/user/user-settings/mfa/MFADevicesPage.ts
msgid "Enroll" msgid "Enroll"
msgstr "" msgstr ""
@ -2108,6 +2120,10 @@ msgstr ""
msgid "Forward auth (single application)" msgid "Forward auth (single application)"
msgstr "Transférer l'authentification (application unique)" msgstr "Transférer l'authentification (application unique)"
#: src/interfaces/locale.ts
msgid "French"
msgstr ""
#: src/pages/property-mappings/PropertyMappingSAMLForm.ts #: src/pages/property-mappings/PropertyMappingSAMLForm.ts
msgid "Friendly Name" msgid "Friendly Name"
msgstr "Nom amical" msgstr "Nom amical"
@ -2722,6 +2738,10 @@ msgstr "Chargement en cours..."
msgid "Local" msgid "Local"
msgstr "Local" msgstr "Local"
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Locale"
msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts #: src/pages/stages/user_login/UserLoginStageForm.ts
msgid "Log the currently pending user in." msgid "Log the currently pending user in."
msgstr "Ouvre la session de l'utilisateur courant." msgstr "Ouvre la session de l'utilisateur courant."
@ -5175,6 +5195,10 @@ msgstr "Transitoire"
msgid "Transports" msgid "Transports"
msgstr "Transports" msgstr "Transports"
#: src/interfaces/locale.ts
msgid "Turkish"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio" msgid "Twilio"
msgstr "" msgstr ""

View File

@ -512,6 +512,10 @@ msgstr ""
msgid "Authorized application:" msgid "Authorized application:"
msgstr "" msgstr ""
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Auto-detect (based on your browser)"
msgstr ""
#: src/interfaces/UserInterface.ts #: src/interfaces/UserInterface.ts
msgid "Avatar image" msgid "Avatar image"
msgstr "" msgstr ""
@ -1311,6 +1315,10 @@ msgstr ""
msgid "Deactivate" msgid "Deactivate"
msgstr "" msgstr ""
#: src/interfaces/locale.ts
msgid "Debug"
msgstr ""
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
msgstr "" msgstr ""
@ -1707,6 +1715,10 @@ msgstr ""
msgid "Enabling this toggle will create a group named after the user, with the user as member." msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr "" msgstr ""
#: src/interfaces/locale.ts
msgid "English"
msgstr ""
#: src/user/user-settings/mfa/MFADevicesPage.ts #: src/user/user-settings/mfa/MFADevicesPage.ts
msgid "Enroll" msgid "Enroll"
msgstr "" msgstr ""
@ -2114,6 +2126,10 @@ msgstr ""
msgid "Forward auth (single application)" msgid "Forward auth (single application)"
msgstr "" msgstr ""
#: src/interfaces/locale.ts
msgid "French"
msgstr ""
#: src/pages/property-mappings/PropertyMappingSAMLForm.ts #: src/pages/property-mappings/PropertyMappingSAMLForm.ts
msgid "Friendly Name" msgid "Friendly Name"
msgstr "" msgstr ""
@ -2732,6 +2748,10 @@ msgstr ""
msgid "Local" msgid "Local"
msgstr "" msgstr ""
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Locale"
msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts #: src/pages/stages/user_login/UserLoginStageForm.ts
msgid "Log the currently pending user in." msgid "Log the currently pending user in."
msgstr "" msgstr ""
@ -5213,6 +5233,10 @@ msgstr ""
msgid "Transports" msgid "Transports"
msgstr "" msgstr ""
#: src/interfaces/locale.ts
msgid "Turkish"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio" msgid "Twilio"
msgstr "" msgstr ""

View File

@ -514,6 +514,10 @@ msgstr "URL'yi yetkilendirme"
msgid "Authorized application:" msgid "Authorized application:"
msgstr "Yetkili başvuru:" msgstr "Yetkili başvuru:"
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Auto-detect (based on your browser)"
msgstr ""
#: src/interfaces/UserInterface.ts #: src/interfaces/UserInterface.ts
msgid "Avatar image" msgid "Avatar image"
msgstr "Avatar resmi" msgstr "Avatar resmi"
@ -1304,6 +1308,10 @@ msgstr "Tarih Saati"
msgid "Deactivate" msgid "Deactivate"
msgstr "Devre dışı bırak" msgstr "Devre dışı bırak"
#: src/interfaces/locale.ts
msgid "Debug"
msgstr ""
#: src/pages/flows/FlowForm.ts #: src/pages/flows/FlowForm.ts
msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
msgstr "Bu Akış'ın ne için kullanıldığına karar verir. Örneğin, kimliği doğrulanmamış bir kullanıcı authentik ziyaret ettiğinde kimlik doğrulama akışı yönlendirir." msgstr "Bu Akış'ın ne için kullanıldığına karar verir. Örneğin, kimliği doğrulanmamış bir kullanıcı authentik ziyaret ettiğinde kimlik doğrulama akışı yönlendirir."
@ -1682,6 +1690,10 @@ msgstr "Etkin"
msgid "Enabling this toggle will create a group named after the user, with the user as member." msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr "Bu geçiş özelliğini etkinleştirmek, kullanıcının adını taşıyan ve kullanıcının üye olduğu bir grup oluşturur." msgstr "Bu geçiş özelliğini etkinleştirmek, kullanıcının adını taşıyan ve kullanıcının üye olduğu bir grup oluşturur."
#: src/interfaces/locale.ts
msgid "English"
msgstr ""
#: src/user/user-settings/mfa/MFADevicesPage.ts #: src/user/user-settings/mfa/MFADevicesPage.ts
msgid "Enroll" msgid "Enroll"
msgstr "Kaydolun" msgstr "Kaydolun"
@ -2085,6 +2097,10 @@ msgstr "İleri kimlik doğrulama (alan düzeyi)"
msgid "Forward auth (single application)" msgid "Forward auth (single application)"
msgstr "İleri kimlik doğrulaması (tek uygulama)" msgstr "İleri kimlik doğrulaması (tek uygulama)"
#: src/interfaces/locale.ts
msgid "French"
msgstr ""
#: src/pages/property-mappings/PropertyMappingSAMLForm.ts #: src/pages/property-mappings/PropertyMappingSAMLForm.ts
msgid "Friendly Name" msgid "Friendly Name"
msgstr "Dostça İsim" msgstr "Dostça İsim"
@ -2696,6 +2712,10 @@ msgstr "Yükleniyor..."
msgid "Local" msgid "Local"
msgstr "Yerel" msgstr "Yerel"
#: src/user/user-settings/details/UserDetailsForm.ts
msgid "Locale"
msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts #: src/pages/stages/user_login/UserLoginStageForm.ts
msgid "Log the currently pending user in." msgid "Log the currently pending user in."
msgstr "Şu anda bekleyen kullanıcıyı oturum açın." msgstr "Şu anda bekleyen kullanıcıyı oturum açın."
@ -5132,6 +5152,10 @@ msgstr "Geçici"
msgid "Transports" msgid "Transports"
msgstr "Taşımacılık" msgstr "Taşımacılık"
#: src/interfaces/locale.ts
msgid "Turkish"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio" msgid "Twilio"
msgstr "Twilio" msgstr "Twilio"

View File

@ -1,3 +1,4 @@
import { i18n } from "@lingui/core";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
@ -9,19 +10,25 @@ import { CoreApi, UserSelf } from "@goauthentik/api";
import { DEFAULT_CONFIG, tenant } from "../../../api/Config"; import { DEFAULT_CONFIG, tenant } from "../../../api/Config";
import { me } from "../../../api/Users"; import { me } from "../../../api/Users";
import { getConfigForUser, uiConfig } from "../../../common/config";
import "../../../elements/EmptyState"; import "../../../elements/EmptyState";
import "../../../elements/forms/Form"; import "../../../elements/forms/Form";
import "../../../elements/forms/FormElement"; import "../../../elements/forms/FormElement";
import "../../../elements/forms/HorizontalFormElement"; import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm"; import { ModelForm } from "../../../elements/forms/ModelForm";
import { LOCALES } from "../../../interfaces/locale";
@customElement("ak-user-details-form") @customElement("ak-user-details-form")
export class UserDetailsForm extends ModelForm<UserSelf, number> { export class UserDetailsForm extends ModelForm<UserSelf, number> {
currentLocale?: string;
viewportCheck = false; viewportCheck = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
loadInstance(pk: number): Promise<UserSelf> { loadInstance(pk: number): Promise<UserSelf> {
return me().then((user) => { return me().then((user) => {
const config = getConfigForUser(user.user);
this.currentLocale = config.locale;
return user.user; return user.user;
}); });
} }
@ -31,6 +38,13 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> {
} }
send = (data: UserSelf): Promise<UserSelf> => { send = (data: UserSelf): Promise<UserSelf> => {
const newConfig = getConfigForUser(data);
const newLocale = LOCALES.find((locale) => locale.code === newConfig.locale);
if (newLocale) {
i18n.activate(newLocale.code);
} else {
console.debug(`authentik/user: invalid locale: '${newConfig.locale}'`);
}
return new CoreApi(DEFAULT_CONFIG) return new CoreApi(DEFAULT_CONFIG)
.coreUsersUpdateSelfUpdate({ .coreUsersUpdateSelfUpdate({
userSelfRequest: data, userSelfRequest: data,
@ -44,8 +58,14 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> {
if (!this.instance) { if (!this.instance) {
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`; return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
} }
return html`${until(
uiConfig().then((config) => {
return html`<form class="pf-c-form pf-m-horizontal"> return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Username`} ?required=${true} name="username"> <ak-form-element-horizontal
label=${t`Username`}
?required=${true}
name="username"
>
<input <input
type="text" type="text"
value="${ifDefined(this.instance?.username)}" value="${ifDefined(this.instance?.username)}"
@ -71,6 +91,21 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> {
class="pf-c-form-control" class="pf-c-form-control"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Locale`} name="settings.locale">
<select class="pf-c-form-control">
<option value="" ?selected=${config.locale === ""}>
${t`Auto-detect (based on your browser)`}
</option>
${LOCALES.map((locale) => {
return html`<option
value=${locale.code}
?selected=${config.locale === locale.code}
>
${locale.label}
</option>`;
})}
</select>
</ak-form-element-horizontal>
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__horizontal-group"> <div class="pf-c-form__horizontal-group">
@ -100,5 +135,7 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> {
</div> </div>
</div> </div>
</form>`; </form>`;
}),
)}`;
} }
} }