Linting of 'generate translations'
This commit is contained in:
parent
c9751a3eb5
commit
5302ec3342
1 changed files with 107 additions and 97 deletions
|
@ -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,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<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")
|
||||
|
|
Loading…
Reference in a new issue