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) {
|
||||||
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))),
|
||||||
|
|
|
@ -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…
Reference in a new issue