From a3fd376b24249a389ea138b6f0213a338ac89efb Mon Sep 17 00:00:00 2001 From: garronej Date: Mon, 20 Mar 2023 00:58:35 +0100 Subject: [PATCH] Refactor of the i18n download mechanism --- .gitignore | 4 +- .prettierignore | 3 +- package.json | 1 + scripts/generate-i18n-messages.ts | 145 +++++++++++++++++------------- src/login/i18n/i18n.tsx | 61 +------------ 5 files changed, 95 insertions(+), 119 deletions(-) diff --git a/.gitignore b/.gitignore index aea7ff38..604525fa 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,6 @@ jspm_packages .idea /keycloak_email -/build_keycloak \ No newline at end of file +/build_keycloak +/src/login/i18n/baseMessages/ +/src/account/i18n/baseMessages/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 4a469ed9..896a774d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,5 +7,6 @@ node_modules/ /src/tools/types/ /sample_react_project /build_keycloak/ -/src/i18n/generated_messages/ /.vscode/ +/src/login/i18n/baseMessages/ +/src/account/i18n/baseMessages/ diff --git a/package.json b/package.json index 3b82307a..c6093a1c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { + "postinstall": "yarn generate-i18n-messages", "build": "rimraf dist/ && tsc -p src/bin && tsc -p src/tsconfig.json && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/", "build:watch": "tsc -p src/tsconfig.json && (concurrently \"tsc -p src/tsconfig.json -w\" \"tsc-alias -p src/tsconfig.json\")", "build:test": "rimraf dist_test/ && tsc -p test/tsconfig.json && tsc-alias -p test/tsconfig.json && yarn copy-files dist_test/src", diff --git a/scripts/generate-i18n-messages.ts b/scripts/generate-i18n-messages.ts index 48de20e0..115caa35 100644 --- a/scripts/generate-i18n-messages.ts +++ b/scripts/generate-i18n-messages.ts @@ -17,76 +17,101 @@ const { isSilent } = getCliOptions(process.argv.slice(2)); const logger = getLogger({ isSilent }); (async () => { - for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1", "21.0.1"]) { - logger.log(JSON.stringify({ keycloakVersion })); + const keycloakVersion = "21.0.1"; - const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44"); + const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44"); - fs.rmSync(tmpDirPath, { "recursive": true, "force": true }); + fs.rmSync(tmpDirPath, { "recursive": true, "force": true }); - await downloadBuiltinKeycloakTheme({ - keycloakVersion, - "destDirPath": tmpDirPath, - isSilent - }); + await downloadBuiltinKeycloakTheme({ + keycloakVersion, + "destDirPath": tmpDirPath, + isSilent + }); - type Dictionary = { [idiomId: string]: string }; + type Dictionary = { [idiomId: string]: string }; - const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {}; + const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {}; - { - const baseThemeDirPath = pathJoin(tmpDirPath, "base"); + { + const baseThemeDirPath = pathJoin(tmpDirPath, "base"); - crawl(baseThemeDirPath).forEach(filePath => { - const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/); + crawl(baseThemeDirPath).forEach(filePath => { + const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/); - if (match === null) { - return; - } + if (match === null) { + return; + } - const [, typeOfPage, language] = match; + const [, typeOfPage, language] = match; - (record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries( - Object.entries(propertiesParser.parse(fs.readFileSync(pathJoin(baseThemeDirPath, filePath)).toString("utf8"))).map( - ([key, value]: any) => [key, value.replace(/''/g, "'")] - ) - ); - }); - } - - fs.rmSync(tmpDirPath, { recursive: true, force: true }); - - Object.keys(record).forEach(themeType => { - const recordForPageType = record[themeType]; - - Object.keys(recordForPageType).forEach(language => { - if (themeType !== "login") { - return; - } - - const filePath = pathJoin(getProjectRoot(), "src", themeType, "i18n", "generated_messages", keycloakVersion, `${language}.ts`); - - fs.mkdirSync(pathDirname(filePath), { "recursive": true }); - - fs.writeFileSync( - filePath, - Buffer.from( - [ - `//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`, - "//PLEASE DO NOT EDIT MANUALLY", - "", - "/* spell-checker: disable */", - `const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`, - "", - "export default messages;", - "/* spell-checker: enable */" - ].join("\n"), - "utf8" - ) - ); - - logger.log(`${filePath} wrote`); - }); + (record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries( + Object.entries(propertiesParser.parse(fs.readFileSync(pathJoin(baseThemeDirPath, filePath)).toString("utf8"))).map( + ([key, value]: any) => [key, value.replace(/''/g, "'")] + ) + ); }); } + + fs.rmSync(tmpDirPath, { recursive: true, force: true }); + + Object.keys(record).forEach(themeType => { + const recordForPageType = record[themeType]; + + if (themeType !== "login") { + return; + } + + const baseMessagesDirPath = pathJoin(getProjectRoot(), "src", themeType, "i18n", "baseMessages"); + + const languages = Object.keys(recordForPageType); + + const generatedFileHeader = [ + `//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`, + "//PLEASE DO NOT EDIT MANUALLY", + "" + ].join("\n"); + + languages.forEach(language => { + const filePath = pathJoin(baseMessagesDirPath, `${language}.ts`); + + fs.mkdirSync(pathDirname(filePath), { "recursive": true }); + + fs.writeFileSync( + filePath, + Buffer.from( + [ + generatedFileHeader, + "/* spell-checker: disable */", + `const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`, + "", + "export default messages;", + "/* spell-checker: enable */" + ].join("\n"), + "utf8" + ) + ); + + logger.log(`${filePath} wrote`); + }); + + fs.writeFileSync( + pathJoin(baseMessagesDirPath, "index.ts"), + Buffer.from( + [ + generatedFileHeader, + "export async function getMessages(currentLanguageTag: string) {", + " const { default: messages } = await (() => {", + " switch (currentLanguageTag) {", + ...languages.map(language => ` case "${language}": return import("./${language}");`), + ' default: return { "default": {} };', + " }", + " })();", + " return messages;", + "}" + ].join("\n"), + "utf8" + ) + ); + }); })(); diff --git a/src/login/i18n/i18n.tsx b/src/login/i18n/i18n.tsx index 97a1a713..8601fa73 100644 --- a/src/login/i18n/i18n.tsx +++ b/src/login/i18n/i18n.tsx @@ -1,7 +1,8 @@ import "minimal-polyfills/Object.fromEntries"; //NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35 import { useEffect, useState, useRef } from "react"; -import type baseMessages from "./generated_messages/18.0.1/login/en"; +import fallbackMessages from "./baseMessages/en"; +import { getMessages } from "./baseMessages"; import { assert } from "tsafe/assert"; import type { KcContext } from "../kcContext/KcContext"; import { Markdown } from "keycloakify/tools/Markdown"; @@ -17,7 +18,7 @@ export type KcContextLike = { assert(); -export type MessageKey = keyof typeof baseMessages | keyof (typeof keycloakifyExtraMessages)[typeof fallbackLanguageTag]; +export type MessageKey = keyof typeof fallbackMessages | keyof (typeof keycloakifyExtraMessages)[typeof fallbackLanguageTag]; export type GenericI18n = { /** @@ -89,60 +90,6 @@ export function createUseI18n(extraMessa (async () => { const { currentLanguageTag = fallbackLanguageTag } = kcContext.locale ?? {}; - const [fallbackMessages, messages] = await Promise.all([ - import("./generated_messages/18.0.1/login/en"), - (() => { - switch (currentLanguageTag) { - case "ca": - return import("./generated_messages/18.0.1/login/ca"); - case "cs": - return import("./generated_messages/18.0.1/login/cs"); - case "da": - return import("./generated_messages/18.0.1/login/da"); - case "de": - return import("./generated_messages/18.0.1/login/de"); - case "en": - return import("./generated_messages/18.0.1/login/en"); - case "es": - return import("./generated_messages/18.0.1/login/es"); - case "fi": - return import("./generated_messages/18.0.1/login/fi"); - case "fr": - return import("./generated_messages/18.0.1/login/fr"); - case "hu": - return import("./generated_messages/18.0.1/login/hu"); - case "it": - return import("./generated_messages/18.0.1/login/it"); - case "ja": - return import("./generated_messages/18.0.1/login/ja"); - case "lt": - return import("./generated_messages/18.0.1/login/lt"); - case "lv": - return import("./generated_messages/18.0.1/login/lv"); - case "nl": - return import("./generated_messages/18.0.1/login/nl"); - case "no": - return import("./generated_messages/18.0.1/login/no"); - case "pl": - return import("./generated_messages/18.0.1/login/pl"); - case "pt-BR": - return import("./generated_messages/18.0.1/login/pt-BR"); - case "ru": - return import("./generated_messages/18.0.1/login/ru"); - case "sk": - return import("./generated_messages/18.0.1/login/sk"); - case "sv": - return import("./generated_messages/18.0.1/login/sv"); - case "tr": - return import("./generated_messages/18.0.1/login/tr"); - case "zh-CN": - return import("./generated_messages/18.0.1/login/zh-CN"); - default: - return { "default": {} }; - } - })() - ]).then(modules => modules.map(module => module.default)); - setI18n({ ...createI18nTranslationFunctions({ "fallbackMessages": { @@ -151,7 +98,7 @@ export function createUseI18n(extraMessa ...(extraMessages[fallbackLanguageTag] ?? {}) } as any, "messages": { - ...messages, + ...(await getMessages(currentLanguageTag)), ...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}), ...(extraMessages[currentLanguageTag] ?? {}) } as any