includes translations
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
|
||||
import { transformCodebase } from "../../tools/transformCodebase";
|
||||
import { transformCodebase } from "../tools/transformCodebase";
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import {
|
||||
|
@ -15,10 +15,10 @@ const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPat
|
||||
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
|
||||
|
||||
|
||||
console.log("🔏 Building the keycloak theme...⌚");
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
console.log("🔏 Building the keycloak theme...⌚");
|
||||
|
||||
generateKeycloakThemeResources({
|
||||
keycloakThemeBuildingDirPath,
|
||||
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
||||
|
@ -1,18 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin, basename as pathBasename } from "path";
|
||||
import { join as pathJoin } from "path";
|
||||
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
|
||||
import child_process from "child_process";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
|
||||
export const keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl =
|
||||
"https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
console.log("execute!");
|
||||
|
||||
downloadAndUnzip({
|
||||
"url": keycloakThemeBuildingDirPath,
|
||||
"destDirPath": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme")
|
||||
});
|
||||
|
||||
if (!fs.existsSync(keycloakThemeBuildingDirPath)) {
|
||||
console.log("Error: The keycloak theme need to be build");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const url = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
|
||||
|
||||
[
|
||||
`wget ${url}`,
|
||||
...["unzip", "rm"].map(prg => `${prg} ${pathBasename(url)}`),
|
||||
].forEach(cmd => child_process.execSync(cmd, { "cwd": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme") }));
|
||||
|
64
src/bin/generate-i18n-messages.ts
Normal file
64
src/bin/generate-i18n-messages.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { crawl } from "./tools/crawl";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
import { keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl } from "./download-sample-keycloak-themes";
|
||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
||||
import * as child_process from "child_process";
|
||||
|
||||
//@ts-ignore
|
||||
const propertiesParser = require("properties-parser");
|
||||
|
||||
console.log(propertiesParser);
|
||||
|
||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
||||
|
||||
child_process.execSync(`rm -rf ${tmpDirPath}`);
|
||||
|
||||
downloadAndUnzip({
|
||||
"destDirPath": tmpDirPath,
|
||||
"url": keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl
|
||||
});
|
||||
|
||||
type Dictionary = { [idiomId: string]: string };
|
||||
|
||||
const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {};
|
||||
|
||||
process.chdir(pathJoin(tmpDirPath, "base"));
|
||||
|
||||
crawl(".").forEach(filePath => {
|
||||
|
||||
const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/);
|
||||
|
||||
if (match === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [, typeOfPage, language] = match;
|
||||
|
||||
(record[typeOfPage] ??= {})[language] =
|
||||
propertiesParser.parse(
|
||||
fs.readFileSync(filePath)
|
||||
.toString("utf8")
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
child_process.execSync(`rm -r ${tmpDirPath}`);
|
||||
|
||||
const targetFilePath = pathJoin("src", "lib", "i18n", "messages.generated.ts");
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(getProjectRoot(), targetFilePath),
|
||||
Buffer.from(
|
||||
[
|
||||
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
|
||||
'//PLEASE DO NOT EDIT MANUALLY',
|
||||
'',
|
||||
'/* spell-checker: disable */',
|
||||
`export const messages= ${JSON.stringify(record, null, 2)} as const;`,
|
||||
'/* spell-checker: enable */'
|
||||
].join("\n"), "utf8")
|
||||
);
|
||||
|
||||
console.log(`${targetFilePath} wrote`);
|
22
src/bin/tools/downloadAndUnzip.ts
Normal file
22
src/bin/tools/downloadAndUnzip.ts
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
import { basename as pathBasename } from "path";
|
||||
import child_process from "child_process";
|
||||
import fs from "fs";
|
||||
|
||||
export function downloadAndUnzip(
|
||||
params: {
|
||||
url: string;
|
||||
destDirPath: string;
|
||||
}
|
||||
) {
|
||||
|
||||
const { url, destDirPath } = params;
|
||||
|
||||
fs.mkdirSync(destDirPath, { "recursive": true });
|
||||
|
||||
[
|
||||
`wget ${url}`,
|
||||
...["unzip", "rm"].map(prg => `${prg} ${pathBasename(url)}`),
|
||||
].forEach(cmd => child_process.execSync(cmd, { "cwd": destDirPath }));
|
||||
|
||||
}
|
19
src/bin/tools/getProjectRoot.ts
Normal file
19
src/bin/tools/getProjectRoot.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
function getProjectRootRec(dirPath: string): string {
|
||||
if (fs.existsSync(path.join(dirPath, "package.json"))) {
|
||||
return dirPath;
|
||||
}
|
||||
return getProjectRootRec(path.join(dirPath, ".."));
|
||||
}
|
||||
|
||||
let result: string | undefined = undefined;
|
||||
|
||||
export function getProjectRoot(): string {
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return (result = getProjectRootRec(__dirname));
|
||||
}
|
8
src/bin/tools/grant-exec-perms.ts
Normal file
8
src/bin/tools/grant-exec-perms.ts
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
import { getProjectRoot } from "./getProjectRoot";
|
||||
import { join as pathJoin } from "path";
|
||||
import child_process from "child_process";
|
||||
|
||||
Object.entries<string>(require(pathJoin(getProjectRoot(), "package.json"))["bin"])
|
||||
.forEach(([, scriptPath]) => child_process.execSync(`chmod +x ${scriptPath}`, { "cwd": getProjectRoot() }));
|
||||
|
39
src/lib/i18n/getLanguageLabel.ts
Normal file
39
src/lib/i18n/getLanguageLabel.ts
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
import type { AvailableLanguages } from "./useKeycloakLanguage";
|
||||
|
||||
export function getLanguageLabel(language: AvailableLanguages): LanguageLabel {
|
||||
|
||||
switch (language) {
|
||||
/* spell-checker: disable */
|
||||
case "es": return "Español";
|
||||
case "it": return "Italiano";
|
||||
case "fr": return "Français";
|
||||
case "ca": return "Català";
|
||||
case "en": return "English";
|
||||
case "de": return "Deutsch";
|
||||
case "no": return "Norsk";
|
||||
case "pt_BR": return "Português (Brasil)";
|
||||
case "ru": return "Русский";
|
||||
case "sk":
|
||||
case "sv": return "Slovenčina";
|
||||
case "ja": return "日本語";
|
||||
case "pl": return "Polish";
|
||||
case "zh_CN": return "中文简体"
|
||||
case "sv": return "Svenska";
|
||||
case "lt": return "Lietuvių";
|
||||
case "cs": return "Čeština";
|
||||
case "nl": return "Nederlands";
|
||||
case "tr": return "tr"
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
|
||||
return language;
|
||||
|
||||
}
|
||||
|
||||
export type LanguageLabel =
|
||||
/* spell-checker: disable */
|
||||
"Deutsch" | "Norsk" | "Русский" | "Svenska" | "Português (Brasil)" | "Lietuvių" |
|
||||
"English" | "Italiano" | "Français" | "中文简体" | "Español" | "Čeština" | "日本語" |
|
||||
"Slovenčina" | "Polish" | "Català" | "Nederlands" | "tr";
|
||||
/* spell-checker: enable */
|
8297
src/lib/i18n/messages.generated.ts
Normal file
8297
src/lib/i18n/messages.generated.ts
Normal file
File diff suppressed because it is too large
Load Diff
40
src/lib/i18n/useKeycloakLanguage.ts
Normal file
40
src/lib/i18n/useKeycloakLanguage.ts
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import { createUseGlobalState } from "powerhooks";
|
||||
import { messages } from "./messages.generated";
|
||||
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
|
||||
import { getLanguageLabel } from "./getLanguageLabel";
|
||||
|
||||
const availableLanguages = objectKeys(messages["login"]);
|
||||
|
||||
export type AvailableLanguages = typeof availableLanguages[number];
|
||||
|
||||
export const { useKeycloakLanguage } = createUseGlobalState(
|
||||
"keycloakLanguage",
|
||||
getKeycloakAvailableLanguageBestGuess,
|
||||
{ "persistance": "cookies" }
|
||||
);
|
||||
|
||||
/**
|
||||
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
|
||||
* it corresponds to.
|
||||
* If there is no reasonable match it's guessed from navigator.language.
|
||||
* If still no matches en is returned.
|
||||
*/
|
||||
export function getKeycloakAvailableLanguageBestGuess(
|
||||
languageLike: string = navigator.language
|
||||
): AvailableLanguages {
|
||||
|
||||
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
|
||||
|
||||
const language = availableLanguages.find(language =>
|
||||
language.toLowerCase().includes(iso2LanguageLike) ||
|
||||
getLanguageLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase()
|
||||
);
|
||||
|
||||
if (language === undefined && languageLike !== navigator.language) {
|
||||
return getKeycloakAvailableLanguageBestGuess(navigator.language);
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
||||
|
28
src/lib/i18n/useKeycloakTranslation.ts
Normal file
28
src/lib/i18n/useKeycloakTranslation.ts
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
import { useKeycloakLanguage } from "./useKeycloakLanguage";
|
||||
import { messages } from "./messages.generated";
|
||||
import { useConstCallback } from "powerhooks";
|
||||
|
||||
export type MessageKey = keyof typeof messages["login"]["en"]
|
||||
|
||||
export function useKeycloakThemeTranslation() {
|
||||
|
||||
const { keycloakLanguage } = useKeycloakLanguage();
|
||||
|
||||
const t = useConstCallback(
|
||||
(key: MessageKey): string => {
|
||||
|
||||
const out: string | undefined = messages["login"][keycloakLanguage as any as "en"][key];
|
||||
|
||||
if (out !== undefined) {
|
||||
return out;
|
||||
}
|
||||
|
||||
return messages["login"]["en"][key];
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
return { t };
|
||||
|
||||
}
|
@ -1 +1,4 @@
|
||||
export * from "./keycloakFtlValues";
|
||||
export * from "./keycloakFtlValues";
|
||||
export * from "./i18n/useKeycloakLanguage";
|
||||
export * from "./i18n/useKeycloakTranslation";
|
||||
export * from "./i18n/getLanguageLabel";
|
@ -5,14 +5,6 @@ import { ftlValuesGlobalName } from "../bin/build-keycloak-theme/generateKeycloa
|
||||
import type { generateFtlFilesCodeFactory } from "../bin/build-keycloak-theme/generateFtl";
|
||||
import { id } from "evt/tools/typeSafety/id";
|
||||
|
||||
export type LanguageLabel =
|
||||
/* spell-checker: disable */
|
||||
"Deutsch" | "Norsk" | "Русский" | "Svenska" | "Português (Brasil)" | "Lietuvių" |
|
||||
"English" | "Italiano" | "Français" | "中文简体" | "Español" | "Čeština" | "日本語" |
|
||||
"Slovenčina" | "Polish" | "Català" | "Nederlands" | "tr";
|
||||
/* spell-checker: enable */
|
||||
|
||||
export type LanguageTag = "de" | "no" | "ru" | "sv" | "pt-BR" | "lt" | "en" | "it" | "fr" | "zh-CN" | "es" | "cs" | "ja" | "sk" | "pl" | "ca" | "nl" | "tr";
|
||||
|
||||
export type KeycloakFtlValues = {
|
||||
pageBasename: Parameters<ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"]>[0]["pageBasename"];
|
||||
@ -28,14 +20,18 @@ export type KeycloakFtlValues = {
|
||||
internationalizationEnabled: boolean;
|
||||
},
|
||||
//NOTE: Undefined if !realm.internationalizationEnabled
|
||||
//We hide this since we provide a client side internationalization engine
|
||||
/*
|
||||
locale?: {
|
||||
supported: {
|
||||
url: string;
|
||||
languageTag: AvailableLanguages;
|
||||
//NOTE: Is determined by languageTag. Ex: languageTag === "en" => label === "English"
|
||||
label: LanguageLabel;
|
||||
languageTag: LanguageTag;
|
||||
},
|
||||
current: LanguageLabel;
|
||||
},
|
||||
*/
|
||||
auth?: {
|
||||
showUsername: boolean;
|
||||
showResetCredentials: boolean;
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
} from "./setupSampleReactProject";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin } from "path";
|
||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
||||
|
||||
|
||||
setupSampleReactProject();
|
||||
@ -14,7 +15,7 @@ console.log(`Running main in ${sampleReactProjectDirPath}`);
|
||||
|
||||
console.log(
|
||||
st.execSync(
|
||||
`node ${pathJoin(__dirname, "../bin/build-keycloak-theme")}`,
|
||||
`node ${pathJoin(getProjectRoot(), "src", "bin", "build-keycloak-theme")}`,
|
||||
{ "cwd": sampleReactProjectDirPath }
|
||||
)
|
||||
);
|
||||
|
@ -2,11 +2,12 @@
|
||||
import { sampleReactProjectDirPath } from "./setupSampleReactProject";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin } from "path";
|
||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
||||
|
||||
console.log(`Running main in ${sampleReactProjectDirPath}`);
|
||||
|
||||
st.execSync(
|
||||
`node ${pathJoin(__dirname, "../bin/download-sample-keycloak-themes")}`,
|
||||
`node ${pathJoin(getProjectRoot(), "src","bin","download-sample-keycloak-themes")}`,
|
||||
{ "cwd": sampleReactProjectDirPath }
|
||||
);
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
|
||||
import { getProjectRoot } from "../bin/tools/getProjectRoot";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin, basename as pathBasename } from "path";
|
||||
|
||||
export const sampleReactProjectDirPath = pathJoin(__dirname, "..", "..", "sample_react_project");
|
||||
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");
|
||||
|
||||
export function setupSampleReactProject() {
|
||||
|
||||
|
Reference in New Issue
Block a user