keycloakify 10.1.3 → 11.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 (198) hide show
  1. package/account/KcContext/kcContextMocks.js +18 -6
  2. package/account/KcContext/kcContextMocks.js.map +1 -1
  3. package/account/Template.js +8 -26
  4. package/account/Template.js.map +1 -1
  5. package/account/Template.useInitialize.d.ts +12 -0
  6. package/account/Template.useInitialize.js +20 -0
  7. package/account/Template.useInitialize.js.map +1 -0
  8. package/account/i18n/index.d.ts +5 -5
  9. package/account/i18n/index.js +1 -1
  10. package/account/i18n/index.js.map +1 -1
  11. package/account/i18n/messages_defaultSet/types.d.ts +3 -0
  12. package/account/i18n/messages_defaultSet/types.js +26 -0
  13. package/account/i18n/messages_defaultSet/types.js.map +1 -0
  14. package/account/i18n/noJsx/GenericI18n_noJsx.d.ts +63 -0
  15. package/account/i18n/noJsx/GenericI18n_noJsx.js +2 -0
  16. package/account/i18n/noJsx/GenericI18n_noJsx.js.map +1 -0
  17. package/account/i18n/noJsx/getI18n.d.ts +41 -0
  18. package/account/i18n/noJsx/getI18n.js +207 -0
  19. package/account/i18n/noJsx/getI18n.js.map +1 -0
  20. package/account/i18n/noJsx/i18nBuilder.d.ts +18 -0
  21. package/account/i18n/noJsx/i18nBuilder.js +27 -0
  22. package/account/i18n/noJsx/i18nBuilder.js.map +1 -0
  23. package/account/i18n/noJsx/index.d.ts +3 -0
  24. package/account/i18n/noJsx/index.js +2 -0
  25. package/account/i18n/noJsx/index.js.map +1 -0
  26. package/account/i18n/withJsx/GenericI18n.d.ts +72 -0
  27. package/account/i18n/withJsx/GenericI18n.js +5 -0
  28. package/account/i18n/withJsx/GenericI18n.js.map +1 -0
  29. package/account/i18n/withJsx/i18nBuilder.d.ts +18 -0
  30. package/account/i18n/withJsx/i18nBuilder.js +27 -0
  31. package/account/i18n/withJsx/i18nBuilder.js.map +1 -0
  32. package/account/i18n/withJsx/index.d.ts +3 -0
  33. package/account/i18n/withJsx/index.js +2 -0
  34. package/account/i18n/withJsx/index.js.map +1 -0
  35. package/account/i18n/withJsx/useI18n.d.ts +27 -0
  36. package/account/i18n/{useI18n.js → withJsx/useI18n.js} +6 -4
  37. package/account/i18n/withJsx/useI18n.js.map +1 -0
  38. package/account/index.d.ts +1 -1
  39. package/account/index.js +1 -1
  40. package/account/index.js.map +1 -1
  41. package/account/pages/Totp.js +3 -2
  42. package/account/pages/Totp.js.map +1 -1
  43. package/bin/246.index.js +191 -191
  44. package/bin/499.index.js +252 -68
  45. package/bin/main.js +1 -1
  46. package/lib/kcSanitize/HtmlPolicyBuilder.d.ts +28 -0
  47. package/lib/kcSanitize/HtmlPolicyBuilder.js +209 -0
  48. package/lib/kcSanitize/HtmlPolicyBuilder.js.map +1 -0
  49. package/lib/kcSanitize/KcSanitizer.d.ts +12 -0
  50. package/lib/kcSanitize/KcSanitizer.js +46 -0
  51. package/lib/kcSanitize/KcSanitizer.js.map +1 -0
  52. package/lib/kcSanitize/KcSanitizerPolicy.d.ts +24 -0
  53. package/lib/kcSanitize/KcSanitizerPolicy.js +149 -0
  54. package/lib/kcSanitize/KcSanitizerPolicy.js.map +1 -0
  55. package/lib/kcSanitize/index.d.ts +1 -0
  56. package/lib/kcSanitize/index.js +5 -0
  57. package/lib/kcSanitize/index.js.map +1 -0
  58. package/login/KcContext/kcContextMocks.js +24 -6
  59. package/login/KcContext/kcContextMocks.js.map +1 -1
  60. package/login/Template.js +7 -7
  61. package/login/Template.js.map +1 -1
  62. package/login/{Template.useStylesAndScripts.d.ts → Template.useInitialize.d.ts} +1 -4
  63. package/login/{Template.useStylesAndScripts.js → Template.useInitialize.js} +5 -23
  64. package/login/Template.useInitialize.js.map +1 -0
  65. package/login/i18n/index.d.ts +5 -5
  66. package/login/i18n/index.js +1 -1
  67. package/login/i18n/index.js.map +1 -1
  68. package/login/i18n/messages_defaultSet/types.d.ts +3 -0
  69. package/login/i18n/messages_defaultSet/types.js +33 -0
  70. package/login/i18n/messages_defaultSet/types.js.map +1 -0
  71. package/login/i18n/noJsx/GenericI18n_noJsx.d.ts +63 -0
  72. package/login/i18n/noJsx/GenericI18n_noJsx.js +2 -0
  73. package/login/i18n/noJsx/GenericI18n_noJsx.js.map +1 -0
  74. package/login/i18n/noJsx/getI18n.d.ts +41 -0
  75. package/login/i18n/noJsx/getI18n.js +207 -0
  76. package/login/i18n/noJsx/getI18n.js.map +1 -0
  77. package/login/i18n/noJsx/i18nBuilder.d.ts +18 -0
  78. package/login/i18n/noJsx/i18nBuilder.js +27 -0
  79. package/login/i18n/noJsx/i18nBuilder.js.map +1 -0
  80. package/login/i18n/noJsx/index.d.ts +3 -0
  81. package/login/i18n/noJsx/index.js +2 -0
  82. package/login/i18n/noJsx/index.js.map +1 -0
  83. package/login/i18n/withJsx/GenericI18n.d.ts +72 -0
  84. package/login/i18n/withJsx/GenericI18n.js +5 -0
  85. package/login/i18n/withJsx/GenericI18n.js.map +1 -0
  86. package/login/i18n/withJsx/i18nBuilder.d.ts +18 -0
  87. package/login/i18n/withJsx/i18nBuilder.js +27 -0
  88. package/login/i18n/withJsx/i18nBuilder.js.map +1 -0
  89. package/login/i18n/withJsx/index.d.ts +3 -0
  90. package/login/i18n/withJsx/index.js +2 -0
  91. package/login/i18n/withJsx/index.js.map +1 -0
  92. package/login/i18n/withJsx/useI18n.d.ts +27 -0
  93. package/login/i18n/{useI18n.js → withJsx/useI18n.js} +6 -4
  94. package/login/i18n/withJsx/useI18n.js.map +1 -0
  95. package/login/index.d.ts +1 -1
  96. package/login/index.js +1 -1
  97. package/login/index.js.map +1 -1
  98. package/login/lib/useUserProfileForm.d.ts +1 -1
  99. package/login/lib/useUserProfileForm.js +2 -1
  100. package/login/lib/useUserProfileForm.js.map +1 -1
  101. package/login/pages/Error.js +2 -1
  102. package/login/pages/Error.js.map +1 -1
  103. package/login/pages/Info.js +4 -3
  104. package/login/pages/Info.js.map +1 -1
  105. package/login/pages/Login.js +4 -3
  106. package/login/pages/Login.js.map +1 -1
  107. package/login/pages/LoginConfigTotp.js +3 -2
  108. package/login/pages/LoginConfigTotp.js.map +1 -1
  109. package/login/pages/LoginOtp.js +2 -1
  110. package/login/pages/LoginOtp.js.map +1 -1
  111. package/login/pages/LoginPassword.js +2 -1
  112. package/login/pages/LoginPassword.js.map +1 -1
  113. package/login/pages/LoginRecoveryAuthnCodeInput.js +2 -1
  114. package/login/pages/LoginRecoveryAuthnCodeInput.js.map +1 -1
  115. package/login/pages/LoginResetPassword.js +2 -1
  116. package/login/pages/LoginResetPassword.js.map +1 -1
  117. package/login/pages/LoginUpdatePassword.js +3 -2
  118. package/login/pages/LoginUpdatePassword.js.map +1 -1
  119. package/login/pages/Register.js +2 -1
  120. package/login/pages/Register.js.map +1 -1
  121. package/package.json +110 -31
  122. package/src/account/KcContext/kcContextMocks.ts +49 -29
  123. package/src/account/Template.tsx +11 -32
  124. package/src/account/Template.useInitialize.ts +35 -0
  125. package/src/account/i18n/index.ts +5 -5
  126. package/src/account/i18n/messages_defaultSet/types.ts +30 -0
  127. package/src/account/i18n/noJsx/GenericI18n_noJsx.ts +64 -0
  128. package/src/account/i18n/noJsx/getI18n.tsx +341 -0
  129. package/src/account/i18n/noJsx/i18nBuilder.ts +117 -0
  130. package/src/account/i18n/noJsx/index.ts +3 -0
  131. package/src/account/i18n/withJsx/GenericI18n.tsx +81 -0
  132. package/src/account/i18n/withJsx/i18nBuilder.ts +117 -0
  133. package/src/account/i18n/withJsx/index.ts +3 -0
  134. package/src/{login/i18n → account/i18n/withJsx}/useI18n.tsx +43 -11
  135. package/src/account/index.ts +1 -1
  136. package/src/account/pages/Totp.tsx +3 -2
  137. package/src/bin/initialize-account-theme/src/multi-page/i18n.ts +10 -3
  138. package/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl +15 -0
  139. package/src/bin/keycloakify/generateResources/generateMessageProperties.ts +371 -121
  140. package/src/bin/keycloakify/generateResources/generateResources.ts +8 -6
  141. package/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +61 -29
  142. package/src/bin/keycloakify/generateResources/generateResourcesForThemeVariant.ts +15 -9
  143. package/src/lib/kcSanitize/HtmlPolicyBuilder.ts +252 -0
  144. package/src/lib/kcSanitize/KcSanitizer.ts +60 -0
  145. package/src/lib/kcSanitize/KcSanitizerPolicy.ts +294 -0
  146. package/src/lib/kcSanitize/index.ts +5 -0
  147. package/src/login/KcContext/kcContextMocks.ts +54 -29
  148. package/src/login/Template.tsx +11 -17
  149. package/src/login/{Template.useStylesAndScripts.ts → Template.useInitialize.ts} +5 -29
  150. package/src/login/i18n/index.ts +5 -5
  151. package/src/login/i18n/messages_defaultSet/types.ts +37 -0
  152. package/src/login/i18n/noJsx/GenericI18n_noJsx.ts +64 -0
  153. package/src/login/i18n/noJsx/getI18n.tsx +341 -0
  154. package/src/login/i18n/noJsx/i18nBuilder.ts +117 -0
  155. package/src/login/i18n/noJsx/index.ts +3 -0
  156. package/src/login/i18n/withJsx/GenericI18n.tsx +81 -0
  157. package/src/login/i18n/withJsx/i18nBuilder.ts +117 -0
  158. package/src/login/i18n/withJsx/index.ts +3 -0
  159. package/src/{account/i18n → login/i18n/withJsx}/useI18n.tsx +43 -11
  160. package/src/login/index.ts +1 -1
  161. package/src/login/lib/useUserProfileForm.tsx +3 -2
  162. package/src/login/pages/Error.tsx +2 -1
  163. package/src/login/pages/Info.tsx +13 -10
  164. package/src/login/pages/Login.tsx +4 -3
  165. package/src/login/pages/LoginConfigTotp.tsx +3 -2
  166. package/src/login/pages/LoginOtp.tsx +2 -1
  167. package/src/login/pages/LoginPassword.tsx +2 -1
  168. package/src/login/pages/LoginRecoveryAuthnCodeInput.tsx +2 -1
  169. package/src/login/pages/LoginResetPassword.tsx +2 -1
  170. package/src/login/pages/LoginUpdatePassword.tsx +3 -2
  171. package/src/login/pages/Register.tsx +2 -1
  172. package/src/tools/useInsertScriptTags.ts +1 -1
  173. package/src/tools/vendor/dompurify.ts +3 -0
  174. package/stories/intro/intro.stories.tsx +0 -1
  175. package/tools/useInsertScriptTags.d.ts +1 -1
  176. package/tools/vendor/dompurify.d.ts +2 -0
  177. package/tools/vendor/dompurify.js +2 -0
  178. package/account/i18n/GenericI18n.d.ts +0 -6
  179. package/account/i18n/GenericI18n.js +0 -2
  180. package/account/i18n/GenericI18n.js.map +0 -1
  181. package/account/i18n/i18n.d.ts +0 -87
  182. package/account/i18n/i18n.js +0 -111
  183. package/account/i18n/i18n.js.map +0 -1
  184. package/account/i18n/useI18n.d.ts +0 -14
  185. package/account/i18n/useI18n.js.map +0 -1
  186. package/login/Template.useStylesAndScripts.js.map +0 -1
  187. package/login/i18n/GenericI18n.d.ts +0 -6
  188. package/login/i18n/GenericI18n.js +0 -2
  189. package/login/i18n/GenericI18n.js.map +0 -1
  190. package/login/i18n/i18n.d.ts +0 -87
  191. package/login/i18n/i18n.js +0 -113
  192. package/login/i18n/i18n.js.map +0 -1
  193. package/login/i18n/useI18n.d.ts +0 -14
  194. package/login/i18n/useI18n.js.map +0 -1
  195. package/src/account/i18n/GenericI18n.tsx +0 -6
  196. package/src/account/i18n/i18n.tsx +0 -250
  197. package/src/login/i18n/GenericI18n.tsx +0 -6
  198. package/src/login/i18n/i18n.tsx +0 -252
