Wikidata language picker

This commit is contained in:
pietervdvn 2022-07-11 09:14:26 +02:00
parent b75581405e
commit 325e30666b
14 changed files with 335 additions and 213 deletions

View file

@ -10,6 +10,7 @@ import Constants from "../Models/Constants";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import Link from "../UI/Base/Link"; import Link from "../UI/Base/Link";
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
export class AllKnownLayouts { export class AllKnownLayouts {
public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts(); public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts();
@ -209,9 +210,9 @@ export class AllKnownLayouts {
]) ])
} }
private static getSharedLayers(): Map<string, LayerConfig> { public static getSharedLayers(): Map<string, LayerConfig> {
const sharedLayers = new Map<string, LayerConfig>(); const sharedLayers = new Map<string, LayerConfig>();
for (const layer of known_themes.layers) { for (const layer of known_themes["layers"]) {
try { try {
// @ts-ignore // @ts-ignore
const parsed = new LayerConfig(layer, "shared_layers") const parsed = new LayerConfig(layer, "shared_layers")
@ -226,6 +227,16 @@ export class AllKnownLayouts {
return sharedLayers; return sharedLayers;
} }
public static getSharedLayersConfigs(): Map<string, LayerConfigJson> {
const sharedLayers = new Map<string, LayerConfigJson>();
for (const layer of known_themes["layers"]) {
// @ts-ignore
sharedLayers.set(layer.id, layer);
}
return sharedLayers;
}
private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] { private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] {
const list = [] const list = []
allKnownLayouts.forEach((layout) => { allKnownLayouts.forEach((layout) => {
@ -236,7 +247,7 @@ export class AllKnownLayouts {
private static AllLayouts(): Map<string, LayoutConfig> { private static AllLayouts(): Map<string, LayoutConfig> {
const dict: Map<string, LayoutConfig> = new Map(); const dict: Map<string, LayoutConfig> = new Map();
for (const layoutConfigJson of known_themes.themes) { for (const layoutConfigJson of known_themes["themes"]) {
const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true) const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true)
dict.set(layout.id, layout) dict.set(layout.id, layout)
for (let i = 0; i < layout.layers.length; i++) { for (let i = 0; i < layout.layers.length; i++) {

View file

@ -95,9 +95,32 @@ export class AddContextToTranslations<T> extends DesugaringStep<T> {
* ] * ]
* } * }
* rewritten // => expected * 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<any>("prefix:").convert(theme, "context").result
* rewritten // => theme
*
*/ */
convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } { 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) => { const result = Utils.WalkJson(json, (leaf, path) => {
if(leaf === undefined || leaf === null){ if(leaf === undefined || leaf === null){
return leaf return leaf

View file

@ -141,17 +141,21 @@ export class Each<X, Y> extends Conversion<X[], Y[]> {
export class On<P, T> extends DesugaringStep<T> { export class On<P, T> extends DesugaringStep<T> {
private readonly key: string; private readonly key: string;
private readonly step: Conversion<P, P>; private readonly step: ((t: T) => Conversion<P, P>);
constructor(key: string, step: Conversion<P, P>) { constructor(key: string, step: Conversion<P, P> | ((t: T )=> Conversion<P, P>)) {
super("Applies " + step.name + " onto property `"+key+"`", [key], `On(${key}, ${step.name})`); 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; this.key = key;
} }
convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[], information?: string[] } { convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[], information?: string[] } {
json = {...json} json = {...json}
const step = this.step const step = this.step(json)
const key = this.key; const key = this.key;
const value: P = json[key] const value: P = json[key]
if (value === undefined || value === null) { if (value === undefined || value === null) {

View file

@ -12,10 +12,12 @@ import {AddContextToTranslations} from "./AddContextToTranslations";
class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> { class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> {
private readonly _state: DesugaringContext; 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"); super("Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question", [], "ExpandTagRendering");
this._state = state; this._state = state;
this._self = self;
} }
convert(json: string | TagRenderingConfigJson | { builtin: string | string[]; override: any }, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings: string[] } { 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<string | TagRenderingConfigJson | {
if (state.tagRenderings.has(name)) { if (state.tagRenderings.has(name)) {
return [state.tagRenderings.get(name)] return [state.tagRenderings.get(name)]
} }
if (name.indexOf(".") >= 0) { if (name.indexOf(".") < 0) {
const spl = name.split("."); return undefined;
const layer = state.sharedLayers.get(spl[0]) }
if (spl.length === 2 && layer !== undefined) {
const id = spl[1];
const layerTrs = <TagRenderingConfigJson[]>layer.tagRenderings.filter(tr => tr["id"] !== undefined) const spl = name.split(".");
let matchingTrs: TagRenderingConfigJson[] let layer = state.sharedLayers.get(spl[0])
if (id === "*") { if (spl[0] === this._self.id) {
matchingTrs = layerTrs layer = this._self
} else if (id.startsWith("*")) { }
const id_ = id.substring(1)
matchingTrs = layerTrs.filter(tr => tr.group === id_ || tr.labels?.indexOf(id_) >= 0) if (spl.length !== 2 || layer === undefined) {
} else { return undefined
matchingTrs = layerTrs.filter(tr => tr.id === id) }
}
const id = spl[1];
const layerTrs = <TagRenderingConfigJson[]>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<TagRenderingConfigJson>("layers:") const contextWriter = new AddContextToTranslations<TagRenderingConfigJson>("layers:")
for (let i = 0; i < matchingTrs.length; i++) { 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 // 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]); let found: TagRenderingConfigJson = Utils.Clone(matchingTrs[i]);
if (found.condition === undefined) { if (found.condition === undefined) {
found.condition = layer.source.osmTags found.condition = layer.source.osmTags
} else { } else {
found.condition = {and: [found.condition, layer.source.osmTags]} 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
}
} }
found = contextWriter.convertStrict(found, layer.id + ".tagRenderings." + found["id"])
matchingTrs[i] = found
}
if (matchingTrs.length !== 0) {
return matchingTrs
} }
return undefined; return undefined;
} }
@ -86,7 +96,7 @@ class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | {
const lookup = this.lookup(tr); const lookup = this.lookup(tr);
if (lookup === undefined) { if (lookup === undefined) {
const isTagRendering = ctx.indexOf("On(mapRendering") < 0 const isTagRendering = ctx.indexOf("On(mapRendering") < 0
if(isTagRendering){ if (isTagRendering) {
warnings.push(ctx + "A literal rendering was detected: " + tr) warnings.push(ctx + "A literal rendering was detected: " + tr)
} }
return [{ return [{
@ -114,13 +124,26 @@ class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | {
for (const name of names) { for (const name of names) {
const lookup = this.lookup(name) const lookup = this.lookup(name)
if (lookup === undefined) { if (lookup === undefined) {
let candidates = Array.from(state.tagRenderings.keys()) let candidates = Array.from(state.tagRenderings.keys())
if(name.indexOf(".") > 0){ if (name.indexOf(".") > 0) {
const [layer, search] = name.split(".") const [layerName, search] = name.split(".")
candidates = Utils.NoNull( state.sharedLayers.get(layer).tagRenderings.map(tr => tr["id"])).map(id => layer+"."+id) 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); 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 continue
} }
for (let foundTr of lookup) { for (let foundTr of lookup) {
@ -187,8 +210,8 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
if (typeof obj === "string") { if (typeof obj === "string") {
// This is a simple string - we do a simple replace // This is a simple string - we do a simple replace
while(obj.indexOf(keyToRewrite) >= 0){ while (obj.indexOf(keyToRewrite) >= 0) {
obj = obj.replace(keyToRewrite, target) obj = obj.replace(keyToRewrite, target)
} }
return obj return obj
} }
@ -204,7 +227,7 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
for (const key in obj) { for (const key in obj) {
let subtarget = target 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 // The target is a translation AND the current object is a translation
// This means we should recursively replace with the translated value // This means we should recursively replace with the translated value
subtarget = target[key] subtarget = target[key]
@ -287,8 +310,8 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
{// sanity check: {rewrite: ["a", "b"] should have the right amount of 'intos' in every case {// 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++) { for (let i = 0; i < rewrite.rewrite.into.length; i++) {
const into = keysToRewrite.into[i] const into = keysToRewrite.into[i]
if(into.length !== rewrite.rewrite.sourceString.length){ 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` 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,7 +338,7 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
*/ */
export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> { export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
constructor() { 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");
} }
/** /**
@ -356,9 +379,9 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
* RewriteSpecial.convertIfNeeded({"special": {}}, errors, "test") // => undefined * RewriteSpecial.convertIfNeeded({"special": {}}, errors, "test") // => undefined
* errors // => ["A 'special'-block should define 'type' to indicate which visualisation should be used"] * 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"] const special = input["special"]
if(special === undefined){ if (special === undefined) {
return input return input
} }
@ -367,12 +390,12 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
} }
const type = special["type"] 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") errors.push("A 'special'-block should define 'type' to indicate which visualisation should be used")
return undefined return undefined
} }
const vis = SpecialVisualizations.specialVisualizations.find(sp => sp.funcName === type) 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) 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`) 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 return undefined
@ -385,9 +408,9 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
.filter(k => !argNames.has(k)) .filter(k => !argNames.has(k))
.filter(k => k !== "type") .filter(k => k !== "type")
.map(wrongArg => { .map(wrongArg => {
const byDistance = Utils.sortedByLevenshteinDistance(wrongArg, argNamesList, x => x) 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(", ")}` ; 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 // Check that all obligated arguments are present. They are obligated if they don't have a preset value
for (const arg of vis.args) { for (const arg of vis.args) {
@ -395,7 +418,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
continue; continue;
} }
const param = special[arg.name] const param = special[arg.name]
if(param === undefined){ if (param === undefined) {
errors.push(`Obligated parameter '${arg.name}' not found`) errors.push(`Obligated parameter '${arg.name}' not found`)
} }
} }
@ -413,17 +436,18 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
const before = Translations.T(input.before) const before = Translations.T(input.before)
const after = Translations.T(input.after) 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) foundLanguages.add(ln)
} }
for (const ln of Object.keys(after?.translations??{})) { for (const ln of Object.keys(after?.translations ?? {})) {
foundLanguages.add(ln) foundLanguages.add(ln)
} }
if(foundLanguages.size === 0){ if (foundLanguages.size === 0) {
const args= argNamesList.map(nm => special[nm] ?? "").join(",") const args = argNamesList.map(nm => special[nm] ?? "").join(",")
return {'*': `{${type}(${args})}` return {
} '*': `{${type}(${args})}`
}
} }
const result = {} const result = {}
@ -433,9 +457,9 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
const args = [] const args = []
for (const argName of argNamesList) { for (const argName of argNamesList) {
const v = special[argName] ?? "" const v = special[argName] ?? ""
if(Translations.isProbablyATranslation(v)){ if (Translations.isProbablyATranslation(v)) {
args.push(new Translation(v).textFor(ln)) args.push(new Translation(v).textFor(ln))
}else{ } else {
args.push(v) args.push(v)
} }
} }
@ -470,16 +494,16 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
const errors = [] const errors = []
json = Utils.Clone(json) 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) { for (const path of paths) {
if(path.typeHint !== "rendered"){ if (path.typeHint !== "rendered") {
continue continue
} }
Utils.WalkPath(path.path, json, ((leaf, travelled) => RewriteSpecial.convertIfNeeded(leaf, errors, travelled.join(".")))) Utils.WalkPath(path.path, json, ((leaf, travelled) => RewriteSpecial.convertIfNeeded(leaf, errors, travelled.join("."))))
} }
return { return {
result:json, result: json,
errors errors
}; };
} }
@ -492,11 +516,11 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
"Fully prepares and expands a layer for the LayerConfig.", "Fully prepares and expands a layer for the LayerConfig.",
new On("tagRenderings", new Each(new RewriteSpecial())), new On("tagRenderings", new Each(new RewriteSpecial())),
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), 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 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 SetDefault("titleIcons", ["defaults"]),
new On("titleIcons", new Concat(new ExpandTagRendering(state))) new On("titleIcons", layer => new Concat(new ExpandTagRendering(state, layer)))
); );
} }
} }

View file

@ -508,6 +508,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
const errors = [] const errors = []
const warnings = [] const warnings = []
const information = [] const information = []
context = "While validating a layer: "+context
if (typeof json === "string") { if (typeof json === "string") {
errors.push(context + ": This layer hasn't been expanded: " + json) errors.push(context + ": This layer hasn't been expanded: " + json)
return { return {

View file

@ -236,13 +236,14 @@ export default class TagRenderingQuestion extends Combine {
configuration: TagRenderingConfig, configuration: TagRenderingConfig,
applicableMappings: { if: TagsFilter; ifnot?: TagsFilter, then: TypedTranslation<object>; icon?: string; iconClass?: string, addExtraTags: Tag[], searchTerms?: Record<string, string[]> }[], tagsSource: UIEventSource<any>): InputElement<TagsFilter> { applicableMappings: { if: TagsFilter; ifnot?: TagsFilter, then: TypedTranslation<object>; icon?: string; iconClass?: string, addExtraTags: Tag[], searchTerms?: Record<string, string[]> }[], tagsSource: UIEventSource<any>): InputElement<TagsFilter> {
const values: { show: BaseUIElement, value: number, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[] = [] const values: { show: BaseUIElement, value: number, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[] = []
const addIcons = applicableMappings.some(m => m.icon !== undefined)
for (let i = 0; i < applicableMappings.length; i++) { for (let i = 0; i < applicableMappings.length; i++) {
const mapping = applicableMappings[i]; const mapping = applicableMappings[i];
const tr = mapping.then.Subs(tagsSource.data) const tr = mapping.then.Subs(tagsSource.data)
const patchedMapping = <{ iconClass: "small-height", then: TypedTranslation<object> }>{ const patchedMapping = <{ iconClass: "small-height", then: TypedTranslation<object> }>{
...mapping, ...mapping,
iconClass: `small-height`, 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") const fancy = TagRenderingQuestion.GenerateMappingContent(patchedMapping, tagsSource, state).SetClass("normal-background")
values.push({ values.push({

10
Utils/LanguageUtils.ts Normal file
View file

@ -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<string> = new Set(used_languages.languages)
}

39
Utils/WikidataUtils.ts Normal file
View file

@ -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<string, string>): Map<string, Map<string, string>>{
console.log("Got "+data.length+" entries")
const perId = new Map<string, Map<string, string>>();
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<string, string>())
}
perId.get(id).set(labelLang, value)
}
console.log("Got "+perId.size+" languages")
return perId
}
}

View file

@ -304,76 +304,25 @@
"phone", "phone",
"email", "email",
{ {
"id": "language", "builtin": "wikidata.school-language",
"question": { "override": {
"en": "What is the main language of this school?<div class='subtle'>What language is spoken with the students in non-language related courses and with the administration?</div>", "+mappings": [
"nl": "Wat is de voertaal van deze school?<div class='subtle'>Welke taal wordt met de studenten gesproken in niet-taal-gerelateerde vakken en met de administratie?</div>", {
"de": "Was ist die Hauptsprache dieser Schule?<div class='subtle'>Welche Sprache wird mit den Schülern in den nicht sprachbezogenen Kursen und mit der Verwaltung gesprochen?</div>", "if": "school:language=",
"fr": "Quelle est la langue principale de cette école ?<div class='subtle'>Quelle langue est parlée avec les élèves des cours non linguistiques et avec l'administration ?</div>" "hideInAnswer": true,
}, "then": {
"render": { "en": "The main language of this school is unknown",
"en": "{school:language} is the main language of {title()}", "nl": "De voertaal van deze school is niet gekend"
"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"
} }
}, ],
{ "question": {
"if": "school:language=french", "en": "What is the main language of this school?<div class='subtle'>What language is spoken with the students in non-language related courses and with the administration?</div>",
"then": { "nl": "Wat is de voertaal van deze school?<div class='subtle'>Welke taal wordt met de studenten gesproken in niet-taal-gerelateerde vakken en met de administratie?</div>",
"en": "French is the main language of {name}", "de": "Was ist die Hauptsprache dieser Schule?<div class='subtle'>Welche Sprache wird mit den Schülern in den nicht sprachbezogenen Kursen und mit der Verwaltung gesprochen?</div>",
"nl": "Frans is de voertaal van {name}", "fr": "Quelle est la langue principale de cette école ?<div class='subtle'>Quelle langue est parlée avec les élèves des cours non linguistiques et avec l'administration ?</div>"
"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
} }
] }
} }
], ],
"presets": [ "presets": [

View file

@ -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: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", "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'", "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": "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 -", "generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -",
"prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh", "prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",

View file

@ -6,19 +6,10 @@ import * as wds from "wikidata-sdk"
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import ScriptUtils from "./ScriptUtils"; import ScriptUtils from "./ScriptUtils";
import {existsSync, readFileSync, writeFileSync} from "fs"; import {existsSync, readFileSync, writeFileSync} from "fs";
import * as used_languages from "../assets/generated/used_languages.json"
import {QuestionableTagRenderingConfigJson} from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; import {QuestionableTagRenderingConfigJson} from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
import WikidataUtils from "../Utils/WikidataUtils";
const languageRemap = { import LanguageUtils from "../Utils/LanguageUtils";
// 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<string> = new Set(used_languages.languages)
async function fetch(target: string){ async function fetch(target: string){
const regular = await fetchRegularLanguages() const regular = await fetchRegularLanguages()
@ -75,32 +66,13 @@ async function fetchSpecial(id: number, code: string) {
return bindings return bindings
} }
function extract(data){
console.log("Got "+data.length+" entries")
const perId = new Map<string, Map<string, string>>();
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<string, string>())
}
perId.get(id).set(labelLang, value)
}
console.log("Got "+perId.size+" languages")
return perId
}
function getNativeList(langs: Map<string, Map<string, string>>){ function getNativeList(langs: Map<string, Map<string, string>>){
const native = {} const native = {}
const keys: string[] = Array.from(langs.keys()) const keys: string[] = Array.from(langs.keys())
keys.sort() keys.sort()
for (const key of keys) { for (const key of keys) {
const translations: Map<string, string> = langs.get(key) const translations: Map<string, string> = langs.get(key)
if(!usedLanguages.has(key)){ if(!LanguageUtils.usedLanguages.has(key)){
continue continue
} }
native[key] = translations.get(key) native[key] = translations.get(key)
@ -143,17 +115,17 @@ async function main(wipeCache = false){
console.log("Reusing the cached file") console.log("Reusing the cached file")
} }
const data = JSON.parse(readFileSync( cacheFile, "UTF8")) const data = JSON.parse(readFileSync( cacheFile, "UTF8"))
const perId = extract(data) const perId = WikidataUtils.extractLanguageData(data, WikidataUtils.languageRemapping)
const nativeList = getNativeList(perId) const nativeList = getNativeList(perId)
writeFileSync("./assets/language_native.json", JSON.stringify(nativeList, null, " ")) writeFileSync("./assets/language_native.json", JSON.stringify(nativeList, null, " "))
const translations = Utils.MapToObj(perId, (value, key) => { const translations = Utils.MapToObj(perId, (value, key) => {
if(!usedLanguages.has(key)){ if(!LanguageUtils.usedLanguages.has(key)){
return undefined // Remove unused languages return undefined // Remove unused languages
} }
return Utils.MapToObj(value, (v, k ) => { return Utils.MapToObj(value, (v, k ) => {
if(!usedLanguages.has(k)){ if(!LanguageUtils.usedLanguages.has(k)){
return undefined return undefined
} }
return v return v

View file

@ -20,6 +20,7 @@ import {PrepareLayer} from "../Models/ThemeConfig/Conversion/PrepareLayer";
import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme"; import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme";
import {DesugaringContext} from "../Models/ThemeConfig/Conversion/Conversion"; import {DesugaringContext} from "../Models/ThemeConfig/Conversion/Conversion";
import {Utils} from "../Utils"; 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. // 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 // 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())})) 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 // mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
const iconsPerTheme = const iconsPerTheme =
Array.from(sharedThemes.values()).map(th => ({ Array.from(sharedThemes.values()).map(th => ({
@ -232,6 +233,10 @@ class LayerOverviewUtils {
this.checkAllSvgs() 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' const green = s => '\x1b[92m' + s + '\x1b[0m'
console.log(green("All done!")) console.log(green("All done!"))
} }
@ -242,11 +247,11 @@ class LayerOverviewUtils {
console.log(" ---------- VALIDATING BUILTIN LAYERS ---------") console.log(" ---------- VALIDATING BUILTIN LAYERS ---------")
const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist); const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist);
const sharedLayers = new Map<string, LayerConfigJson>()
const state: DesugaringContext = { const state: DesugaringContext = {
tagRenderings: sharedTagRenderings, tagRenderings: sharedTagRenderings,
sharedLayers sharedLayers: AllKnownLayouts.getSharedLayersConfigs()
} }
const sharedLayers = new Map<string, LayerConfigJson>()
const prepLayer = new PrepareLayer(state); const prepLayer = new PrepareLayer(state);
const skippedLayers: string[] = [] const skippedLayers: string[] = []
const recompiledLayers: string[] = [] const recompiledLayers: string[] = []
@ -258,6 +263,7 @@ class LayerOverviewUtils {
const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8")) const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8"))
sharedLayers.set(sharedLayer.id, sharedLayer) sharedLayers.set(sharedLayer.id, sharedLayer)
skippedLayers.push(sharedLayer.id) skippedLayers.push(sharedLayer.id)
console.log("Loaded "+sharedLayer.id)
continue; continue;
} }

View file

@ -1,16 +1,12 @@
/*** /***
* Parses presets from the iD repository and extracts some usefull tags from them * 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 {existsSync, readFileSync, writeFileSync} from "fs";
import * as known_languages from "../assets/language_native.json" import * as known_languages from "../../assets/language_native.json"
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson";
import { import {MappingConfigJson} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
MappingConfigJson, import SmallLicense from "../../Models/smallLicense";
QuestionableTagRenderingConfigJson
} from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
import SmallLicense from "../Models/smallLicense";
interface IconThief { interface IconThief {
steal(iconName: string): boolean steal(iconName: string): boolean

View file

@ -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<string, string>= {}
l.forEach((tr, lng) => {
const languageCodeWeblate = WikidataUtils.languageRemapping[lng] ?? lng;
if(!LanguageUtils.usedLanguages.has(languageCodeWeblate)){
return;
}
then[languageCodeWeblate] = tr
})
mappings.push(<MappingConfigJson>{
if: "language:" + code + "=yes",
ifnot: "language:" + code + "=",
searchTerms: {
"*": [code]
},
then
})
schoolmappings.push(<MappingConfigJson>{
if: "school:language=" + code,
then
})
})
const wikidataLayer = <LayerConfigJson>{
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:<lng>=yes' within the mappings",
mappings,
},
{
builtin: "wikidata.language",
override: {
id: "language-multi",
// @ts-ignore
description: "Enables to pick *multiple* 'language:<lng>=yes' within the mappings",
multiAnswer: true
}
},
{
id:"school-language",
// @ts-ignore
description: "Enables to pick a single 'school:language=<lng>' 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()