i18n rebuild from the ground up
This commit is contained in:
parent
cc8b2e72c1
commit
7e7071305f
@ -61,6 +61,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
|
"@types/memoizee": "^0.4.7",
|
||||||
"@types/node": "^17.0.25",
|
"@types/node": "^17.0.25",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
@ -77,6 +78,7 @@
|
|||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"cli-select": "^1.1.2",
|
"cli-select": "^1.1.2",
|
||||||
"evt": "2.0.0-beta.39",
|
"evt": "2.0.0-beta.39",
|
||||||
|
"memoizee": "^0.4.15",
|
||||||
"minimal-polyfills": "^2.2.1",
|
"minimal-polyfills": "^2.2.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"powerhooks": "^0.14.0",
|
"powerhooks": "^0.14.0",
|
||||||
|
@ -2,10 +2,10 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
|
|
||||||
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error } & KcProps) => {
|
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error } & KcProps) => {
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
const { message, client } = kcContext;
|
const { message, client } = kcContext;
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ import { Template } from "./Template";
|
|||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import { assert } from "../tools/assert";
|
import { assert } from "../tools/assert";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
|
|
||||||
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
|
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
assert(kcContext.message !== undefined);
|
assert(kcContext.message !== undefined);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { useState, memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
import type { FormEventHandler } from "react";
|
import type { FormEventHandler } from "react";
|
||||||
@ -10,7 +10,7 @@ import type { FormEventHandler } from "react";
|
|||||||
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login } & KcProps) => {
|
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login } & KcProps) => {
|
||||||
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm } & KcProps) => {
|
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm } & KcProps) => {
|
||||||
const { url, idpAlias } = kcContext;
|
const { url, idpAlias } = kcContext;
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
|
|
||||||
export const LoginIdpLinkEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail } & KcProps) => {
|
export const LoginIdpLinkEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail } & KcProps) => {
|
||||||
const { url, realm, brokerContext, idpAlias } = kcContext;
|
const { url, realm, brokerContext, idpAlias } = kcContext;
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect, memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { headInsert } from "../tools/headInsert";
|
import { headInsert } from "../tools/headInsert";
|
||||||
import { pathJoin } from "../tools/pathJoin";
|
import { pathJoin } from "../tools/pathJoin";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
@ -12,7 +12,7 @@ export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBas
|
|||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCleanedUp = false;
|
let isCleanedUp = false;
|
||||||
|
@ -2,12 +2,12 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
|
|
||||||
export const LoginPageExpired = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginPageExpired } & KcProps) => {
|
export const LoginPageExpired = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginPageExpired } & KcProps) => {
|
||||||
const { url } = kcContext;
|
const { url } = kcContext;
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginResetPassword } & KcProps) => {
|
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginResetPassword } & KcProps) => {
|
||||||
const { url, realm, auth } = kcContext;
|
const { url, realm, auth } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
export const LoginUpdatePassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdatePassword } & KcProps) => {
|
export const LoginUpdatePassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdatePassword } & KcProps) => {
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdateProfile } & KcProps) => {
|
export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdateProfile } & KcProps) => {
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
|
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
|
|
||||||
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail } & KcProps) => {
|
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail } & KcProps) => {
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
const { url } = kcContext;
|
const { url } = kcContext;
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register } & KcProps) => {
|
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register } & KcProps) => {
|
||||||
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { useMemo, memo, useEffect, useState, Fragment } from "react";
|
|||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
import type { ReactComponent } from "../tools/ReactComponent";
|
import type { ReactComponent } from "../tools/ReactComponent";
|
||||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||||
@ -11,7 +11,7 @@ import { useFormValidationSlice } from "../useFormValidationSlice";
|
|||||||
export const RegisterUserProfile = memo(({ kcContext, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile } & KcProps) => {
|
export const RegisterUserProfile = memo(({ kcContext, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile } & KcProps) => {
|
||||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { cx, css } = useCssAndCx();
|
const { cx, css } = useCssAndCx();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile
|
|||||||
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
|
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
|
||||||
const { cx, css } = useCssAndCx();
|
const { cx, css } = useCssAndCx();
|
||||||
|
|
||||||
const { advancedMsg } = useKcMessage();
|
const { advancedMsg } = getMsg(kcContext);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { useReducer, useEffect, memo } from "react";
|
import { useReducer, useEffect, memo } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg, getCurrentKcLanguageTag, changeLocale, getTagLabel } from "../i18n";
|
||||||
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
import type { KcLanguageTag } from "../i18n";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { assert } from "../tools/assert";
|
import { assert } from "../tools/assert";
|
||||||
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
|
||||||
import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
|
|
||||||
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
|
||||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||||
import { headInsert } from "../tools/headInsert";
|
import { headInsert } from "../tools/headInsert";
|
||||||
import { pathJoin } from "../tools/pathJoin";
|
import { pathJoin } from "../tools/pathJoin";
|
||||||
@ -51,36 +48,19 @@ export const Template = memo((props: TemplateProps) => {
|
|||||||
console.log("Rendering this page with react using keycloakify");
|
console.log("Rendering this page with react using keycloakify");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { msg } = useKcMessage();
|
const { msg } = getMsg(kcContext);
|
||||||
|
|
||||||
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
|
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [KcLanguageTag]) =>
|
||||||
|
changeLocale({
|
||||||
const onChangeLanguageClickFactory = useCallbackFactory(([languageTag]: [KcLanguageTag]) => setKcLanguageTag(languageTag));
|
kcContext,
|
||||||
|
kcLanguageTag,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
|
const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
|
||||||
|
|
||||||
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
|
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!realm.internationalizationEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(locale !== undefined);
|
|
||||||
|
|
||||||
const kcContext_kcLanguageTag = getBestMatchAmongKcLanguageTag(locale.current);
|
|
||||||
|
|
||||||
if (["error.ftl", "info.ftl", "login-page-expired.ftl"].indexOf(kcContext.pageId) >= 0) {
|
|
||||||
setKcLanguageTag(kcContext_kcLanguageTag);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kcLanguageTag !== kcContext_kcLanguageTag) {
|
|
||||||
window.location.href = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
|
|
||||||
}
|
|
||||||
}, [kcLanguageTag]);
|
|
||||||
|
|
||||||
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -158,13 +138,13 @@ export const Template = memo((props: TemplateProps) => {
|
|||||||
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
|
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
|
||||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||||
<a href="#" id="kc-current-locale-link">
|
<a href="#" id="kc-current-locale-link">
|
||||||
{getKcLanguageTagLabel(kcLanguageTag)}
|
{getTagLabel({ "kcLanguageTag": getCurrentKcLanguageTag(kcContext), kcContext })}
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
{locale.supported.map(({ languageTag }) => (
|
{locale.supported.map(({ languageTag }) => (
|
||||||
<li key={languageTag} className="kc-dropdown-item">
|
<li key={languageTag} className="kc-dropdown-item">
|
||||||
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
|
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
|
||||||
{getKcLanguageTagLabel(languageTag)}
|
{getTagLabel({ "kcLanguageTag": languageTag, kcContext })}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -1,12 +1,33 @@
|
|||||||
import { memo } from "react";
|
import { useReducer, useEffect, memo } from "react";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { getMsg } from "../i18n";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
import { kcMessages, getCurrentKcLanguageTag } from "../i18n";
|
||||||
|
import type { KcLanguageTag } from "../i18n";
|
||||||
|
|
||||||
|
/** Allow to avoid bundling the terms and download it on demand*/
|
||||||
|
export function useDownloadTerms(params: {
|
||||||
|
kcContext: KcContextBase;
|
||||||
|
downloadTermMarkdown: (params: { currentKcLanguageTag: KcLanguageTag }) => Promise<string>;
|
||||||
|
}) {
|
||||||
|
const { kcContext, downloadTermMarkdown } = params;
|
||||||
|
|
||||||
|
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentKcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
||||||
|
|
||||||
|
downloadTermMarkdown({ currentKcLanguageTag }).then(thermMarkdown => {
|
||||||
|
kcMessages[currentKcLanguageTag].termsText = thermMarkdown;
|
||||||
|
forceUpdate();
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
|
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
|
||||||
const { msg, msgStr } = useKcMessage();
|
const { msg, msgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
|
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
|
||||||
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
import type { KcLanguageTag } from "../i18n";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import type { MessageKey } from "../i18n/useKcMessage";
|
import type { MessageKey } from "../i18n";
|
||||||
import type { LanguageLabel } from "../i18n/KcLanguageTag";
|
|
||||||
|
|
||||||
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||||
|
|
||||||
@ -47,13 +46,10 @@ export declare namespace KcContextBase {
|
|||||||
locale?: {
|
locale?: {
|
||||||
supported: {
|
supported: {
|
||||||
url: string;
|
url: string;
|
||||||
|
label: string;
|
||||||
languageTag: KcLanguageTag;
|
languageTag: KcLanguageTag;
|
||||||
/** Is determined by languageTag. Ex: languageTag === "en" => label === "English"
|
|
||||||
* or getLanguageLabel(languageTag) === label
|
|
||||||
*/
|
|
||||||
//label: LanguageLabel;
|
|
||||||
}[];
|
}[];
|
||||||
current: LanguageLabel;
|
currentLanguageTag: KcLanguageTag;
|
||||||
};
|
};
|
||||||
auth?: {
|
auth?: {
|
||||||
showUsername: boolean;
|
showUsername: boolean;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import "minimal-polyfills/Object.fromEntries";
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
import type { KcContextBase, Attribute } from "../KcContextBase";
|
import type { KcContextBase, Attribute } from "../KcContextBase";
|
||||||
import { getEvtKcLanguage } from "../../i18n/useKcLanguageTag";
|
|
||||||
import { getKcLanguageTagLabel } from "../../i18n/KcLanguageTag";
|
|
||||||
//NOTE: Aside because we want to be able to import them from node
|
//NOTE: Aside because we want to be able to import them from node
|
||||||
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
@ -32,81 +30,100 @@ export const kcContextCommonMock: KcContextBase.Common = {
|
|||||||
},
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"supported": [
|
"supported": [
|
||||||
|
/* spell-checker: disable */
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
||||||
|
"label": "Deutsch",
|
||||||
"languageTag": "de",
|
"languageTag": "de",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||||
|
"label": "Norsk",
|
||||||
"languageTag": "no",
|
"languageTag": "no",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||||
|
"label": "Русский",
|
||||||
"languageTag": "ru",
|
"languageTag": "ru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||||
|
"label": "Svenska",
|
||||||
"languageTag": "sv",
|
"languageTag": "sv",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
||||||
|
"label": "Português (Brasil)",
|
||||||
"languageTag": "pt-BR",
|
"languageTag": "pt-BR",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
||||||
|
"label": "Lietuvių",
|
||||||
"languageTag": "lt",
|
"languageTag": "lt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||||
|
"label": "English",
|
||||||
"languageTag": "en",
|
"languageTag": "en",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||||
|
"label": "Italiano",
|
||||||
"languageTag": "it",
|
"languageTag": "it",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
||||||
|
"label": "Français",
|
||||||
"languageTag": "fr",
|
"languageTag": "fr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
||||||
|
"label": "中文简体",
|
||||||
"languageTag": "zh-CN",
|
"languageTag": "zh-CN",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
||||||
|
"label": "Español",
|
||||||
"languageTag": "es",
|
"languageTag": "es",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
||||||
|
"label": "Čeština",
|
||||||
"languageTag": "cs",
|
"languageTag": "cs",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||||
|
"label": "日本語",
|
||||||
"languageTag": "ja",
|
"languageTag": "ja",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
||||||
|
"label": "Slovenčina",
|
||||||
"languageTag": "sk",
|
"languageTag": "sk",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||||
|
"label": "Polski",
|
||||||
"languageTag": "pl",
|
"languageTag": "pl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||||
|
"label": "Català",
|
||||||
"languageTag": "ca",
|
"languageTag": "ca",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||||
|
"label": "Nederlands",
|
||||||
"languageTag": "nl",
|
"languageTag": "nl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
||||||
|
"label": "Türkçe",
|
||||||
"languageTag": "tr",
|
"languageTag": "tr",
|
||||||
},
|
},
|
||||||
|
/* spell-checker: enable */
|
||||||
],
|
],
|
||||||
//"current": null as any
|
"currentLanguageTag": "en",
|
||||||
"current": "English",
|
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"showUsername": false,
|
"showUsername": false,
|
||||||
@ -124,11 +141,6 @@ export const kcContextCommonMock: KcContextBase.Common = {
|
|||||||
"isAppInitiatedAction": false,
|
"isAppInitiatedAction": false,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(kcContextCommonMock.locale!, "current", {
|
|
||||||
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
|
|
||||||
"enumerable": true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const loginUrl = {
|
const loginUrl = {
|
||||||
...kcContextCommonMock.url,
|
...kcContextCommonMock.url,
|
||||||
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import { objectKeys } from "tsafe/objectKeys";
|
|
||||||
import { kcMessages } from "./kcMessages/login";
|
|
||||||
|
|
||||||
export type KcLanguageTag = keyof typeof kcMessages;
|
|
||||||
|
|
||||||
const kcLanguageByTagLabel = {
|
|
||||||
/* spell-checker: disable */
|
|
||||||
"es": "Español",
|
|
||||||
"it": "Italiano",
|
|
||||||
"fr": "Français",
|
|
||||||
"ca": "Català",
|
|
||||||
"en": "English",
|
|
||||||
"de": "Deutsch",
|
|
||||||
"no": "Norsk",
|
|
||||||
"pt-BR": "Português (Brasil)",
|
|
||||||
"ru": "Русский",
|
|
||||||
"sk": "Slovenčina",
|
|
||||||
"ja": "日本語",
|
|
||||||
"pl": "Polski",
|
|
||||||
"zh-CN": "中文简体",
|
|
||||||
"sv": "Svenska",
|
|
||||||
"lt": "Lietuvių",
|
|
||||||
"cs": "Čeština",
|
|
||||||
"nl": "Nederlands",
|
|
||||||
"tr": "Türkçe",
|
|
||||||
"da": "Dansk",
|
|
||||||
"hu": "Magyar",
|
|
||||||
/* spell-checker: enable */
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type LanguageLabel = typeof kcLanguageByTagLabel[keyof typeof kcLanguageByTagLabel];
|
|
||||||
|
|
||||||
export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
|
|
||||||
return kcLanguageByTagLabel[language] ?? language;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const kcLanguageTags = objectKeys(kcMessages);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
|
|
||||||
* it corresponds to: "fr".
|
|
||||||
* If there is no reasonable match it's guessed from navigator.language.
|
|
||||||
* If still no matches "en" is returned.
|
|
||||||
*/
|
|
||||||
export function getBestMatchAmongKcLanguageTag(languageLike: string): KcLanguageTag {
|
|
||||||
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
|
|
||||||
|
|
||||||
const kcLanguageTag = kcLanguageTags.find(
|
|
||||||
language =>
|
|
||||||
language.toLowerCase().includes(iso2LanguageLike) ||
|
|
||||||
getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (kcLanguageTag !== undefined) {
|
|
||||||
return kcLanguageTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (languageLike !== navigator.language) {
|
|
||||||
return getBestMatchAmongKcLanguageTag(navigator.language);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "en";
|
|
||||||
}
|
|
180
src/lib/i18n/index.tsx
Normal file
180
src/lib/i18n/index.tsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
|
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import memoize from "memoizee";
|
||||||
|
import { kcMessages as kcMessagesBase } from "./generated_kcMessages/15.0.2/login";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
|
||||||
|
export const kcMessages = {
|
||||||
|
...kcMessagesBase,
|
||||||
|
"en": {
|
||||||
|
...kcMessagesBase["en"],
|
||||||
|
"termsText": "⏳",
|
||||||
|
"shouldBeEqual": "{0} should be equal to {1}",
|
||||||
|
"shouldBeDifferent": "{0} should be different to {1}",
|
||||||
|
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||||
|
"mustBeAnInteger": "Must be an integer",
|
||||||
|
"notAValidOption": "Not a valid option",
|
||||||
|
},
|
||||||
|
"fr": {
|
||||||
|
...kcMessagesBase["fr"],
|
||||||
|
/* spell-checker: disable */
|
||||||
|
"shouldBeEqual": "{0} doit être egale à {1}",
|
||||||
|
"shouldBeDifferent": "{0} doit être différent de {1}",
|
||||||
|
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
||||||
|
"mustBeAnInteger": "Doit être un nombre entiers",
|
||||||
|
"notAValidOption": "N'est pas une option valide",
|
||||||
|
/* spell-checker: enable */
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KcLanguageTag = keyof typeof kcMessages;
|
||||||
|
|
||||||
|
type KcContextLike = { locale?: { currentLanguageTag: KcLanguageTag } };
|
||||||
|
|
||||||
|
export function getCurrentKcLanguageTag(kcContext: KcContextLike) {
|
||||||
|
return kcContext.locale?.currentLanguageTag ?? "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTagLabel(params: {
|
||||||
|
kcContext: {
|
||||||
|
locale?: {
|
||||||
|
supported: { languageTag: KcLanguageTag; label: string }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
kcLanguageTag: KcLanguageTag;
|
||||||
|
}): string {
|
||||||
|
const { kcContext, kcLanguageTag } = params;
|
||||||
|
|
||||||
|
const { locale } = kcContext;
|
||||||
|
|
||||||
|
assert(locale !== undefined, "Internationalization not enabled");
|
||||||
|
|
||||||
|
const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag);
|
||||||
|
|
||||||
|
assert(targetSupportedLocale !== undefined, `${kcLanguageTag} need to be enabled in Keycloak admin`);
|
||||||
|
|
||||||
|
return targetSupportedLocale.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeLocale(params: {
|
||||||
|
kcContext: {
|
||||||
|
locale?: {
|
||||||
|
supported: { languageTag: KcLanguageTag; url: string }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
kcLanguageTag: KcLanguageTag;
|
||||||
|
}): never {
|
||||||
|
const { kcContext, kcLanguageTag } = params;
|
||||||
|
|
||||||
|
const { locale } = kcContext;
|
||||||
|
|
||||||
|
assert(locale !== undefined, "Internationalization not enabled");
|
||||||
|
|
||||||
|
const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag);
|
||||||
|
|
||||||
|
assert(targetSupportedLocale !== undefined, `${kcLanguageTag} need to be enabled in Keycloak admin`);
|
||||||
|
|
||||||
|
window.location.href = targetSupportedLocale.url;
|
||||||
|
|
||||||
|
assert(false, "never");
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageKey = keyof typeof kcMessages["en"];
|
||||||
|
|
||||||
|
function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
||||||
|
key: Key;
|
||||||
|
args: (string | undefined)[];
|
||||||
|
kcLanguageTag: string;
|
||||||
|
doRenderMarkdown: DoRenderMarkdown;
|
||||||
|
}): Key extends MessageKey ? (DoRenderMarkdown extends true ? JSX.Element : string) : undefined {
|
||||||
|
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
||||||
|
|
||||||
|
let str = kcMessages[kcLanguageTag as any as "en"][key as MessageKey] ?? kcMessages["en"][key as MessageKey];
|
||||||
|
|
||||||
|
if (str === undefined) {
|
||||||
|
return undefined as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = (() => {
|
||||||
|
const startIndex = str
|
||||||
|
.match(/{[0-9]+}/g)
|
||||||
|
?.map(g => g.match(/{([0-9]+)}/)![1])
|
||||||
|
.map(indexStr => parseInt(indexStr))
|
||||||
|
.sort((a, b) => a - b)[0];
|
||||||
|
|
||||||
|
if (startIndex === undefined) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.forEach((arg, i) => {
|
||||||
|
if (arg === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
||||||
|
});
|
||||||
|
|
||||||
|
return str;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
doRenderMarkdown ? (
|
||||||
|
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
||||||
|
{str}
|
||||||
|
</ReactMarkdown>
|
||||||
|
) : (
|
||||||
|
str
|
||||||
|
)
|
||||||
|
) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
||||||
|
key: Key;
|
||||||
|
args: (string | undefined)[];
|
||||||
|
kcLanguageTag: string;
|
||||||
|
doRenderMarkdown: DoRenderMarkdown;
|
||||||
|
}): DoRenderMarkdown extends true ? JSX.Element : string {
|
||||||
|
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
||||||
|
|
||||||
|
const match = key.match(/^\$\{([^{]+)\}$/);
|
||||||
|
|
||||||
|
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||||
|
|
||||||
|
const out = resolveMsg({
|
||||||
|
"key": keyUnwrappedFromCurlyBraces,
|
||||||
|
args,
|
||||||
|
kcLanguageTag,
|
||||||
|
doRenderMarkdown,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the language is switched the page is reloaded, this may appear
|
||||||
|
* as a bug as you might notice that the language successfully switch before
|
||||||
|
* reload.
|
||||||
|
* However we need to tell Keycloak that the user have changed the language
|
||||||
|
* during login so we can retrieve the "local" field of the JWT encoded accessToken.
|
||||||
|
* https://user-images.githubusercontent.com/6702424/138096682-351bb61f-f24e-4caf-91b7-cca8cfa2cb58.mov
|
||||||
|
*
|
||||||
|
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied")
|
||||||
|
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This function is memoized, it always returns the same object for a given kcContext)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const getMsg = memoize((kcContext: KcContextLike) => {
|
||||||
|
const kcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"msgStr": (key: MessageKey, ...args: (string | undefined)[]): string => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
||||||
|
"msg": (key: MessageKey, ...args: (string | undefined)[]): JSX.Element => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
||||||
|
"advancedMsg": <Key extends string>(key: Key, ...args: (string | undefined)[]): JSX.Element =>
|
||||||
|
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
||||||
|
"advancedMsgStr": <Key extends string>(key: Key, ...args: (string | undefined)[]): string =>
|
||||||
|
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
||||||
|
};
|
||||||
|
});
|
@ -1,50 +0,0 @@
|
|||||||
import { kcMessages as kcMessagesBase } from "../generated_kcMessages/15.0.2/login";
|
|
||||||
import { Evt } from "evt";
|
|
||||||
import { objectKeys } from "tsafe/objectKeys";
|
|
||||||
|
|
||||||
const kcMessages = {
|
|
||||||
...kcMessagesBase,
|
|
||||||
"en": {
|
|
||||||
...kcMessagesBase["en"],
|
|
||||||
"shouldBeEqual": "{0} should be equal to {1}",
|
|
||||||
"shouldBeDifferent": "{0} should be different to {1}",
|
|
||||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
|
||||||
"mustBeAnInteger": "Must be an integer",
|
|
||||||
"notAValidOption": "Not a valid option",
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
...kcMessagesBase["fr"],
|
|
||||||
/* spell-checker: disable */
|
|
||||||
"shouldBeEqual": "{0} doit être egale à {1}",
|
|
||||||
"shouldBeDifferent": "{0} doit être différent de {1}",
|
|
||||||
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
|
||||||
"mustBeAnInteger": "Doit être un nombre entiers",
|
|
||||||
"notAValidOption": "N'est pas une option valide",
|
|
||||||
/* spell-checker: enable */
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());
|
|
||||||
|
|
||||||
(["termsText", "doAccept", "doDecline", "termsTitle"] as const).forEach(key =>
|
|
||||||
objectKeys(kcMessages).forEach(kcLanguage =>
|
|
||||||
Object.defineProperty(
|
|
||||||
kcMessages[kcLanguage],
|
|
||||||
key,
|
|
||||||
(() => {
|
|
||||||
let value = key === "termsText" ? "⏳" : kcMessages[kcLanguage][key];
|
|
||||||
|
|
||||||
return {
|
|
||||||
"enumerable": true,
|
|
||||||
"get": () => value,
|
|
||||||
"set": (newValue: string) => {
|
|
||||||
value = newValue;
|
|
||||||
Evt.asPostable(evtTermsUpdated).post();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
export { kcMessages };
|
|
@ -1,28 +0,0 @@
|
|||||||
import { createUseGlobalState } from "powerhooks/useGlobalState";
|
|
||||||
import { getKcContextFromWindow } from "../getKcContext/getKcContextFromWindow";
|
|
||||||
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
|
|
||||||
import type { StatefulEvt } from "powerhooks";
|
|
||||||
import { KcLanguageTag } from "./KcLanguageTag";
|
|
||||||
|
|
||||||
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
|
|
||||||
const wrap = createUseGlobalState(
|
|
||||||
"kcLanguageTag",
|
|
||||||
() => {
|
|
||||||
const kcContext = getKcContextFromWindow();
|
|
||||||
|
|
||||||
const languageLike = kcContext?.locale?.current ?? (typeof navigator === "undefined" ? undefined : navigator.language);
|
|
||||||
|
|
||||||
if (languageLike === undefined) {
|
|
||||||
return "en";
|
|
||||||
}
|
|
||||||
|
|
||||||
return getBestMatchAmongKcLanguageTag(languageLike);
|
|
||||||
},
|
|
||||||
{ "persistance": "localStorage" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export const { useKcLanguageTag } = wrap;
|
|
||||||
|
|
||||||
export function getEvtKcLanguage(): StatefulEvt<KcLanguageTag> {
|
|
||||||
return wrap.evtKcLanguageTag;
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
import "minimal-polyfills/Object.fromEntries";
|
|
||||||
import { useReducer } from "react";
|
|
||||||
import { useKcLanguageTag } from "./useKcLanguageTag";
|
|
||||||
import { kcMessages, evtTermsUpdated } from "./kcMessages/login";
|
|
||||||
import { useEvt } from "evt/hooks";
|
|
||||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
|
||||||
import ReactMarkdown from "react-markdown";
|
|
||||||
import { useGuaranteedMemo } from "powerhooks/useGuaranteedMemo";
|
|
||||||
|
|
||||||
export { kcMessages };
|
|
||||||
|
|
||||||
export type MessageKey = keyof typeof kcMessages["en"];
|
|
||||||
|
|
||||||
function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
|
||||||
key: Key;
|
|
||||||
args: (string | undefined)[];
|
|
||||||
kcLanguageTag: string;
|
|
||||||
doRenderMarkdown: DoRenderMarkdown;
|
|
||||||
}): Key extends MessageKey ? (DoRenderMarkdown extends true ? JSX.Element : string) : undefined {
|
|
||||||
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
|
||||||
|
|
||||||
let str = kcMessages[kcLanguageTag as any as "en"][key as MessageKey] ?? kcMessages["en"][key as MessageKey];
|
|
||||||
|
|
||||||
if (str === undefined) {
|
|
||||||
return undefined as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
str = (() => {
|
|
||||||
const startIndex = str
|
|
||||||
.match(/{[0-9]+}/g)
|
|
||||||
?.map(g => g.match(/{([0-9]+)}/)![1])
|
|
||||||
.map(indexStr => parseInt(indexStr))
|
|
||||||
.sort((a, b) => a - b)[0];
|
|
||||||
|
|
||||||
if (startIndex === undefined) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.forEach((arg, i) => {
|
|
||||||
if (arg === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
str = str.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
|
||||||
});
|
|
||||||
|
|
||||||
return str;
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (
|
|
||||||
doRenderMarkdown ? (
|
|
||||||
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
|
||||||
{str}
|
|
||||||
</ReactMarkdown>
|
|
||||||
) : (
|
|
||||||
str
|
|
||||||
)
|
|
||||||
) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
|
||||||
key: Key;
|
|
||||||
args: (string | undefined)[];
|
|
||||||
kcLanguageTag: string;
|
|
||||||
doRenderMarkdown: DoRenderMarkdown;
|
|
||||||
}): DoRenderMarkdown extends true ? JSX.Element : string {
|
|
||||||
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
|
||||||
|
|
||||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
|
||||||
|
|
||||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
|
||||||
|
|
||||||
const out = resolveMsg({
|
|
||||||
"key": keyUnwrappedFromCurlyBraces,
|
|
||||||
args,
|
|
||||||
kcLanguageTag,
|
|
||||||
doRenderMarkdown,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the language is switched the page is reloaded, this may appear
|
|
||||||
* as a bug as you might notice that the language successfully switch before
|
|
||||||
* reload.
|
|
||||||
* However we need to tell Keycloak that the user have changed the language
|
|
||||||
* during login so we can retrieve the "local" field of the JWT encoded accessToken.
|
|
||||||
* https://user-images.githubusercontent.com/6702424/138096682-351bb61f-f24e-4caf-91b7-cca8cfa2cb58.mov
|
|
||||||
*
|
|
||||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied")
|
|
||||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function useKcMessage() {
|
|
||||||
const { kcLanguageTag } = useKcLanguageTag();
|
|
||||||
|
|
||||||
const [trigger, forceUpdate] = useReducer((counter: number) => counter + 1, 0);
|
|
||||||
|
|
||||||
useEvt(ctx => evtTermsUpdated.attach(ctx, forceUpdate), []);
|
|
||||||
|
|
||||||
return useGuaranteedMemo(
|
|
||||||
() => ({
|
|
||||||
"msgStr": (key: MessageKey, ...args: (string | undefined)[]): string =>
|
|
||||||
resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
|
||||||
"msg": (key: MessageKey, ...args: (string | undefined)[]): JSX.Element =>
|
|
||||||
resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
|
||||||
"advancedMsg": <Key extends string>(key: Key, ...args: (string | undefined)[]): JSX.Element =>
|
|
||||||
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
|
||||||
"advancedMsgStr": <Key extends string>(key: Key, ...args: (string | undefined)[]): string =>
|
|
||||||
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
|
||||||
}),
|
|
||||||
[kcLanguageTag, trigger],
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,18 +1,10 @@
|
|||||||
export * from "./getKcContext";
|
export * from "./getKcContext";
|
||||||
|
|
||||||
export * from "./i18n/KcLanguageTag";
|
export * from "./i18n";
|
||||||
export * from "./i18n/useKcLanguageTag";
|
|
||||||
export * from "./i18n/useKcMessage";
|
export { useDownloadTerms } from "./components/Terms";
|
||||||
export * from "./i18n/kcMessages/login";
|
|
||||||
|
|
||||||
export * from "./components/KcProps";
|
export * from "./components/KcProps";
|
||||||
export * from "./components/Login";
|
|
||||||
export * from "./components/Template";
|
|
||||||
export * from "./components/KcApp";
|
|
||||||
export * from "./components/Info";
|
|
||||||
export * from "./components/Error";
|
|
||||||
export * from "./components/LoginResetPassword";
|
|
||||||
export * from "./components/LoginVerifyEmail";
|
|
||||||
export * from "./keycloakJsAdapter";
|
export * from "./keycloakJsAdapter";
|
||||||
export * from "./useFormValidationSlice";
|
export * from "./useFormValidationSlice";
|
||||||
|
|
||||||
|
@ -1,35 +1,30 @@
|
|||||||
import "./tools/Array.prototype.every";
|
import "./tools/Array.prototype.every";
|
||||||
import { useMemo, useReducer, Fragment } from "react";
|
import { useMemo, useReducer, Fragment } from "react";
|
||||||
import type { KcContextBase, Validators, Attribute } from "./getKcContext/KcContextBase";
|
import type { KcContextBase, Validators, Attribute } from "./getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "./i18n/useKcMessage";
|
import { getMsg } from "./i18n";
|
||||||
|
import type { KcLanguageTag } from "./i18n";
|
||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import type { MessageKey } from "./i18n/useKcMessage";
|
import type { MessageKey } from "./i18n";
|
||||||
import { emailRegexp } from "./tools/emailRegExp";
|
import { emailRegexp } from "./tools/emailRegExp";
|
||||||
|
|
||||||
export type KcContextLike = {
|
|
||||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
|
||||||
attributes: { name: string; value?: string; validators: Validators }[];
|
|
||||||
passwordRequired: boolean;
|
|
||||||
realm: { registrationEmailAsUsername: boolean };
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useGetErrors(params: {
|
export function useGetErrors(params: {
|
||||||
kcContext: {
|
kcContext: {
|
||||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
||||||
profile: {
|
profile: {
|
||||||
attributes: { name: string; value?: string; validators: Validators }[];
|
attributes: { name: string; value?: string; validators: Validators }[];
|
||||||
};
|
};
|
||||||
|
locale?: { currentLanguageTag: KcLanguageTag };
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { kcContext } = params;
|
||||||
kcContext: {
|
|
||||||
messagesPerField,
|
|
||||||
profile: { attributes },
|
|
||||||
},
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
const { msg, msgStr, advancedMsg, advancedMsgStr } = useKcMessage();
|
const {
|
||||||
|
messagesPerField,
|
||||||
|
profile: { attributes },
|
||||||
|
} = kcContext;
|
||||||
|
|
||||||
|
const { msg, msgStr, advancedMsg, advancedMsgStr } = getMsg(kcContext);
|
||||||
|
|
||||||
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
|
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
|
||||||
const { name, fieldValueByAttributeName } = params;
|
const { name, fieldValueByAttributeName } = params;
|
||||||
|
@ -219,6 +219,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@types/unist" "*"
|
||||||
|
|
||||||
|
"@types/memoizee@^0.4.7":
|
||||||
|
version "0.4.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.7.tgz#fa0025b5ea1c8acb85fd52a76982c5a096c2c91e"
|
||||||
|
integrity sha512-EMtvWnRC/W0KYmXxbPJAMg/M1OXkFboSpd/IzkNMDXfoFPE4uom1RHFl8q7m/4R0y9eG1yaoaIPiMHk0vMA0eA==
|
||||||
|
|
||||||
"@types/node@^17.0.25":
|
"@types/node@^17.0.25":
|
||||||
version "17.0.25"
|
version "17.0.25"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user