keycloakify 5.4.4 → 6.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kcMessages.d.ts +1 -0
- package/kcMessages.js +14 -0
- package/kcMessages.js.map +1 -0
- package/lib/components/Error.d.ts +3 -1
- package/lib/components/Error.js +3 -4
- package/lib/components/Error.js.map +1 -1
- package/lib/components/Info.d.ts +3 -1
- package/lib/components/Info.js +3 -4
- package/lib/components/Info.js.map +1 -1
- package/lib/components/KcApp.d.ts +3 -1
- package/lib/components/KcApp.js +15 -15
- package/lib/components/KcApp.js.map +1 -1
- package/lib/components/Login.d.ts +3 -1
- package/lib/components/Login.js +3 -4
- package/lib/components/Login.js.map +1 -1
- package/lib/components/LoginIdpLinkConfirm.d.ts +3 -1
- package/lib/components/LoginIdpLinkConfirm.js +3 -4
- package/lib/components/LoginIdpLinkConfirm.js.map +1 -1
- package/lib/components/LoginIdpLinkEmail.d.ts +3 -1
- package/lib/components/LoginIdpLinkEmail.js +3 -4
- package/lib/components/LoginIdpLinkEmail.js.map +1 -1
- package/lib/components/LoginOtp.d.ts +3 -1
- package/lib/components/LoginOtp.js +3 -4
- package/lib/components/LoginOtp.js.map +1 -1
- package/lib/components/LoginPageExpired.d.ts +3 -1
- package/lib/components/LoginPageExpired.js +3 -4
- package/lib/components/LoginPageExpired.js.map +1 -1
- package/lib/components/LoginResetPassword.d.ts +3 -1
- package/lib/components/LoginResetPassword.js +3 -4
- package/lib/components/LoginResetPassword.js.map +1 -1
- package/lib/components/LoginUpdatePassword.d.ts +3 -1
- package/lib/components/LoginUpdatePassword.js +3 -4
- package/lib/components/LoginUpdatePassword.js.map +1 -1
- package/lib/components/LoginUpdateProfile.d.ts +3 -1
- package/lib/components/LoginUpdateProfile.js +3 -4
- package/lib/components/LoginUpdateProfile.js.map +1 -1
- package/lib/components/LoginVerifyEmail.d.ts +3 -1
- package/lib/components/LoginVerifyEmail.js +3 -4
- package/lib/components/LoginVerifyEmail.js.map +1 -1
- package/lib/components/Register.d.ts +3 -1
- package/lib/components/Register.js +3 -4
- package/lib/components/Register.js.map +1 -1
- package/lib/components/RegisterUserProfile.d.ts +3 -1
- package/lib/components/RegisterUserProfile.js +6 -6
- package/lib/components/RegisterUserProfile.js.map +1 -1
- package/lib/components/Template.d.ts +2 -0
- package/lib/components/Template.js +2 -2
- package/lib/components/Template.js.map +1 -1
- package/lib/components/Terms.d.ts +4 -2
- package/lib/components/Terms.js +8 -8
- package/lib/components/Terms.js.map +1 -1
- package/lib/i18n/index.d.ts +17 -5248
- package/lib/i18n/index.js +76 -51
- package/lib/i18n/index.js.map +1 -1
- package/lib/i18n/kcMessages.d.ts +5222 -0
- package/lib/i18n/kcMessages.js +19 -0
- package/lib/i18n/kcMessages.js.map +1 -0
- package/lib/tools/createObjectThatThrowsIfAccessed.d.ts +3 -0
- package/lib/tools/createObjectThatThrowsIfAccessed.js +39 -0
- package/lib/tools/createObjectThatThrowsIfAccessed.js.map +1 -0
- package/lib/useFormValidationSlice.d.ts +3 -0
- package/lib/useFormValidationSlice.js +4 -4
- package/lib/useFormValidationSlice.js.map +1 -1
- package/package.json +13 -2
- package/src/kcMessages.ts +1 -0
- package/src/lib/components/Error.tsx +4 -4
- package/src/lib/components/Info.tsx +5 -5
- package/src/lib/components/KcApp.tsx +16 -15
- package/src/lib/components/Login.tsx +4 -4
- package/src/lib/components/LoginIdpLinkConfirm.tsx +39 -37
- package/src/lib/components/LoginIdpLinkEmail.tsx +27 -25
- package/src/lib/components/LoginOtp.tsx +4 -4
- package/src/lib/components/LoginPageExpired.tsx +31 -29
- package/src/lib/components/LoginResetPassword.tsx +60 -53
- package/src/lib/components/LoginUpdatePassword.tsx +98 -96
- package/src/lib/components/LoginUpdateProfile.tsx +100 -92
- package/src/lib/components/LoginVerifyEmail.tsx +25 -23
- package/src/lib/components/Register.tsx +4 -4
- package/src/lib/components/RegisterUserProfile.tsx +68 -55
- package/src/lib/components/Template.tsx +5 -3
- package/src/lib/components/Terms.tsx +14 -8
- package/src/lib/i18n/index.tsx +76 -49
- package/src/lib/i18n/kcMessages.ts +24 -0
- package/src/lib/tools/createObjectThatThrowsIfAccessed.ts +18 -0
- package/src/lib/useFormValidationSlice.tsx +7 -4
@@ -2,19 +2,19 @@ import { memo } from "react";
|
|
2
2
|
import { Template } from "./Template";
|
3
3
|
import type { KcProps } from "./KcProps";
|
4
4
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
5
|
-
import {
|
5
|
+
import type { I18n } from "../i18n";
|
6
6
|
import { useCssAndCx } from "tss-react";
|
7
7
|
|
8
|
-
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register } & KcProps) => {
|
8
|
+
export const Register = memo(({ kcContext, useI18n, ...props }: { kcContext: KcContextBase.Register; useI18n: () => I18n } & KcProps) => {
|
9
9
|
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
10
10
|
|
11
|
-
const { msg, msgStr } =
|
11
|
+
const { msg, msgStr } = useI18n();
|
12
12
|
|
13
13
|
const { cx } = useCssAndCx();
|
14
14
|
|
15
15
|
return (
|
16
16
|
<Template
|
17
|
-
{...{ kcContext, ...props }}
|
17
|
+
{...{ kcContext, useI18n, ...props }}
|
18
18
|
doFetchDefaultThemeResources={true}
|
19
19
|
headerNode={msg("registerTitle")}
|
20
20
|
formNode={
|
@@ -2,79 +2,91 @@ import { useMemo, memo, useEffect, useState, Fragment } from "react";
|
|
2
2
|
import { Template } from "./Template";
|
3
3
|
import type { KcProps } from "./KcProps";
|
4
4
|
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
5
|
-
import {
|
5
|
+
import type { I18n } from "../i18n";
|
6
6
|
import { useCssAndCx } from "tss-react";
|
7
7
|
import type { ReactComponent } from "../tools/ReactComponent";
|
8
8
|
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
9
9
|
import { useFormValidationSlice } from "../useFormValidationSlice";
|
10
10
|
|
11
|
-
export const RegisterUserProfile = memo(
|
12
|
-
|
11
|
+
export const RegisterUserProfile = memo(
|
12
|
+
({ kcContext, useI18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; useI18n: () => I18n } & KcProps) => {
|
13
|
+
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
13
14
|
|
14
|
-
|
15
|
+
const { msg, msgStr } = useI18n();
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
const props = useMemo(
|
19
|
-
() => ({
|
20
|
-
...props_,
|
21
|
-
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 })),
|
22
|
-
}),
|
23
|
-
[cx, css],
|
24
|
-
);
|
25
|
-
|
26
|
-
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
17
|
+
const { cx, css } = useCssAndCx();
|
27
18
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
19
|
+
const props = useMemo(
|
20
|
+
() => ({
|
21
|
+
...props_,
|
22
|
+
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 })),
|
23
|
+
}),
|
24
|
+
[cx, css],
|
25
|
+
);
|
26
|
+
|
27
|
+
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
28
|
+
|
29
|
+
return (
|
30
|
+
<Template
|
31
|
+
{...{ kcContext, useI18n, ...props }}
|
32
|
+
displayMessage={messagesPerField.exists("global")}
|
33
|
+
displayRequiredFields={true}
|
34
|
+
doFetchDefaultThemeResources={true}
|
35
|
+
headerNode={msg("registerTitle")}
|
36
|
+
formNode={
|
37
|
+
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
38
|
+
<UserProfileFormFields
|
39
|
+
kcContext={kcContext}
|
40
|
+
onIsFormSubmittableValueChange={setIsFomSubmittable}
|
41
|
+
useI18n={useI18n}
|
42
|
+
{...props}
|
43
|
+
/>
|
44
|
+
{recaptchaRequired && (
|
45
|
+
<div className="form-group">
|
46
|
+
<div className={cx(props.kcInputWrapperClass)}>
|
47
|
+
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
|
48
|
+
</div>
|
42
49
|
</div>
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
</
|
50
|
+
)}
|
51
|
+
<div className={cx(props.kcFormGroupClass)}>
|
52
|
+
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
53
|
+
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
54
|
+
<span>
|
55
|
+
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
56
|
+
</span>
|
57
|
+
</div>
|
51
58
|
</div>
|
52
|
-
</div>
|
53
59
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
61
|
+
<input
|
62
|
+
className={cx(
|
63
|
+
props.kcButtonClass,
|
64
|
+
props.kcButtonPrimaryClass,
|
65
|
+
props.kcButtonBlockClass,
|
66
|
+
props.kcButtonLargeClass,
|
67
|
+
)}
|
68
|
+
type="submit"
|
69
|
+
value={msgStr("doRegister")}
|
70
|
+
disabled={!isFomSubmittable}
|
71
|
+
/>
|
72
|
+
</div>
|
61
73
|
</div>
|
62
|
-
</
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile } & KcProps &
|
74
|
+
</form>
|
75
|
+
}
|
76
|
+
/>
|
77
|
+
);
|
78
|
+
},
|
79
|
+
);
|
80
|
+
|
81
|
+
type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile; useI18n: () => I18n } & KcProps &
|
70
82
|
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
|
71
83
|
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
72
84
|
};
|
73
85
|
|
74
|
-
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
|
86
|
+
const UserProfileFormFields = memo(({ kcContext, useI18n, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
|
75
87
|
const { cx, css } = useCssAndCx();
|
76
88
|
|
77
|
-
const { advancedMsg } =
|
89
|
+
const { advancedMsg } = useI18n();
|
78
90
|
|
79
91
|
const {
|
80
92
|
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
@@ -82,6 +94,7 @@ const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange,
|
|
82
94
|
attributesWithPassword,
|
83
95
|
} = useFormValidationSlice({
|
84
96
|
kcContext,
|
97
|
+
useI18n,
|
85
98
|
});
|
86
99
|
|
87
100
|
useEffect(() => {
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { useReducer, useEffect, memo } from "react";
|
2
2
|
import type { ReactNode } from "react";
|
3
|
-
import {
|
3
|
+
import { getCurrentKcLanguageTag, changeLocale, getTagLabel } from "../i18n";
|
4
|
+
import type { I18n } from "../i18n";
|
4
5
|
import type { KcLanguageTag } from "../i18n";
|
5
6
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
6
7
|
import { assert } from "../tools/assert";
|
@@ -25,7 +26,7 @@ export type TemplateProps = {
|
|
25
26
|
* to avoid pulling the default theme assets.
|
26
27
|
*/
|
27
28
|
doFetchDefaultThemeResources: boolean;
|
28
|
-
} & { kcContext: KcContextBase } & KcTemplateProps;
|
29
|
+
} & { kcContext: KcContextBase; useI18n: () => I18n } & KcTemplateProps;
|
29
30
|
|
30
31
|
export const Template = memo((props: TemplateProps) => {
|
31
32
|
const {
|
@@ -40,6 +41,7 @@ export const Template = memo((props: TemplateProps) => {
|
|
40
41
|
infoNode = null,
|
41
42
|
kcContext,
|
42
43
|
doFetchDefaultThemeResources,
|
44
|
+
useI18n,
|
43
45
|
} = props;
|
44
46
|
|
45
47
|
const { cx } = useCssAndCx();
|
@@ -48,7 +50,7 @@ export const Template = memo((props: TemplateProps) => {
|
|
48
50
|
console.log("Rendering this page with react using keycloakify");
|
49
51
|
}, []);
|
50
52
|
|
51
|
-
const { msg } =
|
53
|
+
const { msg } = useI18n();
|
52
54
|
|
53
55
|
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [KcLanguageTag]) =>
|
54
56
|
changeLocale({
|
@@ -2,32 +2,38 @@ import { useReducer, useEffect, memo } from "react";
|
|
2
2
|
import { Template } from "./Template";
|
3
3
|
import type { KcProps } from "./KcProps";
|
4
4
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
5
|
-
import { getMsg } from "../i18n";
|
6
5
|
import { useCssAndCx } from "tss-react";
|
7
|
-
import {
|
8
|
-
import type { KcLanguageTag } from "../i18n";
|
6
|
+
import { getCurrentKcLanguageTag } from "../i18n";
|
7
|
+
import type { KcLanguageTag, I18n } from "../i18n";
|
9
8
|
|
10
9
|
/** Allow to avoid bundling the terms and download it on demand*/
|
11
10
|
export function useDownloadTerms(params: {
|
12
11
|
kcContext: KcContextBase;
|
13
12
|
downloadTermMarkdown: (params: { currentKcLanguageTag: KcLanguageTag }) => Promise<string>;
|
13
|
+
useI18n: () => I18n;
|
14
14
|
}) {
|
15
|
-
const { kcContext, downloadTermMarkdown } = params;
|
15
|
+
const { kcContext, downloadTermMarkdown, useI18n } = params;
|
16
16
|
|
17
17
|
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
18
18
|
|
19
|
+
const { evtKcMessages } = useI18n();
|
20
|
+
|
19
21
|
useEffect(() => {
|
20
22
|
const currentKcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
21
23
|
|
22
24
|
downloadTermMarkdown({ currentKcLanguageTag }).then(thermMarkdown => {
|
23
|
-
|
25
|
+
evtKcMessages.$attachOnce(
|
26
|
+
kcMessages => (kcMessages !== undefined ? [kcMessages] : null),
|
27
|
+
kcMessages => (kcMessages[currentKcLanguageTag].termsText = thermMarkdown),
|
28
|
+
);
|
29
|
+
|
24
30
|
forceUpdate();
|
25
31
|
});
|
26
32
|
}, []);
|
27
33
|
}
|
28
34
|
|
29
|
-
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
|
30
|
-
const { msg, msgStr } =
|
35
|
+
export const Terms = memo(({ kcContext, useI18n, ...props }: { kcContext: KcContextBase.Terms; useI18n: () => I18n } & KcProps) => {
|
36
|
+
const { msg, msgStr } = useI18n();
|
31
37
|
|
32
38
|
const { cx } = useCssAndCx();
|
33
39
|
|
@@ -35,7 +41,7 @@ export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.T
|
|
35
41
|
|
36
42
|
return (
|
37
43
|
<Template
|
38
|
-
{...{ kcContext, ...props }}
|
44
|
+
{...{ kcContext, useI18n, ...props }}
|
39
45
|
doFetchDefaultThemeResources={true}
|
40
46
|
displayMessage={false}
|
41
47
|
headerNode={msg("termsTitle")}
|
package/src/lib/i18n/index.tsx
CHANGED
@@ -1,35 +1,18 @@
|
|
1
1
|
import "minimal-polyfills/Object.fromEntries";
|
2
2
|
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
3
3
|
import ReactMarkdown from "react-markdown";
|
4
|
-
import
|
5
|
-
import { kcMessages as kcMessagesBase } from "./generated_kcMessages/15.0.2/login";
|
4
|
+
import type { kcMessages as t_kcMessages } from "./kcMessages";
|
6
5
|
import { assert } from "tsafe/assert";
|
7
6
|
import type { Equals } from "tsafe";
|
7
|
+
import { createObjectThatThrowsIfAccessed } from "../tools/createObjectThatThrowsIfAccessed";
|
8
|
+
import { Evt } from "evt";
|
9
|
+
import { useRerenderOnStateChange } from "evt/hooks";
|
10
|
+
import { useMemo } from "react";
|
11
|
+
import type { StatefulReadonlyEvt } from "evt";
|
8
12
|
|
9
|
-
export
|
10
|
-
...kcMessagesBase,
|
11
|
-
"en": {
|
12
|
-
...kcMessagesBase["en"],
|
13
|
-
"termsText": "⏳",
|
14
|
-
"shouldBeEqual": "{0} should be equal to {1}",
|
15
|
-
"shouldBeDifferent": "{0} should be different to {1}",
|
16
|
-
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
17
|
-
"mustBeAnInteger": "Must be an integer",
|
18
|
-
"notAValidOption": "Not a valid option",
|
19
|
-
},
|
20
|
-
"fr": {
|
21
|
-
...kcMessagesBase["fr"],
|
22
|
-
/* spell-checker: disable */
|
23
|
-
"shouldBeEqual": "{0} doit être egale à {1}",
|
24
|
-
"shouldBeDifferent": "{0} doit être différent de {1}",
|
25
|
-
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
26
|
-
"mustBeAnInteger": "Doit être un nombre entiers",
|
27
|
-
"notAValidOption": "N'est pas une option valide",
|
28
|
-
/* spell-checker: enable */
|
29
|
-
},
|
30
|
-
};
|
13
|
+
export type KcMessages = typeof t_kcMessages;
|
31
14
|
|
32
|
-
export type KcLanguageTag = keyof
|
15
|
+
export type KcLanguageTag = keyof KcMessages;
|
33
16
|
|
34
17
|
export const kcLanguageTags = [
|
35
18
|
"en",
|
@@ -106,15 +89,16 @@ export function changeLocale(params: {
|
|
106
89
|
assert(false, "never");
|
107
90
|
}
|
108
91
|
|
109
|
-
export type MessageKey = keyof
|
92
|
+
export type MessageKey = keyof KcMessages["en"];
|
110
93
|
|
111
94
|
function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
112
95
|
key: Key;
|
113
96
|
args: (string | undefined)[];
|
114
97
|
kcLanguageTag: string;
|
115
98
|
doRenderMarkdown: DoRenderMarkdown;
|
99
|
+
kcMessages: KcMessages;
|
116
100
|
}): Key extends MessageKey ? (DoRenderMarkdown extends true ? JSX.Element : string) : undefined {
|
117
|
-
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
101
|
+
const { key, args, kcLanguageTag, doRenderMarkdown, kcMessages } = props;
|
118
102
|
|
119
103
|
let str = kcMessages[kcLanguageTag as any as "en"][key as MessageKey] ?? kcMessages["en"][key as MessageKey];
|
120
104
|
|
@@ -160,8 +144,9 @@ function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean
|
|
160
144
|
args: (string | undefined)[];
|
161
145
|
kcLanguageTag: string;
|
162
146
|
doRenderMarkdown: DoRenderMarkdown;
|
147
|
+
kcMessages: KcMessages;
|
163
148
|
}): DoRenderMarkdown extends true ? JSX.Element : string {
|
164
|
-
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
149
|
+
const { key, args, kcLanguageTag, doRenderMarkdown, kcMessages } = props;
|
165
150
|
|
166
151
|
const match = key.match(/^\$\{([^{]+)\}$/);
|
167
152
|
|
@@ -172,35 +157,77 @@ function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean
|
|
172
157
|
args,
|
173
158
|
kcLanguageTag,
|
174
159
|
doRenderMarkdown,
|
160
|
+
kcMessages,
|
175
161
|
});
|
176
162
|
|
177
163
|
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
178
164
|
}
|
179
165
|
|
166
|
+
export type I18n = {
|
167
|
+
msg: (key: MessageKey, ...args: (string | undefined)[]) => JSX.Element;
|
168
|
+
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
169
|
+
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
170
|
+
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
171
|
+
evtKcMessages: StatefulReadonlyEvt<KcMessages | undefined>;
|
172
|
+
};
|
173
|
+
|
180
174
|
/**
|
181
|
-
* When the language is switched the page is reloaded, this may appear
|
182
|
-
* as a bug as you might notice that the language successfully switch before
|
183
|
-
* reload.
|
184
|
-
* However we need to tell Keycloak that the user have changed the language
|
185
|
-
* during login so we can retrieve the "local" field of the JWT encoded accessToken.
|
186
|
-
* https://user-images.githubusercontent.com/6702424/138096682-351bb61f-f24e-4caf-91b7-cca8cfa2cb58.mov
|
187
|
-
*
|
188
175
|
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied")
|
189
176
|
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
190
|
-
*
|
191
|
-
*
|
192
|
-
* NOTE: This function is memoized, it always returns the same object for a given kcContext)
|
193
|
-
*
|
194
177
|
*/
|
195
|
-
export
|
178
|
+
export function createUseI18n(props: { kcMessages: KcMessages | (() => Promise<KcMessages>); kcContext: KcContextLike | undefined }) {
|
179
|
+
const { kcContext, kcMessages: kcMessagesOrFetchKcMessages } = props;
|
180
|
+
|
181
|
+
if (kcContext === undefined) {
|
182
|
+
return createObjectThatThrowsIfAccessed({ "debugMessage": "Can't use Keycloakify i18n outside of keycloak" });
|
183
|
+
}
|
184
|
+
|
185
|
+
const { evtKcMessages } = (() => {
|
186
|
+
const evtKcMessages = Evt.create<KcMessages | undefined>(undefined);
|
187
|
+
|
188
|
+
if (typeof kcMessagesOrFetchKcMessages === "function") {
|
189
|
+
kcMessagesOrFetchKcMessages().then(kcMessages => (evtKcMessages.state = kcMessages));
|
190
|
+
} else {
|
191
|
+
evtKcMessages.state = kcMessagesOrFetchKcMessages;
|
192
|
+
}
|
193
|
+
|
194
|
+
return { evtKcMessages };
|
195
|
+
})();
|
196
|
+
Evt.factorize(
|
197
|
+
typeof kcMessagesOrFetchKcMessages === "function"
|
198
|
+
? Evt.from(kcMessagesOrFetchKcMessages()).toStateful()
|
199
|
+
: Evt.create(kcMessagesOrFetchKcMessages),
|
200
|
+
);
|
201
|
+
|
196
202
|
const kcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
197
203
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
204
|
+
function useI18n() {
|
205
|
+
useRerenderOnStateChange(evtKcMessages);
|
206
|
+
|
207
|
+
const i18n = useMemo((): I18n => {
|
208
|
+
const kcMessages = evtKcMessages.state;
|
209
|
+
|
210
|
+
if (kcMessages === undefined) {
|
211
|
+
return {
|
212
|
+
"msgStr": () => "",
|
213
|
+
"msg": () => <></>,
|
214
|
+
"advancedMsg": () => <></>,
|
215
|
+
"advancedMsgStr": () => "",
|
216
|
+
evtKcMessages,
|
217
|
+
};
|
218
|
+
}
|
219
|
+
|
220
|
+
return {
|
221
|
+
"msgStr": (key, ...args) => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": false, kcMessages }),
|
222
|
+
"msg": (key, ...args): JSX.Element => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": true, kcMessages }),
|
223
|
+
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": true, kcMessages }),
|
224
|
+
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": false, kcMessages }),
|
225
|
+
evtKcMessages,
|
226
|
+
};
|
227
|
+
}, [evtKcMessages.state]);
|
228
|
+
|
229
|
+
return i18n;
|
230
|
+
}
|
231
|
+
|
232
|
+
return { useI18n };
|
233
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { kcMessages as kcMessagesBase } from "./generated_kcMessages/15.0.2/login";
|
2
|
+
|
3
|
+
export const kcMessages = {
|
4
|
+
...kcMessagesBase,
|
5
|
+
"en": {
|
6
|
+
...kcMessagesBase["en"],
|
7
|
+
"termsText": "⏳",
|
8
|
+
"shouldBeEqual": "{0} should be equal to {1}",
|
9
|
+
"shouldBeDifferent": "{0} should be different to {1}",
|
10
|
+
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
11
|
+
"mustBeAnInteger": "Must be an integer",
|
12
|
+
"notAValidOption": "Not a valid option",
|
13
|
+
},
|
14
|
+
"fr": {
|
15
|
+
...kcMessagesBase["fr"],
|
16
|
+
/* spell-checker: disable */
|
17
|
+
"shouldBeEqual": "{0} doit être egale à {1}",
|
18
|
+
"shouldBeDifferent": "{0} doit être différent de {1}",
|
19
|
+
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
20
|
+
"mustBeAnInteger": "Doit être un nombre entiers",
|
21
|
+
"notAValidOption": "N'est pas une option valide",
|
22
|
+
/* spell-checker: enable */
|
23
|
+
},
|
24
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
export function createObjectThatThrowsIfAccessed<T extends object>(params?: { debugMessage?: string }): T {
|
2
|
+
if (typeof Proxy === "undefined") {
|
3
|
+
return null as any;
|
4
|
+
}
|
5
|
+
|
6
|
+
const { debugMessage = "" } = params ?? {};
|
7
|
+
|
8
|
+
const get: NonNullable<ProxyHandler<T>["get"]> = (...args) => {
|
9
|
+
const [, prop] = args;
|
10
|
+
|
11
|
+
throw new Error(`Cannot access ${String(prop)} yet ${debugMessage}`);
|
12
|
+
};
|
13
|
+
|
14
|
+
return new Proxy<T>({} as any, {
|
15
|
+
get,
|
16
|
+
"set": get,
|
17
|
+
});
|
18
|
+
}
|
@@ -1,11 +1,10 @@
|
|
1
1
|
import "./tools/Array.prototype.every";
|
2
2
|
import { useMemo, useReducer, Fragment } from "react";
|
3
3
|
import type { KcContextBase, Validators, Attribute } from "./getKcContext/KcContextBase";
|
4
|
-
import { getMsg } from "./i18n";
|
5
4
|
import type { KcLanguageTag } from "./i18n";
|
6
5
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
7
6
|
import { id } from "tsafe/id";
|
8
|
-
import type { MessageKey } from "./i18n";
|
7
|
+
import type { I18n, MessageKey } from "./i18n";
|
9
8
|
import { emailRegexp } from "./tools/emailRegExp";
|
10
9
|
|
11
10
|
export function useGetErrors(params: {
|
@@ -16,15 +15,16 @@ export function useGetErrors(params: {
|
|
16
15
|
};
|
17
16
|
locale?: { currentLanguageTag: KcLanguageTag };
|
18
17
|
};
|
18
|
+
useI18n: () => I18n;
|
19
19
|
}) {
|
20
|
-
const { kcContext } = params;
|
20
|
+
const { kcContext, useI18n } = params;
|
21
21
|
|
22
22
|
const {
|
23
23
|
messagesPerField,
|
24
24
|
profile: { attributes },
|
25
25
|
} = kcContext;
|
26
26
|
|
27
|
-
const { msg, msgStr, advancedMsg, advancedMsgStr } =
|
27
|
+
const { msg, msgStr, advancedMsg, advancedMsgStr } = useI18n();
|
28
28
|
|
29
29
|
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
|
30
30
|
const { name, fieldValueByAttributeName } = params;
|
@@ -314,11 +314,13 @@ export function useFormValidationSlice(params: {
|
|
314
314
|
passwordRequired: boolean;
|
315
315
|
realm: { registrationEmailAsUsername: boolean };
|
316
316
|
};
|
317
|
+
useI18n: () => I18n;
|
317
318
|
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
318
319
|
passwordValidators?: Validators;
|
319
320
|
}) {
|
320
321
|
const {
|
321
322
|
kcContext,
|
323
|
+
useI18n,
|
322
324
|
passwordValidators = {
|
323
325
|
"length": {
|
324
326
|
"ignore.empty.value": true,
|
@@ -383,6 +385,7 @@ export function useFormValidationSlice(params: {
|
|
383
385
|
"attributes": attributesWithPassword,
|
384
386
|
},
|
385
387
|
},
|
388
|
+
useI18n,
|
386
389
|
});
|
387
390
|
|
388
391
|
const initialInternalState = useMemo(
|