forked from MapComplete/MapComplete
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue