From 5302ec3342ef60e9bfc79d0cd2b95f5f0394344d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 29 Aug 2024 21:49:11 +0200 Subject: [PATCH] Linting of 'generate translations' --- scripts/generateTranslations.ts | 204 +++++++++++++++++--------------- 1 file changed, 107 insertions(+), 97 deletions(-) diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 7add3c57b2..901518039a 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -6,6 +6,7 @@ import Script from "./Script" const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"] const ignoreTerms = ["searchTerms"] + class TranslationPart { contents: Map = new Map() @@ -14,7 +15,8 @@ class TranslationPart { const rootTranslation = new TranslationPart() for (const file of files) { const content = JSON.parse(readFileSync(file, { encoding: "utf8" })) - rootTranslation.addTranslation(file.substr(0, file.length - ".json".length), content) + const language = file.substr(0, file.length - ".json".length) + rootTranslation.addTranslation(language, content) } return rootTranslation } @@ -46,18 +48,14 @@ class TranslationPart { return } for (const translationsKey in translations) { - if (!translations.hasOwnProperty(translationsKey)) { - continue - } - const v = translations[translationsKey] if (typeof v != "string") { console.error( `Non-string object at ${context} in translation while trying to add the translation ` + - JSON.stringify(v) + - ` to '` + - translationsKey + - "'. The offending object which _should_ be a translation is: ", + JSON.stringify(v) + + ` to '` + + translationsKey + + "'. The offending object which _should_ be a translation is: ", v, "\n\nThe current object is (only showing en):", this.toJson(), @@ -96,17 +94,14 @@ class TranslationPart { if (noTranslate !== undefined) { console.log( "Ignoring some translations for " + - context + - ": " + - dontTranslateKeys.join(", ") + context + + ": " + + dontTranslateKeys.join(", ") ) } } for (let key in object) { - if (!object.hasOwnProperty(key)) { - continue - } if (ignoreTerms.indexOf(key) >= 0) { continue } @@ -155,13 +150,13 @@ class TranslationPart { this.contents.set(key, new TranslationPart()) } - ;(this.contents.get(key) as TranslationPart).recursiveAdd(v, context + "." + key) + (this.contents.get(key) as TranslationPart).recursiveAdd(v, context + "." + key) } } knownLanguages(): string[] { const languages = [] - for (let key of Array.from(this.contents.keys())) { + for (const key of Array.from(this.contents.keys())) { const value = this.contents.get(key) if (typeof value === "string") { @@ -180,20 +175,20 @@ class TranslationPart { const parts = [] let keys = Array.from(this.contents.keys()) keys = keys.sort() - for (let key of keys) { + for (const key of keys) { let value = this.contents.get(key) if (typeof value === "string") { - value = value.replace(/"/g, '\\"').replace(/\n/g, "\\n") + value = value.replace(/"/g, "\\\"").replace(/\n/g, "\\n") if (neededLanguage === undefined) { - parts.push(`\"${key}\": \"${value}\"`) + parts.push(`"${key}": "${value}"`) } else if (key === neededLanguage) { return `"${value}"` } } else { const sub = (value as TranslationPart).toJson(neededLanguage) if (sub !== "") { - parts.push(`\"${key}\": ${sub}`) + parts.push(`"${key}": ${sub}`) } } } @@ -234,7 +229,7 @@ class TranslationPart { } else if (!isLeaf) { errors.push({ error: "Mixed node: non-leaf node has translation strings", - path: path, + path: path }) } @@ -285,7 +280,7 @@ class TranslationPart { value + "\n" + fixLink, - path: path, + path: path }) } return @@ -297,7 +292,7 @@ class TranslationPart { error: `The translation for ${key} does not have the required subpart ${part} (in ${usedByLanguage}). \tThe full translation is ${value} \t${fixLink}`, - path: path, + path: path }) } } @@ -334,24 +329,6 @@ class TranslationPart { } } -/** - * Checks that the given object only contains string-values - * @param tr - */ -function isTranslation(tr: any): boolean { - if (tr["#"] === "no-translations") { - return false - } - if (tr["special"]) { - return false - } - for (const key in tr) { - if (typeof tr[key] !== "string") { - return false - } - } - return true -} /** * Converts a translation object into something that can be added to the 'generated translations'. @@ -361,9 +338,10 @@ function isTranslation(tr: any): boolean { function transformTranslation( obj: any, path: string[] = [], - languageWhitelist: string[] = undefined + languageWhitelist: string[] = undefined, + shortNotation = false ) { - if (isTranslation(obj)) { + if (GenerateTranslations.isTranslation(obj)) { return `new Translation( ${JSON.stringify(obj)} )` } @@ -380,7 +358,7 @@ function transformTranslation( } let value = obj[key] - if (isTranslation(value)) { + if (GenerateTranslations.isTranslation(value)) { if (languageWhitelist !== undefined) { const nv = {} for (const ln of languageWhitelist) { @@ -395,7 +373,7 @@ function transformTranslation( )}.${key}\n\tThe translations in other languages are ${JSON.stringify(value)}` } const subParts: string[] = value["en"].match(/{[^}]*}/g) - let expr = `return new Translation(${JSON.stringify(value)}, "core:${path.join( + let expr = `new Translation(${JSON.stringify(value)}, "core:${path.join( "." )}.${key}")` if (subParts !== null) { @@ -409,12 +387,16 @@ function transformTranslation( "." )}: A subpart contains invalid characters: ${subParts.join(", ")}` } - expr = `return new TypedTranslation<{ ${types.join(", ")} }>(${JSON.stringify( + expr = `new TypedTranslation<{ ${types.join(", ")} }>(${JSON.stringify( value )}, "core:${path.join(".")}.${key}")` } + if (shortNotation) { + values.push(`${spaces} ${key}: ${expr}`) - values.push(`${spaces}get ${key}() { ${expr} }`) + } else { + values.push(`${spaces}get ${key}() { return ${expr} }`) + } } else { values.push( spaces + key + ": " + transformTranslation(value, [...path, key], languageWhitelist) @@ -469,54 +451,11 @@ function formatFile(path) { writeFileSync(path, JSON.stringify(contents, null, " ") + (endsWithNewline ? "\n" : "")) } -/** - * Generates the big compiledTranslations file - */ -function genTranslations() { - if (!fs.existsSync("./src/assets/generated/")) { - fs.mkdirSync("./src/assets/generated/") - } - const translations = JSON.parse( - fs.readFileSync("./src/assets/generated/translations.json", "utf-8") - ) - const transformed = transformTranslation(translations) - - let module = `import {Translation, TypedTranslation} from "../../UI/i18n/Translation"\n\nexport default class CompiledTranslations {\n\n` - module += " public static t = " + transformed - module += "\n }" - - fs.writeFileSync("./src/assets/generated/CompiledTranslations.ts", module) -} /** * Reads 'lang/*.json', writes them into to 'assets/generated/translations.json'. * This is only for the core translations */ -function compileTranslationsFromWeblate() { - const translations = ScriptUtils.readDirRecSync("./langs", 1).filter( - (path) => path.indexOf(".json") > 0 - ) - - const allTranslations = new TranslationPart() - - allTranslations.validateStrict() - - for (const translationFile of translations) { - try { - const contents = JSON.parse(readFileSync(translationFile, "utf-8")) - let language = translationFile.substring(translationFile.lastIndexOf("/") + 1) - language = language.substring(0, language.length - 5) - allTranslations.add(language, contents) - } catch (e) { - throw "Could not read file " + translationFile + " due to " + e - } - } - - writeFileSync( - "./src/assets/generated/translations.json", - JSON.stringify(JSON.parse(allTranslations.toJson()), null, " ") - ) -} /** * Get all the strings out of the layers; writes them onto the weblate paths @@ -608,7 +547,7 @@ function MergeTranslation(source: any, target: any, language: string, context: s if (targetV[language] !== undefined && targetV[language] !== sourceV) { was = " (overwritten " + targetV[language] + ")" } - console.log(" + ", context + "." + language, "-->", sourceV, was) + // console.log(" + ", context + "." + language, "-->", sourceV, was) continue } if (typeof sourceV === "object") { @@ -697,7 +636,7 @@ function removeNonEnglishTranslations(object: any) { leaf["en"] = en }, (possibleLeaf) => - possibleLeaf !== null && typeof possibleLeaf === "object" && isTranslation(possibleLeaf) + possibleLeaf !== null && typeof possibleLeaf === "object" && GenerateTranslations.isTranslation(possibleLeaf) ) } @@ -732,6 +671,25 @@ class GenerateTranslations extends Script { super("Syncs translations from/to the theme and layer files") } + /** + * Checks that the given object only contains string-values + * @param tr + */ + static isTranslation(tr: Record): boolean { + if (tr["#"] === "no-translations") { + return false + } + if (tr["special"]) { + return false + } + for (const key in tr) { + if (typeof tr[key] !== "string") { + return false + } + } + return true + } + /** * OUtputs the 'used_languages.json'-file */ @@ -754,22 +712,74 @@ class GenerateTranslations extends Script { } } + + /** + * Generates the big compiledTranslations file based on 'translations.json' + */ + genTranslations(englishOnly?: boolean) { + if (!fs.existsSync("./src/assets/generated/")) { + fs.mkdirSync("./src/assets/generated/") + } + const translations = JSON.parse( + fs.readFileSync("./src/assets/generated/translations.json", "utf-8") + ) + const transformed = transformTranslation(translations, undefined, englishOnly ? ["en"] : undefined, englishOnly) + + let module = `import {Translation, TypedTranslation} from "../../UI/i18n/Translation"\n\nexport default class CompiledTranslations {\n\n` + module += " public static t = " + transformed + module += "\n }" + + fs.writeFileSync("./src/assets/generated/CompiledTranslations.ts", module) + } + + compileTranslationsFromWeblate(englishOnly: boolean) { + const translations = ScriptUtils.readDirRecSync("./langs", 1).filter( + (path) => path.indexOf(".json") > 0 + ) + + const allTranslations = new TranslationPart() + + allTranslations.validateStrict() + + for (const translationFile of translations) { + try { + const contents = JSON.parse(readFileSync(translationFile, "utf-8")) + let language = translationFile.substring(translationFile.lastIndexOf("/") + 1) + language = language.substring(0, language.length - 5) + if (englishOnly && language !== "en") { + continue + } + allTranslations.add(language, contents) + } catch (e) { + throw "Could not read file " + translationFile + " due to " + e + } + } + + writeFileSync( + "./src/assets/generated/translations.json", + JSON.stringify(JSON.parse(allTranslations.toJson()), null, " ") + ) + } + async main(args: string[]): Promise { if (!existsSync("./langs/themes")) { mkdirSync("./langs/themes") } const themeOverwritesWeblate = args[0] === "--ignore-weblate" const englishOnly = args[0] === "--english-only" + if (englishOnly) { + console.log("ENGLISH ONLY") + } if (!themeOverwritesWeblate) { - mergeLayerTranslations() - mergeThemeTranslations() - compileTranslationsFromWeblate() + mergeLayerTranslations(englishOnly) + mergeThemeTranslations(englishOnly) + this.compileTranslationsFromWeblate(englishOnly) } else { console.log("Ignore weblate") } this.detectUsedLanguages() - genTranslations() + this.genTranslations(englishOnly) { const allTranslationFiles = ScriptUtils.readDirRecSync("langs").filter((path) => path.endsWith(".json")