Linting of 'generate translations'

This commit is contained in:
Pieter Vander Vennet 2024-08-29 21:49:11 +02:00
parent c9751a3eb5
commit 5302ec3342

View file

@ -6,6 +6,7 @@ import Script from "./Script"
const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"]
const ignoreTerms = ["searchTerms"]
class TranslationPart {
contents: Map<string, TranslationPart | string> = new Map<string, TranslationPart | string>()
@ -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,10 +48,6 @@ class TranslationPart {
return
}
for (const translationsKey in translations) {
if (!translations.hasOwnProperty(translationsKey)) {
continue
}
const v = translations[translationsKey]
if (typeof v != "string") {
console.error(
@ -104,9 +102,6 @@ class TranslationPart {
}
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<string, string | object>): 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<void> {
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")