From cc700f0ba04db027726ff0b0b12aec78ed7a92c3 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 21 Sep 2024 21:33:57 +0200 Subject: [PATCH] Untrack account i18n, code will be generated automatically --- src/account/i18n/GenericI18n.tsx | 6 - src/account/i18n/i18n.tsx | 250 ------------------------------- src/account/i18n/index.ts | 5 - src/account/i18n/useI18n.tsx | 95 ------------ 4 files changed, 356 deletions(-) delete mode 100644 src/account/i18n/GenericI18n.tsx delete mode 100644 src/account/i18n/i18n.tsx delete mode 100644 src/account/i18n/index.ts delete mode 100644 src/account/i18n/useI18n.tsx diff --git a/src/account/i18n/GenericI18n.tsx b/src/account/i18n/GenericI18n.tsx deleted file mode 100644 index a640ece0..00000000 --- a/src/account/i18n/GenericI18n.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import type { GenericI18n_noJsx } from "./i18n"; - -export type GenericI18n = GenericI18n_noJsx & { - msg: (key: MessageKey, ...args: (string | undefined)[]) => JSX.Element; - advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element; -}; diff --git a/src/account/i18n/i18n.tsx b/src/account/i18n/i18n.tsx deleted file mode 100644 index 628705d7..00000000 --- a/src/account/i18n/i18n.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import "keycloakify/tools/Object.fromEntries"; -import { assert } from "tsafe/assert"; -import messages_defaultSet_fallbackLanguage from "./messages_defaultSet/en"; -import { fetchMessages_defaultSet } from "./messages_defaultSet"; -import type { KcContext } from "../KcContext"; -import { FALLBACK_LANGUAGE_TAG } from "keycloakify/bin/shared/constants"; -import { id } from "tsafe/id"; - -export type KcContextLike = { - locale?: { - currentLanguageTag: string; - supported: { languageTag: string; url: string; label: string }[]; - }; - "x-keycloakify": { - messages: Record; - }; -}; - -assert(); - -export type GenericI18n_noJsx = { - /** - * e.g: "en", "fr", "zh-CN" - * - * The current language - */ - currentLanguageTag: string; - /** - * Redirect to this url to change the language. - * After reload currentLanguageTag === newLanguageTag - */ - getChangeLocaleUrl: (newLanguageTag: string) => string; - /** - * e.g. "en" => "English", "fr" => "Français", ... - * - * Used to render a select that enable user to switch language. - * ex: https://user-images.githubusercontent.com/6702424/186044799-38801eec-4e89-483b-81dd-8e9233e8c0eb.png - * */ - labelBySupportedLanguageTag: Record; - /** - * - * Examples assuming currentLanguageTag === "en" - * { - * en: { - * "access-denied": "Access denied", - * "impersonateTitleHtml": "{0} Impersonate User", - * "bar": "Bar {0}" - * } - * } - * - * msgStr("access-denied") === "Access denied" - * msgStr("not-a-message-key") Throws an error - * msgStr("impersonateTitleHtml", "Foo") === "Foo Impersonate User" - * msgStr("${bar}", "c") === "Bar <strong>XXX</strong>" - * The html in the arg is partially escaped for security reasons, it might come from an untrusted source, it's not safe to render it as html. - */ - msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string; - /** - * This is meant to be used when the key argument is variable, something that might have been configured by the user - * in the Keycloak admin for example. - * - * Examples assuming currentLanguageTag === "en" - * { - * en: { - * "access-denied": "Access denied", - * } - * } - * - * advancedMsgStr("${access-denied}") === advancedMsgStr("access-denied") === msgStr("access-denied") === "Access denied" - * advancedMsgStr("${not-a-message-key}") === advancedMsgStr("not-a-message-key") === "not-a-message-key" - */ - advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string; - - /** - * Initially the messages are in english (fallback language). - * The translations in the current language are being fetched dynamically. - * This property is true while the translations are being fetched. - */ - isFetchingTranslations: boolean; -}; - -export type MessageKey_defaultSet = keyof typeof messages_defaultSet_fallbackLanguage; - -export function createGetI18n(messagesByLanguageTag_themeDefined: { - [languageTag: string]: { [key in MessageKey_themeDefined]: string }; -}) { - type I18n = GenericI18n_noJsx; - - type Result = { i18n: I18n; prI18n_currentLanguage: Promise | undefined }; - - const cachedResultByKcContext = new WeakMap(); - - function getI18n(params: { kcContext: KcContextLike }): Result { - const { kcContext } = params; - - use_cache: { - const cachedResult = cachedResultByKcContext.get(kcContext); - - if (cachedResult === undefined) { - break use_cache; - } - - return cachedResult; - } - - const partialI18n: Pick = { - currentLanguageTag: kcContext.locale?.currentLanguageTag ?? FALLBACK_LANGUAGE_TAG, - getChangeLocaleUrl: newLanguageTag => { - const { locale } = kcContext; - - assert(locale !== undefined, "Internationalization not enabled"); - - const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === newLanguageTag); - - assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`); - - return targetSupportedLocale.url; - }, - labelBySupportedLanguageTag: Object.fromEntries((kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])) - }; - - const { createI18nTranslationFunctions } = createI18nTranslationFunctionsFactory({ - messages_themeDefined: - messagesByLanguageTag_themeDefined[partialI18n.currentLanguageTag] ?? - messagesByLanguageTag_themeDefined[FALLBACK_LANGUAGE_TAG] ?? - (() => { - const firstLanguageTag = Object.keys(messagesByLanguageTag_themeDefined)[0]; - if (firstLanguageTag === undefined) { - return undefined; - } - return messagesByLanguageTag_themeDefined[firstLanguageTag]; - })(), - messages_fromKcServer: kcContext["x-keycloakify"].messages - }); - - const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag === FALLBACK_LANGUAGE_TAG; - - const result: Result = { - i18n: { - ...partialI18n, - ...createI18nTranslationFunctions({ - messages_defaultSet_currentLanguage: isCurrentLanguageFallbackLanguage ? messages_defaultSet_fallbackLanguage : undefined - }), - isFetchingTranslations: !isCurrentLanguageFallbackLanguage - }, - prI18n_currentLanguage: isCurrentLanguageFallbackLanguage - ? undefined - : (async () => { - const messages_defaultSet_currentLanguage = await fetchMessages_defaultSet(partialI18n.currentLanguageTag); - - const i18n_currentLanguage: I18n = { - ...partialI18n, - ...createI18nTranslationFunctions({ messages_defaultSet_currentLanguage }), - isFetchingTranslations: false - }; - - // NOTE: This promise.resolve is just because without it we TypeScript - // gives a Variable 'result' is used before being assigned. error - await Promise.resolve().then(() => { - result.i18n = i18n_currentLanguage; - result.prI18n_currentLanguage = undefined; - }); - - return i18n_currentLanguage; - })() - }; - - cachedResultByKcContext.set(kcContext, result); - - return result; - } - - return { getI18n }; -} - -function createI18nTranslationFunctionsFactory(params: { - messages_themeDefined: Record | undefined; - messages_fromKcServer: Record; -}) { - const { messages_themeDefined, messages_fromKcServer } = params; - - function createI18nTranslationFunctions(params: { - messages_defaultSet_currentLanguage: Partial> | undefined; - }): Pick, "msgStr" | "advancedMsgStr"> { - const { messages_defaultSet_currentLanguage } = params; - - function resolveMsg(props: { key: string; args: (string | undefined)[] }): string | undefined { - const { key, args } = props; - - const message = - id>(messages_fromKcServer)[key] ?? - id | undefined>(messages_themeDefined)?.[key] ?? - id | undefined>(messages_defaultSet_currentLanguage)?.[key] ?? - id>(messages_defaultSet_fallbackLanguage)[key]; - - if (message === undefined) { - return undefined; - } - - const startIndex = message - .match(/{[0-9]+}/g) - ?.map(g => g.match(/{([0-9]+)}/)![1]) - .map(indexStr => parseInt(indexStr)) - .sort((a, b) => a - b)[0]; - - if (startIndex === undefined) { - // No {0} in message (no arguments expected) - return message; - } - - let messageWithArgsInjected = message; - - args.forEach((arg, i) => { - if (arg === undefined) { - return; - } - - messageWithArgsInjected = messageWithArgsInjected.replace( - new RegExp(`\\{${i + startIndex}\\}`, "g"), - arg.replace(//g, ">") - ); - }); - - return messageWithArgsInjected; - } - - function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[] }): string { - const { key, args } = props; - - const match = key.match(/^\$\{(.+)\}$/); - - if (match === null) { - return key; - } - - return resolveMsg({ key: match[1], args }) ?? key; - } - - return { - msgStr: (key, ...args) => { - const resolvedMessage = resolveMsg({ key, args }); - assert(resolvedMessage !== undefined, `Message with key "${key}" not found`); - return resolvedMessage; - }, - advancedMsgStr: (key, ...args) => resolveMsgAdvanced({ key, args }) - }; - } - - return { createI18nTranslationFunctions }; -} diff --git a/src/account/i18n/index.ts b/src/account/i18n/index.ts deleted file mode 100644 index f492a960..00000000 --- a/src/account/i18n/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { GenericI18n } from "./GenericI18n"; -import type { MessageKey_defaultSet, KcContextLike } from "./i18n"; -export type { MessageKey_defaultSet, KcContextLike }; -export type I18n = GenericI18n; -export { createUseI18n } from "./useI18n"; diff --git a/src/account/i18n/useI18n.tsx b/src/account/i18n/useI18n.tsx deleted file mode 100644 index fb19f1c4..00000000 --- a/src/account/i18n/useI18n.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useEffect, useState } from "react"; -import { createGetI18n, type GenericI18n_noJsx, type KcContextLike, type MessageKey_defaultSet } from "./i18n"; -import { GenericI18n } from "./GenericI18n"; -import { Reflect } from "tsafe/Reflect"; - -export function createUseI18n(messagesByLanguageTag: { - [languageTag: string]: { [key in MessageKey_themeDefined]: string }; -}) { - type MessageKey = MessageKey_defaultSet | MessageKey_themeDefined; - - type I18n = GenericI18n; - - const { withJsx } = (() => { - const cache = new WeakMap, GenericI18n>(); - - function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element { - const { htmlString, msgKey } = params; - return ( -
- ); - } - - function withJsx(i18n_noJsx: GenericI18n_noJsx): I18n { - use_cache: { - const i18n = cache.get(i18n_noJsx); - - if (i18n === undefined) { - break use_cache; - } - - return i18n; - } - - const i18n: I18n = { - ...i18n_noJsx, - msg: (msgKey, ...args) => renderHtmlString({ htmlString: i18n_noJsx.msgStr(msgKey, ...args), msgKey }), - advancedMsg: (msgKey, ...args) => renderHtmlString({ htmlString: i18n_noJsx.advancedMsgStr(msgKey, ...args), msgKey }) - }; - - cache.set(i18n_noJsx, i18n); - - return i18n; - } - - return { withJsx }; - })(); - - add_style: { - const attributeName = "data-kc-i18n"; - - // Check if already exists in head - if (document.querySelector(`style[${attributeName}]`) !== null) { - break add_style; - } - - const styleElement = document.createElement("style"); - styleElement.attributes.setNamedItem(document.createAttribute(attributeName)); - (styleElement.textContent = `[data-kc-msg] { display: inline-block; }`), document.head.prepend(styleElement); - } - - const { getI18n } = createGetI18n(messagesByLanguageTag); - - function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } { - const { kcContext } = params; - - const { i18n, prI18n_currentLanguage } = getI18n({ kcContext }); - - const [i18n_toReturn, setI18n_toReturn] = useState(withJsx(i18n)); - - useEffect(() => { - let isActive = true; - - prI18n_currentLanguage?.then(i18n => { - if (!isActive) { - return; - } - - setI18n_toReturn(withJsx(i18n)); - }); - - return () => { - isActive = false; - }; - }, []); - - return { i18n: i18n_toReturn }; - } - - return { useI18n, ofTypeI18n: Reflect() }; -}