@@ -1,6 +1,6 @@
1
1
  import { type ThemeType, FALLBACK_LANGUAGE_TAG } from "../../shared/constants";
2
2
  import { crawl } from "../../tools/crawl";
3
- import { join as pathJoin } from "path";
3
+ import { join as pathJoin, dirname as pathDirname } from "path";
4
4
  import { symToStr } from "tsafe/symToStr";
5
5
  import * as recast from "recast";
6
6
  import * as babelParser from "@babel/parser";
@@ -10,12 +10,27 @@ import { escapeStringForPropertiesFile } from "../../tools/escapeStringForProper
10
10
  import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
11
11
  import * as fs from "fs";
12
12
  import { assert } from "tsafe/assert";
13
+ import type { BuildContext } from "../../shared/buildContext";
14
+ import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
13
15
 
14
- export function generateMessageProperties(params: {
16
+ export type BuildContextLike = {
17
+ themeNames: string[];
15
18
  themeSrcDirPath: string;
19
+ };
20
+
21
+ assert<BuildContext extends BuildContextLike ? true : false>();
22
+
23
+ export function generateMessageProperties(params: {
24
+ buildContext: BuildContextLike;
16
25
  themeType: ThemeType;
17
- }): { languageTag: string; propertiesFileSource: string }[] {
18
- const { themeSrcDirPath, themeType } = params;
26
+ }): {
27
+ languageTags: string[];
28
+ writeMessagePropertiesFiles: (params: {
29
+ messageDirPath: string;
30
+ themeName: string;
31
+ }) => void;
32
+ } {
33
+ const { buildContext, themeType } = params;
19
34
 
20
35
  const baseMessagesDirPath = pathJoin(
21
36
  getThisCodebaseRootDirPath(),
@@ -25,51 +40,49 @@ export function generateMessageProperties(params: {
25
40
  "messages_defaultSet"
26
41
  );
27
42
 
28
- const baseMessageBundle: { [languageTag: string]: Record<string, string> } =
29
- Object.fromEntries(
30
- fs
31
- .readdirSync(baseMessagesDirPath)
32
- .filter(baseName => baseName !== "index.ts")
33
- .map(basename => ({
34
- languageTag: basename.replace(/\.ts$/, ""),
35
- filePath: pathJoin(baseMessagesDirPath, basename)
36
- }))
37
- .map(({ languageTag, filePath }) => {
38
- const lines = fs
39
- .readFileSync(filePath)
40
- .toString("utf8")
41
- .split(/\r?\n/);
42
-
43
- let messagesJson = "{";
44
-
45
- let isInDeclaration = false;
46
-
47
- for (const line of lines) {
48
- if (!isInDeclaration) {
49
- if (line.startsWith("const messages")) {
50
- isInDeclaration = true;
51
- }
43
+ const messages_defaultSet_by_languageTag_defaultSet: {
44
+ [languageTag_defaultSet: string]: Record<string, string>;
45
+ } = Object.fromEntries(
46
+ fs
47
+ .readdirSync(baseMessagesDirPath)
48
+ .filter(basename => basename !== "index.ts" && basename !== "types.ts")
49
+ .map(basename => ({
50
+ languageTag: basename.replace(/\.ts$/, ""),
51
+ filePath: pathJoin(baseMessagesDirPath, basename)
52
+ }))
53
+ .map(({ languageTag, filePath }) => {
54
+ const lines = fs.readFileSync(filePath).toString("utf8").split(/\r?\n/);
52
55
 
53
- continue;
54
- }
56
+ let messagesJson = "{";
57
+
58
+ let isInDeclaration = false;
55
59
 
56
- if (line.startsWith("}")) {
57
- messagesJson += "}";
58
- break;
60
+ for (const line of lines) {
61
+ if (!isInDeclaration) {
62
+ if (line.startsWith("const messages")) {
63
+ isInDeclaration = true;
59
64
  }
60
65
 
61
- messagesJson += line;
66
+ continue;
62
67
  }
63
68
 
64
- const messages = JSON.parse(messagesJson) as Record<string, string>;
69
+ if (line.startsWith("}")) {
70
+ messagesJson += "}";
71
+ break;
72
+ }
65
73
 
66
- return [languageTag, messages];
67
- })
68
- );
74
+ messagesJson += line;
75
+ }
76
+
77
+ const messages = JSON.parse(messagesJson) as Record<string, string>;
78
+
79
+ return [languageTag, messages];
80
+ })
81
+ );
69
82
 
70
83
  const { i18nTsFilePath } = (() => {
71
84
  let files = crawl({
72
- dirPath: pathJoin(themeSrcDirPath, themeType),
85
+ dirPath: pathJoin(buildContext.themeSrcDirPath, themeType),
73
86
  returnedPathsType: "absolute"
74
87
  });
75
88
 
@@ -88,7 +101,7 @@ export function generateMessageProperties(params: {
88
101
  files = files.sort((a, b) => a.length - b.length);
89
102
 
90
103
  files = files.filter(file =>
91
- fs.readFileSync(file).toString("utf8").includes("createUseI18n(")
104
+ fs.readFileSync(file).toString("utf8").includes("i18nBuilder")
92
105
  );
93
106
 
94
107
  const i18nTsFilePath: string | undefined = files[0];
@@ -96,97 +109,334 @@ export function generateMessageProperties(params: {
96
109
  return { i18nTsFilePath };
97
110
  })();
98
111
 
99
- const messageBundle: { [languageTag: string]: Record<string, string> } | undefined =
100
- (() => {
101
- if (i18nTsFilePath === undefined) {
102
- return undefined;
112
+ const i18nTsRoot = (() => {
113
+ if (i18nTsFilePath === undefined) {
114
+ return undefined;
115
+ }
116
+ const root = recastParseTs(i18nTsFilePath);
117
+ return root;
118
+ })();
119
+
120
+ const messages_defaultSet_by_languageTag_notInDefaultSet:
121
+ | { [languageTag_notInDefaultSet: string]: Record<string, string> }
122
+ | undefined = (() => {
123
+ if (i18nTsRoot === undefined) {
124
+ return undefined;
125
+ }
126
+
127
+ let extraLanguageEntryByLanguageTag: Record<
128
+ string,
129
+ { label: string; path: string }
130
+ > = {};
131
+
132
+ recast.visit(i18nTsRoot, {
133
+ visitCallExpression: function (path) {
134
+ const node = path.node;
135
+
136
+ // Check if the callee is a MemberExpression with property 'withExtraLanguages'
137
+ if (
138
+ node.callee.type === "MemberExpression" &&
139
+ node.callee.property.type === "Identifier" &&
140
+ node.callee.property.name === "withExtraLanguages"
141
+ ) {
142
+ const arg = node.arguments[0];
143
+ if (arg && arg.type === "ObjectExpression") {
144
+ // Iterate over the properties of the object
145
+ arg.properties.forEach(prop => {
146
+ if (
147
+ prop.type === "ObjectProperty" &&
148
+ prop.key.type === "Identifier"
149
+ ) {
150
+ const lang = prop.key.name;
151
+ const value = prop.value;
152
+
153
+ if (value.type === "ObjectExpression") {
154
+ let label: string | undefined = undefined;
155
+ let pathStr: string | undefined = undefined;
156
+
157
+ // Iterate over the properties of the language object
158
+ value.properties.forEach(p => {
159
+ if (
160
+ p.type === "ObjectProperty" &&
161
+ p.key.type === "Identifier"
162
+ ) {
163
+ if (
164
+ p.key.name === "label" &&
165
+ p.value.type === "StringLiteral"
166
+ ) {
167
+ label = p.value.value;
168
+ }
169
+ if (
170
+ p.key.name === "getMessages" &&
171
+ (p.value.type ===
172
+ "ArrowFunctionExpression" ||
173
+ p.value.type === "FunctionExpression")
174
+ ) {
175
+ // Extract the import path from the function body
176
+ const body = p.value.body;
177
+ if (
178
+ body.type === "CallExpression" &&
179
+ body.callee.type === "Import"
180
+ ) {
181
+ const importArg = body.arguments[0];
182
+ if (
183
+ importArg.type === "StringLiteral"
184
+ ) {
185
+ pathStr = importArg.value;
186
+ }
187
+ } else if (
188
+ body.type === "BlockStatement"
189
+ ) {
190
+ // If the function body is a block (e.g., function with braces {})
191
+ // Look for return statement
192
+ body.body.forEach(statement => {
193
+ if (
194
+ statement.type ===
195
+ "ReturnStatement" &&
196
+ statement.argument &&
197
+ statement.argument.type ===
198
+ "CallExpression" &&
199
+ statement.argument.callee
200
+ .type === "Import"
201
+ ) {
202
+ const importArg =
203
+ statement.argument
204
+ .arguments[0];
205
+ if (
206
+ importArg.type ===
207
+ "StringLiteral"
208
+ ) {
209
+ pathStr = importArg.value;
210
+ }
211
+ }
212
+ });
213
+ }
214
+ }
215
+ }
216
+ });
217
+
218
+ if (label && pathStr) {
219
+ extraLanguageEntryByLanguageTag[lang] = {
220
+ label,
221
+ path: pathStr
222
+ };
223
+ }
224
+ }
225
+ }
226
+ });
227
+ }
228
+
229
+ return false; // Stop traversing this path
230
+ }
231
+
232
+ this.traverse(path); // Continue traversing other paths
103
233
  }
234
+ });
235
+
236
+ const messages_defaultSet_by_languageTag_notInDefaultSet = Object.fromEntries(
237
+ Object.entries(extraLanguageEntryByLanguageTag).map(
238
+ ([languageTag, { path: relativePathWithoutExt }]) => [
239
+ languageTag,
240
+ (() => {
241
+ const filePath = getAbsoluteAndInOsFormatPath({
242
+ pathIsh: relativePathWithoutExt.endsWith(".ts")
243
+ ? relativePathWithoutExt
244
+ : `${relativePathWithoutExt}.ts`,
245
+ cwd: pathDirname(i18nTsFilePath)
246
+ });
104
247
 
105
- const root = recast.parse(fs.readFileSync(i18nTsFilePath).toString("utf8"), {
106
- parser: {
107
- parse: (code: string) =>
108
- babelParser.parse(code, {
109
- sourceType: "module",
110
- plugins: ["typescript"]
111
- }),
112
- generator: babelGenerate,
113
- types: babelTypes
248
+ const root = recastParseTs(filePath);
249
+
250
+ let declarationCode: string | undefined = "";
251
+
252
+ recast.visit(root, {
253
+ visitVariableDeclarator: function (path) {
254
+ const node = path.node;
255
+
256
+ // Check if the variable name is 'messages'
257
+ if (
258
+ node.id.type === "Identifier" &&
259
+ node.id.name === "messages"
260
+ ) {
261
+ // Ensure there is an initializer
262
+ if (node.init) {
263
+ // Generate code from the initializer, preserving comments
264
+ declarationCode = recast
265
+ .print(node.init)
266
+ .code.replace(/}.*$/, "}");
267
+ }
268
+ return false; // Stop traversing this path
269
+ }
270
+
271
+ this.traverse(path); // Continue traversing other paths
272
+ }
273
+ });
274
+
275
+ assert(
276
+ declarationCode !== undefined,
277
+ `${filePath} does not contain a 'messages' variable declaration`
278
+ );
279
+
280
+ let messages: Record<string, string> = {};
281
+
282
+ try {
283
+ eval(`${symToStr({ messages })} = ${declarationCode};`);
284
+ } catch {
285
+ throw new Error(
286
+ `The declaration of 'message' in ${filePath} cannot be statically evaluated: ${declarationCode}`
287
+ );
288
+ }
289
+
290
+ return messages;
291
+ })()
292
+ ]
293
+ )
294
+ );
295
+
296
+ return messages_defaultSet_by_languageTag_notInDefaultSet;
297
+ })();
298
+
299
+ const messages_defaultSet_by_languageTag = {
300
+ ...messages_defaultSet_by_languageTag_defaultSet,
301
+ ...messages_defaultSet_by_languageTag_notInDefaultSet
302
+ };
303
+
304
+ const messages_themeDefined_by_languageTag:
305
+ | {
306
+ [languageTag: string]:
307
+ | Record<string, string | Record<string, string>>
308
+ | undefined;
309
+ }
310
+ | undefined = (() => {
311
+ if (i18nTsRoot === undefined) {
312
+ return undefined;
313
+ }
314
+
315
+ let firstArgumentCode: string | undefined = undefined;
316
+
317
+ recast.visit(i18nTsRoot, {
318
+ visitCallExpression: function (path) {
319
+ const node = path.node;
320
+
321
+ if (
322
+ node.callee.type === "MemberExpression" &&
323
+ node.callee.property.type === "Identifier" &&
324
+ node.callee.property.name === "withCustomTranslations"
325
+ ) {
326
+ firstArgumentCode = babelGenerate(node.arguments[0] as any).code;
327
+ return false;
114
328
  }
115
- });
116
-
117
- let messageBundleDeclarationTsCode: string | undefined = undefined;
118
-
119
- recast.visit(root, {
120
- visitCallExpression: function (path) {
121
- if (
122
- path.node.callee.type === "Identifier" &&
123
- path.node.callee.name === "createUseI18n"
124
- ) {
125
- messageBundleDeclarationTsCode = babelGenerate(
126
- path.node.arguments[0] as any
127
- ).code;
128
- return false;
329
+
330
+ this.traverse(path);
331
+ }
332
+ });
333
+
334
+ if (firstArgumentCode === undefined) {
335
+ return undefined;
336
+ }
337
+
338
+ let messages_themeDefined_by_languageTag: {
339
+ [languageTag: string]: Record<string, string | Record<string, string>>;
340
+ } = {};
341
+
342
+ try {
343
+ eval(
344
+ `${symToStr({ messages_themeDefined_by_languageTag })} = ${firstArgumentCode}`
345
+ );
346
+ } catch {
347
+ console.warn(
348
+ [
349
+ "WARNING: The argument of withCustomTranslations can't be statically evaluated!",
350
+ "This needs to be fixed refer to the documentation: https://docs.keycloakify.dev/i18n",
351
+ firstArgumentCode
352
+ ].join(" ")
353
+ );
354
+ return undefined;
355
+ }
356
+
357
+ return messages_themeDefined_by_languageTag;
358
+ })();
359
+
360
+ const languageTags = Object.keys(messages_defaultSet_by_languageTag);
361
+
362
+ return {
363
+ languageTags,
364
+ writeMessagePropertiesFiles: ({ messageDirPath, themeName }) => {
365
+ for (const languageTag of languageTags) {
366
+ const messages = {
367
+ ...messages_defaultSet_by_languageTag[languageTag]
368
+ };
369
+
370
+ add_theme_defined_messages: {
371
+ if (messages_themeDefined_by_languageTag === undefined) {
372
+ break add_theme_defined_messages;
373
+ }
374
+
375
+ let messages_themeDefined =
376
+ messages_themeDefined_by_languageTag[languageTag];
377
+
378
+ if (messages_themeDefined === undefined) {
379
+ messages_themeDefined =
380
+ messages_themeDefined_by_languageTag[FALLBACK_LANGUAGE_TAG];
129
381
  }
382
+ if (messages_themeDefined === undefined) {
383
+ messages_themeDefined =
384
+ messages_themeDefined_by_languageTag[
385
+ Object.keys(messages_themeDefined_by_languageTag)[0]
386
+ ];
387
+ }
388
+ if (messages_themeDefined === undefined) {
389
+ break add_theme_defined_messages;
390
+ }
391
+
392
+ for (const [key, messageOrMessageByThemeName] of Object.entries(
393
+ messages_themeDefined
394
+ )) {
395
+ const message = (() => {
396
+ if (typeof messageOrMessageByThemeName === "string") {
397
+ return messageOrMessageByThemeName;
398
+ }
130
399
 
131
- this.traverse(path);
400
+ const message = messageOrMessageByThemeName[themeName];
401
+
402
+ assert(message !== undefined);
403
+
404
+ return message;
405
+ })();
406
+
407
+ messages[key] = message;
408
+ }
132
409
  }
133
- });
134
410
 
135
- assert(messageBundleDeclarationTsCode !== undefined);
411
+ const propertiesFileSource = [
412
+ "",
413
+ ...Object.entries(messages).map(
414
+ ([key, value]) => `${key}=${escapeStringForPropertiesFile(value)}`
415
+ ),
416
+ ""
417
+ ].join("\n");
136
418
 
137
- let messageBundle: {
138
- [languageTag: string]: Record<string, string>;
139
- } = {};
419
+ fs.mkdirSync(messageDirPath, { recursive: true });
140
420
 
141
- try {
142
- eval(
143
- `${symToStr({ messageBundle })} = ${messageBundleDeclarationTsCode}`
144
- );
145
- } catch {
146
- console.warn(
147
- [
148
- "WARNING: Make sure the messageBundle your provided as argument of createUseI18n can be statically evaluated.",
149
- "This is important because we need to put your i18n messages in messages_*.properties files",
150
- "or they won't be available server side.",
151
- "\n",
152
- "The following code could not be evaluated:",
153
- "\n",
154
- messageBundleDeclarationTsCode
155
- ].join(" ")
421
+ fs.writeFileSync(
422
+ pathJoin(messageDirPath, `messages_${languageTag}.properties`),
423
+ Buffer.from(propertiesFileSource, "utf8")
156
424
  );
157
425
  }
426
+ }
427
+ };
428
+ }
158
429
 
159
- return messageBundle;
160
- })();
161
-
162
- const mergedMessageBundle: { [languageTag: string]: Record<string, string> } =
163
- Object.fromEntries(
164
- Object.entries(baseMessageBundle).map(([languageTag, messages]) => [
165
- languageTag,
166
- {
167
- ...messages,
168
- ...(messageBundle === undefined
169
- ? {}
170
- : messageBundle[languageTag] ??
171
- messageBundle[FALLBACK_LANGUAGE_TAG] ??
172
- messageBundle[Object.keys(messageBundle)[0]] ??
173
- {})
174
- }
175
- ])
176
- );
177
-
178
- const messageProperties: { languageTag: string; propertiesFileSource: string }[] =
179
- Object.entries(mergedMessageBundle).map(([languageTag, messages]) => ({
180
- languageTag,
181
- propertiesFileSource: [
182
- "",
183
- ...(themeType !== "account" ? ["parent=base"] : []),
184
- ...Object.entries(messages).map(
185
- ([key, value]) => `${key}=${escapeStringForPropertiesFile(value)}`
186
- ),
187
- ""
188
- ].join("\n")
189
- }));
190
-
191
- return messageProperties;
430
+ function recastParseTs(filePath: string): recast.types.ASTNode {
431
+ return recast.parse(fs.readFileSync(filePath).toString("utf8"), {
432
+ parser: {
433
+ parse: (code: string) =>
434
+ babelParser.parse(code, {
435
+ sourceType: "module",
436
+ plugins: ["typescript"]
437
+ }),
438
+ generator: babelGenerate,
439
+ types: babelTypes
440
+ }
441
+ });
192
442
  }
@@ -26,17 +26,19 @@ export async function generateResources(params: {
26
26
  rmSync(resourcesDirPath, { recursive: true });
27
27
  }
28
28
 
29
- await generateResourcesForMainTheme({
30
- resourcesDirPath,
31
- themeName,
32
- buildContext
33
- });
29
+ const { writeMessagePropertiesFilesForThemeVariant } =
30
+ await generateResourcesForMainTheme({
31
+ resourcesDirPath,
32
+ themeName,
33
+ buildContext
34
+ });
34
35
 
35
36
  for (const themeVariantName of themeVariantNames) {
36
37
  generateResourcesForThemeVariant({
37
38
  resourcesDirPath,
38
39
  themeName,
39
- themeVariantName
40
+ themeVariantName,
41
+ writeMessagePropertiesFiles: writeMessagePropertiesFilesForThemeVariant
40
42
  });
41
43
  }
42
44
  }