diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 82e969564..ca24d94f8 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -10,6 +10,7 @@ import Constants from "../Models/Constants"; import {Utils} from "../Utils"; import Link from "../UI/Base/Link"; import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; +import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; export class AllKnownLayouts { public static allKnownLayouts: Map = AllKnownLayouts.AllLayouts(); @@ -209,9 +210,9 @@ export class AllKnownLayouts { ]) } - private static getSharedLayers(): Map { + public static getSharedLayers(): Map { const sharedLayers = new Map(); - for (const layer of known_themes.layers) { + for (const layer of known_themes["layers"]) { try { // @ts-ignore const parsed = new LayerConfig(layer, "shared_layers") @@ -226,6 +227,16 @@ export class AllKnownLayouts { return sharedLayers; } + public static getSharedLayersConfigs(): Map { + const sharedLayers = new Map(); + for (const layer of known_themes["layers"]) { + // @ts-ignore + sharedLayers.set(layer.id, layer); + } + + return sharedLayers; + } + private static GenerateOrderedList(allKnownLayouts: Map): LayoutConfig[] { const list = [] allKnownLayouts.forEach((layout) => { @@ -236,7 +247,7 @@ export class AllKnownLayouts { private static AllLayouts(): Map { const dict: Map = new Map(); - for (const layoutConfigJson of known_themes.themes) { + for (const layoutConfigJson of known_themes["themes"]) { const layout = new LayoutConfig(layoutConfigJson, true) dict.set(layout.id, layout) for (let i = 0; i < layout.layers.length; i++) { diff --git a/Models/ThemeConfig/Conversion/AddContextToTranslations.ts b/Models/ThemeConfig/Conversion/AddContextToTranslations.ts index 9e3dc8c56..af9961a89 100644 --- a/Models/ThemeConfig/Conversion/AddContextToTranslations.ts +++ b/Models/ThemeConfig/Conversion/AddContextToTranslations.ts @@ -95,9 +95,32 @@ export class AddContextToTranslations extends DesugaringStep { * ] * } * rewritten // => expected + * + * + * // Should ignore all if '#dont-translate' is set + * const theme = { + * "#dont-translate": "*", + * layers: [ + * { + * builtin: ["abc"], + * override: { + * title:{ + * en: "Some title" + * } + * } + * } + * ] + * } + * const rewritten = new AddContextToTranslations("prefix:").convert(theme, "context").result + * rewritten // => theme + * */ convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } { + if(json["#dont-translate"] === "*"){ + return {result: json} + } + const result = Utils.WalkJson(json, (leaf, path) => { if(leaf === undefined || leaf === null){ return leaf diff --git a/Models/ThemeConfig/Conversion/Conversion.ts b/Models/ThemeConfig/Conversion/Conversion.ts index 2b7880c1f..48177827f 100644 --- a/Models/ThemeConfig/Conversion/Conversion.ts +++ b/Models/ThemeConfig/Conversion/Conversion.ts @@ -141,17 +141,21 @@ export class Each extends Conversion { export class On extends DesugaringStep { private readonly key: string; - private readonly step: Conversion; + private readonly step: ((t: T) => Conversion); - constructor(key: string, step: Conversion) { + constructor(key: string, step: Conversion | ((t: T )=> Conversion)) { super("Applies " + step.name + " onto property `"+key+"`", [key], `On(${key}, ${step.name})`); - this.step = step; + if(typeof step === "function"){ + this.step = step; + }else{ + this.step = _ => step + } this.key = key; } convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[], information?: string[] } { json = {...json} - const step = this.step + const step = this.step(json) const key = this.key; const value: P = json[key] if (value === undefined || value === null) { diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 64858e1f8..529b8397e 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -12,10 +12,12 @@ import {AddContextToTranslations} from "./AddContextToTranslations"; class ExpandTagRendering extends Conversion { private readonly _state: DesugaringContext; + private readonly _self: LayerConfigJson; - constructor(state: DesugaringContext) { + constructor(state: DesugaringContext, self: LayerConfigJson) { super("Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question", [], "ExpandTagRendering"); this._state = state; + this._self = self; } convert(json: string | TagRenderingConfigJson | { builtin: string | string[]; override: any }, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings: string[] } { @@ -33,42 +35,50 @@ class ExpandTagRendering extends Conversion= 0) { - const spl = name.split("."); - const layer = state.sharedLayers.get(spl[0]) - if (spl.length === 2 && layer !== undefined) { - const id = spl[1]; + if (name.indexOf(".") < 0) { + return undefined; + } + + const spl = name.split("."); + let layer = state.sharedLayers.get(spl[0]) + if (spl[0] === this._self.id) { + layer = this._self + } - const layerTrs = layer.tagRenderings.filter(tr => tr["id"] !== undefined) - let matchingTrs: TagRenderingConfigJson[] - if (id === "*") { - matchingTrs = layerTrs - } else if (id.startsWith("*")) { - const id_ = id.substring(1) - matchingTrs = layerTrs.filter(tr => tr.group === id_ || tr.labels?.indexOf(id_) >= 0) - } else { - matchingTrs = layerTrs.filter(tr => tr.id === id) - } + if (spl.length !== 2 || layer === undefined) { + return undefined + } + + const id = spl[1]; + + const layerTrs = layer.tagRenderings.filter(tr => tr["id"] !== undefined) + let matchingTrs: TagRenderingConfigJson[] + if (id === "*") { + matchingTrs = layerTrs + } else if (id.startsWith("*")) { + const id_ = id.substring(1) + matchingTrs = layerTrs.filter(tr => tr.group === id_ || tr.labels?.indexOf(id_) >= 0) + } else { + matchingTrs = layerTrs.filter(tr => tr.id === id) + } - const contextWriter = new AddContextToTranslations("layers:") - for (let i = 0; i < matchingTrs.length; i++) { - // The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown - let found : TagRenderingConfigJson = Utils.Clone(matchingTrs[i]); - if (found.condition === undefined) { - found.condition = layer.source.osmTags - } else { - found.condition = {and: [found.condition, layer.source.osmTags]} - } - - found = contextWriter.convertStrict(found, layer.id+ ".tagRenderings."+found["id"]) - matchingTrs[i] = found - } - - if (matchingTrs.length !== 0) { - return matchingTrs - } + const contextWriter = new AddContextToTranslations("layers:") + for (let i = 0; i < matchingTrs.length; i++) { + // The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown + let found: TagRenderingConfigJson = Utils.Clone(matchingTrs[i]); + if (found.condition === undefined) { + found.condition = layer.source.osmTags + } else { + found.condition = {and: [found.condition, layer.source.osmTags]} } + + found = contextWriter.convertStrict(found, layer.id + ".tagRenderings." + found["id"]) + matchingTrs[i] = found + } + + if (matchingTrs.length !== 0) { + return matchingTrs } return undefined; } @@ -86,7 +96,7 @@ class ExpandTagRendering extends Conversion 0){ - const [layer, search] = name.split(".") - candidates = Utils.NoNull( state.sharedLayers.get(layer).tagRenderings.map(tr => tr["id"])).map(id => layer+"."+id) + let candidates = Array.from(state.tagRenderings.keys()) + if (name.indexOf(".") > 0) { + const [layerName, search] = name.split(".") + let layer = state.sharedLayers.get(layerName) + if (layerName === this._self.id) { + layer = this._self; + } + if (layer === undefined) { + const candidates = Utils.sortedByLevenshteinDistance(layerName, Array.from(state.sharedLayers.keys()), s => s) + if(state.sharedLayers.size === 0){ + warnings.push(ctx + ": BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates.slice(0, 3).join(", ")) + }else{ + errors.push(ctx + ": While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates.slice(0, 3).join(", ")) + } + continue + } + candidates = Utils.NoNull(layer.tagRenderings.map(tr => tr["id"])).map(id => layerName + "." + id) } candidates = Utils.sortedByLevenshteinDistance(name, candidates, i => i); - errors.push(ctx + ": The tagRendering with identifier " + name + " was not found.\n\tDid you mean one of " +candidates.join(", ") + "?") + errors.push(ctx + ": The tagRendering with identifier " + name + " was not found.\n\tDid you mean one of " + candidates.join(", ") + "?") continue } for (let foundTr of lookup) { @@ -168,7 +191,7 @@ export class ExpandRewrite extends Conversion, T[ * "someKey": "somevalue {xyz}" * } * ExpandRewrite.RewriteParts("{xyz}", "rewritten", spec) // => {"someKey": "somevalue rewritten"} - * + * * // should substitute all occurances in strings * const spec = { * "someKey": "The left|right side has {key:left|right}" @@ -187,8 +210,8 @@ export class ExpandRewrite extends Conversion, T[ if (typeof obj === "string") { // This is a simple string - we do a simple replace - while(obj.indexOf(keyToRewrite) >= 0){ - obj = obj.replace(keyToRewrite, target) + while (obj.indexOf(keyToRewrite) >= 0) { + obj = obj.replace(keyToRewrite, target) } return obj } @@ -199,17 +222,17 @@ export class ExpandRewrite extends Conversion, T[ if (typeof obj === "object") { obj = {...obj} - + const isTr = targetIsTranslation && Translations.isProbablyATranslation(obj) - + for (const key in obj) { let subtarget = target - if(isTr && target[key] !== undefined){ + if (isTr && target[key] !== undefined) { // The target is a translation AND the current object is a translation // This means we should recursively replace with the translated value subtarget = target[key] } - + obj[key] = replaceRecursive(obj[key], subtarget) } return obj @@ -233,7 +256,7 @@ export class ExpandRewrite extends Conversion, T[ * renderings: "The value of xyz is abc" * } * new ExpandRewrite().convertStrict(spec, "test") // => ["The value of X is A", "The value of Y is B", "The value of Z is C"] - * + * * // should rewrite with translations * const spec = >{ * rewrite: { @@ -287,9 +310,9 @@ export class ExpandRewrite extends Conversion, T[ {// sanity check: {rewrite: ["a", "b"] should have the right amount of 'intos' in every case for (let i = 0; i < rewrite.rewrite.into.length; i++) { const into = keysToRewrite.into[i] - if(into.length !== rewrite.rewrite.sourceString.length){ - throw `${context}.into.${i} Error in rewrite: there are ${rewrite.rewrite.sourceString.length} keys to rewrite, but entry ${i} has only ${into.length} values` - + if (into.length !== rewrite.rewrite.sourceString.length) { + throw `${context}.into.${i} Error in rewrite: there are ${rewrite.rewrite.sourceString.length} keys to rewrite, but entry ${i} has only ${into.length} values` + } } } @@ -315,50 +338,50 @@ export class ExpandRewrite extends Conversion, T[ */ export class RewriteSpecial extends DesugaringStep { constructor() { - super("Converts a 'special' translation into a regular translation which uses parameters", ["special"],"RewriteSpecial"); + super("Converts a 'special' translation into a regular translation which uses parameters", ["special"], "RewriteSpecial"); } /** * Does the heavy lifting and conversion - * + * * // should not do anything if no 'special'-key is present * RewriteSpecial.convertIfNeeded({"en": "xyz", "nl": "abc"}, [], "test") // => {"en": "xyz", "nl": "abc"} - * + * * // should handle a simple special case * RewriteSpecial.convertIfNeeded({"special": {"type":"image_carousel"}}, [], "test") // => {'*': "{image_carousel()}"} - * + * * // should handle special case with a parameter * RewriteSpecial.convertIfNeeded({"special": {"type":"image_carousel", "image_key": "some_image_key"}}, [], "test") // => {'*': "{image_carousel(some_image_key)}"} - * + * * // should handle special case with a translated parameter * const spec = {"special": {"type":"image_upload", "label": {"en": "Add a picture to this object", "nl": "Voeg een afbeelding toe"}}} * const r = RewriteSpecial.convertIfNeeded(spec, [], "test") * r // => {"en": "{image_upload(,Add a picture to this object)}", "nl": "{image_upload(,Voeg een afbeelding toe)}" } - * + * * // should handle special case with a prefix and postfix * const spec = {"special": {"type":"image_upload" }, before: {"en": "PREFIX "}, after: {"en": " POSTFIX", nl: " Achtervoegsel"} } * const r = RewriteSpecial.convertIfNeeded(spec, [], "test") * r // => {"en": "PREFIX {image_upload(,)} POSTFIX", "nl": "PREFIX {image_upload(,)} Achtervoegsel" } - * + * * // should warn for unexpected keys * const errors = [] * RewriteSpecial.convertIfNeeded({"special": {type: "image_carousel"}, "en": "xyz"}, errors, "test") // => {'*': "{image_carousel()}"} * errors // => ["At test: Unexpected key in a special block: en"] - * + * * // should give an error on unknown visualisations * const errors = [] * RewriteSpecial.convertIfNeeded({"special": {type: "qsdf"}}, errors, "test") // => undefined * errors.length // => 1 * errors[0].indexOf("Special visualisation 'qsdf' not found") >= 0 // => true - * + * * // should give an error is 'type' is missing * const errors = [] * RewriteSpecial.convertIfNeeded({"special": {}}, errors, "test") // => undefined * errors // => ["A 'special'-block should define 'type' to indicate which visualisation should be used"] */ - private static convertIfNeeded(input: (object & {special : {type: string}}) | any, errors: string[], context: string): any { + private static convertIfNeeded(input: (object & { special: { type: string } }) | any, errors: string[], context: string): any { const special = input["special"] - if(special === undefined){ + if (special === undefined) { return input } @@ -367,17 +390,17 @@ export class RewriteSpecial extends DesugaringStep { } const type = special["type"] - if(type === undefined){ + if (type === undefined) { errors.push("A 'special'-block should define 'type' to indicate which visualisation should be used") return undefined } const vis = SpecialVisualizations.specialVisualizations.find(sp => sp.funcName === type) - if(vis === undefined){ + if (vis === undefined) { const options = Utils.sortedByLevenshteinDistance(type, SpecialVisualizations.specialVisualizations, sp => sp.funcName) errors.push(`Special visualisation '${type}' not found. Did you perhaps mean ${options[0].funcName}, ${options[1].funcName} or ${options[2].funcName}?\n\tFor all known special visualisations, please see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialRenderings.md`) return undefined } - + const argNamesList = vis.args.map(a => a.name) const argNames = new Set(argNamesList) // Check for obsolete and misspelled arguments @@ -385,21 +408,21 @@ export class RewriteSpecial extends DesugaringStep { .filter(k => !argNames.has(k)) .filter(k => k !== "type") .map(wrongArg => { - const byDistance = Utils.sortedByLevenshteinDistance(wrongArg, argNamesList, x => x) - return `Unexpected argument with name '${wrongArg}'. Did you mean ${byDistance[0]}?\n\tAll known arguments are ${ argNamesList.join(", ")}` ; - })) - + const byDistance = Utils.sortedByLevenshteinDistance(wrongArg, argNamesList, x => x) + return `Unexpected argument with name '${wrongArg}'. Did you mean ${byDistance[0]}?\n\tAll known arguments are ${argNamesList.join(", ")}`; + })) + // Check that all obligated arguments are present. They are obligated if they don't have a preset value for (const arg of vis.args) { if (arg.required !== true) { continue; } const param = special[arg.name] - if(param === undefined){ + if (param === undefined) { errors.push(`Obligated parameter '${arg.name}' not found`) } } - + const foundLanguages = new Set() const translatedArgs = argNamesList.map(nm => special[nm]) .filter(v => v !== undefined) @@ -407,25 +430,26 @@ export class RewriteSpecial extends DesugaringStep { for (const translatedArg of translatedArgs) { for (const ln of Object.keys(translatedArg)) { foundLanguages.add(ln) - } + } } - + const before = Translations.T(input.before) const after = Translations.T(input.after) - for (const ln of Object.keys(before?.translations??{})) { + for (const ln of Object.keys(before?.translations ?? {})) { foundLanguages.add(ln) } - for (const ln of Object.keys(after?.translations??{})) { + for (const ln of Object.keys(after?.translations ?? {})) { foundLanguages.add(ln) } - if(foundLanguages.size === 0){ - const args= argNamesList.map(nm => special[nm] ?? "").join(",") - return {'*': `{${type}(${args})}` + if (foundLanguages.size === 0) { + const args = argNamesList.map(nm => special[nm] ?? "").join(",") + return { + '*': `{${type}(${args})}` + } } - } - + const result = {} const languages = Array.from(foundLanguages) languages.sort() @@ -433,9 +457,9 @@ export class RewriteSpecial extends DesugaringStep { const args = [] for (const argName of argNamesList) { const v = special[argName] ?? "" - if(Translations.isProbablyATranslation(v)){ + if (Translations.isProbablyATranslation(v)) { args.push(new Translation(v).textFor(ln)) - }else{ + } else { args.push(v) } } @@ -459,7 +483,7 @@ export class RewriteSpecial extends DesugaringStep { * const result = new RewriteSpecial().convert(tr,"test").result * const expected = {render: {'*': "{image_carousel(image)}"}, mappings: [{if: "other_image_key", then: {'*': "{image_carousel(other_image_key)}"}} ]} * result // => expected - * + * * const tr = { * render: {special: {type: "image_carousel", image_key: "image"}, before: {en: "Some introduction"} }, * } @@ -470,20 +494,20 @@ export class RewriteSpecial extends DesugaringStep { convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { const errors = [] json = Utils.Clone(json) - const paths : {path: string[], type?: any, typeHint?: string}[] = tagrenderingconfigmeta["default"] ?? tagrenderingconfigmeta + const paths: { path: string[], type?: any, typeHint?: string }[] = tagrenderingconfigmeta["default"] ?? tagrenderingconfigmeta for (const path of paths) { - if(path.typeHint !== "rendered"){ + if (path.typeHint !== "rendered") { continue } Utils.WalkPath(path.path, json, ((leaf, travelled) => RewriteSpecial.convertIfNeeded(leaf, errors, travelled.join(".")))) } - + return { - result:json, + result: json, errors }; } - + } export class PrepareLayer extends Fuse { @@ -492,11 +516,11 @@ export class PrepareLayer extends Fuse { "Fully prepares and expands a layer for the LayerConfig.", new On("tagRenderings", new Each(new RewriteSpecial())), new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), - new On("tagRenderings", new Concat(new ExpandTagRendering(state))), + new On("tagRenderings", layer => new Concat(new ExpandTagRendering(state, layer))), new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), - new On("mapRendering",new Each( new On("icon", new FirstOf(new ExpandTagRendering(state))))), + new On("mapRendering", layer => new Each(new On("icon", new FirstOf(new ExpandTagRendering(state, layer))))), new SetDefault("titleIcons", ["defaults"]), - new On("titleIcons", new Concat(new ExpandTagRendering(state))) + new On("titleIcons", layer => new Concat(new ExpandTagRendering(state, layer))) ); } } \ No newline at end of file diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index ea49777a9..bfbe094ec 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -508,6 +508,7 @@ export class ValidateLayer extends DesugaringStep { const errors = [] const warnings = [] const information = [] + context = "While validating a layer: "+context if (typeof json === "string") { errors.push(context + ": This layer hasn't been expanded: " + json) return { diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index ed00ceb82..e300a7a72 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -236,13 +236,14 @@ export default class TagRenderingQuestion extends Combine { configuration: TagRenderingConfig, applicableMappings: { if: TagsFilter; ifnot?: TagsFilter, then: TypedTranslation; icon?: string; iconClass?: string, addExtraTags: Tag[], searchTerms?: Record }[], tagsSource: UIEventSource): InputElement { const values: { show: BaseUIElement, value: number, mainTerm: Record, searchTerms?: Record }[] = [] + const addIcons = applicableMappings.some(m => m.icon !== undefined) for (let i = 0; i < applicableMappings.length; i++) { const mapping = applicableMappings[i]; const tr = mapping.then.Subs(tagsSource.data) const patchedMapping = <{ iconClass: "small-height", then: TypedTranslation }>{ ...mapping, iconClass: `small-height`, - icon: mapping.icon ?? "./assets/svg/none.svg" + icon: mapping.icon ?? (addIcons ? "./assets/svg/none.svg": undefined) } const fancy = TagRenderingQuestion.GenerateMappingContent(patchedMapping, tagsSource, state).SetClass("normal-background") values.push({ diff --git a/Utils/LanguageUtils.ts b/Utils/LanguageUtils.ts new file mode 100644 index 000000000..045e5e2da --- /dev/null +++ b/Utils/LanguageUtils.ts @@ -0,0 +1,10 @@ +import * as used_languages from "../assets/generated/used_languages.json" + +export default class LanguageUtils { + + /** + * All the languages there is currently language support for in MapComplete + */ + public static readonly usedLanguages : Set = new Set(used_languages.languages) +} + diff --git a/Utils/WikidataUtils.ts b/Utils/WikidataUtils.ts new file mode 100644 index 000000000..8b0146060 --- /dev/null +++ b/Utils/WikidataUtils.ts @@ -0,0 +1,39 @@ + +export default class WikidataUtils { + + /** + * Mapping from wikidata-codes to weblate-codes. The wikidata-code is the key, mapcomplete/weblate is the value + */ + public static readonly languageRemapping = { + "nb":"nb_NO", + "zh-hant":"zh_Hant", + "zh-hans":"zh_Hans", + "pt-br":"pt_BR" + } + + /** + * Extract languages and their language in every language from the data source. + * The returned mapping will be {languageCode --> {languageCode0 --> language as written in languageCode0 } } + * @param data + * @param remapLanguages + */ + public static extractLanguageData(data: {lang: {value:string}, code: {value: string}, label: {value: string}} [], remapLanguages: Record): Map>{ + console.log("Got "+data.length+" entries") + const perId = new Map>(); + for (const element of data) { + let id = element.code.value + id = remapLanguages[id] ?? id + let labelLang = element.label["xml:lang"] + labelLang = remapLanguages[labelLang] ?? labelLang + const value = element.label.value + if(!perId.has(id)){ + perId.set(id, new Map()) + } + perId.get(id).set(labelLang, value) + } + + console.log("Got "+perId.size+" languages") + return perId + } + +} \ No newline at end of file diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json index ae5cf3809..ac877be74 100644 --- a/assets/layers/school/school.json +++ b/assets/layers/school/school.json @@ -304,76 +304,25 @@ "phone", "email", { - "id": "language", - "question": { - "en": "What is the main language of this school?
What language is spoken with the students in non-language related courses and with the administration?
", - "nl": "Wat is de voertaal van deze school?
Welke taal wordt met de studenten gesproken in niet-taal-gerelateerde vakken en met de administratie?
", - "de": "Was ist die Hauptsprache dieser Schule?
Welche Sprache wird mit den Schülern in den nicht sprachbezogenen Kursen und mit der Verwaltung gesprochen?
", - "fr": "Quelle est la langue principale de cette école ?
Quelle langue est parlée avec les élèves des cours non linguistiques et avec l'administration ?
" - }, - "render": { - "en": "{school:language} is the main language of {title()}", - "nl": "{school:language} is de voertaal van {title()}", - "de": "{school:language} ist die Hauptsprache von {title()}", - "fr": "{school:language} est la langue principale de {title()}" - }, - "freeform": { - "key": "school:language", - "inline": true, - "placeholder": { - "en": "Language in lowercase English", - "nl": "Taal in lowercase Engel", - "de": "Sprache in Englisch in Kleinbuchstaben" - }, - "addExtraTags": [ - "fixme=Freeform tag `school:language` used, to be doublechecked" - ] - }, - "mappings": [ - { - "if": "school:language=english", - "then": { - "en": "The main language of this school is unknown", - "nl": "De voertaal van deze school is niet gekend", - "de": "Die Hauptsprache dieser Schule ist unbekannt", - "fr": "La langue principale de cette école est inconnue" + "builtin": "wikidata.school-language", + "override": { + "+mappings": [ + { + "if": "school:language=", + "hideInAnswer": true, + "then": { + "en": "The main language of this school is unknown", + "nl": "De voertaal van deze school is niet gekend" + } } - }, - { - "if": "school:language=french", - "then": { - "en": "French is the main language of {name}", - "nl": "Frans is de voertaal van {name}", - "de": "Französisch ist die Hauptsprache von {name}" - } - }, - { - "if": "school:language=dutch", - "then": { - "en": "Dutch is the main language of {name}", - "nl": "Nederlands is de voertaal van {name}", - "de": "Niederländisch ist die Hauptsprache von {name}" - } - }, - { - "if": "school:language=german", - "then": { - "en": "German is the main language of {name}", - "nl": "Duits is de voertaal van {name}", - "de": "Deutsch ist die Hauptsprache von {name}" - } - }, - { - "if": "school:language=", - "then": { - "en": "The main language of this school is unknown", - "nl": "De voertaal van deze school is niet gekend", - "de": "Die Hauptsprache dieser Schule ist unbekannt", - "fr": "La langue principale de cette école est inconnue" - }, - "hideInAnswer": true + ], + "question": { + "en": "What is the main language of this school?
What language is spoken with the students in non-language related courses and with the administration?
", + "nl": "Wat is de voertaal van deze school?
Welke taal wordt met de studenten gesproken in niet-taal-gerelateerde vakken en met de administratie?
", + "de": "Was ist die Hauptsprache dieser Schule?
Welche Sprache wird mit den Schülern in den nicht sprachbezogenen Kursen und mit der Verwaltung gesprochen?
", + "fr": "Quelle est la langue principale de cette école ?
Quelle langue est parlée avec les élèves des cours non linguistiques et avec l'administration ?
" } - ] + } } ], "presets": [ diff --git a/package.json b/package.json index 1d97a1908..ed5812044 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "generate:schemas": "ts2json-schema -p Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && ts-node scripts/fixSchemas.ts ", "generate:service-worker": "tsc service-worker.ts && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" service-worker.js", "optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'", - "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json && rm ./asssets/generated/layers/* && rm ./assets/generated/themes/*", + "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json && rm ./assets/generated/layers/*.json && rm ./assets/generated/themes/*.json", "generate": "mkdir -p ./assets/generated; npm run reset:layeroverview; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run generate:licenses; npm run generate:layeroverview; npm run generate:service-worker", "generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -", "prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh", diff --git a/scripts/fetchLanguages.ts b/scripts/fetchLanguages.ts index 12c9f4ae6..a622a8c07 100644 --- a/scripts/fetchLanguages.ts +++ b/scripts/fetchLanguages.ts @@ -6,19 +6,10 @@ import * as wds from "wikidata-sdk" import {Utils} from "../Utils"; import ScriptUtils from "./ScriptUtils"; import {existsSync, readFileSync, writeFileSync} from "fs"; -import * as used_languages from "../assets/generated/used_languages.json" import {QuestionableTagRenderingConfigJson} from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; - -const languageRemap = { - // MapComplete (or weblate) on the left, language of wikimedia on the right - "nb":"nb_NO", - "zh-hant":"zh_Hant", - "zh-hans":"zh_Hans", - "pt-br":"pt_BR" -} - -const usedLanguages : Set = new Set(used_languages.languages) +import WikidataUtils from "../Utils/WikidataUtils"; +import LanguageUtils from "../Utils/LanguageUtils"; async function fetch(target: string){ const regular = await fetchRegularLanguages() @@ -75,32 +66,13 @@ async function fetchSpecial(id: number, code: string) { return bindings } -function extract(data){ - console.log("Got "+data.length+" entries") - const perId = new Map>(); - for (const element of data) { - let id = element.code.value - id = languageRemap[id] ?? id - let labelLang = element.label["xml:lang"] - labelLang = languageRemap[labelLang] ?? labelLang - const value = element.label.value - if(!perId.has(id)){ - perId.set(id, new Map()) - } - perId.get(id).set(labelLang, value) - } - - console.log("Got "+perId.size+" languages") - return perId -} - function getNativeList(langs: Map>){ const native = {} const keys: string[] = Array.from(langs.keys()) keys.sort() for (const key of keys) { const translations: Map = langs.get(key) - if(!usedLanguages.has(key)){ + if(!LanguageUtils.usedLanguages.has(key)){ continue } native[key] = translations.get(key) @@ -143,17 +115,17 @@ async function main(wipeCache = false){ console.log("Reusing the cached file") } const data = JSON.parse(readFileSync( cacheFile, "UTF8")) - const perId = extract(data) + const perId = WikidataUtils.extractLanguageData(data, WikidataUtils.languageRemapping) const nativeList = getNativeList(perId) writeFileSync("./assets/language_native.json", JSON.stringify(nativeList, null, " ")) const translations = Utils.MapToObj(perId, (value, key) => { - if(!usedLanguages.has(key)){ + if(!LanguageUtils.usedLanguages.has(key)){ return undefined // Remove unused languages } return Utils.MapToObj(value, (v, k ) => { - if(!usedLanguages.has(k)){ + if(!LanguageUtils.usedLanguages.has(k)){ return undefined } return v diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 36fb6d9e5..44c2b4f0f 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -20,6 +20,7 @@ import {PrepareLayer} from "../Models/ThemeConfig/Conversion/PrepareLayer"; import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme"; import {DesugaringContext} from "../Models/ThemeConfig/Conversion/Conversion"; import {Utils} from "../Utils"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; // This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files. // It spits out an overview of those to be used to load them @@ -216,7 +217,7 @@ class LayerOverviewUtils { writeFileSync("./assets/generated/known_layers.json", JSON.stringify({layers: Array.from(sharedLayers.values())})) - if (recompiledThemes.length > 0) { + if (recompiledThemes.length > 0 && !(recompiledThemes.length === 1 && recompiledThemes[0] === "mapcomplate-changes")) { // mapcomplete-changes shows an icon for each corresponding mapcomplete-theme const iconsPerTheme = Array.from(sharedThemes.values()).map(th => ({ @@ -231,6 +232,10 @@ class LayerOverviewUtils { } this.checkAllSvgs() + + if(AllKnownLayouts.getSharedLayersConfigs().size == 0){ + throw "This was a bootstrapping-run. Run generate layeroverview again!" + } const green = s => '\x1b[92m' + s + '\x1b[0m' console.log(green("All done!")) @@ -242,11 +247,11 @@ class LayerOverviewUtils { console.log(" ---------- VALIDATING BUILTIN LAYERS ---------") const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist); - const sharedLayers = new Map() const state: DesugaringContext = { tagRenderings: sharedTagRenderings, - sharedLayers + sharedLayers: AllKnownLayouts.getSharedLayersConfigs() } + const sharedLayers = new Map() const prepLayer = new PrepareLayer(state); const skippedLayers: string[] = [] const recompiledLayers: string[] = [] @@ -258,6 +263,7 @@ class LayerOverviewUtils { const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8")) sharedLayers.set(sharedLayer.id, sharedLayer) skippedLayers.push(sharedLayer.id) + console.log("Loaded "+sharedLayer.id) continue; } diff --git a/scripts/readIdPresets.ts b/scripts/thieves/readIdPresets.ts similarity index 95% rename from scripts/readIdPresets.ts rename to scripts/thieves/readIdPresets.ts index 5ed2023fb..0420c7064 100644 --- a/scripts/readIdPresets.ts +++ b/scripts/thieves/readIdPresets.ts @@ -1,16 +1,12 @@ /*** * Parses presets from the iD repository and extracts some usefull tags from them */ -import ScriptUtils from "./ScriptUtils"; +import ScriptUtils from "../ScriptUtils"; import {existsSync, readFileSync, writeFileSync} from "fs"; -import * as known_languages from "../assets/language_native.json" -import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; -import { - MappingConfigJson, - - QuestionableTagRenderingConfigJson -} from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; -import SmallLicense from "../Models/smallLicense"; +import * as known_languages from "../../assets/language_native.json" +import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson"; +import {MappingConfigJson} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; +import SmallLicense from "../../Models/smallLicense"; interface IconThief { steal(iconName: string): boolean diff --git a/scripts/thieves/stealLanguages.ts b/scripts/thieves/stealLanguages.ts new file mode 100644 index 000000000..04c0731bc --- /dev/null +++ b/scripts/thieves/stealLanguages.ts @@ -0,0 +1,86 @@ +/* +* Uses the languages in and to every translation from wikidata to generate a language question in wikidata/wikidata +* */ + +import WikidataUtils from "../../Utils/WikidataUtils"; +import {existsSync, mkdirSync, readFileSync, writeFileSync} from "fs"; +import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson"; +import {MappingConfigJson} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; +import LanguageUtils from "../../Utils/LanguageUtils"; + +function main(){ + const sourcepath = "assets/generated/languages-wd.json"; + console.log(`Converting language data file '${sourcepath}' into a tagMapping`) + const languages = WikidataUtils.extractLanguageData(JSON.parse(readFileSync(sourcepath, "utf8")), {}) + const mappings : MappingConfigJson[] = [] + const schoolmappings : MappingConfigJson[] = [] + + languages.forEach((l, code) => { + const then : Record= {} + l.forEach((tr, lng) => { + const languageCodeWeblate = WikidataUtils.languageRemapping[lng] ?? lng; + if(!LanguageUtils.usedLanguages.has(languageCodeWeblate)){ + return; + } + then[languageCodeWeblate] = tr + }) + mappings.push({ + if: "language:" + code + "=yes", + ifnot: "language:" + code + "=", + searchTerms: { + "*": [code] + }, + then + }) + + schoolmappings.push({ + if: "school:language=" + code, + then + }) + }) + + + const wikidataLayer = { + id: "wikidata", + description: "Various tagrenderings which are generated from Wikidata. Automatically generated with a script, don't edit manually", + "#dont-translate": "*", + "source": { + "osmTags": "id~*" + }, + "mapRendering": null, + tagRenderings: [ + { + id: "language", + // @ts-ignore + description: "Enables to pick *a single* 'language:=yes' within the mappings", + mappings, + }, + { + builtin: "wikidata.language", + override: { + id: "language-multi", + // @ts-ignore + description: "Enables to pick *multiple* 'language:=yes' within the mappings", + multiAnswer: true + } + + }, + { + id:"school-language", + // @ts-ignore + description: "Enables to pick a single 'school:language=' within the mappings", + multiAnswer: true, + mappings: schoolmappings + } + ] + } + const dir = "./assets/layers/wikidata/" + if(!existsSync(dir)){ + mkdirSync(dir) + } + const path = dir + "wikidata.json" + writeFileSync(path, JSON.stringify(wikidataLayer, null, " ")) + console.log("Written "+path) +} + +main() \ No newline at end of file