Refactor i18n, make component use the hook directly
This commit is contained in:
parent
01fb89674c
commit
8d365dae53
@ -1,7 +1,6 @@
|
|||||||
import "keycloakify/tools/Object.fromEntries";
|
import "keycloakify/tools/Object.fromEntries";
|
||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
import { useConst } from "keycloakify/tools/useConst";
|
import { useConst } from "keycloakify/tools/useConst";
|
||||||
import { id } from "tsafe/id";
|
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import fallbackMessages from "./baseMessages/en";
|
import fallbackMessages from "./baseMessages/en";
|
||||||
import { getMessages } from "./baseMessages";
|
import { getMessages } from "./baseMessages";
|
||||||
@ -81,6 +80,13 @@ export type GenericI18n<MessageKey extends string> = {
|
|||||||
* See advancedMsg() but instead of returning a JSX.Element it returns a string.
|
* See advancedMsg() but instead of returning a JSX.Element it returns a string.
|
||||||
*/
|
*/
|
||||||
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
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 I18n = GenericI18n<MessageKey>;
|
export type I18n = GenericI18n<MessageKey>;
|
||||||
@ -90,7 +96,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
|||||||
}) {
|
}) {
|
||||||
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
|
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
|
||||||
|
|
||||||
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n; isTranslationsDownloadOngoing: boolean } {
|
function useI18n(params: { kcContext: KcContextLike }): I18n {
|
||||||
const { kcContext } = params;
|
const { kcContext } = params;
|
||||||
|
|
||||||
const partialI18n = useMemo(
|
const partialI18n = useMemo(
|
||||||
@ -129,6 +135,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
|||||||
const refHasStartedFetching = useConst(() => ({ current: false }));
|
const refHasStartedFetching = useConst(() => ({ current: false }));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (partialI18n.currentLanguageTag === fallbackLanguageTag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (refHasStartedFetching.current) {
|
if (refHasStartedFetching.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -137,39 +147,33 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
|||||||
|
|
||||||
refHasStartedFetching.current = true;
|
refHasStartedFetching.current = true;
|
||||||
|
|
||||||
(async () => {
|
getMessages(partialI18n.currentLanguageTag).then(messages => {
|
||||||
const messages = await getMessages(partialI18n.currentLanguageTag);
|
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setI18n({
|
setI18n({
|
||||||
...partialI18n,
|
...partialI18n,
|
||||||
...createI18nTranslationFunctions({ messages })
|
...createI18nTranslationFunctions({ messages }),
|
||||||
|
isFetchingTranslations: false
|
||||||
});
|
});
|
||||||
})();
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isActive = false;
|
isActive = false;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const pendingI18n = useMemo(() => {
|
const fallbackI18n = useMemo(
|
||||||
if (i18n !== undefined) {
|
(): I18n => ({
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return id<I18n>({
|
|
||||||
...partialI18n,
|
...partialI18n,
|
||||||
...createI18nTranslationFunctions({ messages: undefined })
|
...createI18nTranslationFunctions({ messages: undefined }),
|
||||||
});
|
isFetchingTranslations: partialI18n.currentLanguageTag !== fallbackLanguageTag
|
||||||
}, []);
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return i18n ?? fallbackI18n;
|
||||||
i18n: i18n ?? (assert(pendingI18n !== undefined), pendingI18n),
|
|
||||||
isTranslationsDownloadOngoing: i18n === undefined
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,2 +1,10 @@
|
|||||||
export type { I18n } from "./i18n";
|
export type { MessageKey } from "./i18n";
|
||||||
export { createUseI18n } from "./i18n";
|
import { createUseI18n } from "./i18n";
|
||||||
|
export { createUseI18n };
|
||||||
|
export { fallbackLanguageTag } from "./i18n";
|
||||||
|
|
||||||
|
const { useI18n, ofTypeI18n } = createUseI18n({});
|
||||||
|
|
||||||
|
export type I18n = typeof ofTypeI18n;
|
||||||
|
|
||||||
|
export { useI18n };
|
||||||
|
@ -3,7 +3,6 @@ import { assert, type Equals } from "tsafe/assert";
|
|||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import type { I18n } from "./i18n";
|
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
||||||
|
|
||||||
const Login = lazy(() => import("keycloakify/login/pages/Login"));
|
const Login = lazy(() => import("keycloakify/login/pages/Login"));
|
||||||
@ -41,7 +40,7 @@ const LoginResetOtp = lazy(() => import("keycloakify/login/pages/LoginResetOtp")
|
|||||||
const LoginX509Info = lazy(() => import("keycloakify/login/pages/LoginX509Info"));
|
const LoginX509Info = lazy(() => import("keycloakify/login/pages/LoginX509Info"));
|
||||||
const WebauthnError = lazy(() => import("keycloakify/login/pages/WebauthnError"));
|
const WebauthnError = lazy(() => import("keycloakify/login/pages/WebauthnError"));
|
||||||
|
|
||||||
type FallbackProps = PageProps<KcContext, I18n> & {
|
type FallbackProps = PageProps<KcContext> & {
|
||||||
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
|||||||
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||||
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import type { I18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
|
|
||||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
export default function Template(props: TemplateProps<KcContext>) {
|
||||||
const {
|
const {
|
||||||
displayInfo = false,
|
displayInfo = false,
|
||||||
displayMessage = true,
|
displayMessage = true,
|
||||||
@ -21,7 +21,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
documentTitle,
|
documentTitle,
|
||||||
bodyClassName,
|
bodyClassName,
|
||||||
kcContext,
|
kcContext,
|
||||||
i18n,
|
|
||||||
doUseDefaultCss,
|
doUseDefaultCss,
|
||||||
classes,
|
classes,
|
||||||
children
|
children
|
||||||
@ -29,7 +28,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
|
|
||||||
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
|
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
|
||||||
|
|
||||||
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = useI18n({ kcContext });
|
||||||
|
|
||||||
const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;
|
const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;
|
||||||
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import type { I18n } from "./i18n";
|
|
||||||
|
|
||||||
export type TemplateProps<
|
export type TemplateProps<KcContext extends KcContext.Common> = {
|
||||||
KcContext extends KcContext.Common,
|
|
||||||
I18nExtended extends I18n
|
|
||||||
> = {
|
|
||||||
kcContext: KcContext;
|
kcContext: KcContext;
|
||||||
i18n: I18nExtended;
|
|
||||||
doUseDefaultCss: boolean;
|
doUseDefaultCss: boolean;
|
||||||
classes?: Partial<Record<ClassKey, string>>;
|
classes?: Partial<Record<ClassKey, string>>;
|
||||||
|
|
||||||
|
@ -9,11 +9,10 @@ import {
|
|||||||
type FormFieldError
|
type FormFieldError
|
||||||
} from "keycloakify/login/lib/useUserProfileForm";
|
} from "keycloakify/login/lib/useUserProfileForm";
|
||||||
import type { Attribute } from "keycloakify/login/KcContext";
|
import type { Attribute } from "keycloakify/login/KcContext";
|
||||||
import type { I18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
|
|
||||||
export type UserProfileFormFieldsProps = {
|
export type UserProfileFormFieldsProps = {
|
||||||
kcContext: KcContextLike;
|
kcContext: KcContextLike;
|
||||||
i18n: I18n;
|
|
||||||
getClassName: (classKey: ClassKey) => string;
|
getClassName: (classKey: ClassKey) => string;
|
||||||
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
||||||
BeforeField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
|
BeforeField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import "keycloakify/tools/Object.fromEntries";
|
import "keycloakify/tools/Object.fromEntries";
|
||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useConst } from "keycloakify/tools/useConst";
|
|
||||||
import { id } from "tsafe/id";
|
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import fallbackMessages from "./baseMessages/en";
|
import messages_fallbackLanguage from "./baseMessages/en";
|
||||||
import { getMessages } from "./baseMessages";
|
import { getMessages } from "./baseMessages";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import { Reflect } from "tsafe/Reflect";
|
import { Reflect } from "tsafe/Reflect";
|
||||||
@ -20,7 +18,7 @@ export type KcContextLike = {
|
|||||||
|
|
||||||
assert<KcContext extends KcContextLike ? true : false>();
|
assert<KcContext extends KcContextLike ? true : false>();
|
||||||
|
|
||||||
export type MessageKey = keyof typeof fallbackMessages;
|
export type MessageKey = keyof typeof messages_fallbackLanguage;
|
||||||
|
|
||||||
export type GenericI18n<MessageKey extends string> = {
|
export type GenericI18n<MessageKey extends string> = {
|
||||||
/**
|
/**
|
||||||
@ -82,116 +80,143 @@ export type GenericI18n<MessageKey extends string> = {
|
|||||||
* See advancedMsg() but instead of returning a JSX.Element it returns a string.
|
* See advancedMsg() but instead of returning a JSX.Element it returns a string.
|
||||||
*/
|
*/
|
||||||
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
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 I18n = GenericI18n<MessageKey>;
|
function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } }) {
|
||||||
|
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
|
||||||
|
|
||||||
|
type Result = { i18n: I18n; prI18n_currentLanguage: Promise<I18n> | undefined };
|
||||||
|
|
||||||
|
const cachedResultByKcContext = new WeakMap<KcContextLike, Result>();
|
||||||
|
|
||||||
|
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<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> = {
|
||||||
|
currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
|
||||||
|
getChangeLocalUrl: 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<MessageKey, ExtraMessageKey>({
|
||||||
|
messages_fallbackLanguage,
|
||||||
|
extraMessages_fallbackLanguage: extraMessages[fallbackLanguageTag],
|
||||||
|
extraMessages: extraMessages[partialI18n.currentLanguageTag],
|
||||||
|
__localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
|
||||||
|
});
|
||||||
|
|
||||||
|
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag !== fallbackLanguageTag;
|
||||||
|
|
||||||
|
const result: Result = {
|
||||||
|
i18n: {
|
||||||
|
...partialI18n,
|
||||||
|
...createI18nTranslationFunctions({ messages: undefined }),
|
||||||
|
isFetchingTranslations: !isCurrentLanguageFallbackLanguage
|
||||||
|
},
|
||||||
|
prI18n_currentLanguage: isCurrentLanguageFallbackLanguage
|
||||||
|
? undefined
|
||||||
|
: (async () => {
|
||||||
|
const messages = await getMessages(partialI18n.currentLanguageTag);
|
||||||
|
|
||||||
|
const i18n_currentLanguage: I18n = {
|
||||||
|
...partialI18n,
|
||||||
|
...createI18nTranslationFunctions({ messages }),
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
export function createUseI18n<ExtraMessageKey extends string = never>(extraMessages: {
|
export function createUseI18n<ExtraMessageKey extends string = never>(extraMessages: {
|
||||||
[languageTag: string]: { [key in ExtraMessageKey]: string };
|
[languageTag: string]: { [key in ExtraMessageKey]: string };
|
||||||
}) {
|
}) {
|
||||||
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
|
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
|
||||||
|
|
||||||
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n; isTranslationsDownloadOngoing: boolean } {
|
const { getI18n } = createGetI18n(extraMessages);
|
||||||
|
|
||||||
|
function useI18n(params: { kcContext: KcContextLike }): I18n {
|
||||||
const { kcContext } = params;
|
const { kcContext } = params;
|
||||||
|
|
||||||
const partialI18n = useMemo(
|
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
|
||||||
(): Pick<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> => ({
|
|
||||||
currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
|
|
||||||
getChangeLocalUrl: newLanguageTag => {
|
|
||||||
const { locale } = kcContext;
|
|
||||||
|
|
||||||
assert(locale !== undefined, "Internationalization not enabled");
|
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
|
||||||
|
|
||||||
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 } = useMemo(
|
|
||||||
() =>
|
|
||||||
createI18nTranslationFunctionsFactory<MessageKey, ExtraMessageKey>({
|
|
||||||
fallbackMessages,
|
|
||||||
extraFallbackMessages: extraMessages[fallbackLanguageTag],
|
|
||||||
extraMessages: extraMessages[partialI18n.currentLanguageTag],
|
|
||||||
__localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [i18n, setI18n] = useState<I18n | undefined>(undefined);
|
|
||||||
|
|
||||||
const refHasStartedFetching = useConst(() => ({ current: false }));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refHasStartedFetching.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let isActive = true;
|
let isActive = true;
|
||||||
|
|
||||||
refHasStartedFetching.current = true;
|
prI18n_currentLanguage?.then(i18n => {
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const messages = await getMessages(partialI18n.currentLanguageTag);
|
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setI18n({
|
setI18n_toReturn(i18n);
|
||||||
...partialI18n,
|
});
|
||||||
...createI18nTranslationFunctions({ messages })
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isActive = false;
|
isActive = false;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const pendingI18n = useMemo(() => {
|
return i18n_toReturn;
|
||||||
if (i18n !== undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return id<I18n>({
|
|
||||||
...partialI18n,
|
|
||||||
...createI18nTranslationFunctions({ messages: undefined })
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
i18n: i18n ?? (assert(pendingI18n !== undefined), pendingI18n),
|
|
||||||
isTranslationsDownloadOngoing: i18n === undefined
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { useI18n, ofTypeI18n: Reflect<I18n>() };
|
||||||
useI18n,
|
|
||||||
ofTypeI18n: Reflect<I18n>()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Note exported only for hypothetical usage in non react framework */
|
function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
|
||||||
export function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
|
messages_fallbackLanguage: Record<MessageKey, string>;
|
||||||
fallbackMessages: Record<MessageKey, string>;
|
extraMessages_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
|
||||||
extraFallbackMessages: Record<ExtraMessageKey, string> | undefined;
|
|
||||||
extraMessages: Partial<Record<ExtraMessageKey, string>> | undefined;
|
extraMessages: Partial<Record<ExtraMessageKey, string>> | undefined;
|
||||||
__localizationRealmOverridesUserProfile: Record<string, string> | undefined;
|
__localizationRealmOverridesUserProfile: Record<string, string> | undefined;
|
||||||
}) {
|
}) {
|
||||||
const { __localizationRealmOverridesUserProfile, extraMessages } = params;
|
const { __localizationRealmOverridesUserProfile, extraMessages } = params;
|
||||||
|
|
||||||
const fallbackMessages = {
|
const messages_fallbackLanguage = {
|
||||||
...params.fallbackMessages,
|
...params.messages_fallbackLanguage,
|
||||||
...params.extraFallbackMessages
|
...params.extraMessages_fallbackLanguage
|
||||||
};
|
};
|
||||||
|
|
||||||
function createI18nTranslationFunctions(params: {
|
function createI18nTranslationFunctions(params: {
|
||||||
@ -205,7 +230,7 @@ export function createI18nTranslationFunctionsFactory<MessageKey extends string,
|
|||||||
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
|
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
|
||||||
const { key, args, doRenderAsHtml } = props;
|
const { key, args, doRenderAsHtml } = props;
|
||||||
|
|
||||||
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
|
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (messages_fallbackLanguage as any)[key];
|
||||||
|
|
||||||
if (messageOrUndefined === undefined) {
|
if (messageOrUndefined === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -1,2 +1,10 @@
|
|||||||
export type { I18n, MessageKey } from "./i18n";
|
export type { MessageKey } from "./i18n";
|
||||||
export { createUseI18n, fallbackLanguageTag } from "./i18n";
|
import { createUseI18n } from "./i18n";
|
||||||
|
export { createUseI18n };
|
||||||
|
export { fallbackLanguageTag } from "./i18n";
|
||||||
|
|
||||||
|
const { useI18n, ofTypeI18n } = createUseI18n({});
|
||||||
|
|
||||||
|
export type I18n = typeof ofTypeI18n;
|
||||||
|
|
||||||
|
export { useI18n };
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import { useI18n } from "../i18n";
|
||||||
|
|
||||||
export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>, I18n>) {
|
export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
const { getClassName } = useGetClassName({
|
const { getClassName } = useGetClassName({
|
||||||
doUseDefaultCss,
|
doUseDefaultCss,
|
||||||
@ -13,11 +13,11 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
|
|||||||
|
|
||||||
const { code } = kcContext;
|
const { code } = kcContext;
|
||||||
|
|
||||||
const { msg } = i18n;
|
const { msg } = useI18n({ kcContext });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
{...{ kcContext, doUseDefaultCss, classes }}
|
||||||
headerNode={code.success ? msg("codeSuccessTitle") : msg("codeErrorTitle", code.error)}
|
headerNode={code.success ? msg("codeSuccessTitle") : msg("codeErrorTitle", code.error)}
|
||||||
>
|
>
|
||||||
<div id="kc-code">
|
<div id="kc-code">
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import type { I18n } from "keycloakify/login/i18n";
|
|
||||||
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import type { KcContext } from "keycloakify/account/KcContext";
|
import type { KcContext } from "keycloakify/account/KcContext";
|
||||||
|
|
||||||
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
export type PageProps<NarowedKcContext = KcContext> = {
|
||||||
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||||
kcContext: NarowedKcContext;
|
kcContext: NarowedKcContext;
|
||||||
i18n: I18nExtended;
|
|
||||||
doUseDefaultCss: boolean;
|
doUseDefaultCss: boolean;
|
||||||
classes?: Partial<Record<ClassKey, string>>;
|
classes?: Partial<Record<ClassKey, string>>;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user