forked from MapComplete/MapComplete
		
	Add _context key to themes for translations, all strings can now be translated
This commit is contained in:
		
							parent
							
								
									db2b14cd95
								
							
						
					
					
						commit
						a9aff5e16e
					
				
					 7 changed files with 105 additions and 21 deletions
				
			
		|  | @ -9,6 +9,7 @@ import LayerConfig from "../LayerConfig"; | |||
| import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"; | ||||
| import {SubstitutedTranslation} from "../../../UI/SubstitutedTranslation"; | ||||
| import DependencyCalculator from "../DependencyCalculator"; | ||||
| import Translations from "../../../UI/i18n/Translations"; | ||||
| 
 | ||||
| class SubstituteLayer extends Conversion<(string | LayerConfigJson), LayerConfigJson[]> { | ||||
|     private readonly _state: DesugaringContext; | ||||
|  | @ -279,6 +280,72 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| class AddContextToTransltionsInLayout extends DesugaringStep <LayoutConfigJson>{ | ||||
|      | ||||
|     constructor() { | ||||
|         super("Adds context to translations, including the prefix 'themes:json.id'; this is to make sure terms in an 'overrides' or inline layer are linkable too",["_context"], "AddContextToTranlationsInLayout"); | ||||
|     } | ||||
|      | ||||
|     convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { | ||||
|         const conversion = new AddContextToTranslations<LayoutConfigJson>("themes:") | ||||
|         return conversion.convert(json, json.id); | ||||
|     } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| class AddContextToTranslations<T> extends DesugaringStep<T> { | ||||
|     private readonly _prefix: string; | ||||
|      | ||||
|     constructor(prefix = "") { | ||||
|         super("Adds a '_context' to every object that is probably a translation", ["_context"], "AddContextToTranslation"); | ||||
|         this._prefix = prefix; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * const theme = { | ||||
|      *   layers: [ | ||||
|      *       { | ||||
|      *           builtin: ["abc"], | ||||
|      *           override: { | ||||
|      *               title:{ | ||||
|      *                   en: "Some title" | ||||
|      *               } | ||||
|      *           } | ||||
|      *       } | ||||
|      *   ]   | ||||
|      * }  | ||||
|      * const rewritten = new AddContextToTranslations<any>("prefix:").convert(theme, "context").result  | ||||
|      * const expected = { | ||||
|      *   layers: [ | ||||
|      *       { | ||||
|      *           builtin: ["abc"], | ||||
|      *           override: { | ||||
|      *               title:{ | ||||
|      *                  _context: "prefix:context.layers.0.override.title" | ||||
|      *                   en: "Some title" | ||||
|      *               } | ||||
|      *           } | ||||
|      *       } | ||||
|      *   ]   | ||||
|      * } | ||||
|      * rewritten // => expected
 | ||||
|      */ | ||||
|     convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } { | ||||
|          | ||||
|         const result = Utils.WalkJson(json, (leaf, path) => { | ||||
|             if(typeof leaf === "object"){ | ||||
|                 return {...leaf, _context: this._prefix + context+"."+ path.join(".")} | ||||
|             }else{ | ||||
|                 return leaf | ||||
|             } | ||||
|         }, obj => obj !== undefined && obj !== null && Translations.isProbablyATranslation(obj)) | ||||
|          | ||||
|         return { | ||||
|             result | ||||
|         }; | ||||
|     } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> { | ||||
| 
 | ||||
|  | @ -327,8 +394,13 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> { | |||
|             const dependencies: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = [] | ||||
| 
 | ||||
|             for (const layerConfig of alreadyLoaded) { | ||||
|                 const layerDeps = DependencyCalculator.getLayerDependencies(new LayerConfig(layerConfig)) | ||||
|                 dependencies.push(...layerDeps) | ||||
|                 try{ | ||||
|                     const layerDeps = DependencyCalculator.getLayerDependencies(new LayerConfig(layerConfig)) | ||||
|                     dependencies.push(...layerDeps) | ||||
|                 }catch(e){ | ||||
|                     console.error(e) | ||||
|                     throw "Detecting layer dependencies for "+layerConfig.id+" failed due to "+e | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             for (const dependency of dependencies) { | ||||
|  | @ -454,6 +526,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> { | |||
|     constructor(state: DesugaringContext) { | ||||
|         super( | ||||
|             "Fully prepares and expands a theme", | ||||
|             new AddContextToTransltionsInLayout(), | ||||
|             new PreparePersonalTheme(state), | ||||
|             new WarnForUnsubstitutedLayersInTheme(), | ||||
|             new On("layers", new Concat(new SubstituteLayer(state))), | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ import FilterConfigJson from "./Json/FilterConfigJson"; | |||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {Overpass} from "../../Logic/Osm/Overpass"; | ||||
| import Constants from "../Constants"; | ||||
| import undefinedError = Mocha.utils.undefinedError; | ||||
| 
 | ||||
| export default class LayerConfig extends WithContextLoader { | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,11 +15,15 @@ export class Translation extends BaseUIElement { | |||
| 
 | ||||
|     constructor(translations: object, context?: string) { | ||||
|         super() | ||||
|         this.context = context; | ||||
|         if (translations === undefined) { | ||||
|             console.error("Translation without content at "+context) | ||||
|             throw `Translation without content (${context})` | ||||
|         } | ||||
|         this.context = translations["_context"] ?? context; | ||||
|         if(translations["_context"] !== undefined){ | ||||
|             translations = {...translations} | ||||
|             delete translations["_context"] | ||||
|         } | ||||
|         if (typeof translations === "string") { | ||||
|             translations = {"*": translations}; | ||||
|         } | ||||
|  | @ -28,6 +32,9 @@ export class Translation extends BaseUIElement { | |||
|             if (!translations.hasOwnProperty(translationsKey)) { | ||||
|                 continue | ||||
|             } | ||||
|             if(translationsKey === "_context"){ | ||||
|                 continue | ||||
|             } | ||||
|             count++; | ||||
|             if (typeof (translations[translationsKey]) != "string") { | ||||
|                 console.error("Non-string object in translation: ", translations[translationsKey]) | ||||
|  |  | |||
|  | @ -107,7 +107,7 @@ export default class Translations { | |||
|             return false | ||||
|         } | ||||
|         // is a weird key found?
 | ||||
|         if(Object.keys(transl).some(key => !this.knownLanguages.has(key))){ | ||||
|         if(Object.keys(transl).some(key => key !== '_context' && !this.knownLanguages.has(key))){ | ||||
|             return false | ||||
|         } | ||||
|          | ||||
|  |  | |||
							
								
								
									
										31
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								Utils.ts
									
										
									
									
									
								
							|  | @ -515,41 +515,46 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Apply a function on every leaf of the JSON; used to rewrite parts of the JSON | ||||
|      * Apply a function on every leaf of the JSON; used to rewrite parts of the JSON. | ||||
|      * Returns a modified copy of the original object. | ||||
|      *  | ||||
|      * Hangs if the object contains a loop | ||||
|      */ | ||||
|     static WalkJson(json: any, f: (v: number | string | boolean | undefined) => any, isLeaf: (object) => boolean = undefined) { | ||||
|     static WalkJson(json: any, f: (v: object | number | string | boolean | undefined, path: string[]) => any, isLeaf: (object) => boolean = undefined, path: string[] = []) { | ||||
|         if (json === undefined) { | ||||
|             return f(undefined) | ||||
|             return f(undefined, path) | ||||
|         } | ||||
|         const jtp = typeof json | ||||
|         if (isLeaf !== undefined) { | ||||
|             if (jtp === "object") { | ||||
|                 if (isLeaf(json)) { | ||||
|                     return f(json) | ||||
|                     return f(json, path) | ||||
|                 } | ||||
|             } else { | ||||
|                 return json | ||||
|             } | ||||
|         } else if (jtp === "boolean" || jtp === "string" || jtp === "number") { | ||||
|             return f(json) | ||||
|             return f(json, path) | ||||
|         } | ||||
|         if (Array.isArray(json)) { | ||||
|             return json.map(sub => { | ||||
|                 return Utils.WalkJson(sub, f, isLeaf); | ||||
|             return json.map((sub,i) => { | ||||
|                 return Utils.WalkJson(sub, f, isLeaf, [...path,""+i]); | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         const cp = {...json} | ||||
|         for (const key in json) { | ||||
|             cp[key] = Utils.WalkJson(json[key], f, isLeaf) | ||||
|             cp[key] = Utils.WalkJson(json[key], f, isLeaf, [...path, key]) | ||||
|         } | ||||
|         return cp | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Walks an object recursively. Will hang on objects with loops | ||||
|      * Walks an object recursively, will execute the 'collect'-callback on every leaf. | ||||
|      *  | ||||
|      * Will hang on objects with loops | ||||
|      */ | ||||
|     static WalkObject(json: any, collect: (v: number | string | boolean | undefined, path: string[]) => any, isLeaf: (object) => boolean = undefined, path = []) { | ||||
|     static WalkObject(json: any, collect: (v: number | string | boolean | undefined, path: string[]) => any, isLeaf: (object) => boolean = undefined, path = []): void { | ||||
|         if (json === undefined) { | ||||
|             return; | ||||
|         } | ||||
|  | @ -563,12 +568,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|                 return collect(json, path) | ||||
|             } | ||||
|         } else if (jtp === "boolean" || jtp === "string" || jtp === "number") { | ||||
|             return collect(json, path) | ||||
|             collect(json, path) | ||||
|             return | ||||
|         } | ||||
|         if (Array.isArray(json)) { | ||||
|             return json.map((sub, i) => { | ||||
|             json.map((sub, i) => { | ||||
|                 return Utils.WalkObject(sub, collect, isLeaf, [...path, i]); | ||||
|             }) | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         for (const key in json) { | ||||
|  |  | |||
|  | @ -231,10 +231,6 @@ | |||
|                 "if": "theme=openwindpowermap", | ||||
|                 "then": "./assets/themes/openwindpowermap/logo.svg" | ||||
|               }, | ||||
|               { | ||||
|                 "if": "theme=parking-lanes", | ||||
|                 "then": "./assets/svg/bug.svg" | ||||
|               }, | ||||
|               { | ||||
|                 "if": "theme=parkings", | ||||
|                 "then": "./assets/themes/parkings/parkings.svg" | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ | |||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         "=filter": null, | ||||
|         "filter": null, | ||||
|         "=mapRendering": [ | ||||
|           { | ||||
|             "location": [ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue