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 {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"; | ||||||
| import {SubstitutedTranslation} from "../../../UI/SubstitutedTranslation"; | import {SubstitutedTranslation} from "../../../UI/SubstitutedTranslation"; | ||||||
| import DependencyCalculator from "../DependencyCalculator"; | import DependencyCalculator from "../DependencyCalculator"; | ||||||
|  | import Translations from "../../../UI/i18n/Translations"; | ||||||
| 
 | 
 | ||||||
| class SubstituteLayer extends Conversion<(string | LayerConfigJson), LayerConfigJson[]> { | class SubstituteLayer extends Conversion<(string | LayerConfigJson), LayerConfigJson[]> { | ||||||
|     private readonly _state: DesugaringContext; |     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> { | class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> { | ||||||
| 
 | 
 | ||||||
|  | @ -327,8 +394,13 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|             const dependencies: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = [] |             const dependencies: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = [] | ||||||
| 
 | 
 | ||||||
|             for (const layerConfig of alreadyLoaded) { |             for (const layerConfig of alreadyLoaded) { | ||||||
|  |                 try{ | ||||||
|                     const layerDeps = DependencyCalculator.getLayerDependencies(new LayerConfig(layerConfig)) |                     const layerDeps = DependencyCalculator.getLayerDependencies(new LayerConfig(layerConfig)) | ||||||
|                     dependencies.push(...layerDeps) |                     dependencies.push(...layerDeps) | ||||||
|  |                 }catch(e){ | ||||||
|  |                     console.error(e) | ||||||
|  |                     throw "Detecting layer dependencies for "+layerConfig.id+" failed due to "+e | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             for (const dependency of dependencies) { |             for (const dependency of dependencies) { | ||||||
|  | @ -454,6 +526,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> { | ||||||
|     constructor(state: DesugaringContext) { |     constructor(state: DesugaringContext) { | ||||||
|         super( |         super( | ||||||
|             "Fully prepares and expands a theme", |             "Fully prepares and expands a theme", | ||||||
|  |             new AddContextToTransltionsInLayout(), | ||||||
|             new PreparePersonalTheme(state), |             new PreparePersonalTheme(state), | ||||||
|             new WarnForUnsubstitutedLayersInTheme(), |             new WarnForUnsubstitutedLayersInTheme(), | ||||||
|             new On("layers", new Concat(new SubstituteLayer(state))), |             new On("layers", new Concat(new SubstituteLayer(state))), | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import FilterConfigJson from "./Json/FilterConfigJson"; | ||||||
| import {And} from "../../Logic/Tags/And"; | import {And} from "../../Logic/Tags/And"; | ||||||
| import {Overpass} from "../../Logic/Osm/Overpass"; | import {Overpass} from "../../Logic/Osm/Overpass"; | ||||||
| import Constants from "../Constants"; | import Constants from "../Constants"; | ||||||
|  | import undefinedError = Mocha.utils.undefinedError; | ||||||
| 
 | 
 | ||||||
| export default class LayerConfig extends WithContextLoader { | export default class LayerConfig extends WithContextLoader { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,11 +15,15 @@ export class Translation extends BaseUIElement { | ||||||
| 
 | 
 | ||||||
|     constructor(translations: object, context?: string) { |     constructor(translations: object, context?: string) { | ||||||
|         super() |         super() | ||||||
|         this.context = context; |  | ||||||
|         if (translations === undefined) { |         if (translations === undefined) { | ||||||
|             console.error("Translation without content at "+context) |             console.error("Translation without content at "+context) | ||||||
|             throw `Translation without content (${context})` |             throw `Translation without content (${context})` | ||||||
|         } |         } | ||||||
|  |         this.context = translations["_context"] ?? context; | ||||||
|  |         if(translations["_context"] !== undefined){ | ||||||
|  |             translations = {...translations} | ||||||
|  |             delete translations["_context"] | ||||||
|  |         } | ||||||
|         if (typeof translations === "string") { |         if (typeof translations === "string") { | ||||||
|             translations = {"*": translations}; |             translations = {"*": translations}; | ||||||
|         } |         } | ||||||
|  | @ -28,6 +32,9 @@ export class Translation extends BaseUIElement { | ||||||
|             if (!translations.hasOwnProperty(translationsKey)) { |             if (!translations.hasOwnProperty(translationsKey)) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|  |             if(translationsKey === "_context"){ | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|             count++; |             count++; | ||||||
|             if (typeof (translations[translationsKey]) != "string") { |             if (typeof (translations[translationsKey]) != "string") { | ||||||
|                 console.error("Non-string object in translation: ", translations[translationsKey]) |                 console.error("Non-string object in translation: ", translations[translationsKey]) | ||||||
|  |  | ||||||
|  | @ -107,7 +107,7 @@ export default class Translations { | ||||||
|             return false |             return false | ||||||
|         } |         } | ||||||
|         // is a weird key found?
 |         // 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 |             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) { |         if (json === undefined) { | ||||||
|             return f(undefined) |             return f(undefined, path) | ||||||
|         } |         } | ||||||
|         const jtp = typeof json |         const jtp = typeof json | ||||||
|         if (isLeaf !== undefined) { |         if (isLeaf !== undefined) { | ||||||
|             if (jtp === "object") { |             if (jtp === "object") { | ||||||
|                 if (isLeaf(json)) { |                 if (isLeaf(json)) { | ||||||
|                     return f(json) |                     return f(json, path) | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 return json |                 return json | ||||||
|             } |             } | ||||||
|         } else if (jtp === "boolean" || jtp === "string" || jtp === "number") { |         } else if (jtp === "boolean" || jtp === "string" || jtp === "number") { | ||||||
|             return f(json) |             return f(json, path) | ||||||
|         } |         } | ||||||
|         if (Array.isArray(json)) { |         if (Array.isArray(json)) { | ||||||
|             return json.map(sub => { |             return json.map((sub,i) => { | ||||||
|                 return Utils.WalkJson(sub, f, isLeaf); |                 return Utils.WalkJson(sub, f, isLeaf, [...path,""+i]); | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const cp = {...json} |         const cp = {...json} | ||||||
|         for (const key in 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 |         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) { |         if (json === undefined) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  | @ -563,12 +568,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | ||||||
|                 return collect(json, path) |                 return collect(json, path) | ||||||
|             } |             } | ||||||
|         } else if (jtp === "boolean" || jtp === "string" || jtp === "number") { |         } else if (jtp === "boolean" || jtp === "string" || jtp === "number") { | ||||||
|             return collect(json, path) |             collect(json, path) | ||||||
|  |             return | ||||||
|         } |         } | ||||||
|         if (Array.isArray(json)) { |         if (Array.isArray(json)) { | ||||||
|             return json.map((sub, i) => { |             json.map((sub, i) => { | ||||||
|                 return Utils.WalkObject(sub, collect, isLeaf, [...path, i]); |                 return Utils.WalkObject(sub, collect, isLeaf, [...path, i]); | ||||||
|             }) |             }) | ||||||
|  |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const key in json) { |         for (const key in json) { | ||||||
|  |  | ||||||
|  | @ -231,10 +231,6 @@ | ||||||
|                 "if": "theme=openwindpowermap", |                 "if": "theme=openwindpowermap", | ||||||
|                 "then": "./assets/themes/openwindpowermap/logo.svg" |                 "then": "./assets/themes/openwindpowermap/logo.svg" | ||||||
|               }, |               }, | ||||||
|               { |  | ||||||
|                 "if": "theme=parking-lanes", |  | ||||||
|                 "then": "./assets/svg/bug.svg" |  | ||||||
|               }, |  | ||||||
|               { |               { | ||||||
|                 "if": "theme=parkings", |                 "if": "theme=parkings", | ||||||
|                 "then": "./assets/themes/parkings/parkings.svg" |                 "then": "./assets/themes/parkings/parkings.svg" | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ | ||||||
|             ] |             ] | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "=filter": null, |         "filter": null, | ||||||
|         "=mapRendering": [ |         "=mapRendering": [ | ||||||
|           { |           { | ||||||
|             "location": [ |             "location": [ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue