keycloakify 10.0.0-rc.36 → 10.0.0-rc.38

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 (272) hide show
  1. package/account/Fallback.d.ts +1 -2
  2. package/account/Fallback.js.map +1 -1
  3. package/account/KcContext/KcContext.d.ts +6 -6
  4. package/account/KcContext/getKcContextMock.d.ts +9 -9
  5. package/account/KcContext/getKcContextMock.js +3 -3
  6. package/account/KcContext/getKcContextMock.js.map +1 -1
  7. package/account/Template.d.ts +1 -2
  8. package/account/Template.js +7 -6
  9. package/account/Template.js.map +1 -1
  10. package/account/TemplateProps.d.ts +1 -3
  11. package/account/i18n/i18n.d.ts +9 -4
  12. package/account/i18n/i18n.js +132 -87
  13. package/account/i18n/i18n.js.map +1 -1
  14. package/account/i18n/index.d.ts +9 -2
  15. package/account/i18n/index.js +5 -1
  16. package/account/i18n/index.js.map +1 -1
  17. package/account/index.d.ts +1 -1
  18. package/account/lib/kcClsx.d.ts +9 -0
  19. package/account/lib/{useGetClassName.js → kcClsx.js} +3 -3
  20. package/account/lib/kcClsx.js.map +1 -0
  21. package/account/pages/Account.d.ts +1 -2
  22. package/account/pages/Account.js +9 -7
  23. package/account/pages/Account.js.map +1 -1
  24. package/account/pages/Applications.d.ts +1 -2
  25. package/account/pages/Applications.js +7 -7
  26. package/account/pages/Applications.js.map +1 -1
  27. package/account/pages/FederatedIdentity.d.ts +1 -2
  28. package/account/pages/FederatedIdentity.js +4 -3
  29. package/account/pages/FederatedIdentity.js.map +1 -1
  30. package/account/pages/Log.d.ts +1 -2
  31. package/account/pages/Log.js +6 -5
  32. package/account/pages/Log.js.map +1 -1
  33. package/account/pages/PageProps.d.ts +2 -4
  34. package/account/pages/Password.d.ts +1 -2
  35. package/account/pages/Password.js +10 -9
  36. package/account/pages/Password.js.map +1 -1
  37. package/account/pages/Sessions.d.ts +1 -2
  38. package/account/pages/Sessions.js +6 -6
  39. package/account/pages/Sessions.js.map +1 -1
  40. package/account/pages/Totp.d.ts +1 -2
  41. package/account/pages/Totp.js +6 -5
  42. package/account/pages/Totp.js.map +1 -1
  43. package/bin/{314.index.js → 21.index.js} +162 -4
  44. package/bin/{430.index.js → 214.index.js} +161 -2
  45. package/bin/3.index.js +81 -65
  46. package/bin/526.index.js +3 -2
  47. package/bin/538.index.js +552 -0
  48. package/bin/795.index.js +3 -2
  49. package/bin/{890.index.js → 941.index.js} +2 -159
  50. package/bin/main.js +19 -7
  51. package/lib/getKcClsx.d.ts +11 -0
  52. package/lib/getKcClsx.js +44 -0
  53. package/lib/getKcClsx.js.map +1 -0
  54. package/login/Fallback.d.ts +1 -2
  55. package/login/Fallback.js.map +1 -1
  56. package/login/KcContext/KcContext.d.ts +6 -6
  57. package/login/KcContext/getKcContextMock.d.ts +9 -9
  58. package/login/KcContext/getKcContextMock.js +3 -3
  59. package/login/KcContext/getKcContextMock.js.map +1 -1
  60. package/login/Template.d.ts +1 -2
  61. package/login/Template.js +10 -9
  62. package/login/Template.js.map +1 -1
  63. package/login/TemplateProps.d.ts +1 -3
  64. package/login/UserProfileFormFields.d.ts +5 -5
  65. package/login/UserProfileFormFields.js +35 -34
  66. package/login/UserProfileFormFields.js.map +1 -1
  67. package/login/i18n/i18n.d.ts +9 -4
  68. package/login/i18n/i18n.js +136 -91
  69. package/login/i18n/i18n.js.map +1 -1
  70. package/login/i18n/index.d.ts +9 -2
  71. package/login/i18n/index.js +5 -1
  72. package/login/i18n/index.js.map +1 -1
  73. package/login/index.d.ts +1 -1
  74. package/login/lib/kcClsx.d.ts +9 -0
  75. package/login/lib/{useGetClassName.js → kcClsx.js} +3 -3
  76. package/login/lib/kcClsx.js.map +1 -0
  77. package/login/lib/useUserProfileForm.d.ts +9 -6
  78. package/login/lib/useUserProfileForm.js +7 -5
  79. package/login/lib/useUserProfileForm.js.map +1 -1
  80. package/login/pages/Code.d.ts +1 -2
  81. package/login/pages/Code.js +6 -5
  82. package/login/pages/Code.js.map +1 -1
  83. package/login/pages/DeleteAccountConfirm.d.ts +1 -2
  84. package/login/pages/DeleteAccountConfirm.js +7 -7
  85. package/login/pages/DeleteAccountConfirm.js.map +1 -1
  86. package/login/pages/DeleteCredential.d.ts +1 -2
  87. package/login/pages/DeleteCredential.js +6 -6
  88. package/login/pages/DeleteCredential.js.map +1 -1
  89. package/login/pages/Error.d.ts +1 -2
  90. package/login/pages/Error.js +4 -3
  91. package/login/pages/Error.js.map +1 -1
  92. package/login/pages/FrontchannelLogout.d.ts +1 -2
  93. package/login/pages/FrontchannelLogout.js +4 -3
  94. package/login/pages/FrontchannelLogout.js.map +1 -1
  95. package/login/pages/IdpReviewUserProfile.d.ts +1 -2
  96. package/login/pages/IdpReviewUserProfile.js +6 -6
  97. package/login/pages/IdpReviewUserProfile.js.map +1 -1
  98. package/login/pages/Info.d.ts +1 -2
  99. package/login/pages/Info.js +4 -3
  100. package/login/pages/Info.js.map +1 -1
  101. package/login/pages/Login.d.ts +1 -2
  102. package/login/pages/Login.js +10 -8
  103. package/login/pages/Login.js.map +1 -1
  104. package/login/pages/LoginConfigTotp.d.ts +1 -2
  105. package/login/pages/LoginConfigTotp.js +8 -7
  106. package/login/pages/LoginConfigTotp.js.map +1 -1
  107. package/login/pages/LoginIdpLinkConfirm.d.ts +1 -2
  108. package/login/pages/LoginIdpLinkConfirm.js +6 -6
  109. package/login/pages/LoginIdpLinkConfirm.js.map +1 -1
  110. package/login/pages/LoginIdpLinkEmail.d.ts +1 -2
  111. package/login/pages/LoginIdpLinkEmail.js +4 -3
  112. package/login/pages/LoginIdpLinkEmail.js.map +1 -1
  113. package/login/pages/LoginOauth2DeviceVerifyUserCode.d.ts +1 -2
  114. package/login/pages/LoginOauth2DeviceVerifyUserCode.js +6 -6
  115. package/login/pages/LoginOauth2DeviceVerifyUserCode.js.map +1 -1
  116. package/login/pages/LoginOauthGrant.d.ts +1 -2
  117. package/login/pages/LoginOauthGrant.js +7 -7
  118. package/login/pages/LoginOauthGrant.js.map +1 -1
  119. package/login/pages/LoginOtp.d.ts +1 -2
  120. package/login/pages/LoginOtp.js +6 -6
  121. package/login/pages/LoginOtp.js.map +1 -1
  122. package/login/pages/LoginPageExpired.d.ts +1 -2
  123. package/login/pages/LoginPageExpired.js +4 -3
  124. package/login/pages/LoginPageExpired.js.map +1 -1
  125. package/login/pages/LoginPassword.d.ts +1 -2
  126. package/login/pages/LoginPassword.js +9 -7
  127. package/login/pages/LoginPassword.js.map +1 -1
  128. package/login/pages/LoginRecoveryAuthnCodeConfig.d.ts +1 -2
  129. package/login/pages/LoginRecoveryAuthnCodeConfig.js +9 -7
  130. package/login/pages/LoginRecoveryAuthnCodeConfig.js.map +1 -1
  131. package/login/pages/LoginRecoveryAuthnCodeInput.d.ts +1 -2
  132. package/login/pages/LoginRecoveryAuthnCodeInput.js +6 -6
  133. package/login/pages/LoginRecoveryAuthnCodeInput.js.map +1 -1
  134. package/login/pages/LoginResetOtp.d.ts +1 -2
  135. package/login/pages/LoginResetOtp.js +6 -6
  136. package/login/pages/LoginResetOtp.js.map +1 -1
  137. package/login/pages/LoginResetPassword.d.ts +1 -2
  138. package/login/pages/LoginResetPassword.js +7 -7
  139. package/login/pages/LoginResetPassword.js.map +1 -1
  140. package/login/pages/LoginUpdatePassword.d.ts +1 -2
  141. package/login/pages/LoginUpdatePassword.js +10 -9
  142. package/login/pages/LoginUpdatePassword.js.map +1 -1
  143. package/login/pages/LoginUpdateProfile.d.ts +1 -2
  144. package/login/pages/LoginUpdateProfile.js +7 -12
  145. package/login/pages/LoginUpdateProfile.js.map +1 -1
  146. package/login/pages/LoginUsername.d.ts +1 -2
  147. package/login/pages/LoginUsername.js +8 -7
  148. package/login/pages/LoginUsername.js.map +1 -1
  149. package/login/pages/LoginVerifyEmail.d.ts +1 -2
  150. package/login/pages/LoginVerifyEmail.js +4 -3
  151. package/login/pages/LoginVerifyEmail.js.map +1 -1
  152. package/login/pages/LoginX509Info.d.ts +1 -2
  153. package/login/pages/LoginX509Info.js +6 -6
  154. package/login/pages/LoginX509Info.js.map +1 -1
  155. package/login/pages/LogoutConfirm.d.ts +1 -2
  156. package/login/pages/LogoutConfirm.js +6 -6
  157. package/login/pages/LogoutConfirm.js.map +1 -1
  158. package/login/pages/PageProps.d.ts +2 -4
  159. package/login/pages/Register.d.ts +1 -2
  160. package/login/pages/Register.js +8 -16
  161. package/login/pages/Register.js.map +1 -1
  162. package/login/pages/SamlPostForm.d.ts +1 -2
  163. package/login/pages/SamlPostForm.js +4 -3
  164. package/login/pages/SamlPostForm.js.map +1 -1
  165. package/login/pages/SelectAuthenticator.d.ts +1 -2
  166. package/login/pages/SelectAuthenticator.js +6 -9
  167. package/login/pages/SelectAuthenticator.js.map +1 -1
  168. package/login/pages/Terms.d.ts +1 -2
  169. package/login/pages/Terms.js +7 -7
  170. package/login/pages/Terms.js.map +1 -1
  171. package/login/pages/UpdateEmail.d.ts +1 -2
  172. package/login/pages/UpdateEmail.js +8 -12
  173. package/login/pages/UpdateEmail.js.map +1 -1
  174. package/login/pages/WebauthnAuthenticate.d.ts +1 -2
  175. package/login/pages/WebauthnAuthenticate.js +13 -12
  176. package/login/pages/WebauthnAuthenticate.js.map +1 -1
  177. package/login/pages/WebauthnError.d.ts +1 -2
  178. package/login/pages/WebauthnError.js +7 -7
  179. package/login/pages/WebauthnError.js.map +1 -1
  180. package/login/pages/WebauthnRegister.d.ts +1 -2
  181. package/login/pages/WebauthnRegister.js +9 -8
  182. package/login/pages/WebauthnRegister.js.map +1 -1
  183. package/package.json +24 -16
  184. package/src/account/Fallback.tsx +1 -2
  185. package/src/account/KcContext/KcContext.ts +7 -7
  186. package/src/account/KcContext/getKcContextMock.ts +13 -24
  187. package/src/account/Template.tsx +8 -8
  188. package/src/account/TemplateProps.ts +1 -6
  189. package/src/account/i18n/i18n.tsx +204 -125
  190. package/src/account/i18n/index.ts +10 -2
  191. package/src/account/index.ts +1 -1
  192. package/src/account/lib/{useGetClassName.ts → kcClsx.ts} +6 -2
  193. package/src/account/pages/Account.tsx +15 -21
  194. package/src/account/pages/Applications.tsx +8 -9
  195. package/src/account/pages/FederatedIdentity.tsx +5 -5
  196. package/src/account/pages/Log.tsx +8 -8
  197. package/src/account/pages/PageProps.ts +2 -4
  198. package/src/account/pages/Password.tsx +13 -16
  199. package/src/account/pages/Sessions.tsx +9 -10
  200. package/src/account/pages/Totp.tsx +19 -28
  201. package/src/bin/keycloakify/generateSrcMainResources/generateMessageProperties.ts +2 -66
  202. package/src/bin/keycloakify/generateSrcMainResources/generateSrcMainResourcesForMainTheme.ts +7 -1
  203. package/src/bin/main.ts +15 -0
  204. package/src/bin/shared/buildOptions.ts +5 -2
  205. package/src/bin/shared/generateKcGenTs.ts +61 -0
  206. package/src/bin/tools/escapeStringForPropertiesFile.ts +64 -0
  207. package/src/bin/update-kc-gen.ts +13 -0
  208. package/src/lib/getKcClsx.ts +75 -0
  209. package/src/login/Fallback.tsx +1 -2
  210. package/src/login/KcContext/KcContext.ts +7 -7
  211. package/src/login/KcContext/getKcContextMock.ts +13 -24
  212. package/src/login/Template.tsx +36 -37
  213. package/src/login/TemplateProps.ts +1 -6
  214. package/src/login/UserProfileFormFields.tsx +66 -81
  215. package/src/login/i18n/i18n.tsx +208 -129
  216. package/src/login/i18n/index.ts +10 -2
  217. package/src/login/index.ts +1 -1
  218. package/src/login/lib/{useGetClassName.ts → kcClsx.ts} +6 -2
  219. package/src/login/lib/useUserProfileForm.tsx +29 -21
  220. package/src/login/pages/Code.tsx +10 -8
  221. package/src/login/pages/DeleteAccountConfirm.tsx +9 -10
  222. package/src/login/pages/DeleteCredential.tsx +11 -10
  223. package/src/login/pages/Error.tsx +5 -5
  224. package/src/login/pages/FrontchannelLogout.tsx +7 -5
  225. package/src/login/pages/IdpReviewUserProfile.tsx +16 -25
  226. package/src/login/pages/Info.tsx +7 -5
  227. package/src/login/pages/Login.tsx +34 -53
  228. package/src/login/pages/LoginConfigTotp.tsx +30 -38
  229. package/src/login/pages/LoginIdpLinkConfirm.tsx +10 -21
  230. package/src/login/pages/LoginIdpLinkEmail.tsx +5 -5
  231. package/src/login/pages/LoginOauth2DeviceVerifyUserCode.tsx +19 -24
  232. package/src/login/pages/LoginOauthGrant.tsx +14 -21
  233. package/src/login/pages/LoginOtp.tsx +29 -33
  234. package/src/login/pages/LoginPageExpired.tsx +5 -5
  235. package/src/login/pages/LoginPassword.tsx +23 -33
  236. package/src/login/pages/LoginRecoveryAuthnCodeConfig.tsx +21 -25
  237. package/src/login/pages/LoginRecoveryAuthnCodeInput.tsx +21 -25
  238. package/src/login/pages/LoginResetOtp.tsx +21 -25
  239. package/src/login/pages/LoginResetPassword.tsx +21 -25
  240. package/src/login/pages/LoginUpdatePassword.tsx +42 -52
  241. package/src/login/pages/LoginUpdateProfile.tsx +26 -33
  242. package/src/login/pages/LoginUsername.tsx +23 -35
  243. package/src/login/pages/LoginVerifyEmail.tsx +7 -5
  244. package/src/login/pages/LoginX509Info.tsx +27 -36
  245. package/src/login/pages/LogoutConfirm.tsx +11 -17
  246. package/src/login/pages/PageProps.ts +2 -4
  247. package/src/login/pages/Register.tsx +24 -49
  248. package/src/login/pages/SamlPostForm.tsx +5 -5
  249. package/src/login/pages/SelectAuthenticator.tsx +24 -26
  250. package/src/login/pages/Terms.tsx +11 -18
  251. package/src/login/pages/UpdateEmail.tsx +26 -36
  252. package/src/login/pages/WebauthnAuthenticate.tsx +26 -32
  253. package/src/login/pages/WebauthnError.tsx +11 -22
  254. package/src/login/pages/WebauthnRegister.tsx +20 -28
  255. package/src/tools/clsx.ts +6 -48
  256. package/src/tools/clsx_withTransform.ts +55 -0
  257. package/src/vite-plugin/vite-plugin.ts +14 -6
  258. package/tools/clsx.d.ts +3 -2
  259. package/tools/clsx.js +5 -41
  260. package/tools/clsx.js.map +1 -1
  261. package/tools/clsx_withTransform.d.ts +5 -0
  262. package/tools/clsx_withTransform.js +43 -0
  263. package/tools/clsx_withTransform.js.map +1 -0
  264. package/vite-plugin/index.js +167 -37
  265. package/account/lib/useGetClassName.d.ts +0 -7
  266. package/account/lib/useGetClassName.js.map +0 -1
  267. package/lib/useGetClassName.d.ts +0 -10
  268. package/lib/useGetClassName.js +0 -14
  269. package/lib/useGetClassName.js.map +0 -1
  270. package/login/lib/useGetClassName.d.ts +0 -7
  271. package/login/lib/useGetClassName.js.map +0 -1
  272. package/src/lib/useGetClassName.ts +0 -27
@@ -1,7 +1,7 @@
1
1
  import "keycloakify/tools/Object.fromEntries";
2
- import { useEffect, useState, useRef } from "react";
2
+ import { useEffect, useState } from "react";
3
3
  import { assert } from "tsafe/assert";
4
- import fallbackMessages from "./baseMessages/en";
4
+ import messages_fallbackLanguage from "./baseMessages/en";
5
5
  import { getMessages } from "./baseMessages";
6
6
  import type { KcContext } from "../KcContext";
7
7
  import { Reflect } from "tsafe/Reflect";
@@ -18,7 +18,7 @@ export type KcContextLike = {
18
18
 
19
19
  assert<KcContext extends KcContextLike ? true : false>();
20
20
 
21
- export type MessageKey = keyof typeof fallbackMessages;
21
+ export type MessageKey = keyof typeof messages_fallbackLanguage;
22
22
 
23
23
  export type GenericI18n<MessageKey extends string> = {
24
24
  /**
@@ -80,183 +80,262 @@ export type GenericI18n<MessageKey extends string> = {
80
80
  * See advancedMsg() but instead of returning a JSX.Element it returns a string.
81
81
  */
82
82
  advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
83
+
84
+ /**
85
+ * Initially the messages are in english (fallback language).
86
+ * The translations in the current language are being fetched dynamically.
87
+ * This property is true while the translations are being fetched.
88
+ */
89
+ isFetchingTranslations: boolean;
83
90
  };
84
91
 
85
- export type I18n = GenericI18n<MessageKey>;
92
+ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } }) {
93
+ type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
94
+
95
+ type Result = { i18n: I18n; prI18n_currentLanguage: Promise<I18n> | undefined };
96
+
97
+ const cachedResultByKcContext = new WeakMap<KcContextLike, Result>();
98
+
99
+ function getI18n(params: { kcContext: KcContextLike }): Result {
100
+ const { kcContext } = params;
101
+
102
+ use_cache: {
103
+ const cachedResult = cachedResultByKcContext.get(kcContext);
104
+
105
+ if (cachedResult === undefined) {
106
+ break use_cache;
107
+ }
108
+
109
+ return cachedResult;
110
+ }
111
+
112
+ const partialI18n: Pick<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> = {
113
+ currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
114
+ getChangeLocalUrl: newLanguageTag => {
115
+ const { locale } = kcContext;
116
+
117
+ assert(locale !== undefined, "Internationalization not enabled");
118
+
119
+ const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === newLanguageTag);
120
+
121
+ assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`);
122
+
123
+ return targetSupportedLocale.url;
124
+ },
125
+ labelBySupportedLanguageTag: Object.fromEntries((kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label]))
126
+ };
127
+
128
+ const { createI18nTranslationFunctions } = createI18nTranslationFunctionsFactory<MessageKey, ExtraMessageKey>({
129
+ messages_fallbackLanguage,
130
+ extraMessages_fallbackLanguage: extraMessages[fallbackLanguageTag],
131
+ extraMessages: extraMessages[partialI18n.currentLanguageTag],
132
+ __localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
133
+ });
134
+
135
+ const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag !== fallbackLanguageTag;
136
+
137
+ const result: Result = {
138
+ i18n: {
139
+ ...partialI18n,
140
+ ...createI18nTranslationFunctions({ messages: undefined }),
141
+ isFetchingTranslations: !isCurrentLanguageFallbackLanguage
142
+ },
143
+ prI18n_currentLanguage: isCurrentLanguageFallbackLanguage
144
+ ? undefined
145
+ : (async () => {
146
+ const messages = await getMessages(partialI18n.currentLanguageTag);
147
+
148
+ const i18n_currentLanguage: I18n = {
149
+ ...partialI18n,
150
+ ...createI18nTranslationFunctions({ messages }),
151
+ isFetchingTranslations: false
152
+ };
153
+
154
+ // NOTE: This promise.resolve is just because without it we TypeScript
155
+ // gives a Variable 'result' is used before being assigned. error
156
+ await Promise.resolve().then(() => {
157
+ result.i18n = i18n_currentLanguage;
158
+ result.prI18n_currentLanguage = undefined;
159
+ });
160
+
161
+ return i18n_currentLanguage;
162
+ })()
163
+ };
164
+
165
+ cachedResultByKcContext.set(kcContext, result);
166
+
167
+ return result;
168
+ }
169
+
170
+ return { getI18n };
171
+ }
86
172
 
87
173
  export function createUseI18n<ExtraMessageKey extends string = never>(extraMessages: {
88
174
  [languageTag: string]: { [key in ExtraMessageKey]: string };
89
175
  }) {
90
- function useI18n(params: { kcContext: KcContextLike }): GenericI18n<MessageKey | ExtraMessageKey> | null {
176
+ type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
177
+
178
+ const { getI18n } = createGetI18n(extraMessages);
179
+
180
+ function useI18n(params: { kcContext: KcContextLike }): I18n {
91
181
  const { kcContext } = params;
92
182
 
93
- const [i18n, setI18n] = useState<GenericI18n<ExtraMessageKey | MessageKey> | undefined>(undefined);
183
+ const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
94
184
 
95
- const refHasStartedFetching = useRef(false);
185
+ const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
96
186
 
97
187
  useEffect(() => {
98
- if (refHasStartedFetching.current) {
99
- return;
100
- }
188
+ let isActive = true;
101
189
 
102
- refHasStartedFetching.current = true;
103
-
104
- (async () => {
105
- const { currentLanguageTag = fallbackLanguageTag } = kcContext.locale ?? {};
106
-
107
- setI18n({
108
- ...createI18nTranslationFunctions({
109
- fallbackMessages: {
110
- ...fallbackMessages,
111
- ...(extraMessages[fallbackLanguageTag] ?? {})
112
- } as any,
113
- messages: {
114
- ...(await getMessages(currentLanguageTag)),
115
- ...(extraMessages[currentLanguageTag] ?? {})
116
- } as any,
117
- __localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
118
- }),
119
- currentLanguageTag,
120
- getChangeLocalUrl: newLanguageTag => {
121
- const { locale } = kcContext;
122
-
123
- assert(locale !== undefined, "Internationalization not enabled");
124
-
125
- const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === newLanguageTag);
126
-
127
- assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`);
128
-
129
- return targetSupportedLocale.url;
130
- },
131
- labelBySupportedLanguageTag: Object.fromEntries(
132
- (kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
133
- )
134
- });
135
- })();
190
+ prI18n_currentLanguage?.then(i18n => {
191
+ if (!isActive) {
192
+ return;
193
+ }
194
+
195
+ setI18n_toReturn(i18n);
196
+ });
197
+
198
+ return () => {
199
+ isActive = false;
200
+ };
136
201
  }, []);
137
202
 
138
- return i18n ?? null;
203
+ return i18n_toReturn;
139
204
  }
140
205
 
141
- return {
142
- useI18n,
143
- ofTypeI18n: Reflect<GenericI18n<MessageKey | ExtraMessageKey>>()
144
- };
206
+ return { useI18n, ofTypeI18n: Reflect<I18n>() };
145
207
  }
146
208
 
147
- function createI18nTranslationFunctions<MessageKey extends string>(params: {
148
- fallbackMessages: Record<MessageKey, string>;
149
- messages: Record<MessageKey, string>;
209
+ function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
210
+ messages_fallbackLanguage: Record<MessageKey, string>;
211
+ extraMessages_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
212
+ extraMessages: Partial<Record<ExtraMessageKey, string>> | undefined;
150
213
  __localizationRealmOverridesUserProfile: Record<string, string> | undefined;
151
- }): Pick<GenericI18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
152
- const { fallbackMessages, messages, __localizationRealmOverridesUserProfile } = params;
153
-
154
- function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
155
- const { key, args, doRenderAsHtml } = props;
214
+ }) {
215
+ const { __localizationRealmOverridesUserProfile, extraMessages } = params;
156
216
 
157
- const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
217
+ const messages_fallbackLanguage = {
218
+ ...params.messages_fallbackLanguage,
219
+ ...params.extraMessages_fallbackLanguage
220
+ };
158
221
 
159
- if (messageOrUndefined === undefined) {
160
- return undefined;
161
- }
222
+ function createI18nTranslationFunctions(params: {
223
+ messages: Partial<Record<MessageKey, string>> | undefined;
224
+ }): Pick<GenericI18n<MessageKey | ExtraMessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
225
+ const messages = {
226
+ ...params.messages,
227
+ ...extraMessages
228
+ };
162
229
 
163
- const message = messageOrUndefined;
230
+ function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
231
+ const { key, args, doRenderAsHtml } = props;
164
232
 
165
- const messageWithArgsInjectedIfAny = (() => {
166
- const startIndex = message
167
- .match(/{[0-9]+}/g)
168
- ?.map(g => g.match(/{([0-9]+)}/)![1])
169
- .map(indexStr => parseInt(indexStr))
170
- .sort((a, b) => a - b)[0];
233
+ const messageOrUndefined: string | undefined = (messages as any)[key] ?? (messages_fallbackLanguage as any)[key];
171
234
 
172
- if (startIndex === undefined) {
173
- // No {0} in message (no arguments expected)
174
- return message;
235
+ if (messageOrUndefined === undefined) {
236
+ return undefined;
175
237
  }
176
238
 
177
- let messageWithArgsInjected = message;
239
+ const message = messageOrUndefined;
178
240
 
179
- args.forEach((arg, i) => {
180
- if (arg === undefined) {
181
- return;
241
+ const messageWithArgsInjectedIfAny = (() => {
242
+ const startIndex = message
243
+ .match(/{[0-9]+}/g)
244
+ ?.map(g => g.match(/{([0-9]+)}/)![1])
245
+ .map(indexStr => parseInt(indexStr))
246
+ .sort((a, b) => a - b)[0];
247
+
248
+ if (startIndex === undefined) {
249
+ // No {0} in message (no arguments expected)
250
+ return message;
182
251
  }
183
252
 
184
- messageWithArgsInjected = messageWithArgsInjected.replace(
185
- new RegExp(`\\{${i + startIndex}\\}`, "g"),
186
- arg.replace(/</g, "&lt;").replace(/>/g, "&gt;")
187
- );
188
- });
253
+ let messageWithArgsInjected = message;
189
254
 
190
- return messageWithArgsInjected;
191
- })();
192
-
193
- return doRenderAsHtml ? (
194
- <span
195
- // NOTE: The message is trusted. The arguments are not but are escaped.
196
- dangerouslySetInnerHTML={{
197
- __html: messageWithArgsInjectedIfAny
198
- }}
199
- />
200
- ) : (
201
- messageWithArgsInjectedIfAny
202
- );
203
- }
255
+ args.forEach((arg, i) => {
256
+ if (arg === undefined) {
257
+ return;
258
+ }
204
259
 
205
- function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
206
- const { key, args, doRenderAsHtml } = props;
260
+ messageWithArgsInjected = messageWithArgsInjected.replace(
261
+ new RegExp(`\\{${i + startIndex}\\}`, "g"),
262
+ arg.replace(/</g, "&lt;").replace(/>/g, "&gt;")
263
+ );
264
+ });
207
265
 
208
- if (__localizationRealmOverridesUserProfile !== undefined && key in __localizationRealmOverridesUserProfile) {
209
- const resolvedMessage = __localizationRealmOverridesUserProfile[key];
266
+ return messageWithArgsInjected;
267
+ })();
210
268
 
211
269
  return doRenderAsHtml ? (
212
270
  <span
213
271
  // NOTE: The message is trusted. The arguments are not but are escaped.
214
272
  dangerouslySetInnerHTML={{
215
- __html: resolvedMessage
273
+ __html: messageWithArgsInjectedIfAny
216
274
  }}
217
275
  />
218
276
  ) : (
219
- resolvedMessage
277
+ messageWithArgsInjectedIfAny
220
278
  );
221
279
  }
222
280
 
223
- if (!/\$\{[^}]+\}/.test(key)) {
224
- const resolvedMessage = resolveMsg({ key, args, doRenderAsHtml });
281
+ function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
282
+ const { key, args, doRenderAsHtml } = props;
283
+
284
+ if (__localizationRealmOverridesUserProfile !== undefined && key in __localizationRealmOverridesUserProfile) {
285
+ const resolvedMessage = __localizationRealmOverridesUserProfile[key];
286
+
287
+ return doRenderAsHtml ? (
288
+ <span
289
+ // NOTE: The message is trusted. The arguments are not but are escaped.
290
+ dangerouslySetInnerHTML={{
291
+ __html: resolvedMessage
292
+ }}
293
+ />
294
+ ) : (
295
+ resolvedMessage
296
+ );
297
+ }
298
+
299
+ if (!/\$\{[^}]+\}/.test(key)) {
300
+ const resolvedMessage = resolveMsg({ key, args, doRenderAsHtml });
225
301
 
226
- if (resolvedMessage === undefined) {
227
- return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: key }} /> : key;
302
+ if (resolvedMessage === undefined) {
303
+ return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: key }} /> : key;
304
+ }
305
+
306
+ return resolvedMessage;
228
307
  }
229
308
 
230
- return resolvedMessage;
231
- }
309
+ let isFirstMatch = true;
232
310
 
233
- let isFirstMatch = true;
311
+ const resolvedComplexMessage = key.replace(/\$\{([^}]+)\}/g, (...[, key_i]) => {
312
+ const replaceBy = resolveMsg({ key: key_i, args: isFirstMatch ? args : [], doRenderAsHtml: false }) ?? key_i;
234
313
 
235
- const resolvedComplexMessage = key.replace(/\$\{([^}]+)\}/g, (...[, key_i]) => {
236
- const replaceBy = resolveMsg({ key: key_i, args: isFirstMatch ? args : [], doRenderAsHtml: false }) ?? key_i;
314
+ isFirstMatch = false;
237
315
 
238
- isFirstMatch = false;
316
+ return replaceBy;
317
+ });
239
318
 
240
- return replaceBy;
241
- });
319
+ return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: resolvedComplexMessage }} /> : resolvedComplexMessage;
320
+ }
242
321
 
243
- return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: resolvedComplexMessage }} /> : resolvedComplexMessage;
322
+ return {
323
+ msgStr: (key, ...args) => resolveMsg({ key, args, doRenderAsHtml: false }) as string,
324
+ msg: (key, ...args) => resolveMsg({ key, args, doRenderAsHtml: true }) as JSX.Element,
325
+ advancedMsg: (key, ...args) =>
326
+ resolveMsgAdvanced({
327
+ key,
328
+ args,
329
+ doRenderAsHtml: true
330
+ }) as JSX.Element,
331
+ advancedMsgStr: (key, ...args) =>
332
+ resolveMsgAdvanced({
333
+ key,
334
+ args,
335
+ doRenderAsHtml: false
336
+ }) as string
337
+ };
244
338
  }
245
339
 
246
- return {
247
- msgStr: (key, ...args) => resolveMsg({ key, args, doRenderAsHtml: false }) as string,
248
- msg: (key, ...args) => resolveMsg({ key, args, doRenderAsHtml: true }) as JSX.Element,
249
- advancedMsg: (key, ...args) =>
250
- resolveMsgAdvanced({
251
- key,
252
- args,
253
- doRenderAsHtml: true
254
- }) as JSX.Element,
255
- advancedMsgStr: (key, ...args) =>
256
- resolveMsgAdvanced({
257
- key,
258
- args,
259
- doRenderAsHtml: false
260
- }) as string
261
- };
340
+ return { createI18nTranslationFunctions };
262
341
  }
@@ -1,2 +1,10 @@
1
- export type { I18n, MessageKey } from "./i18n";
2
- export { createUseI18n, fallbackLanguageTag } from "./i18n";
1
+ export type { MessageKey, KcContextLike } from "./i18n";
2
+ import { createUseI18n } from "./i18n";
3
+ export { createUseI18n };
4
+ export { fallbackLanguageTag } from "./i18n";
5
+
6
+ const { useI18n, ofTypeI18n } = createUseI18n({});
7
+
8
+ export type I18n = typeof ofTypeI18n;
9
+
10
+ export { useI18n };
@@ -1,4 +1,4 @@
1
1
  export type { ExtendKcContext, Attribute } from "keycloakify/login/KcContext";
