Add _context key to themes for translations, all strings can now be translated

This commit is contained in:
pietervdvn 2022-04-06 17:28:51 +02:00
parent db2b14cd95
commit a9aff5e16e
7 changed files with 105 additions and 21 deletions

View file

@ -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) {
const layerDeps = DependencyCalculator.getLayerDependencies(new LayerConfig(layerConfig)) try{
dependencies.push(...layerDeps) 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) { 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))),

View file

@ -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 {

View file

@ -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])

View file

@ -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
} }

View file

@ -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) {

View file

@ -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"

View file

@ -50,7 +50,7 @@
] ]
} }
}, },
"=filter": null, "filter": null,
"=mapRendering": [ "=mapRendering": [
{ {
"location": [ "location": [