From c07af8491cdc50a6fe3d6572be3ed1835f93e6d4 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sun, 22 Sep 2024 22:46:56 +0200 Subject: [PATCH] Complete statical parsing of withExtraLanguages --- .../generateMessageProperties.ts | 114 ++++++++++++++---- 1 file changed, 89 insertions(+), 25 deletions(-) diff --git a/src/bin/keycloakify/generateResources/generateMessageProperties.ts b/src/bin/keycloakify/generateResources/generateMessageProperties.ts index 433cd237..e2db5790 100644 --- a/src/bin/keycloakify/generateResources/generateMessageProperties.ts +++ b/src/bin/keycloakify/generateResources/generateMessageProperties.ts @@ -1,6 +1,6 @@ import { type ThemeType, FALLBACK_LANGUAGE_TAG } from "../../shared/constants"; import { crawl } from "../../tools/crawl"; -import { join as pathJoin } from "path"; +import { join as pathJoin, dirname as pathDirname } from "path"; import { symToStr } from "tsafe/symToStr"; import * as recast from "recast"; import * as babelParser from "@babel/parser"; @@ -11,6 +11,7 @@ import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPa import * as fs from "fs"; import { assert } from "tsafe/assert"; import type { BuildContext } from "../../shared/buildContext"; +import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath"; export type BuildContextLike = { themeNames: string[]; @@ -112,20 +113,7 @@ export function generateMessageProperties(params: { if (i18nTsFilePath === undefined) { return undefined; } - const root: recast.types.ASTNode = recast.parse( - fs.readFileSync(i18nTsFilePath).toString("utf8"), - { - parser: { - parse: (code: string) => - babelParser.parse(code, { - sourceType: "module", - plugins: ["typescript"] - }), - generator: babelGenerate, - types: babelTypes - } - } - ); + const root = recastParseTs(i18nTsFilePath); return root; })(); @@ -136,7 +124,10 @@ export function generateMessageProperties(params: { return undefined; } - let out: Record = {}; + let extraLanguageEntryByLanguageTag: Record< + string, + { label: string; path: string } + > = {}; recast.visit(i18nTsRoot, { visitCallExpression: function (path) { @@ -225,7 +216,10 @@ export function generateMessageProperties(params: { }); if (label && pathStr) { - out[lang] = { label, path: pathStr }; + extraLanguageEntryByLanguageTag[lang] = { + label, + path: pathStr + }; } } } @@ -239,9 +233,69 @@ export function generateMessageProperties(params: { } }); - console.log(out); + const messages_defaultSet_by_languageTag_notInDefaultSet = Object.fromEntries( + Object.entries(extraLanguageEntryByLanguageTag).map( + ([languageTag, { path: relativePathWithoutExt }]) => [ + languageTag, + (() => { + const filePath = getAbsoluteAndInOsFormatPath({ + pathIsh: relativePathWithoutExt.endsWith(".ts") + ? relativePathWithoutExt + : `${relativePathWithoutExt}.ts`, + cwd: pathDirname(i18nTsFilePath) + }); - return {}; + const root = recastParseTs(filePath); + + let declarationCode: string | undefined = ""; + + recast.visit(root, { + visitVariableDeclarator: function (path) { + const node = path.node; + + // Check if the variable name is 'messages' + if ( + node.id.type === "Identifier" && + node.id.name === "messages" + ) { + // Ensure there is an initializer + if (node.init) { + // Generate code from the initializer, preserving comments + declarationCode = recast + .print(node.init) + .code.replace(/}.*$/, "}"); + } + return false; // Stop traversing this path + } + + this.traverse(path); // Continue traversing other paths + } + }); + + assert( + declarationCode !== undefined, + `${filePath} does not contain a 'messages' variable declaration` + ); + + let messages: Record = {}; + + try { + eval(`${symToStr({ messages })} = ${declarationCode};`); + } catch { + throw new Error( + `The declaration of 'message' in ${filePath} cannot be statically evaluated: ${declarationCode}` + ); + } + + return messages; + })() + ] + ) + ); + + console.log(messages_defaultSet_by_languageTag_notInDefaultSet); + + return messages_defaultSet_by_languageTag_notInDefaultSet; })(); const messages_defaultSet_by_languageTag = { @@ -294,12 +348,8 @@ export function generateMessageProperties(params: { } catch { console.warn( [ - "WARNING: Make sure the messageBundle your provided as argument of createUseI18n can be statically evaluated.", - "This is important because we need to put your i18n messages in messages_*.properties files", - "or they won't be available server side.", - "\n", - "The following code could not be evaluated:", - "\n", + "WARNING: The argument of withCustomTranslations can't be statically evaluated!", + "This needs to be fixed refer to the documentation: https://docs.keycloakify.dev/i18n", firstArgumentCode ].join(" ") ); @@ -376,3 +426,17 @@ export function generateMessageProperties(params: { } }; } + +function recastParseTs(filePath: string): recast.types.ASTNode { + return recast.parse(fs.readFileSync(filePath).toString("utf8"), { + parser: { + parse: (code: string) => + babelParser.parse(code, { + sourceType: "module", + plugins: ["typescript"] + }), + generator: babelGenerate, + types: babelTypes + } + }); +}