From 9deed3447965c6ae7bcda106be6079d0ac5f0d06 Mon Sep 17 00:00:00 2001 From: Teffen Ellis Date: Wed, 23 Apr 2025 14:29:20 +0200 Subject: [PATCH] web: Fix issue stemming from locale initialization triggering UI repeat reloads. --- web/src/common/api/config.ts | 8 +- web/src/common/constants.ts | 2 - web/src/common/users.ts | 7 +- .../ak-locale-context.stories.ts | 18 ++- .../ak-locale-context/ak-locale-context.ts | 115 +++++++++++------- .../ak-locale-context/configureLocale.ts | 57 +++++---- .../elements/ak-locale-context/definitions.ts | 74 ++++++----- web/src/elements/ak-locale-context/events.ts | 6 + web/src/elements/ak-locale-context/helpers.ts | 96 +++++++++------ web/src/elements/ak-locale-context/types.ts | 25 ++-- web/src/flow/stages/prompt/PromptStage.ts | 12 +- 11 files changed, 258 insertions(+), 162 deletions(-) create mode 100644 web/src/elements/ak-locale-context/events.ts diff --git a/web/src/common/api/config.ts b/web/src/common/api/config.ts index c6731d7ad4..7e3abb6295 100644 --- a/web/src/common/api/config.ts +++ b/web/src/common/api/config.ts @@ -4,8 +4,12 @@ import { EventMiddleware, LoggingMiddleware, } from "@goauthentik/common/api/middleware"; -import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants"; +import { VERSION } from "@goauthentik/common/constants"; import { globalAK } from "@goauthentik/common/global"; +import { + EVENT_LOCALE_REQUEST, + LocaleContextEventDetail, +} from "@goauthentik/elements/ak-locale-context/events.js"; import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api"; @@ -44,7 +48,7 @@ export function brandSetLocale(brand: CurrentBrand) { } console.debug("authentik/locale: setting locale from brand default"); window.dispatchEvent( - new CustomEvent(EVENT_LOCALE_REQUEST, { + new CustomEvent(EVENT_LOCALE_REQUEST, { composed: true, bubbles: true, detail: { locale: brand.defaultLocale }, diff --git a/web/src/common/constants.ts b/web/src/common/constants.ts index 4d40751cee..69dfdfd735 100644 --- a/web/src/common/constants.ts +++ b/web/src/common/constants.ts @@ -14,8 +14,6 @@ export const EVENT_FLOW_INSPECTOR_TOGGLE = "ak-flow-inspector-toggle"; export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle"; export const EVENT_WS_MESSAGE = "ak-ws-message"; export const EVENT_FLOW_ADVANCE = "ak-flow-advance"; -export const EVENT_LOCALE_CHANGE = "ak-locale-change"; -export const EVENT_LOCALE_REQUEST = "ak-locale-request"; export const EVENT_REQUEST_POST = "ak-request-post"; export const EVENT_MESSAGE = "ak-message"; export const EVENT_THEME_CHANGE = "ak-theme-change"; diff --git a/web/src/common/users.ts b/web/src/common/users.ts index 067b1ddc68..f86faba236 100644 --- a/web/src/common/users.ts +++ b/web/src/common/users.ts @@ -1,6 +1,9 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; import { isResponseErrorLike } from "@goauthentik/common/errors/network"; +import { + EVENT_LOCALE_REQUEST, + LocaleContextEventDetail, +} from "@goauthentik/elements/ak-locale-context/events.js"; import { CoreApi, SessionUser } from "@goauthentik/api"; @@ -57,7 +60,7 @@ export async function me(): Promise { console.debug(`authentik/locale: Activating user's configured locale '${locale}'`); window.dispatchEvent( - new CustomEvent(EVENT_LOCALE_REQUEST, { + new CustomEvent(EVENT_LOCALE_REQUEST, { composed: true, bubbles: true, detail: { locale }, diff --git a/web/src/elements/ak-locale-context/ak-locale-context.stories.ts b/web/src/elements/ak-locale-context/ak-locale-context.stories.ts index 2d144f1496..f4f713d0d5 100644 --- a/web/src/elements/ak-locale-context/ak-locale-context.stories.ts +++ b/web/src/elements/ak-locale-context/ak-locale-context.stories.ts @@ -1,11 +1,9 @@ -import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; -import { customEvent } from "@goauthentik/elements/utils/customEvents"; - import { localized, msg } from "@lit/localize"; import { LitElement, html } from "lit"; import { customElement } from "lit/decorators.js"; import "./ak-locale-context"; +import { EVENT_LOCALE_REQUEST, LocaleContextEventDetail } from "./events.js"; export default { title: "Elements / Shell / Locale Context", @@ -37,10 +35,18 @@ export const InFrench = () => `; export const SwitchingBackAndForth = () => { - let lang = "en"; + let languageCode = "en"; + window.setInterval(() => { - lang = lang === "en" ? "fr" : "en"; - window.dispatchEvent(customEvent(EVENT_LOCALE_REQUEST, { locale: lang })); + languageCode = languageCode === "en" ? "fr" : "en"; + + window.dispatchEvent( + new CustomEvent(EVENT_LOCALE_REQUEST, { + composed: true, + bubbles: true, + detail: { locale: languageCode }, + }), + ); }, 1000); return html`
diff --git a/web/src/elements/ak-locale-context/ak-locale-context.ts b/web/src/elements/ak-locale-context/ak-locale-context.ts index 67a91374c3..67c57a2d7e 100644 --- a/web/src/elements/ak-locale-context/ak-locale-context.ts +++ b/web/src/elements/ak-locale-context/ak-locale-context.ts @@ -1,19 +1,18 @@ -import { EVENT_LOCALE_CHANGE, EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; import { AKElement } from "@goauthentik/elements/Base"; -import { customEvent } from "@goauthentik/elements/utils/customEvents"; import { html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { WithBrandConfig } from "../Interface/brandProvider"; -import { initializeLocalization } from "./configureLocale"; -import type { LocaleGetter, LocaleSetter } from "./configureLocale"; -import { DEFAULT_LOCALE, autoDetectLanguage, getBestMatchLocale } from "./helpers"; +import { initializeLocalization } from "./configureLocale.js"; +import type { GetLocale, SetLocale } from "./configureLocale.js"; +import { EVENT_LOCALE_CHANGE, EVENT_LOCALE_REQUEST, LocaleContextEventDetail } from "./events.js"; +import { DEFAULT_LOCALE, autoDetectLanguage, findLocaleDefinition } from "./helpers.js"; /** * A component to manage your locale settings. * - * ## Details + * @remarks * * This component exists to take a locale setting from several different places, find the * appropriate locale file in our catalog of locales, and set the lit-localization context @@ -25,70 +24,98 @@ import { DEFAULT_LOCALE, autoDetectLanguage, getBestMatchLocale } from "./helper */ @customElement("ak-locale-context") export class LocaleContext extends WithBrandConfig(AKElement) { - /// @attribute The text representation of the current locale */ + protected static singleton: LocaleContext | null = null; + + /** + * The text representation of the current locale + * @attribute + */ @property({ attribute: true, type: String }) - locale = DEFAULT_LOCALE; + public locale = DEFAULT_LOCALE; - /// @attribute The URL parameter to look for (if any) + /** + * The URL parameter to look for (if any) + * @attribute + */ @property({ attribute: true, type: String }) - param = "locale"; + public param = "locale"; - getLocale: LocaleGetter; - - setLocale: LocaleSetter; + protected readonly getLocale: GetLocale; + protected readonly setLocale: SetLocale; constructor(code = DEFAULT_LOCALE) { super(); - this.notifyApplication = this.notifyApplication.bind(this); - this.updateLocaleHandler = this.updateLocaleHandler.bind(this); - try { - const [getLocale, setLocale] = initializeLocalization(); - this.getLocale = getLocale; - this.setLocale = setLocale; - this.setLocale(code).then(() => { - window.setTimeout(this.notifyApplication, 0); - }); - } catch (e) { - throw new Error(`Developer error: Must have only one locale context per session: ${e}`); + + if (LocaleContext.singleton) { + throw new Error(`Developer error: Must have only one locale context per session`); } + + LocaleContext.singleton = this; + + const [getLocale, setLocale] = initializeLocalization(); + + this.getLocale = getLocale; + this.setLocale = setLocale; + + this.setLocale(code).then(this.#notifyApplication); } connectedCallback() { - super.connectedCallback(); - this.updateLocale(); - window.addEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler as EventListener); + this.#updateLocale(); + + window.addEventListener(EVENT_LOCALE_REQUEST, this.#localeUpdateListener as EventListener); } disconnectedCallback() { - window.removeEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler as EventListener); + LocaleContext.singleton = null; + + window.removeEventListener( + EVENT_LOCALE_REQUEST, + this.#localeUpdateListener as EventListener, + ); super.disconnectedCallback(); } - updateLocaleHandler(ev: CustomEvent<{ locale: string }>) { + #localeUpdateListener = (ev: CustomEvent) => { console.debug("authentik/locale: Locale update request received."); - this.updateLocale(ev.detail.locale); - } + this.#updateLocale(ev.detail.locale); + }; + + #updateLocale(requestedLanguageCode?: string) { + const localeRequest = autoDetectLanguage(requestedLanguageCode, this.brand?.defaultLocale); + + const locale = findLocaleDefinition(localeRequest); - updateLocale(requestedLocale: string | undefined = undefined) { - const localeRequest = autoDetectLanguage(requestedLocale, this.brand?.defaultLocale); - const locale = getBestMatchLocale(localeRequest); if (!locale) { console.warn(`authentik/locale: failed to find locale for code ${localeRequest}`); return; } - locale.locale().then(() => { - console.debug(`authentik/locale: Setting Locale to ${locale.label()} (${locale.code})`); - this.setLocale(locale.code).then(() => { - window.setTimeout(this.notifyApplication, 0); - }); + + return locale.fetch().then(() => { + console.debug( + `authentik/locale: Setting Locale to ${locale.formatLabel()} (${locale.languageCode})`, + ); + + this.setLocale(locale.languageCode).then(this.#notifyApplication); }); } - notifyApplication() { - // You will almost never have cause to catch this event. Lit's own `@localized()` decorator - // works just fine for almost every use case. - this.dispatchEvent(customEvent(EVENT_LOCALE_CHANGE)); - } + #notifyFrameID = -1; + + #notifyApplication = () => { + cancelAnimationFrame(this.#notifyFrameID); + + requestAnimationFrame(() => { + // You will almost never have cause to catch this event. + // Lit's own `@localized()` decorator works just fine for almost every use case. + this.dispatchEvent( + new CustomEvent(EVENT_LOCALE_CHANGE, { + bubbles: true, + composed: true, + }), + ); + }); + }; render() { return html``; diff --git a/web/src/elements/ak-locale-context/configureLocale.ts b/web/src/elements/ak-locale-context/configureLocale.ts index ddf47c85b1..0fa0d8269e 100644 --- a/web/src/elements/ak-locale-context/configureLocale.ts +++ b/web/src/elements/ak-locale-context/configureLocale.ts @@ -1,39 +1,44 @@ import { configureLocalization } from "@lit/localize"; -import { sourceLocale, targetLocales } from "../../locale-codes"; -import { getBestMatchLocale } from "./helpers"; +import { sourceLocale, targetLocales } from "../../locale-codes.js"; +import { findLocaleDefinition } from "./helpers.js"; -type LocaleGetter = ReturnType["getLocale"]; -type LocaleSetter = ReturnType["setLocale"]; +export type ConfigureLocalizationResult = ReturnType; -// Internal use only. -// -// This is where the lit-localization module is initialized with our loader, which associates our -// collection of locales with its getter and setter functions. +export type GetLocale = ConfigureLocalizationResult["getLocale"]; +export type SetLocale = ConfigureLocalizationResult["setLocale"]; -let getLocale: LocaleGetter | undefined = undefined; -let setLocale: LocaleSetter | undefined = undefined; +export type LocaleState = [GetLocale, SetLocale]; -export function initializeLocalization(): [LocaleGetter, LocaleSetter] { - if (getLocale && setLocale) { - return [getLocale, setLocale]; - } +let cachedLocaleState: LocaleState | undefined = undefined; - ({ getLocale, setLocale } = configureLocalization({ +/** + * This is where the lit-localization module is initialized with our loader, + * which associates our collection of locales with its getter and setter functions. + * + * @returns A tuple of getter and setter functions. + * @internal + */ +export function initializeLocalization(): LocaleState { + if (cachedLocaleState) return cachedLocaleState; + + const { getLocale, setLocale } = configureLocalization({ sourceLocale, targetLocales, - loadLocale: async (locale: string) => { - const localeDef = getBestMatchLocale(locale); - if (!localeDef) { - console.warn(`Unrecognized locale: ${localeDef}`); - return Promise.reject(""); - } - return localeDef.locale(); - }, - })); + loadLocale: (languageCode) => { + const localeDef = findLocaleDefinition(languageCode); - return [getLocale, setLocale]; + if (!localeDef) { + throw new Error(`Unrecognized locale: ${localeDef}`); + } + + return localeDef.fetch(); + }, + }); + + cachedLocaleState = [getLocale, setLocale]; + + return cachedLocaleState; } export default initializeLocalization; -export type { LocaleGetter, LocaleSetter }; diff --git a/web/src/elements/ak-locale-context/definitions.ts b/web/src/elements/ak-locale-context/definitions.ts index 8d22a024bc..97374209a8 100644 --- a/web/src/elements/ak-locale-context/definitions.ts +++ b/web/src/elements/ak-locale-context/definitions.ts @@ -1,15 +1,19 @@ -import * as _enLocale from "@goauthentik/locales/en"; +import * as EnglishLocaleModule from "@goauthentik/locales/en"; import type { LocaleModule } from "@lit/localize"; import { msg } from "@lit/localize"; -import { AkLocale, LocaleRow } from "./types"; +import { AKLocaleDefinition, LocaleRow } from "./types.js"; -export const DEFAULT_FALLBACK = "en"; +/** + * The default ISO 639-1 language code. + */ +export const DEFAULT_LANGUAGE_CODE = "en"; -const enLocale: LocaleModule = _enLocale; - -export { enLocale }; +/** + * The default English locale module. + */ +export const DefaultLocaleModule: LocaleModule = EnglishLocaleModule; // NOTE: This table cannot be made any shorter, despite all the repetition of syntax. Bundlers look // for the `await import` string as a *string target* for doing alias substitution, so putting @@ -35,34 +39,44 @@ export { enLocale }; // - Text Label // - Locale loader. -// prettier-ignore const debug: LocaleRow = [ - "pseudo-LOCALE", /^pseudo/i, () => msg("Pseudolocale (for testing)"), async () => await import("@goauthentik/locales/pseudo-LOCALE"), + "pseudo-LOCALE", + /^pseudo/i, + () => msg("Pseudolocale (for testing)"), + () => import("@goauthentik/locales/pseudo-LOCALE"), ]; // prettier-ignore -const LOCALE_TABLE: LocaleRow[] = [ - ["de", /^de([_-]|$)/i, () => msg("German"), async () => await import("@goauthentik/locales/de")], - ["en", /^en([_-]|$)/i, () => msg("English"), async () => await import("@goauthentik/locales/en")], - ["es", /^es([_-]|$)/i, () => msg("Spanish"), async () => await import("@goauthentik/locales/es")], - ["fr", /^fr([_-]|$)/i, () => msg("French"), async () => await import("@goauthentik/locales/fr")], - ["it", /^it([_-]|$)/i, () => msg("Italian"), async () => await import("@goauthentik/locales/it")], - ["ko", /^ko([_-]|$)/i, () => msg("Korean"), async () => await import("@goauthentik/locales/ko")], - ["nl", /^nl([_-]|$)/i, () => msg("Dutch"), async () => await import("@goauthentik/locales/nl")], - ["pl", /^pl([_-]|$)/i, () => msg("Polish"), async () => await import("@goauthentik/locales/pl")], - ["ru", /^ru([_-]|$)/i, () => msg("Russian"), async () => await import("@goauthentik/locales/ru")], - ["tr", /^tr([_-]|$)/i, () => msg("Turkish"), async () => await import("@goauthentik/locales/tr")], - ["zh_TW", /^zh[_-]TW$/i, () => msg("Taiwanese Mandarin"), async () => await import("@goauthentik/locales/zh_TW")], - ["zh-Hans", /^zh(\b|_)/i, () => msg("Chinese (simplified)"), async () => await import("@goauthentik/locales/zh-Hans")], - ["zh-Hant", /^zh[_-](HK|Hant)/i, () => msg("Chinese (traditional)"), async () => await import("@goauthentik/locales/zh-Hant")], - debug +const LOCALE_TABLE: readonly LocaleRow[] = [ + // English loaded when the application is first instantiated. + ["en", /^en([_-]|$)/i, () => msg("English"), () => Promise.resolve(DefaultLocaleModule)], + ["de", /^de([_-]|$)/i, () => msg("German"), () => import("@goauthentik/locales/de")], + ["es", /^es([_-]|$)/i, () => msg("Spanish"), () => import("@goauthentik/locales/es")], + ["fr", /^fr([_-]|$)/i, () => msg("French"), () => import("@goauthentik/locales/fr")], + ["it", /^it([_-]|$)/i, () => msg("Italian"), () => import("@goauthentik/locales/it")], + ["ko", /^ko([_-]|$)/i, () => msg("Korean"), () => import("@goauthentik/locales/ko")], + ["nl", /^nl([_-]|$)/i, () => msg("Dutch"), () => import("@goauthentik/locales/nl")], + ["pl", /^pl([_-]|$)/i, () => msg("Polish"), () => import("@goauthentik/locales/pl")], + ["ru", /^ru([_-]|$)/i, () => msg("Russian"), () => import("@goauthentik/locales/ru")], + ["tr", /^tr([_-]|$)/i, () => msg("Turkish"), () => import("@goauthentik/locales/tr")], + ["zh_TW", /^zh[_-]TW$/i, () => msg("Taiwanese Mandarin"), () => import("@goauthentik/locales/zh_TW")], + ["zh-Hans", /^zh(\b|_)/i, () => msg("Chinese (simplified)"), () => import("@goauthentik/locales/zh-Hans")], + ["zh-Hant", /^zh[_-](HK|Hant)/i, () => msg("Chinese (traditional)"), () => import("@goauthentik/locales/zh-Hant")], + debug, ]; -export const LOCALES: AkLocale[] = LOCALE_TABLE.map(([code, match, label, locale]) => ({ - code, - match, - label, - locale, -})); +/** + * Available locales, identified by their ISO 639-1 language code. + */ +export const AKLocalDefinitions: readonly AKLocaleDefinition[] = LOCALE_TABLE.map( + ([languageCode, pattern, formatLabel, fetch]) => { + return { + languageCode, + pattern, + formatLabel, + fetch, + }; + }, +); -export default LOCALES; +export default AKLocalDefinitions; diff --git a/web/src/elements/ak-locale-context/events.ts b/web/src/elements/ak-locale-context/events.ts new file mode 100644 index 0000000000..ee235c9ee4 --- /dev/null +++ b/web/src/elements/ak-locale-context/events.ts @@ -0,0 +1,6 @@ +export const EVENT_LOCALE_REQUEST = "ak-locale-request"; +export const EVENT_LOCALE_CHANGE = "ak-locale-change"; + +export interface LocaleContextEventDetail { + locale: string; +} diff --git a/web/src/elements/ak-locale-context/helpers.ts b/web/src/elements/ak-locale-context/helpers.ts index 751371485a..901c09178a 100644 --- a/web/src/elements/ak-locale-context/helpers.ts +++ b/web/src/elements/ak-locale-context/helpers.ts @@ -1,59 +1,80 @@ import { globalAK } from "@goauthentik/common/global"; -import { LOCALES as RAW_LOCALES, enLocale } from "./definitions"; -import { AkLocale } from "./types"; +import { AKLocalDefinitions } from "./definitions.js"; +import { AKLocaleDefinition } from "./types.js"; export const DEFAULT_LOCALE = "en"; export const EVENT_REQUEST_LOCALE = "ak-request-locale"; -const TOMBSTONE = "⛼⛼tombstone⛼⛼"; +/** + * Find the locale definition for a given language code. + */ +export function findLocaleDefinition(languageCode: string): AKLocaleDefinition | null { + for (const locale of AKLocalDefinitions) { + if (locale.pattern.test(languageCode)) { + return locale; + } + } -// NOTE: This is the definition of the LOCALES table that most of the code uses. The 'definitions' -// file is relatively pure, but here we establish that we want the English locale to loaded when an -// application is first instantiated. - -export const LOCALES = RAW_LOCALES.map((locale) => - locale.code === "en" ? { ...locale, locale: async () => enLocale } : locale, -); - -export function getBestMatchLocale(locale: string): AkLocale | undefined { - return LOCALES.find((l) => l.match.test(locale)); + return null; } // This looks weird, but it's sensible: we have several candidates, and we want to find the first // one that has a supported locale. Then, from *that*, we have to extract that first supported // locale. -export function findSupportedLocale(candidates: string[]) { - const candidate = candidates.find((candidate: string) => getBestMatchLocale(candidate)); - return candidate ? getBestMatchLocale(candidate) : undefined; +export function findSupportedLocale(candidates: string[]): AKLocaleDefinition | null { + for (const candidate of candidates) { + const locale = findLocaleDefinition(candidate); + + if (locale) return locale; + } + + return null; } -export function localeCodeFromUrl(param = "locale") { - const url = new URL(window.location.href); - return url.searchParams.get(param) || ""; +export function localeCodeFromURL(param = "locale") { + const searchParams = new URLSearchParams(window.location.search); + + return searchParams.get(param); } -// Get all locales we can, in order -// - Global authentik settings (contains user settings) -// - URL parameter -// - A requested code passed in, if any -// - Navigator -// - Fallback (en) +function isLocaleCodeCandidate(input: unknown): input is string { + if (typeof input !== "string") return false; -const isLocaleCandidate = (v: unknown): v is string => - typeof v === "string" && v !== "" && v !== TOMBSTONE; + return !!input; +} -export function autoDetectLanguage(userReq = TOMBSTONE, brandReq = TOMBSTONE): string { - const localeCandidates: string[] = [ - localeCodeFromUrl("locale"), - userReq, - window.navigator?.language ?? TOMBSTONE, - brandReq, - globalAK()?.locale ?? TOMBSTONE, - DEFAULT_LOCALE, - ].filter(isLocaleCandidate); +/** + * Auto-detect the most appropriate locale. + * + * @remarks + * + * The order of precedence is: + * + * 1. URL parameter `locale`. + * 2. User's preferred locale, if any. + * 3. Browser's preferred locale, if any. + * 4. Brand's preferred locale, if any. + * 5. Default locale. + * + * @param requestedLanguageCode - The user's preferred locale, if any. + * @param brandLanguageCode - The brand's preferred locale, if any. + * + * @returns The most appropriate locale. + */ +export function autoDetectLanguage( + requestedLanguageCode?: string, + brandLanguageCode?: string, +): string { + const localeCandidates = [ + localeCodeFromURL("locale"), + requestedLanguageCode, + window.navigator?.language, + brandLanguageCode, + globalAK()?.locale, + ].filter(isLocaleCodeCandidate); const firstSupportedLocale = findSupportedLocale(localeCandidates); @@ -61,10 +82,11 @@ export function autoDetectLanguage(userReq = TOMBSTONE, brandReq = TOMBSTONE): s console.debug( `authentik/locale: No locale found for '[${localeCandidates}.join(',')]', falling back to ${DEFAULT_LOCALE}`, ); + return DEFAULT_LOCALE; } - return firstSupportedLocale.code; + return firstSupportedLocale.languageCode; } export default autoDetectLanguage; diff --git a/web/src/elements/ak-locale-context/types.ts b/web/src/elements/ak-locale-context/types.ts index 55147d2473..49897a4830 100644 --- a/web/src/elements/ak-locale-context/types.ts +++ b/web/src/elements/ak-locale-context/types.ts @@ -1,10 +1,21 @@ import type { LocaleModule } from "@lit/localize"; -export type LocaleRow = [string, RegExp, () => string, () => Promise]; +/** + * - ISO 639-1 code for the locale. + * - Pattern to match the user-supplied locale. + * - Human-readable label for the locale. + * - Locale loader. + */ +export type LocaleRow = [ + languageCode: string, + pattern: RegExp, + formatLabel: () => string, + fetch: () => Promise, +]; -export type AkLocale = { - code: string; - match: RegExp; - label: () => string; - locale: () => Promise; -}; +export interface AKLocaleDefinition { + languageCode: string; + pattern: RegExp; + formatLabel(): string; + fetch(): Promise; +} diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index cef4997f6b..46d8772b1a 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -4,7 +4,7 @@ import { CapabilitiesEnum, WithCapabilitiesConfig, } from "@goauthentik/elements/Interface/capabilitiesProvider"; -import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions"; +import { AKLocalDefinitions } from "@goauthentik/elements/ak-locale-context/definitions"; import "@goauthentik/elements/forms/FormElement"; import { BaseStage } from "@goauthentik/flow/stages/base"; @@ -199,15 +199,15 @@ ${prompt.initialValue} locale.code !== "debug"); + ? AKLocalDefinitions + : AKLocalDefinitions.filter((locale) => locale.languageCode !== "debug"); const options = locales.map( (locale) => html` `, );