keycloakify 4.8.7 → 5.0.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +4 -0
  3. package/bin/build-keycloak-theme/build-keycloak-theme.js +3 -3
  4. package/bin/build-keycloak-theme/build-keycloak-theme.js.map +1 -1
  5. package/bin/build-keycloak-theme/generateFtl/generateFtl.d.ts +1 -1
  6. package/bin/build-keycloak-theme/generateFtl/generateFtl.js +1 -0
  7. package/bin/build-keycloak-theme/generateFtl/generateFtl.js.map +1 -1
  8. package/lib/components/Error.js +2 -2
  9. package/lib/components/Error.js.map +1 -1
  10. package/lib/components/Info.js +2 -2
  11. package/lib/components/Info.js.map +1 -1
  12. package/lib/components/KcApp.js +3 -0
  13. package/lib/components/KcApp.js.map +1 -1
  14. package/lib/components/Login.js +2 -2
  15. package/lib/components/Login.js.map +1 -1
  16. package/lib/components/LoginIdpLinkConfirm.js +2 -2
  17. package/lib/components/LoginIdpLinkConfirm.js.map +1 -1
  18. package/lib/components/LoginIdpLinkEmail.d.ts +6 -0
  19. package/lib/components/LoginIdpLinkEmail.js +36 -0
  20. package/lib/components/LoginIdpLinkEmail.js.map +1 -0
  21. package/lib/components/LoginOtp.js +2 -2
  22. package/lib/components/LoginOtp.js.map +1 -1
  23. package/lib/components/LoginPageExpired.js +2 -2
  24. package/lib/components/LoginPageExpired.js.map +1 -1
  25. package/lib/components/LoginResetPassword.js +2 -2
  26. package/lib/components/LoginResetPassword.js.map +1 -1
  27. package/lib/components/LoginUpdatePassword.js +2 -2
  28. package/lib/components/LoginUpdatePassword.js.map +1 -1
  29. package/lib/components/LoginUpdateProfile.js +2 -2
  30. package/lib/components/LoginUpdateProfile.js.map +1 -1
  31. package/lib/components/LoginVerifyEmail.js +2 -2
  32. package/lib/components/LoginVerifyEmail.js.map +1 -1
  33. package/lib/components/Register.js +2 -2
  34. package/lib/components/Register.js.map +1 -1
  35. package/lib/components/RegisterUserProfile.js +3 -3
  36. package/lib/components/RegisterUserProfile.js.map +1 -1
  37. package/lib/components/Template.js +10 -28
  38. package/lib/components/Template.js.map +1 -1
  39. package/lib/components/Terms.d.ts +8 -0
  40. package/lib/components/Terms.js +33 -3
  41. package/lib/components/Terms.js.map +1 -1
  42. package/lib/getKcContext/KcContextBase.d.ts +12 -5
  43. package/lib/getKcContext/KcContextBase.js.map +1 -1
  44. package/lib/getKcContext/kcContextMocks/kcContextMocks.js +24 -8
  45. package/lib/getKcContext/kcContextMocks/kcContextMocks.js.map +1 -1
  46. package/lib/i18n/{kcMessages/login.d.ts → index.d.ts} +62 -4
  47. package/lib/i18n/{useKcMessage.js → index.js} +47 -33
  48. package/lib/i18n/index.js.map +1 -0
  49. package/lib/index.d.ts +2 -11
  50. package/lib/index.js +4 -11
  51. package/lib/index.js.map +1 -1
  52. package/lib/useFormValidationSlice.d.ts +4 -12
  53. package/lib/useFormValidationSlice.js +4 -3
  54. package/lib/useFormValidationSlice.js.map +1 -1
  55. package/package.json +11 -17
  56. package/src/bin/build-keycloak-theme/build-keycloak-theme.ts +3 -3
  57. package/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts +1 -0
  58. package/src/lib/components/Error.tsx +2 -2
  59. package/src/lib/components/Info.tsx +2 -2
  60. package/src/lib/components/KcApp.tsx +3 -0
  61. package/src/lib/components/Login.tsx +2 -2
  62. package/src/lib/components/LoginIdpLinkConfirm.tsx +2 -2
  63. package/src/lib/components/LoginIdpLinkEmail.tsx +32 -0
  64. package/src/lib/components/LoginOtp.tsx +2 -2
  65. package/src/lib/components/LoginPageExpired.tsx +2 -2
  66. package/src/lib/components/LoginResetPassword.tsx +2 -2
  67. package/src/lib/components/LoginUpdatePassword.tsx +2 -2
  68. package/src/lib/components/LoginUpdateProfile.tsx +2 -2
  69. package/src/lib/components/LoginVerifyEmail.tsx +2 -2
  70. package/src/lib/components/Register.tsx +2 -2
  71. package/src/lib/components/RegisterUserProfile.tsx +3 -3
  72. package/src/lib/components/Template.tsx +11 -31
  73. package/src/lib/components/Terms.tsx +24 -3
  74. package/src/lib/getKcContext/KcContextBase.ts +13 -8
  75. package/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts +29 -9
  76. package/src/lib/i18n/index.tsx +180 -0
  77. package/src/lib/index.ts +3 -11
  78. package/src/lib/useFormValidationSlice.tsx +10 -15
  79. package/lib/i18n/KcLanguageTag.d.ts +0 -35
  80. package/lib/i18n/KcLanguageTag.js +0 -57
  81. package/lib/i18n/KcLanguageTag.js.map +0 -1
  82. package/lib/i18n/kcMessages/login.js +0 -38
  83. package/lib/i18n/kcMessages/login.js.map +0 -1
  84. package/lib/i18n/useKcLanguageTag.d.ts +0 -4
  85. package/lib/i18n/useKcLanguageTag.js +0 -22
  86. package/lib/i18n/useKcLanguageTag.js.map +0 -1
  87. package/lib/i18n/useKcMessage.d.ts +0 -23
  88. package/lib/i18n/useKcMessage.js.map +0 -1
  89. package/src/lib/i18n/KcLanguageTag.ts +0 -63
  90. package/src/lib/i18n/kcMessages/login.ts +0 -50
  91. package/src/lib/i18n/useKcLanguageTag.ts +0 -28
  92. package/src/lib/i18n/useKcMessage.tsx +0 -115
@@ -2,7 +2,7 @@ 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 { useKcMessage } from "../i18n/useKcMessage";
5
+ import { getMsg } from "../i18n";
6
6
  import { useCssAndCx } from "tss-react";
7
7
  import type { ReactComponent } from "../tools/ReactComponent";
8
8
  import { useCallbackFactory } from "powerhooks/useCallbackFactory";
@@ -11,7 +11,7 @@ import { useFormValidationSlice } from "../useFormValidationSlice";
11
11
  export const RegisterUserProfile = memo(({ kcContext, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile } & KcProps) => {
12
12
  const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
13
13
 
14
- const { msg, msgStr } = useKcMessage();
14
+ const { msg, msgStr } = getMsg(kcContext);
15
15
 
16
16
  const { cx, css } = useCssAndCx();
17
17
 
@@ -74,7 +74,7 @@ type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile
74
74
  const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
75
75
  const { cx, css } = useCssAndCx();
76
76
 
77
- const { advancedMsg } = useKcMessage();
77
+ const { advancedMsg } = getMsg(kcContext);
78
78
 
79
79
  const {
80
80
  formValidationState: { fieldStateByAttributeName, isFormSubmittable },
@@ -1,12 +1,9 @@
1
1
  import { useReducer, useEffect, memo } from "react";
2
2
  import type { ReactNode } from "react";
3
- import { useKcMessage } from "../i18n/useKcMessage";
4
- import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
3
+ import { getMsg, getCurrentKcLanguageTag, changeLocale, getTagLabel } from "../i18n";
4
+ import type { KcLanguageTag } from "../i18n";
5
5
  import type { KcContextBase } from "../getKcContext/KcContextBase";
6
6
  import { assert } from "../tools/assert";
7
- import type { KcLanguageTag } from "../i18n/KcLanguageTag";
8
- import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
9
- import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
10
7
  import { useCallbackFactory } from "powerhooks/useCallbackFactory";
11
8
  import { headInsert } from "../tools/headInsert";
12
9
  import { pathJoin } from "../tools/pathJoin";
@@ -51,36 +48,19 @@ export const Template = memo((props: TemplateProps) => {
51
48
  console.log("Rendering this page with react using keycloakify");
52
49
  }, []);
53
50
 
54
- const { msg } = useKcMessage();
51
+ const { msg } = getMsg(kcContext);
55
52
 
56
- const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
57
-
58
- const onChangeLanguageClickFactory = useCallbackFactory(([languageTag]: [KcLanguageTag]) => setKcLanguageTag(languageTag));
53
+ const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [KcLanguageTag]) =>
54
+ changeLocale({
55
+ kcContext,
56
+ kcLanguageTag,
57
+ }),
58
+ );
59
59
 
60
60
  const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
61
61
 
62
62
  const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
63
63
 
64
- useEffect(() => {
65
- if (!realm.internationalizationEnabled) {
66
- return;
67
- }
68
-
69
- assert(locale !== undefined);
70
-
71
- const kcContext_kcLanguageTag = getBestMatchAmongKcLanguageTag(locale.current);
72
-
73
- if (["error.ftl", "info.ftl", "login-page-expired.ftl"].indexOf(kcContext.pageId) >= 0) {
74
- setKcLanguageTag(kcContext_kcLanguageTag);
75
-
76
- return;
77
- }
78
-
79
- if (kcLanguageTag !== kcContext_kcLanguageTag) {
80
- window.location.href = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
81
- }
82
- }, [kcLanguageTag]);
83
-
84
64
  const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
85
65
 
86
66
  useEffect(() => {
@@ -158,13 +138,13 @@ export const Template = memo((props: TemplateProps) => {
158
138
  <div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
159
139
  <div className="kc-dropdown" id="kc-locale-dropdown">
160
140
  <a href="#" id="kc-current-locale-link">
161
- {getKcLanguageTagLabel(kcLanguageTag)}
141
+ {getTagLabel({ "kcLanguageTag": getCurrentKcLanguageTag(kcContext), kcContext })}
162
142
  </a>
163
143
  <ul>
164
144
  {locale.supported.map(({ languageTag }) => (
165
145
  <li key={languageTag} className="kc-dropdown-item">
166
146
  <a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
167
- {getKcLanguageTagLabel(languageTag)}
147
+ {getTagLabel({ "kcLanguageTag": languageTag, kcContext })}
168
148
  </a>
169
149
  </li>
170
150
  ))}
@@ -1,12 +1,33 @@
1
- import { memo } from "react";
1
+ 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 { useKcMessage } from "../i18n/useKcMessage";
5
+ import { getMsg } from "../i18n";
6
6
  import { useCssAndCx } from "tss-react";
7
+ import { kcMessages, getCurrentKcLanguageTag } from "../i18n";
8
+ import type { KcLanguageTag } from "../i18n";
9
+
10
+ /** Allow to avoid bundling the terms and download it on demand*/
11
+ export function useDownloadTerms(params: {
12
+ kcContext: KcContextBase;
13
+ downloadTermMarkdown: (params: { currentKcLanguageTag: KcLanguageTag }) => Promise<string>;
14
+ }) {
15
+ const { kcContext, downloadTermMarkdown } = params;
16
+
17
+ const [, forceUpdate] = useReducer(x => x + 1, 0);
18
+
19
+ useEffect(() => {
20
+ const currentKcLanguageTag = getCurrentKcLanguageTag(kcContext);
21
+
22
+ downloadTermMarkdown({ currentKcLanguageTag }).then(thermMarkdown => {
23
+ kcMessages[currentKcLanguageTag].termsText = thermMarkdown;
24
+ forceUpdate();
25
+ });
26
+ }, []);
27
+ }
7
28
 
8
29
  export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
9
- const { msg, msgStr } = useKcMessage();
30
+ const { msg, msgStr } = getMsg(kcContext);
10
31
 
11
32
  const { cx } = useCssAndCx();
12
33
 
@@ -1,9 +1,8 @@
1
1
  import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
2
- import type { KcLanguageTag } from "../i18n/KcLanguageTag";
2
+ import type { KcLanguageTag } from "../i18n";
3
3
  import { assert } from "tsafe/assert";
4
4
  import type { Equals } from "tsafe";
5
- import type { MessageKey } from "../i18n/useKcMessage";
6
- import type { LanguageLabel } from "../i18n/KcLanguageTag";
5
+ import type { MessageKey } from "../i18n";
7
6
 
8
7
  type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
9
8
 
@@ -24,6 +23,7 @@ export type KcContextBase =
24
23
  | KcContextBase.LoginUpdatePassword
25
24
  | KcContextBase.LoginUpdateProfile
26
25
  | KcContextBase.LoginIdpLinkConfirm
26
+ | KcContextBase.LoginIdpLinkEmail
27
27
  | KcContextBase.LoginPageExpired;
28
28
 
29
29
  export declare namespace KcContextBase {
@@ -46,13 +46,10 @@ export declare namespace KcContextBase {
46
46
  locale?: {
47
47
  supported: {
48
48
  url: string;
49
+ label: string;
49
50
  languageTag: KcLanguageTag;
50
- /** Is determined by languageTag. Ex: languageTag === "en" => label === "English"
51
- * or getLanguageLabel(languageTag) === label
52
- */
53
- //label: LanguageLabel;
54
51
  }[];
55
- current: LanguageLabel;
52
+ currentLanguageTag: KcLanguageTag;
56
53
  };
57
54
  auth?: {
58
55
  showUsername: boolean;
@@ -215,6 +212,14 @@ export declare namespace KcContextBase {
215
212
  idpAlias: string;
216
213
  };
217
214
 
215
+ export type LoginIdpLinkEmail = Common & {
216
+ pageId: "login-idp-link-email.ftl";
217
+ brokerContext: {
218
+ username: string;
219
+ };
220
+ idpAlias: string;
221
+ };
222
+
218
223
  export type LoginPageExpired = Common & {
219
224
  pageId: "login-page-expired.ftl";
220
225
  };
@@ -1,7 +1,5 @@
1
1
  import "minimal-polyfills/Object.fromEntries";
2
2
  import type { KcContextBase, Attribute } from "../KcContextBase";
3
- import { getEvtKcLanguage } from "../../i18n/useKcLanguageTag";
4
- import { getKcLanguageTagLabel } from "../../i18n/KcLanguageTag";
5
3
  //NOTE: Aside because we want to be able to import them from node
6
4
  import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
7
5
  import { id } from "tsafe/id";
@@ -32,81 +30,100 @@ export const kcContextCommonMock: KcContextBase.Common = {
32
30
  },
33
31
  "locale": {
34
32
  "supported": [
33
+ /* spell-checker: disable */
35
34
  {
36
35
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
36
+ "label": "Deutsch",
37
37
  "languageTag": "de",
38
38
  },
39
39
  {
40
40
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
41
+ "label": "Norsk",
41
42
  "languageTag": "no",
42
43
  },
43
44
  {
44
45
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
46
+ "label": "Русский",
45
47
  "languageTag": "ru",
46
48
  },
47
49
  {
48
50
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
51
+ "label": "Svenska",
49
52
  "languageTag": "sv",
50
53
  },
51
54
  {
52
55
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
56
+ "label": "Português (Brasil)",
53
57
  "languageTag": "pt-BR",
54
58
  },
55
59
  {
56
60
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
61
+ "label": "Lietuvių",
57
62
  "languageTag": "lt",
58
63
  },
59
64
  {
60
65
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
66
+ "label": "English",
61
67
  "languageTag": "en",
62
68
  },
63
69
  {
64
70
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
71
+ "label": "Italiano",
65
72
  "languageTag": "it",
66
73
  },
67
74
  {
68
75
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
76
+ "label": "Français",
69
77
  "languageTag": "fr",
70
78
  },
71
79
  {
72
80
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
81
+ "label": "中文简体",
73
82
  "languageTag": "zh-CN",
74
83
  },
75
84
  {
76
85
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
86
+ "label": "Español",
77
87
  "languageTag": "es",
78
88
  },
79
89
  {
80
90
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
91
+ "label": "Čeština",
81
92
  "languageTag": "cs",
82
93
  },
83
94
  {
84
95
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
96
+ "label": "日本語",
85
97
  "languageTag": "ja",
86
98
  },
87
99
  {
88
100
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
101
+ "label": "Slovenčina",
89
102
  "languageTag": "sk",
90
103
  },
91
104
  {
92
105
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
106
+ "label": "Polski",
93
107
  "languageTag": "pl",
94
108
  },
95
109
  {
96
110
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
111
+ "label": "Català",
97
112
  "languageTag": "ca",
98
113
  },
99
114
  {
100
115
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
116
+ "label": "Nederlands",
101
117
  "languageTag": "nl",
102
118
  },
103
119
  {
104
120
  "url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
121
+ "label": "Türkçe",
105
122
  "languageTag": "tr",
106
123
  },
124
+ /* spell-checker: enable */
107
125
  ],
108
- //"current": null as any
109
- "current": "English",
126
+ "currentLanguageTag": "en",
110
127
  },
111
128
  "auth": {
112
129
  "showUsername": false,
@@ -124,11 +141,6 @@ export const kcContextCommonMock: KcContextBase.Common = {
124
141
  "isAppInitiatedAction": false,
125
142
  };
126
143
 
127
- Object.defineProperty(kcContextCommonMock.locale!, "current", {
128
- "get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
129
- "enumerable": true,
130
- });
131
-
132
144
  const loginUrl = {
133
145
  ...kcContextCommonMock.url,
134
146
  "loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
@@ -367,4 +379,12 @@ export const kcContextMocks: KcContextBase[] = [
367
379
  "pageId": "login-idp-link-confirm.ftl",
368
380
  "idpAlias": "FranceConnect",
369
381
  }),
382
+ id<KcContextBase.LoginIdpLinkEmail>({
383
+ ...kcContextCommonMock,
384
+ "pageId": "login-idp-link-email.ftl",
385
+ "idpAlias": "FranceConnect",
386
+ "brokerContext": {
387
+ "username": "anUsername",
388
+ },
389
+ }),
370
390
  ];
@@ -0,0 +1,180 @@
1
+ import "minimal-polyfills/Object.fromEntries";
2
+ //NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
3
+ import ReactMarkdown from "react-markdown";
4
+ import memoize from "memoizee";
5
+ import { kcMessages as kcMessagesBase } from "./generated_kcMessages/15.0.2/login";
6
+ import { assert } from "tsafe/assert";
7
+
8
+ export const kcMessages = {
9
+ ...kcMessagesBase,
10
+ "en": {
11
+ ...kcMessagesBase["en"],
12
+ "termsText": "⏳",
13
+ "shouldBeEqual": "{0} should be equal to {1}",
14
+ "shouldBeDifferent": "{0} should be different to {1}",
15
+ "shouldMatchPattern": "Pattern should match: `/{0}/`",
16
+ "mustBeAnInteger": "Must be an integer",
17
+ "notAValidOption": "Not a valid option",
18
+ },
19
+ "fr": {
20
+ ...kcMessagesBase["fr"],
21
+ /* spell-checker: disable */
22
+ "shouldBeEqual": "{0} doit être egale à {1}",
23
+ "shouldBeDifferent": "{0} doit être différent de {1}",
24
+ "shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
25
+ "mustBeAnInteger": "Doit être un nombre entiers",
26
+ "notAValidOption": "N'est pas une option valide",
27
+ /* spell-checker: enable */
28
+ },
29
+ };
30
+
31
+ export type KcLanguageTag = keyof typeof kcMessages;
32
+
33
+ type KcContextLike = { locale?: { currentLanguageTag: KcLanguageTag } };
34
+
35
+ export function getCurrentKcLanguageTag(kcContext: KcContextLike) {
36
+ return kcContext.locale?.currentLanguageTag ?? "en";
37
+ }
38
+
39
+ export function getTagLabel(params: {
40
+ kcContext: {
41
+ locale?: {
42
+ supported: { languageTag: KcLanguageTag; label: string }[];
43
+ };
44
+ };
45
+ kcLanguageTag: KcLanguageTag;
46
+ }): string {
47
+ const { kcContext, kcLanguageTag } = params;
48
+
49
+ const { locale } = kcContext;
50
+
51
+ assert(locale !== undefined, "Internationalization not enabled");
52
+
53
+ const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag);
54
+
55
+ assert(targetSupportedLocale !== undefined, `${kcLanguageTag} need to be enabled in Keycloak admin`);
56
+
57
+ return targetSupportedLocale.label;
58
+ }
59
+
60
+ export function changeLocale(params: {
61
+ kcContext: {
62
+ locale?: {
63
+ supported: { languageTag: KcLanguageTag; url: string }[];
64
+ };
65
+ };
66
+ kcLanguageTag: KcLanguageTag;
67
+ }): never {
68
+ const { kcContext, kcLanguageTag } = params;
69
+
70
+ const { locale } = kcContext;
71
+
72
+ assert(locale !== undefined, "Internationalization not enabled");
73
+
74
+ const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag);
75
+
76
+ assert(targetSupportedLocale !== undefined, `${kcLanguageTag} need to be enabled in Keycloak admin`);
77
+
78
+ window.location.href = targetSupportedLocale.url;
79
+
80
+ assert(false, "never");
81
+ }
82
+
83
+ export type MessageKey = keyof typeof kcMessages["en"];
84
+
85
+ function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props: {
86
+ key: Key;
87
+ args: (string | undefined)[];
88
+ kcLanguageTag: string;
89
+ doRenderMarkdown: DoRenderMarkdown;
90
+ }): Key extends MessageKey ? (DoRenderMarkdown extends true ? JSX.Element : string) : undefined {
91
+ const { key, args, kcLanguageTag, doRenderMarkdown } = props;
92
+
93
+ let str = kcMessages[kcLanguageTag as any as "en"][key as MessageKey] ?? kcMessages["en"][key as MessageKey];
94
+
95
+ if (str === undefined) {
96
+ return undefined as any;
97
+ }
98
+
99
+ str = (() => {
100
+ const startIndex = str
101
+ .match(/{[0-9]+}/g)
102
+ ?.map(g => g.match(/{([0-9]+)}/)![1])
103
+ .map(indexStr => parseInt(indexStr))
104
+ .sort((a, b) => a - b)[0];
105
+
106
+ if (startIndex === undefined) {
107
+ return str;
108
+ }
109
+
110
+ args.forEach((arg, i) => {
111
+ if (arg === undefined) {
112
+ return;
113
+ }
114
+
115
+ str = str.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
116
+ });
117
+
118
+ return str;
119
+ })();
120
+
121
+ return (
122
+ doRenderMarkdown ? (
123
+ <ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
124
+ {str}
125
+ </ReactMarkdown>
126
+ ) : (
127
+ str
128
+ )
129
+ ) as any;
130
+ }
131
+
132
+ function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean>(props: {
133
+ key: Key;
134
+ args: (string | undefined)[];
135
+ kcLanguageTag: string;
136
+ doRenderMarkdown: DoRenderMarkdown;
137
+ }): DoRenderMarkdown extends true ? JSX.Element : string {
138
+ const { key, args, kcLanguageTag, doRenderMarkdown } = props;
139
+
140
+ const match = key.match(/^\$\{([^{]+)\}$/);
141
+
142
+ const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
143
+
144
+ const out = resolveMsg({
145
+ "key": keyUnwrappedFromCurlyBraces,
146
+ args,
147
+ kcLanguageTag,
148
+ doRenderMarkdown,
149
+ });
150
+
151
+ return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
152
+ }
153
+
154
+ /**
155
+ * When the language is switched the page is reloaded, this may appear
156
+ * as a bug as you might notice that the language successfully switch before
157
+ * reload.
158
+ * However we need to tell Keycloak that the user have changed the language
159
+ * during login so we can retrieve the "local" field of the JWT encoded accessToken.
160
+ * https://user-images.githubusercontent.com/6702424/138096682-351bb61f-f24e-4caf-91b7-cca8cfa2cb58.mov
161
+ *
162
+ * advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied")
163
+ * advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
164
+ *
165
+ *
166
+ * NOTE: This function is memoized, it always returns the same object for a given kcContext)
167
+ *
168
+ */
169
+ export const getMsg = memoize((kcContext: KcContextLike) => {
170
+ const kcLanguageTag = getCurrentKcLanguageTag(kcContext);
171
+
172
+ return {
173
+ "msgStr": (key: MessageKey, ...args: (string | undefined)[]): string => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
174
+ "msg": (key: MessageKey, ...args: (string | undefined)[]): JSX.Element => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
175
+ "advancedMsg": <Key extends string>(key: Key, ...args: (string | undefined)[]): JSX.Element =>
176
+ resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
177
+ "advancedMsgStr": <Key extends string>(key: Key, ...args: (string | undefined)[]): string =>
178
+ resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
179
+ };
180
+ });
package/src/lib/index.ts CHANGED
@@ -1,18 +1,10 @@
1
1
  export * from "./getKcContext";
2
2
 
3
- export * from "./i18n/KcLanguageTag";
4
- export * from "./i18n/useKcLanguageTag";
5
- export * from "./i18n/useKcMessage";
6
- export * from "./i18n/kcMessages/login";
3
+ export * from "./i18n";
4
+
5
+ export { useDownloadTerms } from "./components/Terms";
7
6
 
8
7
  export * from "./components/KcProps";
9
- export * from "./components/Login";
10
- export * from "./components/Template";
11
- export * from "./components/KcApp";
12
- export * from "./components/Info";
13
- export * from "./components/Error";
14
- export * from "./components/LoginResetPassword";
15
- export * from "./components/LoginVerifyEmail";
16
8
  export * from "./keycloakJsAdapter";
17
9
  export * from "./useFormValidationSlice";
18
10
 
@@ -1,35 +1,30 @@
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 { useKcMessage } from "./i18n/useKcMessage";
4
+ import { getMsg } from "./i18n";
5
+ import type { KcLanguageTag } from "./i18n";
5
6
  import { useConstCallback } from "powerhooks/useConstCallback";
6
7
  import { id } from "tsafe/id";
7
- import type { MessageKey } from "./i18n/useKcMessage";
8
+ import type { MessageKey } from "./i18n";
8
9
  import { emailRegexp } from "./tools/emailRegExp";
9
10
 
10
- export type KcContextLike = {
11
- messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
12
- attributes: { name: string; value?: string; validators: Validators }[];
13
- passwordRequired: boolean;
14
- realm: { registrationEmailAsUsername: boolean };
15
- };
16
-
17
11
  export function useGetErrors(params: {
18
12
  kcContext: {
19
13
  messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
20
14
  profile: {
21
15
  attributes: { name: string; value?: string; validators: Validators }[];
22
16
  };
17
+ locale?: { currentLanguageTag: KcLanguageTag };
23
18
  };
24
19
  }) {
20
+ const { kcContext } = params;
21
+
25
22
  const {
26
- kcContext: {
27
- messagesPerField,
28
- profile: { attributes },
29
- },
30
- } = params;
23
+ messagesPerField,
24
+ profile: { attributes },
25
+ } = kcContext;
31
26
 
32
- const { msg, msgStr, advancedMsg, advancedMsgStr } = useKcMessage();
27
+ const { msg, msgStr, advancedMsg, advancedMsgStr } = getMsg(kcContext);
33
28
 
34
29
  const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
35
30
  const { name, fieldValueByAttributeName } = params;
@@ -1,35 +0,0 @@
1
- import { kcMessages } from "./kcMessages/login";
2
- export declare type KcLanguageTag = keyof typeof kcMessages;
3
- declare const kcLanguageByTagLabel: {
4
- readonly es: "Español";
5
- readonly it: "Italiano";
6
- readonly fr: "Français";
7
- readonly ca: "Català";
8
- readonly en: "English";
9
- readonly de: "Deutsch";
10
- readonly no: "Norsk";
11
- readonly "pt-BR": "Português (Brasil)";
12
- readonly ru: "Русский";
13
- readonly sk: "Slovenčina";
14
- readonly ja: "日本語";
15
- readonly pl: "Polski";
16
- readonly "zh-CN": "中文简体";
17
- readonly sv: "Svenska";
18
- readonly lt: "Lietuvių";
19
- readonly cs: "Čeština";
20
- readonly nl: "Nederlands";
21
- readonly tr: "Türkçe";
22
- readonly da: "Dansk";
23
- readonly hu: "Magyar";
24
- };
25
- export declare type LanguageLabel = typeof kcLanguageByTagLabel[keyof typeof kcLanguageByTagLabel];
26
- export declare function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel;
27
- export declare const kcLanguageTags: ("tr" | "no" | "ca" | "en" | "fr" | "cs" | "da" | "de" | "es" | "hu" | "it" | "ja" | "lt" | "nl" | "pl" | "pt-BR" | "ru" | "sk" | "sv" | "zh-CN")[];
28
- /**
29
- * Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
30
- * it corresponds to: "fr".
31
- * If there is no reasonable match it's guessed from navigator.language.
32
- * If still no matches "en" is returned.
33
- */
34
- export declare function getBestMatchAmongKcLanguageTag(languageLike: string): KcLanguageTag;
35
- export {};
@@ -1,57 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBestMatchAmongKcLanguageTag = exports.kcLanguageTags = exports.getKcLanguageTagLabel = void 0;
4
- var objectKeys_1 = require("tsafe/objectKeys");
5
- var login_1 = require("./kcMessages/login");
6
- var kcLanguageByTagLabel = {
7
- /* spell-checker: disable */
8
- "es": "Español",
9
- "it": "Italiano",
10
- "fr": "Français",
11
- "ca": "Català",
12
- "en": "English",
13
- "de": "Deutsch",
14
- "no": "Norsk",
15
- "pt-BR": "Português (Brasil)",
16
- "ru": "Русский",
17
- "sk": "Slovenčina",
18
- "ja": "日本語",
19
- "pl": "Polski",
20
- "zh-CN": "中文简体",
21
- "sv": "Svenska",
22
- "lt": "Lietuvių",
23
- "cs": "Čeština",
24
- "nl": "Nederlands",
25
- "tr": "Türkçe",
26
- "da": "Dansk",
27
- "hu": "Magyar",
28
- /* spell-checker: enable */
29
- };
30
- function getKcLanguageTagLabel(language) {
31
- var _a;
32
- return (_a = kcLanguageByTagLabel[language]) !== null && _a !== void 0 ? _a : language;
33
- }
34
- exports.getKcLanguageTagLabel = getKcLanguageTagLabel;
35
- exports.kcLanguageTags = (0, objectKeys_1.objectKeys)(login_1.kcMessages);
36
- /**
37
- * Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
38
- * it corresponds to: "fr".
39
- * If there is no reasonable match it's guessed from navigator.language.
40
- * If still no matches "en" is returned.
41
- */
42
- function getBestMatchAmongKcLanguageTag(languageLike) {
43
- var iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
44
- var kcLanguageTag = exports.kcLanguageTags.find(function (language) {
45
- return language.toLowerCase().includes(iso2LanguageLike) ||
46
- getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase();
47
- });
48
- if (kcLanguageTag !== undefined) {
49
- return kcLanguageTag;
50
- }
51
- if (languageLike !== navigator.language) {
52
- return getBestMatchAmongKcLanguageTag(navigator.language);
53
- }
54
- return "en";
55
- }
56
- exports.getBestMatchAmongKcLanguageTag = getBestMatchAmongKcLanguageTag;
57
- //# sourceMappingURL=KcLanguageTag.js.map