2
- export type { PageProps } from "keycloakify/login/pages/PageProps";
2
+ export type { ClassKey } from "keycloakify/login/TemplateProps";
3
3
  export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
4
4
  export { createUseI18n } from "keycloakify/login/i18n";
@@ -1,7 +1,7 @@
1
- import { createUseClassName } from "keycloakify/lib/useGetClassName";
1
+ import { createGetKcClsx } from "keycloakify/lib/getKcClsx";
2
2
  import type { ClassKey } from "keycloakify/login/TemplateProps";
3
3
 
4
- export const { useGetClassName } = createUseClassName<ClassKey>({
4
+ export const { getKcClsx } = createGetKcClsx<ClassKey>({
5
5
  defaultClasses: {
6
6
  kcHtmlClass: "login-pf",
7
7
  kcBodyClass: undefined,
@@ -137,3 +137,7 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
137
137
  kcLabelClass: "pf-c-form__label pf-c-form__label-text"
138
138
  }
139
139
  });
140
+
141
+ export type { ClassKey };
142
+
143
+ export type KcClsx = ReturnType<typeof getKcClsx>["kcClsx"];
@@ -7,9 +7,11 @@ import { useConstCallback } from "keycloakify/tools/useConstCallback";
7
7
  import { emailRegexp } from "keycloakify/tools/emailRegExp";
8
8
  import { formatNumber } from "keycloakify/tools/formatNumber";
9
9
  import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
10
- import type { KcContext, PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
10
+ import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
11
+ import type { KcContext } from "../KcContext";
11
12
  import type { MessageKey } from "keycloakify/login/i18n";
12
- import type { I18n } from "../i18n";
13
+ import { KcContextLike as KcContextLike_i18n } from "keycloakify/login/i18n";
14
+ import { useI18n } from "../i18n";
13
15
 
14
16
  export type FormFieldError = {
15
17
  errorMessage: JSX.Element;
@@ -64,23 +66,23 @@ export type FormAction =
64
66
  fieldIndex: number | undefined;
65
67
  };
66
68
 
67
- export type KcContextLike = {
68
- messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
69
- profile: {
70
- attributesByName: Record<string, Attribute>;
71
- html5DataAnnotations?: Record<string, string>;
72
- };
73
- passwordRequired?: boolean;
74
- realm: { registrationEmailAsUsername: boolean };
75
- passwordPolicies?: PasswordPolicies;
76
- url: {
77
- resourcesPath: string;
69
+ export type KcContextLike = KcContextLike_i18n &
70
+ KcContextLike_useGetErrors & {
71
+ profile: {
72
+ attributesByName: Record<string, Attribute>;
73
+ html5DataAnnotations?: Record<string, string>;
74
+ };
75
+ passwordRequired?: boolean;
76
+ realm: { registrationEmailAsUsername: boolean };
77
+ url: {
78
+ resourcesPath: string;
79
+ };
78
80
  };
79
- };
81
+
82
+ assert<Extract<KcContext.Register, { pageId: "register.ftl" }> extends KcContextLike ? true : false>();
80
83
 
81
84
  export type ParamsOfUseUserProfileForm = {
82
85
  kcContext: KcContextLike;
83
- i18n: I18n;
84
86
  doMakeUserConfirmPassword: boolean;
85
87
  };
86
88
 
@@ -103,7 +105,7 @@ namespace internal {
103
105
  }
104
106
 
105
107
  export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
106
- const { kcContext, i18n, doMakeUserConfirmPassword } = params;
108
+ const { kcContext, doMakeUserConfirmPassword } = params;
107
109
 
108
110
  const { insertScriptTags } = useInsertScriptTags({
109
111
  componentOrHookName: "useUserProfileForm",
@@ -120,8 +122,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
120
122
  }, []);
121
123
 
122
124
  const { getErrors } = useGetErrors({
123
- kcContext,
124
- i18n
125
+ kcContext
125
126
  });
126
127
 
127
128
  const initialState = useMemo((): internal.State => {
@@ -515,12 +516,19 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
515
516
  };
516
517
  }
517
518
 
518
- function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField" | "passwordPolicies">; i18n: I18n }) {
519
- const { kcContext, i18n } = params;
519
+ type KcContextLike_useGetErrors = KcContextLike_i18n & {
520
+ messagesPerField: Pick<KcContext["messagesPerField"], "existsError" | "get">;
521
+ passwordPolicies?: PasswordPolicies;
522
+ };
523
+
524
+ assert<KcContextLike extends KcContextLike_useGetErrors ? true : false>();
525
+
526
+ function useGetErrors(params: { kcContext: KcContextLike_useGetErrors }) {
527
+ const { kcContext } = params;
520
528
 
521
529
  const { messagesPerField, passwordPolicies } = kcContext;
522
530
 
523
- const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
531
+ const { msg, msgStr, advancedMsg, advancedMsgStr } = useI18n({ kcContext });
524
532
 
525
533
  const getErrors = useConstCallback(
526
534
  (params: {
@@ -1,30 +1,32 @@
1
- import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
1
+ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
2
2
  import type { PageProps } from "keycloakify/login/pages/PageProps";
3
3
  import type { KcContext } from "../KcContext";
4
- import type { I18n } from "../i18n";
4
+ import { useI18n } from "../i18n";
5
5
 
6
- export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>, I18n>) {
7
- const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
6
+ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>>) {
7
+ const { kcContext, doUseDefaultCss, Template, classes } = props;
8
8
 
9
- const { getClassName } = useGetClassName({
9
+ const { kcClsx } = getKcClsx({
10
10
  doUseDefaultCss,
11
11
  classes
12
12
  });
13
13
 
14
14
  const { code } = kcContext;
15
15
 
16
- const { msg } = i18n;
16
+ const { msg } = useI18n({ kcContext });
17
17
 
18
18
  return (
19
19
  <Template
20
- {...{ kcContext, i18n, doUseDefaultCss, classes }}
20
+ kcContext={kcContext}
21
+ doUseDefaultCss={doUseDefaultCss}
22
+ classes={classes}
21
23
  headerNode={code.success ? msg("codeSuccessTitle") : msg("codeErrorTitle", code.error)}
22
24
  >
23
25
  <div id="kc-code">
24
26
  {code.success ? (
25
27
  <>
26
28
  <p>{msg("copyCodeInstruction")}</p>
27
- <input id="code" className={getClassName("kcTextareaClass")} defaultValue={code.code} />
29
+ <input id="code" className={kcClsx("kcTextareaClass")} defaultValue={code.code} />
28
30
  </>
29
31
  ) : (
30
32
  <p id="error">{code.error}</p>
@@ -1,23 +1,22 @@
1
- import { clsx } from "keycloakify/tools/clsx";
2
- import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
1
+ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
3
2
  import type { PageProps } from "keycloakify/login/pages/PageProps";
4
3
  import type { KcContext } from "../KcContext";
5
- import type { I18n } from "../i18n";
4
+ import { useI18n } from "../i18n";
6
5
 
7
- export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>, I18n>) {
8
- const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
6
+ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>>) {
7
+ const { kcContext, doUseDefaultCss, Template, classes } = props;
9
8
 
10
- const { getClassName } = useGetClassName({
9
+ const { kcClsx } = getKcClsx({
11
10
  doUseDefaultCss,
12
11
  classes
13
12
  });
14
13
 
15
14
  const { url, triggered_from_aia } = kcContext;
16
15
 
17
- const { msg, msgStr } = i18n;
16
+ const { msg, msgStr } = useI18n({ kcContext });
18
17
 
19
18
  return (
20
- <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("deleteAccountConfirm")}>
19
+ <Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("deleteAccountConfirm")}>
21
20
  <form action={url.loginAction} className="form-vertical" method="post">
22
21
  <div className="alert alert-warning" style={{ marginTop: "0", marginBottom: "30px" }}>
23
22
  <span className="pficon pficon-warning-triangle-o"></span>
@@ -37,13 +36,13 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
37
36
  <p className="delete-account-text">{msg("finalDeletionConfirmation")}</p>
38
37
  <div id="kc-form-buttons">
39
38
  <input
40
- className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonPrimaryClass"), getClassName("kcButtonLargeClass"))}
39
+ className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonLargeClass")}
41
40
  type="submit"
42
41
  value={msgStr("doConfirmDelete")}
43
42
  />
44
43
  {triggered_from_aia && (
45
44
  <button
46
- className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))}
45
+ className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonLargeClass")}
47
46
  style={{ marginLeft: "calc(100% - 220px)" }}
48
47
  type="submit"
49
48
  name="cancel-aia"