web/user: add language selection
closes #2041 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,10 +1,21 @@ | ||||
| import { CoreApi, SessionUser } from "@goauthentik/api"; | ||||
| import { i18n } from "@lingui/core"; | ||||
| import { DEFAULT_CONFIG } from "./Config"; | ||||
|  | ||||
| let globalMePromise: Promise<SessionUser>; | ||||
| export function me(): Promise<SessionUser> { | ||||
|     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 = { | ||||
|                 user: { | ||||
|                     pk: -1, | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import { UserSelf } from "@goauthentik/api"; | ||||
|  | ||||
| import { me } from "../api/Users"; | ||||
|  | ||||
| export enum UserDisplay { | ||||
| @ -29,6 +31,7 @@ export interface UIConfig { | ||||
|     pagination: { | ||||
|         perPage: number; | ||||
|     }; | ||||
|     locale: string; | ||||
| } | ||||
|  | ||||
| export class DefaultUIConfig implements UIConfig { | ||||
| @ -49,22 +52,25 @@ export class DefaultUIConfig implements UIConfig { | ||||
|     pagination = { | ||||
|         perPage: 20, | ||||
|     }; | ||||
|     locale = ""; | ||||
| } | ||||
|  | ||||
| let globalUiConfig: Promise<UIConfig>; | ||||
|  | ||||
| export function uiConfig(): Promise<UIConfig> { | ||||
|     if (!globalUiConfig) { | ||||
|         globalUiConfig = me().then((user) => { | ||||
|             const settings = user.user.settings; | ||||
| export function getConfigForUser(user: UserSelf): UIConfig { | ||||
|     const settings = user.settings; | ||||
|     let config = new DefaultUIConfig(); | ||||
|     if (!settings) { | ||||
|         return config; | ||||
|     } | ||||
|             if ("userInterface" in settings) { | ||||
|                 config = Object.assign(new DefaultUIConfig(), settings.userInterface); | ||||
|             } | ||||
|     config = Object.assign(new DefaultUIConfig(), settings); | ||||
|     return config; | ||||
| } | ||||
|  | ||||
| export function uiConfig(): Promise<UIConfig> { | ||||
|     if (!globalUiConfig) { | ||||
|         globalUiConfig = me().then((user) => { | ||||
|             return getConfigForUser(user.user); | ||||
|         }); | ||||
|     } | ||||
|     return globalUiConfig; | ||||
|  | ||||
| @ -1,21 +1,51 @@ | ||||
| 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 { t } from "@lingui/macro"; | ||||
|  | ||||
| import { messages as localeEN } from "../locales/en"; | ||||
| import { messages as localeFR_FR } from "../locales/fr_FR"; | ||||
| import { messages as localeDEBUG } from "../locales/pseudo-LOCALE"; | ||||
| import { messages as localeTR } from "../locales/tr"; | ||||
|  | ||||
| i18n.loadLocaleData("en", { plurals: en }); | ||||
| i18n.loadLocaleData("debug", { plurals: en }); | ||||
| i18n.loadLocaleData("tr", { plurals: tr }); | ||||
| i18n.loadLocaleData("fr_FR", { plurals: fr }); | ||||
| i18n.load("en", localeEN); | ||||
| i18n.load("tr", localeTR); | ||||
| i18n.load("fr_FR", localeFR_FR); | ||||
| i18n.load("debug", localeDEBUG); | ||||
| export const LOCALES: { | ||||
|     code: string; | ||||
|     label: string; | ||||
|     // eslint-disable-next-line @typescript-eslint/ban-types | ||||
|     plurals: Function; | ||||
|     locale: Messages; | ||||
| }[] = [ | ||||
|     { | ||||
|         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"; | ||||
|  | ||||
|  | ||||
| @ -516,6 +516,10 @@ msgstr "Authorize URL" | ||||
| msgid "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 | ||||
| msgid "Avatar image" | ||||
| msgstr "Avatar image" | ||||
| @ -1317,6 +1321,10 @@ msgstr "Date Time" | ||||
| msgid "Deactivate" | ||||
| msgstr "Deactivate" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Debug" | ||||
| msgstr "Debug" | ||||
|  | ||||
| #: 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." | ||||
| 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." | ||||
| 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 | ||||
| msgid "Enroll" | ||||
| msgstr "Enroll" | ||||
| @ -2122,6 +2134,10 @@ msgstr "Forward auth (domain-level)" | ||||
| msgid "Forward auth (single application)" | ||||
| msgstr "Forward auth (single application)" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "French" | ||||
| msgstr "French" | ||||
|  | ||||
| #: src/pages/property-mappings/PropertyMappingSAMLForm.ts | ||||
| msgid "Friendly Name" | ||||
| msgstr "Friendly Name" | ||||
| @ -2742,6 +2758,10 @@ msgstr "Loading..." | ||||
| msgid "Local" | ||||
| msgstr "Local" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Locale" | ||||
| msgstr "Locale" | ||||
|  | ||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||
| msgid "Log the currently pending user in." | ||||
| msgstr "Log the currently pending user in." | ||||
| @ -5233,6 +5253,10 @@ msgstr "Transient" | ||||
| msgid "Transports" | ||||
| msgstr "Transports" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Turkish" | ||||
| msgstr "Turkish" | ||||
|  | ||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||
| msgid "Twilio" | ||||
| msgstr "Twilio" | ||||
|  | ||||
| @ -520,6 +520,10 @@ msgstr "URL d'authorisation" | ||||
| msgid "Authorized application:" | ||||
| msgstr "Application autorisée :" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Auto-detect (based on your browser)" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/UserInterface.ts | ||||
| msgid "Avatar image" | ||||
| msgstr "Image d'avatar" | ||||
| @ -1316,6 +1320,10 @@ msgstr "Date et heure" | ||||
| msgid "Deactivate" | ||||
| msgstr "Désactiver" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Debug" | ||||
| msgstr "" | ||||
|  | ||||
| #: 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." | ||||
| 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." | ||||
| 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 | ||||
| msgid "Enroll" | ||||
| msgstr "" | ||||
| @ -2108,6 +2120,10 @@ msgstr "" | ||||
| msgid "Forward auth (single application)" | ||||
| msgstr "Transférer l'authentification (application unique)" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "French" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/property-mappings/PropertyMappingSAMLForm.ts | ||||
| msgid "Friendly Name" | ||||
| msgstr "Nom amical" | ||||
| @ -2722,6 +2738,10 @@ msgstr "Chargement en cours..." | ||||
| msgid "Local" | ||||
| msgstr "Local" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Locale" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||
| msgid "Log the currently pending user in." | ||||
| msgstr "Ouvre la session de l'utilisateur courant." | ||||
| @ -5175,6 +5195,10 @@ msgstr "Transitoire" | ||||
| msgid "Transports" | ||||
| msgstr "Transports" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Turkish" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||
| msgid "Twilio" | ||||
| msgstr "" | ||||
|  | ||||
| @ -512,6 +512,10 @@ msgstr "" | ||||
| msgid "Authorized application:" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Auto-detect (based on your browser)" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/UserInterface.ts | ||||
| msgid "Avatar image" | ||||
| msgstr "" | ||||
| @ -1311,6 +1315,10 @@ msgstr "" | ||||
| msgid "Deactivate" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Debug" | ||||
| msgstr "" | ||||
|  | ||||
| #: 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." | ||||
| msgstr "" | ||||
| @ -1707,6 +1715,10 @@ msgstr "" | ||||
| msgid "Enabling this toggle will create a group named after the user, with the user as member." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "English" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/mfa/MFADevicesPage.ts | ||||
| msgid "Enroll" | ||||
| msgstr "" | ||||
| @ -2114,6 +2126,10 @@ msgstr "" | ||||
| msgid "Forward auth (single application)" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "French" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/property-mappings/PropertyMappingSAMLForm.ts | ||||
| msgid "Friendly Name" | ||||
| msgstr "" | ||||
| @ -2732,6 +2748,10 @@ msgstr "" | ||||
| msgid "Local" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Locale" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||
| msgid "Log the currently pending user in." | ||||
| msgstr "" | ||||
| @ -5213,6 +5233,10 @@ msgstr "" | ||||
| msgid "Transports" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Turkish" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||
| msgid "Twilio" | ||||
| msgstr "" | ||||
|  | ||||
| @ -514,6 +514,10 @@ msgstr "URL'yi yetkilendirme" | ||||
| msgid "Authorized application:" | ||||
| msgstr "Yetkili başvuru:" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Auto-detect (based on your browser)" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/interfaces/UserInterface.ts | ||||
| msgid "Avatar image" | ||||
| msgstr "Avatar resmi" | ||||
| @ -1304,6 +1308,10 @@ msgstr "Tarih Saati" | ||||
| msgid "Deactivate" | ||||
| msgstr "Devre dışı bırak" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Debug" | ||||
| msgstr "" | ||||
|  | ||||
| #: 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." | ||||
| 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." | ||||
| 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 | ||||
| msgid "Enroll" | ||||
| msgstr "Kaydolun" | ||||
| @ -2085,6 +2097,10 @@ msgstr "İleri kimlik doğrulama (alan düzeyi)" | ||||
| msgid "Forward auth (single application)" | ||||
| msgstr "İleri kimlik doğrulaması (tek uygulama)" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "French" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/property-mappings/PropertyMappingSAMLForm.ts | ||||
| msgid "Friendly Name" | ||||
| msgstr "Dostça İsim" | ||||
| @ -2696,6 +2712,10 @@ msgstr "Yükleniyor..." | ||||
| msgid "Local" | ||||
| msgstr "Yerel" | ||||
|  | ||||
| #: src/user/user-settings/details/UserDetailsForm.ts | ||||
| msgid "Locale" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||
| msgid "Log the currently pending user in." | ||||
| msgstr "Şu anda bekleyen kullanıcıyı oturum açın." | ||||
| @ -5132,6 +5152,10 @@ msgstr "Geçici" | ||||
| msgid "Transports" | ||||
| msgstr "Taşımacılık" | ||||
|  | ||||
| #: src/interfaces/locale.ts | ||||
| msgid "Turkish" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts | ||||
| msgid "Twilio" | ||||
| msgstr "Twilio" | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import { i18n } from "@lingui/core"; | ||||
| import { t } from "@lingui/macro"; | ||||
|  | ||||
| import { TemplateResult, html } from "lit"; | ||||
| @ -9,19 +10,25 @@ import { CoreApi, UserSelf } from "@goauthentik/api"; | ||||
|  | ||||
| import { DEFAULT_CONFIG, tenant } from "../../../api/Config"; | ||||
| import { me } from "../../../api/Users"; | ||||
| import { getConfigForUser, uiConfig } from "../../../common/config"; | ||||
| import "../../../elements/EmptyState"; | ||||
| import "../../../elements/forms/Form"; | ||||
| import "../../../elements/forms/FormElement"; | ||||
| import "../../../elements/forms/HorizontalFormElement"; | ||||
| import { ModelForm } from "../../../elements/forms/ModelForm"; | ||||
| import { LOCALES } from "../../../interfaces/locale"; | ||||
|  | ||||
| @customElement("ak-user-details-form") | ||||
| export class UserDetailsForm extends ModelForm<UserSelf, number> { | ||||
|     currentLocale?: string; | ||||
|  | ||||
|     viewportCheck = false; | ||||
|  | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|     loadInstance(pk: number): Promise<UserSelf> { | ||||
|         return me().then((user) => { | ||||
|             const config = getConfigForUser(user.user); | ||||
|             this.currentLocale = config.locale; | ||||
|             return user.user; | ||||
|         }); | ||||
|     } | ||||
| @ -31,6 +38,13 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> { | ||||
|     } | ||||
|  | ||||
|     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) | ||||
|             .coreUsersUpdateSelfUpdate({ | ||||
|                 userSelfRequest: data, | ||||
| @ -44,8 +58,14 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> { | ||||
|         if (!this.instance) { | ||||
|             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"> | ||||
|             <ak-form-element-horizontal label=${t`Username`} ?required=${true} name="username"> | ||||
|                     <ak-form-element-horizontal | ||||
|                         label=${t`Username`} | ||||
|                         ?required=${true} | ||||
|                         name="username" | ||||
|                     > | ||||
|                         <input | ||||
|                             type="text" | ||||
|                             value="${ifDefined(this.instance?.username)}" | ||||
| @ -71,6 +91,21 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> { | ||||
|                             class="pf-c-form-control" | ||||
|                         /> | ||||
|                     </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__horizontal-group"> | ||||
| @ -100,5 +135,7 @@ export class UserDetailsForm extends ModelForm<UserSelf, number> { | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </form>`; | ||||
|             }), | ||||
|         )}`; | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer