diff --git a/Docs/BuiltinIndex.md b/Docs/BuiltinIndex.md index 75fc2a4109..680260c765 100644 --- a/Docs/BuiltinIndex.md +++ b/Docs/BuiltinIndex.md @@ -21,6 +21,7 @@ + [bicycle_rental.*bicycle_rental](#bicycle_rental*bicycle_rental) + [bike_cleaning.bike_cleaning-service:bicycle:cleaning:charge](#bike_cleaningbike_cleaning-service:bicycle:cleaning:charge) + [wheelchair-access](#wheelchair-access) + + [smoking](#smoking) + [service:electricity](#serviceelectricity) + [dog-access](#dog-access) + [climbing.website](#climbingwebsite) @@ -287,6 +288,17 @@ +### smoking + + + + + + - cafe_pub + + + + ### service:electricity diff --git a/Docs/BuiltinQuestions.md b/Docs/BuiltinQuestions.md index b75eea8790..95ba3a4913 100644 --- a/Docs/BuiltinQuestions.md +++ b/Docs/BuiltinQuestions.md @@ -33,11 +33,13 @@ The following items can be easily reused in your layers + [last_edit](#last_edit) + [all_tags](#all_tags) + [level](#level) + + [smoking](#smoking) + [default](#default) + [defaults](#defaults) + [isOpen](#isopen) + [phonelink](#phonelink) + [emaillink](#emaillink) + + [smokingicon](#smokingicon) + [sharelink](#sharelink) @@ -341,6 +343,21 @@ On what level is this feature located? +### smoking + + + +Is smoking allowed at {title()}? + + + + - Smoking is allowed + - Smoking is not allowed + - Smoking is allowed outside. + + + + ### default @@ -377,7 +394,7 @@ Read-only tagrendering -phone +phone Read-only tagrendering @@ -387,12 +404,26 @@ Read-only tagrendering -email +email Read-only tagrendering +### smokingicon + + + +Read-only tagrendering + + + + - no-smoking + - smoking-allowed + + + + ### sharelink diff --git a/Docs/Layers/cafe_pub.md b/Docs/Layers/cafe_pub.md index 00b04ef59a..52de985fdd 100644 --- a/Docs/Layers/cafe_pub.md +++ b/Docs/Layers/cafe_pub.md @@ -40,10 +40,10 @@ Elements must have the all of following tags to be shown on this layer: - - amenity=bar|amenity=pub|amenity=cafe|amenity=biergarten + - amenity=bar|amenity=pub|amenity=cafe|amenity=biergarten|amenity=nightclub -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22amenity%22%3D%22bar%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22pub%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22cafe%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22biergarten%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) +[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22amenity%22%3D%22bar%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22pub%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22cafe%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22biergarten%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22amenity%22%3D%22nightclub%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) @@ -61,12 +61,13 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | -[](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [pub](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dpub) [bar](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dbar) [cafe](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dcafe) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [biergarten](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dbiergarten) +[](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [pub](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dpub) [bar](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dbar) [cafe](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dcafe) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [biergarten](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dbiergarten) [nightclub](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dnightclub) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | [](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) +[](https://taginfo.openstreetmap.org/keys/smoking#values) [smoking](https://wiki.openstreetmap.org/wiki/Key:smoking) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:smoking%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:smoking%3Dno) [outside](https://wiki.openstreetmap.org/wiki/Tag:smoking%3Doutside) [](https://taginfo.openstreetmap.org/keys/service:electricity#values) [service:electricity](https://wiki.openstreetmap.org/wiki/Key:service:electricity) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dlimited) [ask](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dask) [no](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dno) [](https://taginfo.openstreetmap.org/keys/dog#values) [dog](https://wiki.openstreetmap.org/wiki/Key:dog) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:dog%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:dog%3Dno) [leashed](https://wiki.openstreetmap.org/wiki/Tag:dog%3Dleashed) [unleashed](https://wiki.openstreetmap.org/wiki/Tag:dog%3Dunleashed) @@ -112,6 +113,7 @@ The question is What kind of cafe is this? - A cafe to drink tea, coffee or an alcoholical bevarage in a quiet environment corresponds with amenity=cafe - A restuarant where one can get a proper meal corresponds with amenity=restaurant - An open space where beer is served, typically seen in Germany corresponds with amenity=biergarten + - This is a nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks corresponds with amenity=nightclub @@ -226,6 +228,23 @@ The question is Is this place accessible with a wheelchair? +### smoking + + + +The question is Is smoking allowed at {title()}? + + + + + + - Smoking is allowed corresponds with smoking=yes + - Smoking is not allowed corresponds with smoking=no + - Smoking is allowed outside. corresponds with smoking=outside + + + + ### service:electricity diff --git a/Docs/TagInfo/mapcomplete_cafes_and_pubs.json b/Docs/TagInfo/mapcomplete_cafes_and_pubs.json index 26d78bf69e..7f889ce4d1 100644 --- a/Docs/TagInfo/mapcomplete_cafes_and_pubs.json +++ b/Docs/TagInfo/mapcomplete_cafes_and_pubs.json @@ -30,6 +30,11 @@ "description": "The MapComplete theme Cafés and pubs has a layer Cafés and pubs showing features with this tag", "value": "biergarten" }, + { + "key": "amenity", + "description": "The MapComplete theme Cafés and pubs has a layer Cafés and pubs showing features with this tag", + "value": "nightclub" + }, { "key": "image", "description": "The layer 'Cafés and pubs allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" @@ -75,6 +80,11 @@ "description": "Layer 'Cafés and pubs' shows amenity=biergarten with a fixed text, namely 'An open space where beer is served, typically seen in Germany' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", "value": "biergarten" }, + { + "key": "amenity", + "description": "Layer 'Cafés and pubs' shows amenity=nightclub with a fixed text, namely 'This is a nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", + "value": "nightclub" + }, { "key": "opening_hours", "description": "Layer 'Cafés and pubs' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Cafés and pubs')" @@ -133,6 +143,21 @@ "description": "Layer 'Cafés and pubs' shows wheelchair=no with a fixed text, namely 'This place is not reachable with a wheelchair' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", "value": "no" }, + { + "key": "smoking", + "description": "Layer 'Cafés and pubs' shows smoking=yes with a fixed text, namely 'Smoking is allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", + "value": "yes" + }, + { + "key": "smoking", + "description": "Layer 'Cafés and pubs' shows smoking=no with a fixed text, namely 'Smoking is not allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", + "value": "no" + }, + { + "key": "smoking", + "description": "Layer 'Cafés and pubs' shows smoking=outside with a fixed text, namely 'Smoking is allowed outside.' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", + "value": "outside" + }, { "key": "service:electricity", "description": "Layer 'Cafés and pubs' shows service:electricity=yes with a fixed text, namely 'There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cafés and pubs')", diff --git a/Docs/TagInfo/mapcomplete_personal.json b/Docs/TagInfo/mapcomplete_personal.json index a5216ce228..146b216850 100644 --- a/Docs/TagInfo/mapcomplete_personal.json +++ b/Docs/TagInfo/mapcomplete_personal.json @@ -1920,6 +1920,11 @@ "description": "The MapComplete theme Personal theme has a layer Cafés and pubs showing features with this tag", "value": "biergarten" }, + { + "key": "amenity", + "description": "The MapComplete theme Personal theme has a layer Cafés and pubs showing features with this tag", + "value": "nightclub" + }, { "key": "image", "description": "The layer 'Cafés and pubs allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" @@ -1965,6 +1970,11 @@ "description": "Layer 'Cafés and pubs' shows amenity=biergarten with a fixed text, namely 'An open space where beer is served, typically seen in Germany' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", "value": "biergarten" }, + { + "key": "amenity", + "description": "Layer 'Cafés and pubs' shows amenity=nightclub with a fixed text, namely 'This is a nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "nightclub" + }, { "key": "opening_hours", "description": "Layer 'Cafés and pubs' shows and asks freeform values for key 'opening_hours' (in the MapComplete.osm.be theme 'Personal theme')" @@ -2023,6 +2033,21 @@ "description": "Layer 'Cafés and pubs' shows wheelchair=no with a fixed text, namely 'This place is not reachable with a wheelchair' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", "value": "no" }, + { + "key": "smoking", + "description": "Layer 'Cafés and pubs' shows smoking=yes with a fixed text, namely 'Smoking is allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "yes" + }, + { + "key": "smoking", + "description": "Layer 'Cafés and pubs' shows smoking=no with a fixed text, namely 'Smoking is not allowed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "no" + }, + { + "key": "smoking", + "description": "Layer 'Cafés and pubs' shows smoking=outside with a fixed text, namely 'Smoking is allowed outside.' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "outside" + }, { "key": "service:electricity", "description": "Layer 'Cafés and pubs' shows service:electricity=yes with a fixed text, namely 'There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", diff --git a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 547f8c57b4..5d9ae5862d 100644 --- a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -18,10 +18,12 @@ export class UpdateLegacyLayer extends DesugaringStep { - + private readonly _languages: string[]; constructor(...languages: string[]) { @@ -98,8 +98,8 @@ class ValidateTheme extends DesugaringStep { continue } if (image.match(/[a-z]*/)) { - - if(Svg.All[image + ".svg"] !== undefined){ + + if (Svg.All[image + ".svg"] !== undefined) { // This is a builtin img, e.g. 'checkmark' or 'crosshair' continue;// => } @@ -121,18 +121,18 @@ class ValidateTheme extends DesugaringStep { ` Width = ${width} height = ${height}`; (json.hideFromOverview ? warnings : errors).push(e) } - + const w = parseInt(width); const h = parseInt(height) - if(w < 370 || h < 370){ - const e : string = [ + if (w < 370 || h < 370) { + const e: string = [ `the icon for theme ${json.id} is too small. Please rescale the icon at ${json.icon}`, `Even though an SVG is 'infinitely scaleable', the icon should be dimensioned bigger. One of the build steps of the theme does convert the image to a PNG (to serve as PWA-icon) and having a small dimension will cause blurry images.`, ` Width = ${width} height = ${height}; we recommend a size of at least 500px * 500px and to use a square aspect ratio.`, - ].join("\n"); + ].join("\n"); (json.hideFromOverview ? warnings : errors).push(e) } - + }) } catch (e) { console.error("Could not read " + json.icon + " due to " + e) @@ -186,7 +186,7 @@ export class ValidateThemeAndLayers extends Fuse { constructor(knownImagePaths: Set, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { super("Validates a theme and the contained layers", new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings), - new On("layers", new Each(new ValidateLayer(undefined, false))) + new On("layers", new Each(new ValidateLayer(undefined, false, knownImagePaths))) ); } } @@ -222,22 +222,22 @@ class OverrideShadowingCheck extends DesugaringStep { } -class MiscThemeChecks extends DesugaringStep{ +class MiscThemeChecks extends DesugaringStep { constructor() { - super("Miscelleanous checks on the theme", [],"MiscThemesChecks"); + super("Miscelleanous checks on the theme", [], "MiscThemesChecks"); } - + convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { const warnings = [] const errors = [] - if(json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)){ - errors.push("The theme "+json.id+" has no 'layers' defined ("+context+")") + if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) { + errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")") } - if(json.socialImage === ""){ - warnings.push("Social image for theme "+json.id+" is the emtpy string") + if (json.socialImage === "") { + warnings.push("Social image for theme " + json.id + " is the emtpy string") } return { - result :json, + result: json, warnings, errors }; @@ -258,28 +258,29 @@ export class PrevalidateTheme extends Fuse { export class DetectShadowedMappings extends DesugaringStep { private readonly _calculatedTagNames: string[]; + constructor(layerConfig?: LayerConfigJson) { super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings"); this._calculatedTagNames = DetectShadowedMappings.extractCalculatedTagNames(layerConfig); } /** - * + * * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc:=js()"]}) // => ["_abc"] * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"] */ - private static extractCalculatedTagNames(layerConfig?: LayerConfigJson | {calculatedTags : string []}){ + private static extractCalculatedTagNames(layerConfig?: LayerConfigJson | { calculatedTags: string [] }) { return layerConfig?.calculatedTags?.map(ct => { - if(ct.indexOf(':=') >= 0){ + if (ct.indexOf(':=') >= 0) { return ct.split(':=')[0] } return ct.split("=")[0] }) ?? [] - + } /** - * + * * // should detect a simple shadowed mapping * const tr = {mappings: [ * { @@ -319,20 +320,20 @@ export class DetectShadowedMappings extends DesugaringStep { + const parsedConditions = json.mappings.map((m, i) => { const ctx = `${context}.mappings[${i}]` const ifTags = TagUtils.Tag(m.if, ctx); - if(m.hideInAnswer !== undefined && m.hideInAnswer !== false && m.hideInAnswer !== true){ - let conditionTags = TagUtils.Tag( m.hideInAnswer) + if (m.hideInAnswer !== undefined && m.hideInAnswer !== false && m.hideInAnswer !== true) { + let conditionTags = TagUtils.Tag(m.hideInAnswer) // Merge the condition too! return new And([conditionTags, ifTags]) } return ifTags }) for (let i = 0; i < json.mappings.length; i++) { - if(!parsedConditions[i].isUsableAsAnswer()){ + if (!parsedConditions[i].isUsableAsAnswer()) { // There is no straightforward way to convert this mapping.if into a properties-object, so we simply skip this one // Yes, it might be shadowed, but running this check is to difficult right now continue @@ -372,12 +373,14 @@ export class DetectShadowedMappings extends DesugaringStep { - constructor() { + private knownImagePaths: Set; + constructor(knownImagePaths: Set) { super("Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", [], "DetectMappingsWithImages"); + this.knownImagePaths = knownImagePaths; } /** - * const r = new DetectMappingsWithImages().convert({ + * const r = new DetectMappingsWithImages(new Set()).convert({ * "mappings": [ * { * "if": "bicycle_parking=stands", @@ -407,21 +410,29 @@ export class DetectMappingsWithImages extends DesugaringStep=0 + const ignore = mapping["#"]?.indexOf(ignoreToken) >= 0 const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? []) const ctx = `${context}.mappings[${i}]` if (images.length > 0) { - if(!ignore){ + if (!ignore) { errors.push(`${ctx}: A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": \` instead. The images found are ${images.join(", ")}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`) - }else{ + } else { information.push(`${ctx}: Ignored image ${images.join(", ")} in 'then'-clause of a mapping as this check has been disabled`) + + for (const image of images) { + if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) { + const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}` + errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`) + } + } + } - }else if (ignore){ + } else if (ignore) { warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`) } } - return { + return { errors, warnings, information, @@ -431,10 +442,10 @@ export class DetectMappingsWithImages extends DesugaringStep { - constructor(layerConfig: LayerConfigJson) { + constructor(layerConfig?: LayerConfigJson, knownImagePaths?: Set) { super("Various validation on tagRenderingConfigs", - new DetectShadowedMappings( layerConfig), - new DetectMappingsWithImages() + new DetectShadowedMappings(layerConfig), + new DetectMappingsWithImages(knownImagePaths) ); } } @@ -446,11 +457,13 @@ export class ValidateLayer extends DesugaringStep { */ private readonly _path?: string; private readonly _isBuiltin: boolean; + private knownImagePaths: Set | undefined; - constructor(path: string, isBuiltin: boolean) { + constructor(path: string, isBuiltin: boolean, knownImagePaths: Set) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer"); this._path = path; this._isBuiltin = isBuiltin; + this.knownImagePaths = knownImagePaths } convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[], information?: string[] } { @@ -475,13 +488,13 @@ export class ValidateLayer extends DesugaringStep { { // duplicate ids in tagrenderings check - const duplicates = Utils.Dedup(Utils.Dupiclates( Utils.NoNull((json.tagRenderings ?? []).map(tr => tr["id"])))) - .filter(dupl => dupl !== "questions") - if(duplicates.length > 0){ - errors.push("At "+context+": some tagrenderings have a duplicate id: "+duplicates.join(", ")) + const duplicates = Utils.Dedup(Utils.Dupiclates(Utils.NoNull((json.tagRenderings ?? []).map(tr => tr["id"])))) + .filter(dupl => dupl !== "questions") + if (duplicates.length > 0) { + errors.push("At " + context + ": some tagrenderings have a duplicate id: " + duplicates.join(", ")) } } - + try { { // Some checks for legacy elements @@ -538,10 +551,10 @@ export class ValidateLayer extends DesugaringStep { } } if (json.tagRenderings !== undefined) { - const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json))).convert(json, context) - warnings.push(...(r.warnings??[])) - errors.push(...(r.errors??[])) - information.push(...(r.information??[])) + const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this.knownImagePaths))).convert(json, context) + warnings.push(...(r.warnings ?? [])) + errors.push(...(r.errors ?? [])) + information.push(...(r.information ?? [])) } if (json.presets !== undefined) { diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 048c86dec2..0f871573ca 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -547,7 +547,7 @@ class LengthTextField extends TextFieldDef { constructor() { super( - "length", "A geographical length in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `[\"21\", \"map,photo\"]" + "distance", "A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `[\"21\", \"map,photo\"]" ) } diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index 6a817de9a4..ab3f6e1c8f 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -6,7 +6,7 @@ import CompiledTranslations from "../../assets/generated/CompiledTranslations"; export default class Translations { - static t = CompiledTranslations.t; + static readonly t : typeof CompiledTranslations.t & Readonly = CompiledTranslations.t; private static knownLanguages = new Set(known_languages.languages) constructor() { throw "Translations is static. If you want to intitialize a new translation, use the singular form" diff --git a/assets/contributors.json b/assets/contributors.json index e8f356cc56..7b52791431 100644 --- a/assets/contributors.json +++ b/assets/contributors.json @@ -1,7 +1,7 @@ { "contributors": [ { - "commits": 3912, + "commits": 3871, "contributor": "Pieter Vander Vennet" }, { diff --git a/assets/layers/address/address.json b/assets/layers/address/address.json index ac324ffd7e..2fc720e71a 100644 --- a/assets/layers/address/address.json +++ b/assets/layers/address/address.json @@ -118,7 +118,8 @@ "id": "Bangunan ini tidak memiliki nomor rumah", "es": "Esta edificación no tiene número", "zh_Hans": "这个建筑物没有门牌号", - "da": "Denne bygning har intet husnummer" + "da": "Denne bygning har intet husnummer", + "zh_Hant": "這棟建築沒有門牌" } } ] diff --git a/assets/layers/barrier/barrier.json b/assets/layers/barrier/barrier.json index 508f0e29f7..8e99da33f9 100644 --- a/assets/layers/barrier/barrier.json +++ b/assets/layers/barrier/barrier.json @@ -373,7 +373,7 @@ }, "freeform": { "key": "maxwidth:physical", - "type": "length", + "type": "distance", "helperArgs": [ "20", "map" @@ -406,7 +406,7 @@ }, "freeform": { "key": "width:separation", - "type": "length", + "type": "distance", "helperArgs": [ "21", "map" @@ -440,7 +440,7 @@ }, "freeform": { "key": "width:opening", - "type": "length", + "type": "distance", "helperArgs": [ "21", "map" @@ -474,7 +474,7 @@ }, "freeform": { "key": "overlap", - "type": "length", + "type": "distance", "helperArgs": [ "21", "map" diff --git a/assets/layers/cafe_pub/cafe_pub.json b/assets/layers/cafe_pub/cafe_pub.json index 1215b45367..2c77c8d2f1 100644 --- a/assets/layers/cafe_pub/cafe_pub.json +++ b/assets/layers/cafe_pub/cafe_pub.json @@ -16,7 +16,8 @@ "amenity=bar", "amenity=pub", "amenity=cafe", - "amenity=biergarten" + "amenity=biergarten", + "amenity=nightclub" ] } }, @@ -94,6 +95,22 @@ "preciseInput": { "preferredBackground": "map" } + }, + { + "tags": [ + "amenity=nightclub" + ], + "title": { + "en": "a nightclub or disco", + "nl": "een nachtclub of disco" + }, + "description": { + "en": "A nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks", + "nl": "Een nachtclub met dansvloer, DJ met bijhorende lichteffecten en bar waar men (alcoholische) dranken kan nuttigen" + }, + "preciseInput": { + "preferredBackground": "map" + } } ], "title": { @@ -209,6 +226,13 @@ "es": "Un espacio abierto donde se sirve cerveza, típico de Alemania" }, "hideInAnswer": "_country!=de" + }, + { + "if": "amenity=nightclub", + "then": { + "en": "This is a nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks", + "nl": "Dit is een nachtclub met dansvloer, DJ met bijhorende lichteffecten en bar waar men (alcoholische) dranken kan nuttigen" + } } ], "id": "Classification" @@ -219,6 +243,7 @@ "phone", "payment-options", "wheelchair-access", + "smoking", "service:electricity", "dog-access", "reviews" @@ -272,6 +297,10 @@ { "if": "amenity=cafe", "then": "circle:white;./assets/layers/cafe_pub/cafe.svg" + }, + { + "if": "amenity=nightclub", + "then": "circle:white;./assets/layers/cafe_pub/nightclub.svg" } ] }, diff --git a/assets/layers/cafe_pub/license_info.json b/assets/layers/cafe_pub/license_info.json index dd4798197e..9ba0c84577 100644 --- a/assets/layers/cafe_pub/license_info.json +++ b/assets/layers/cafe_pub/license_info.json @@ -9,6 +9,16 @@ "https://wiki.openstreetmap.org/wiki/File:Cafe-16.svg" ] }, + { + "path": "nightclub.svg", + "license": "CC0", + "authors": [ + "Osm Carto" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Nightclub-16.svg" + ] + }, { "path": "pub.svg", "license": "CC0", diff --git a/assets/layers/cafe_pub/nightclub.svg b/assets/layers/cafe_pub/nightclub.svg new file mode 100644 index 0000000000..5e34e56312 --- /dev/null +++ b/assets/layers/cafe_pub/nightclub.svg @@ -0,0 +1,27 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json index 2874b8a36c..8b767a15dd 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -836,7 +836,7 @@ }, "freeform": { "key": "width:carriageway", - "type": "length", + "type": "distance", "helperArgs": [ "20", "map" @@ -1260,7 +1260,7 @@ }, "freeform": { "key": "cycleway:buffer", - "type": "length", + "type": "distance", "helperArgs": [ "20", "map" diff --git a/assets/layers/entrance/entrance.json b/assets/layers/entrance/entrance.json index 6cb1883c31..d1aaf587e7 100644 --- a/assets/layers/entrance/entrance.json +++ b/assets/layers/entrance/entrance.json @@ -338,7 +338,7 @@ }, "freeform": { "key": "width", - "type": "length" + "type": "distance" } } ], diff --git a/assets/license_info.json b/assets/license_info.json index 3e657b34d7..c4510d13af 100644 --- a/assets/license_info.json +++ b/assets/license_info.json @@ -17,6 +17,22 @@ ], "sources": [] }, + { + "path": "SocialImageBanner.png", + "license": "CC0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, + { + "path": "SocialImageBanner.svg", + "license": "CC0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, { "path": "SocialImageSmall.png", "license": "CC-BY-SA 4.0", diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index f992a5304d..133544ecf8 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -141,16 +141,6 @@ "https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg" ] }, - { - "path": "cash.svg", - "license": "CC-BY 3.0", - "authors": [ - "Online Web Fonts" - ], - "sources": [ - "https://www.onlinewebfonts.com/icon/464494" - ] - }, { "path": "checkbox-empty.svg", "license": "CC0", @@ -859,16 +849,6 @@ ], "sources": [] }, - { - "path": "nfc_card.svg", - "license": "CC0", - "authors": [ - "Stijn Wens" - ], - "sources": [ - "https://wens.be/free-antwerpenize-bicycle-font" - ] - }, { "path": "no_checkmark.svg", "license": "CC0; trivial", @@ -951,16 +931,6 @@ "https://github.com/twitter/twemoji" ] }, - { - "path": "payment_card.svg", - "license": "CC0", - "authors": [ - " \tMaxi Koichi (maxixam)" - ], - "sources": [ - "https://commons.wikimedia.org/wiki/File:Credit_Card_-_The_Noun_Project.svg" - ] - }, { "path": "pencil.svg", "license": "MIT", @@ -983,26 +953,6 @@ " https://commons.wikimedia.org/wiki/File:Octicons-pencil.svg" ] }, - { - "path": "phone.svg", - "license": "CC-BY 3.0", - "authors": [ - "@ tyskrat" - ], - "sources": [ - "https://www.onlinewebfonts.com/icon/1059" - ] - }, - { - "path": "phone.svg", - "license": "CC-BY 3.0", - "authors": [ - "@ tyskrat" - ], - "sources": [ - "https://www.onlinewebfonts.com/icon/1059" - ] - }, { "path": "pin.svg", "license": "CC0; trivial", @@ -1151,18 +1101,6 @@ "https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/AUTHORS.txt" ] }, - { - "path": "send_email.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, - { - "path": "send_email.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, { "path": "share.svg", "license": "CC0; trivial", @@ -1175,16 +1113,6 @@ "authors": [], "sources": [] }, - { - "path": "smartphone.svg", - "license": "CC-BY 3.0", - "authors": [ - "To Uyen" - ], - "sources": [ - "https://commons.wikimedia.org/wiki/File:Smartphone_icon_-_Noun_Project_283536.svg" - ] - }, { "path": "speech_bubble.svg", "license": "CC-BY 4.0", diff --git a/assets/svg/cash.svg b/assets/tagRenderings/cash.svg similarity index 100% rename from assets/svg/cash.svg rename to assets/tagRenderings/cash.svg diff --git a/assets/tagRenderings/icons.json b/assets/tagRenderings/icons.json index 3abd316843..6a0d786d78 100644 --- a/assets/tagRenderings/icons.json +++ b/assets/tagRenderings/icons.json @@ -5,6 +5,7 @@ "phonelink", "emaillink", "wikipedialink", + "smokingicon", "osmlink", "sharelink" ], @@ -20,6 +21,7 @@ }, "mappings": [ { + "#": "ignore-image-in-then", "if": "wikipedia=", "then": "WD" } @@ -59,13 +61,27 @@ ] }, "phonelink": { - "render": "phone", + "render": "phone", "condition": "phone~*" }, "emaillink": { - "render": "email", + "render": "email", "condition": "email~*" }, + "smokingicon": { + "mappings": [ + { + "#": "ignore-image-in-then", + "if": "smoking=no", + "then": "no-smoking" + }, + { + "#": "ignore-image-in-then", + "if": "smoking=yes", + "then": "smoking-allowed" + } + ] + }, "osmlink": { "render": "on osm", "mappings": [ @@ -74,6 +90,7 @@ "then": "" }, { + "#": "ignore-image-in-then", "if": "_backend~*", "then": "" } diff --git a/assets/tagRenderings/license_info.json b/assets/tagRenderings/license_info.json new file mode 100644 index 0000000000..c65bd6f938 --- /dev/null +++ b/assets/tagRenderings/license_info.json @@ -0,0 +1,96 @@ +[ + { + "path": "cash.svg", + "license": "CC-BY 3.0", + "authors": [ + "Online Web Fonts" + ], + "sources": [ + "https://www.onlinewebfonts.com/icon/464494" + ] + }, + { + "path": "nfc_card.svg", + "license": "CC0", + "authors": [ + "Stijn Wens" + ], + "sources": [ + "https://wens.be/free-antwerpenize-bicycle-font" + ] + }, + { + "path": "no_smoking.svg", + "license": "CC0", + "authors": [ + "AIGA" + ], + "sources": [ + "https://upload.wikimedia.org/wikipedia/commons/6/6b/No_Smoking.svg", + "https://www.aiga.org/content.cfm/symbol-signs" + ] + }, + { + "path": "payment_card.svg", + "license": "CC0", + "authors": [ + " \tMaxi Koichi (maxixam)" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Credit_Card_-_The_Noun_Project.svg" + ] + }, + { + "path": "phone.svg", + "license": "CC-BY 3.0", + "authors": [ + "@ tyskrat" + ], + "sources": [ + "https://www.onlinewebfonts.com/icon/1059" + ] + }, + { + "path": "phone.svg", + "license": "CC-BY 3.0", + "authors": [ + "@ tyskrat" + ], + "sources": [ + "https://www.onlinewebfonts.com/icon/1059" + ] + }, + { + "path": "send_email.svg", + "license": "CC0; trivial", + "authors": [], + "sources": [] + }, + { + "path": "send_email.svg", + "license": "CC0; trivial", + "authors": [], + "sources": [] + }, + { + "path": "smartphone.svg", + "license": "CC-BY 3.0", + "authors": [ + "To Uyen" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Smartphone_icon_-_Noun_Project_283536.svg" + ] + }, + { + "path": "smoking.svg", + "license": "CC0", + "authors": [ + "Wiki-User03", + "ZooFari" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Smoking_pictogram_(black).svg" + ] + } +] \ No newline at end of file diff --git a/assets/svg/nfc_card.svg b/assets/tagRenderings/nfc_card.svg similarity index 100% rename from assets/svg/nfc_card.svg rename to assets/tagRenderings/nfc_card.svg diff --git a/assets/tagRenderings/no_smoking.svg b/assets/tagRenderings/no_smoking.svg new file mode 100644 index 0000000000..b0a1161087 --- /dev/null +++ b/assets/tagRenderings/no_smoking.svg @@ -0,0 +1,160 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/svg/payment_card.svg b/assets/tagRenderings/payment_card.svg similarity index 100% rename from assets/svg/payment_card.svg rename to assets/tagRenderings/payment_card.svg diff --git a/assets/svg/phone.svg b/assets/tagRenderings/phone.svg similarity index 100% rename from assets/svg/phone.svg rename to assets/tagRenderings/phone.svg diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index d1dd19d588..862a64043a 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -745,7 +745,7 @@ { "if": "payment:cash=yes", "ifnot": "payment:cash=no", - "icon": "./assets/svg/cash.svg", + "icon": "./assets/tagRenderings/cash.svg", "then": { "en": "Cash is accepted here", "nl": "Cash geld wordt hier aanvaard", @@ -773,7 +773,7 @@ { "if": "payment:cards=yes", "ifnot": "payment:cards=no", - "icon": "./assets/svg/payment_card.svg", + "icon": "./assets/tagRenderings/payment_card.svg", "then": { "en": "Payment cards are accepted here", "nl": "Betalen met bankkaarten kan hier", @@ -807,7 +807,7 @@ { "if": "payment:app=yes", "ifnot": "payment:app=no", - "icon": "./assets/svg/smartphone.svg", + "icon": "./assets/tagRenderings/smartphone.svg", "then": { "en": "Payment is done using a dedicated app", "nl": "Betalen via een app van het netwerk", @@ -828,7 +828,7 @@ { "if": "payment:membership_card=yes", "ifnot": "payment:membership_card=no", - "icon": "./assets/svg/nfc_card.svg", + "icon": "./assets/tagRenderings/nfc_card.svg", "then": { "en": "Payment is done using a membership card", "nl": "Betalen via een lidkaart van het netwerk", @@ -1031,5 +1031,40 @@ } } ] + }, + "smoking": { + "question": { + "en": "Is smoking allowed at {title()}?" + }, + "#condition": "Based on https://en.wikipedia.org/wiki/List_of_smoking_bans", + "condition": "_country!~al|be", + "mappings": [ + { + "if": "smoking=yes", + "icon": { + "path": "./assets/tagRenderings/smoking.svg", + "size": "small" + }, + "then": { + "en": "Smoking is allowed" + } + }, + { + "if": "smoking=no", + "icon": { + "path": "./assets/tagRenderings/no_smoking.svg", + "size": "small" + }, + "then": { + "en": "Smoking is not allowed" + } + }, + { + "if": "smoking=outside", + "then": { + "en": "Smoking is allowed outside." + } + } + ] } } \ No newline at end of file diff --git a/assets/svg/send_email.svg b/assets/tagRenderings/send_email.svg similarity index 100% rename from assets/svg/send_email.svg rename to assets/tagRenderings/send_email.svg diff --git a/assets/svg/smartphone.svg b/assets/tagRenderings/smartphone.svg similarity index 100% rename from assets/svg/smartphone.svg rename to assets/tagRenderings/smartphone.svg diff --git a/assets/tagRenderings/smoking.svg b/assets/tagRenderings/smoking.svg new file mode 100644 index 0000000000..f9d91d82b8 --- /dev/null +++ b/assets/tagRenderings/smoking.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 78d8fd8800..cfa960cdf4 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -1,13 +1,24 @@ { "id": "mapcomplete-changes", "title": { - "en": "Changes made with MapComplete" + "en": "Changes made with MapComplete", + "de": "Änderungen mit MapComplete", + "es": "Cambios hechos con MapComplete", + "nb_NO": "Endringer utført med MapComplete", + "nl": "Wijzigingen gemaakt met MapComplete" }, "shortDescription": { - "en": "Shows changes made by MapComplete" + "en": "Shows changes made by MapComplete", + "de": "Zeigt Änderungen von MapComplete", + "es": "Muestra los cambios hechos por MapComplete", + "nb_NO": "Vis endringer utført med MapComplete", + "nl": "Toont wijzigingen gemaakt met MapComplete" }, "description": { - "en": "This maps shows all the changes made with MapComplete" + "en": "This maps shows all the changes made with MapComplete", + "de": "Diese Karte zeigt alle Änderungen die mit MapComplete gemacht wurden", + "es": "Este mapa muestra todos los cambios hechos con MapComplete", + "nl": "Deze kaart toont alle wijzigingen die met MapComplete werden gemaakt" }, "maintainer": "", "icon": "./assets/svg/logo.svg", @@ -22,7 +33,10 @@ { "id": "mapcomplete-changes", "name": { - "en": "Changeset centers" + "en": "Changeset centers", + "de": "Schwerpunkte von Änderungssätzen", + "es": "Centros de conjuntos de cambios", + "nb_NO": "Endringssettsenter" }, "minzoom": 0, "source": { @@ -36,35 +50,55 @@ ], "title": { "render": { - "en": "Changeset for {theme}" + "en": "Changeset for {theme}", + "de": "Änderungen für {theme}", + "es": "Conjunto de cambios para {theme}", + "nb_NO": "Endringssett for {theme}", + "nl": "Wijzigingset voor {theme}" } }, "description": { - "en": "Shows all MapComplete changes" + "en": "Shows all MapComplete changes", + "de": "Zeigt alle MapComplete Änderungen", + "es": "Muestra todos los cambios de MapComplete", + "nl": "Toont alle wijzigingen met MapComplete" }, "tagRenderings": [ { "id": "render_id", "render": { - "en": "Changeset {id}" + "en": "Changeset {id}", + "de": "Änderung {id}", + "es": "Conjunto de cambios {id}", + "nb_NO": "Endringssett {id}", + "nl": "Wijzigingset {id}" } }, { "id": "contributor", "render": { - "en": "Change made by {_last_edit:contributor}" + "en": "Change made by {_last_edit:contributor}", + "de": "Änderung wurde von {_last_edit:contributor} gemacht", + "es": "Cambio hecho por {_last_edit:contributor}", + "nl": "Wijziging gemaakt door {_last_edit:contributor}" } }, { "id": "theme", "render": { - "en": "Change with theme {theme}" + "en": "Change with theme {theme}", + "de": "Änderung mit Thema {theme}", + "es": "Cambio con tema {theme}", + "nl": "Wijziging met thema {theme}" }, "mappings": [ { "if": "theme~http.*", "then": { - "en": "Change with unofficial theme {theme}" + "en": "Change with unofficial theme {theme}", + "de": "Änderung mit inoffiziellem Thema {theme}", + "es": "Cambio con tema no oficial {theme}", + "nl": "Wijziging met officieus thema {theme}" } } ] @@ -336,7 +370,11 @@ } ], "question": { - "en": "Themename contains {search}" + "en": "Themename contains {search}", + "de": "Themenname enthält {search}", + "es": "Nombre del tema contiene {search}", + "nb_NO": "Temanavn inneholder {search}", + "nl": "Themanaam bevat {search}" } } ] @@ -352,7 +390,10 @@ } ], "question": { - "en": "Made by contributor {search}" + "en": "Made by contributor {search}", + "de": "Erstellt von {search}", + "es": "Hecho por contributor/a {search}", + "nl": "Gemaakt door bijdrager {search}" } } ] @@ -368,7 +409,10 @@ } ], "question": { - "en": "Not made by contributor {search}" + "en": "Not made by contributor {search}", + "de": "Nicht erstellt von {search}", + "es": "No hecho por contributor/a {search}", + "nl": "Niet gemaakt door bijdrager {search}" } } ] @@ -383,7 +427,10 @@ { "id": "link_to_more", "render": { - "en": "More statistics can be found here" + "en": "More statistics can be found here", + "de": "Weitere Statistiken finden Sie hier", + "es": "Se pueden encontrar más estadísticas aquí", + "nl": "Meer statistieken kunnen hier gevonden worden" } }, { diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json index 48114ad57d..918d09bf51 100644 --- a/assets/themes/sidewalks/sidewalks.json +++ b/assets/themes/sidewalks/sidewalks.json @@ -126,7 +126,7 @@ "condition": "sidewalk:left|right=yes", "freeform": { "key": "sidewalk:left|right:width", - "type": "length", + "type": "distance", "helperArgs": [ "21", "map" diff --git a/assets/translators.json b/assets/translators.json index 05def3dbe0..b3f079df4e 100644 --- a/assets/translators.json +++ b/assets/translators.json @@ -29,7 +29,7 @@ "contributor": "Iago" }, { - "commits": 22, + "commits": 23, "contributor": "Supaplex" }, { diff --git a/langs/ca.json b/langs/ca.json index 4d81684387..7c2b0f1056 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -548,9 +548,6 @@ "date": { "description": "Una data, començant per l'any" }, - "decimal": { - "description": "Un número" - }, "direction": { "description": "Una orientació" }, diff --git a/langs/da.json b/langs/da.json index 4119962395..0f2e9ce1ec 100644 --- a/langs/da.json +++ b/langs/da.json @@ -634,9 +634,6 @@ "date": { "description": "En dato, der starter med årstallet" }, - "decimal": { - "description": "Et tal" - }, "direction": { "description": "En retning" }, diff --git a/langs/de.json b/langs/de.json index eed4dc34c7..8e07c60f85 100644 --- a/langs/de.json +++ b/langs/de.json @@ -456,8 +456,10 @@ "sendReason": "Ich habe Ihnen diese Nachricht geschickt, weil {sender} mich gebeten hat, diese mit {cmd} zu senden" }, "documentation": { + "argid": "Die ID einer {list} oder {list_end}, für welche die Dokumente benötigt werden. Alternativ können Sie auch eine von {coded_list} schreiben, um die verfügbaren IDs zu sehen.", "didYouMean": " Vielleicht meinten Sie einen von: ", "docs": "Ruft die Dokumentation zu einer MapComplete-Ebene, einem Thema oder einem URL-Parameter ab", + "noIdIntro": "Geben Sie eine ID an, um weitere Informationen über ein MapComplete-Programmierelement zu erhalten. Bekannte Typen sind {list}", "notFound": "Kein {singular} mit Namen {id} gefunden.", "urlParam": "URL-Parameter {id}" }, @@ -753,9 +755,6 @@ "date": { "description": "Ein Datum, beginnend mit der Jahreszahl" }, - "decimal": { - "description": "Eine Zahl" - }, "direction": { "description": "Eine Himmelsrichtung" }, diff --git a/langs/en.json b/langs/en.json index 6cd0494e12..0eeafcb6ab 100644 --- a/langs/en.json +++ b/langs/en.json @@ -828,12 +828,12 @@ "date": { "description": "A date, starting with the year" }, - "decimal": { - "description": "A number" - }, "direction": { "description": "An orientation" }, + "distance": { + "description": "A distance in meter" + }, "email": { "description": "email-adres", "feedback": "This is not a valid email address", @@ -846,9 +846,6 @@ "int": { "description": "a whole number" }, - "length": { - "description": "a length measurement in meter" - }, "nat": { "description": "a positive, whole number or zero", "mustBePositive": "This number should be positive", diff --git a/langs/es.json b/langs/es.json index 0c21fd6af6..66e4fe6a80 100644 --- a/langs/es.json +++ b/langs/es.json @@ -516,9 +516,6 @@ "deactivate": "Deshabilitar los botones de traducción" }, "validation": { - "decimal": { - "description": "Un número" - }, "direction": { "description": "Una orientación" }, diff --git a/langs/id.json b/langs/id.json index fac405153e..c8011e04b5 100644 --- a/langs/id.json +++ b/langs/id.json @@ -144,9 +144,6 @@ "date": { "description": "Tanggal, dimulai dari tahun" }, - "decimal": { - "description": "Nomor" - }, "direction": { "description": "Orientasi" }, diff --git a/langs/layers/en.json b/langs/layers/en.json index e052bccbc0..c582615c4d 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -1274,6 +1274,10 @@ "2": { "description": "A cafe to drink tea, coffee or an alcoholical bevarage in a quiet environment", "title": "a cafe" + }, + "3": { + "description": "A nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks", + "title": "a nightclub or disco" } }, "tagRenderings": { @@ -1293,6 +1297,9 @@ }, "4": { "then": "An open space where beer is served, typically seen in Germany" + }, + "5": { + "then": "This is a nightclub or disco with a focus on dancing, music by a DJ with accompanying light show and a bar to get (alcoholic) drinks" } }, "question": "What kind of cafe is this?" diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 305f85991b..a751b75789 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -1280,6 +1280,10 @@ "2": { "description": "Dit is een cafe - een plaats waar men rustig kan zitten om een thee, koffie of alcoholische drank te nuttigen.", "title": "een café" + }, + "3": { + "description": "Een nachtclub met dansvloer, DJ met bijhorende lichteffecten en bar waar men (alcoholische) dranken kan nuttigen", + "title": "een nachtclub of disco" } }, "tagRenderings": { @@ -1299,6 +1303,9 @@ }, "4": { "then": "Een open ruimte waar bier geserveerd wordt. Typisch in Duitsland" + }, + "5": { + "then": "Dit is een nachtclub met dansvloer, DJ met bijhorende lichteffecten en bar waar men (alcoholische) dranken kan nuttigen" } }, "question": "Welk soort café is dit?" diff --git a/langs/layers/zh_Hant.json b/langs/layers/zh_Hant.json index 6a0ea50c11..f9a2f96070 100644 --- a/langs/layers/zh_Hant.json +++ b/langs/layers/zh_Hant.json @@ -5,6 +5,13 @@ "tagRenderings": { "fixme": { "question": "這裡需要修什麼?請直接解釋" + }, + "housenumber": { + "mappings": { + "0": { + "then": "這棟建築沒有門牌" + } + } } } }, diff --git a/langs/nb_NO.json b/langs/nb_NO.json index a521d3409f..c08fb9ce17 100644 --- a/langs/nb_NO.json +++ b/langs/nb_NO.json @@ -480,9 +480,6 @@ "date": { "description": "En dato, som starter med året" }, - "decimal": { - "description": "Et tall" - }, "direction": { "description": "En retning" }, diff --git a/langs/nl.json b/langs/nl.json index 874b900cda..16d2729f52 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -753,12 +753,12 @@ "date": { "description": "Een datum (beginnend met het jaar)" }, - "decimal": { - "description": "Een getal" - }, "direction": { "description": "Een orientatie" }, + "distance": { + "description": "Een afstand in meter" + }, "email": { "description": "email-adres", "feedback": "Dit is geen geldig email-adres", diff --git a/langs/shared-questions/en.json b/langs/shared-questions/en.json index fce4c92175..944c5d5cb3 100644 --- a/langs/shared-questions/en.json +++ b/langs/shared-questions/en.json @@ -91,6 +91,20 @@ }, "question": "Does this amenity have electrical outlets, available to customers when they are inside?" }, + "smoking": { + "mappings": { + "0": { + "then": "Smoking is allowed" + }, + "1": { + "then": "Smoking is not allowed" + }, + "2": { + "then": "Smoking is allowed outside." + } + }, + "question": "Is smoking allowed at {title()}?" + }, "website": { "question": "What is the website of {title()}?" }, diff --git a/package.json b/package.json index c4057fbc32..b9dc215605 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "main": "index.js", "scripts": { "start": "npm run generate:layeroverview && npm run strt", - "strt": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/layers/*/*.css assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.ttf assets/themes/*/*/*.ttf assets/themes/*/*.otf assets/themes/*/*/*.otf assets/themes/*/*.css assets/themes/*/*.jpg assets/themes/*/*.woff assets/themes/*/*.png vendor/* vendor/*/*", + "strt": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/layers/*/*.css assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.ttf assets/themes/*/*/*.ttf assets/themes/*/*.otf assets/themes/*/*/*.otf assets/themes/*/*.css assets/themes/*/*.jpg assets/themes/*/*.woff assets/themes/*/*.png vendor/* vendor/*/* assets/tagRenderings/*.svg", "strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html", "watch:css": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 70c70739bf..a87b3ade51 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -4,7 +4,12 @@ import * as licenses from "../assets/generated/license_info.json" import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; import Constants from "../Models/Constants"; -import {PrevalidateTheme, ValidateLayer, ValidateThemeAndLayers} from "../Models/ThemeConfig/Conversion/Validation"; +import { + PrevalidateTheme, + ValidateLayer, + ValidateTagRenderings, + ValidateThemeAndLayers +} from "../Models/ThemeConfig/Conversion/Validation"; import {Translation} from "../UI/i18n/Translation"; import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; import * as questions from "../assets/tagRenderings/questions.json"; @@ -14,24 +19,25 @@ 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 {And} from "../Logic/Tags/And"; // 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 class LayerOverviewUtils { - writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean, mustHaveLanguage: boolean, layers: (LayerConfigJson | string | {builtin})[] }[]) { + writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean, mustHaveLanguage: boolean, layers: (LayerConfigJson | string | { builtin })[] }[]) { const perId = new Map(); for (const theme of themes) { - - const keywords : {}[] = [] + + const keywords: {}[] = [] for (const layer of (theme.layers ?? [])) { - const l = layer; + const l = layer; keywords.push({"*": l.id}) keywords.push(l.title) keywords.push(l.description) } - + const data = { id: theme.id, title: theme.title, @@ -77,16 +83,19 @@ class LayerOverviewUtils { writeFileSync(`./assets/generated/layers/${layer.id}.json`, JSON.stringify(layer, null, " "), "UTF8"); } - getSharedTagRenderings(): Map { + getSharedTagRenderings(knownImagePaths: Set): Map { const dict = new Map(); - + + const validator = new ValidateTagRenderings(undefined, knownImagePaths); for (const key in questions["default"]) { if (key === "id") { continue } questions[key].id = key; questions[key]["source"] = "shared-questions" - dict.set(key, questions[key]) + const config = questions[key] + validator.convertStrict(config, "generate-layer-overview:tagRenderings/questions.json:"+key) + dict.set(key, config) } for (const key in icons["default"]) { if (key === "id") { @@ -96,7 +105,9 @@ class LayerOverviewUtils { continue } icons[key].id = key; - dict.set(key, icons[key]) + const config = icons[key] + validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:"+key) + dict.set(key,config) } dict.forEach((value, key) => { @@ -114,9 +125,9 @@ class LayerOverviewUtils { .filter(path => path.endsWith(".svg")) .filter(path => !path.startsWith("./assets/generated")) let errCount = 0; - const exempt = ["assets/SocialImageTemplate.svg","assets/SocialImageTemplateWide.svg","assets/SocialImageBanner.svg", "assets/svg/osm-logo.svg"]; + const exempt = ["assets/SocialImageTemplate.svg", "assets/SocialImageTemplateWide.svg", "assets/SocialImageBanner.svg", "assets/svg/osm-logo.svg"]; for (const path of allSvgs) { - if(exempt.some(p => "./"+p === path)) { + if (exempt.some(p => "./" + p === path)) { continue } @@ -128,7 +139,7 @@ class LayerOverviewUtils { throw "A core SVG is actually a PNG. Don't do this!" } } - if(contents.indexOf("0){ + if (contents.indexOf(" 0) { console.warn("The SVG at " + path + " contains a `text`-tag. This is highly discouraged. Every machine viewing your theme has their own font libary, and the font you choose might not be present, resulting in a different font being rendered. Solution: open your .svg in inkscape (or another program), select the text and convert it to a path") errCount++; @@ -183,7 +194,7 @@ class LayerOverviewUtils { // At the same time, an index of available layers is built. console.log(" ---------- VALIDATING BUILTIN LAYERS ---------") - const sharedTagRenderings = this.getSharedTagRenderings(); + const sharedTagRenderings = this.getSharedTagRenderings(knownImagePaths); const layerFiles = ScriptUtils.getLayerFiles(); const sharedLayers = new Map() const state: DesugaringContext = { @@ -194,7 +205,12 @@ class LayerOverviewUtils { for (const sharedLayerJson of layerFiles) { const context = "While building builtin layer " + sharedLayerJson.path const fixed = prepLayer.convertStrict(sharedLayerJson.parsed, context) - const validator = new ValidateLayer(sharedLayerJson.path, true); + + if(fixed.source.osmTags["and"] === undefined){ + fixed.source.osmTags = {"and": [fixed.source.osmTags]} + } + + const validator = new ValidateLayer(sharedLayerJson.path, true, knownImagePaths); validator.convertStrict(fixed, context) if (sharedLayers.has(fixed.id)) { @@ -208,25 +224,25 @@ class LayerOverviewUtils { } return sharedLayers; } - - private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set{ + + private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set { const publicLayers = [].concat(...themefiles .filter(th => !th.hideFromOverview) .map(th => th.layers)) const publicLayerIds = new Set() for (const publicLayer of publicLayers) { - if(typeof publicLayer === "string"){ + if (typeof publicLayer === "string") { publicLayerIds.add(publicLayer) continue } - if(publicLayer["builtin"] !== undefined){ + if (publicLayer["builtin"] !== undefined) { const bi = publicLayer["builtin"] - if(typeof bi === "string"){ + if (typeof bi === "string") { publicLayerIds.add(bi) continue } - bi.forEach(id=>publicLayerIds.add(id)) + bi.forEach(id => publicLayerIds.add(id)) continue } publicLayerIds.add(publicLayer.id) @@ -243,7 +259,7 @@ class LayerOverviewUtils { const convertState: DesugaringContext = { sharedLayers, - tagRenderings: this.getSharedTagRenderings(), + tagRenderings: this.getSharedTagRenderings(knownImagePaths), publicLayers } for (const themeInfo of themeFiles) { @@ -251,20 +267,20 @@ class LayerOverviewUtils { const themePath = themeInfo.path new PrevalidateTheme().convertStrict(themeFile, themePath) - try{ - + try { + themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath) - - if(knownImagePaths === undefined){ + + if (knownImagePaths === undefined) { throw "Could not load known images/licenses" } new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings) .convertStrict(themeFile, themePath) - + this.writeTheme(themeFile) fixed.set(themeFile.id, themeFile) - }catch(e){ - console.error("ERROR: could not prepare theme "+themePath+" due to "+e) + } catch (e) { + console.error("ERROR: could not prepare theme " + themePath + " due to " + e) throw e; } } diff --git a/scripts/generateLicenseInfo.ts b/scripts/generateLicenseInfo.ts index bb8e77cea8..ea129440b6 100644 --- a/scripts/generateLicenseInfo.ts +++ b/scripts/generateLicenseInfo.ts @@ -2,6 +2,7 @@ import {existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync} from "fs import SmallLicense from "../Models/smallLicense"; import ScriptUtils from "./ScriptUtils"; +const prompt = require('prompt-sync')(); function validateLicenseInfo(l : SmallLicense){ l.sources.map(s => new URL(s)) @@ -55,7 +56,6 @@ function missingLicenseInfos(licenseInfos: SmallLicense[], allIcons: string[]) { return missing; } -const prompt = require('prompt-sync')(); const knownLicenses = new Map() knownLicenses.set("me", { @@ -64,45 +64,36 @@ knownLicenses.set("me", { license: "CC0", sources: [] }) - knownLicenses.set("streetcomplete", { authors: ["Tobias Zwick (westnordost)"], path: undefined, license: "CC0", sources: ["https://github.com/streetcomplete/StreetComplete/tree/master/res/graphics", "https://f-droid.org/packages/de.westnordost.streetcomplete/"] }) - - knownLicenses.set("t", { authors: [], path: undefined, license: "CC0; trivial", sources: [] }) - knownLicenses.set("na", { authors: [], path: undefined, license: "CC0", sources: [] }) - knownLicenses.set("tv", { authors: ["Toerisme Vlaanderen"], path: undefined, license: "CC0", sources: ["https://toerismevlaanderen.be/pinjepunt","https://mapcomplete.osm.be/toerisme_vlaanderenn"] }) - knownLicenses.set("tvf", { authors: ["Jo De Baerdemaeker "], path: undefined, license: "All rights reserved", sources: ["https://www.studiotype.be/fonts/flandersart"] }) - - - knownLicenses.set("twemoji", { authors: ["Twemoji"], path: undefined, @@ -154,7 +145,7 @@ function createLicenseInfoFor(path): void { function cleanLicenseInfo(allPaths: string[], allLicenseInfos: SmallLicense[]) { // Read the license info file from the generated assets, creates a compiled license info in every directory // Note: this removes all the old license infos - for (const licensePath of licensePaths) { + for (const licensePath of allPaths) { unlinkSync(licensePath) } @@ -219,10 +210,13 @@ function queryMissingLicenses(missingLicenses: string[]) { * Creates the humongous license_info in the generated assets, containing all licenses with a path relative to the root * @param licensePaths */ -function createFullLicenseOverview(licensePaths) { +function createFullLicenseOverview(licensePaths: string[]) { const allLicenses: SmallLicense[] = [] for (const licensePath of licensePaths) { + if(!existsSync(licensePath)){ + continue + } const licenses = JSON.parse(readFileSync(licensePath, "UTF-8")) for (const license of licenses) { validateLicenseInfo(license) @@ -235,52 +229,54 @@ function createFullLicenseOverview(licensePaths) { writeFileSync("./assets/generated/license_info.json", JSON.stringify(allLicenses, null, " ")) } -console.log("Checking and compiling license info") - -if (!existsSync("./assets/generated")) { - mkdirSync("./assets/generated") -} - - -let contents = ScriptUtils.readDirRecSync("./assets") - .filter(entry => entry.indexOf("./assets/generated") != 0) -let licensePaths = contents.filter(entry => entry.indexOf("license_info.json") >= 0) -let licenseInfos = generateLicenseInfos(licensePaths); - - - -const artwork = contents.filter(pth => pth.match(/(.svg|.png|.jpg|.ttf|.otf|.woff)$/i) != null) -const missingLicenses = missingLicenseInfos(licenseInfos, artwork) -if (process.argv.indexOf("--prompt") >= 0 || process.argv.indexOf("--query") >= 0) { - queryMissingLicenses(missingLicenses) - contents = ScriptUtils.readDirRecSync("./assets") +function main(args: string[]){ + + console.log("Checking and compiling license info") + + if (!existsSync("./assets/generated")) { + mkdirSync("./assets/generated") + } + + + let contents = ScriptUtils.readDirRecSync("./assets") .filter(entry => entry.indexOf("./assets/generated") != 0) - licensePaths = contents.filter(entry => entry.indexOf("license_info.json") >= 0) - licenseInfos = generateLicenseInfos(licensePaths); -} - -const invalidLicenses = licenseInfos.filter(l => (l.license ?? "") === "").map(l => `License for artwork ${l.path} is empty string or undefined`) -for (const licenseInfo of licenseInfos) { - for (const source of licenseInfo.sources) { - if (source == "") { - invalidLicenses.push("Invalid license: empty string in " + JSON.stringify(licenseInfo)) - } - try { - new URL(source); - } catch { - invalidLicenses.push("Not a valid URL: " + source) + let licensePaths = contents.filter(entry => entry.indexOf("license_info.json") >= 0) + let licenseInfos = generateLicenseInfos(licensePaths); + + + + const artwork = contents.filter(pth => pth.match(/(.svg|.png|.jpg|.ttf|.otf|.woff)$/i) != null) + const missingLicenses = missingLicenseInfos(licenseInfos, artwork) + if (args.indexOf("--prompt") >= 0 || args.indexOf("--query") >= 0) { + queryMissingLicenses(missingLicenses) + return main([]) + } + + const invalidLicenses = licenseInfos.filter(l => (l.license ?? "") === "").map(l => `License for artwork ${l.path} is empty string or undefined`) + for (const licenseInfo of licenseInfos) { + for (const source of licenseInfo.sources) { + if (source == "") { + invalidLicenses.push("Invalid license: empty string in " + JSON.stringify(licenseInfo)) + } + try { + new URL(source); + } catch { + invalidLicenses.push("Not a valid URL: " + source) + } } } -} - -if (missingLicenses.length > 0) { - const msg = `There are ${missingLicenses.length} licenses missing and ${invalidLicenses.length} invalid licenses.` - console.log(missingLicenses.concat(invalidLicenses).join("\n")) - console.error(msg) - if (process.argv.indexOf("--no-fail") < 0) { - throw msg + + if (missingLicenses.length > 0) { + const msg = `There are ${missingLicenses.length} licenses missing and ${invalidLicenses.length} invalid licenses.` + console.log(missingLicenses.concat(invalidLicenses).join("\n")) + console.error(msg) + if (args.indexOf("--no-fail") < 0) { + throw msg + } } + + cleanLicenseInfo(licensePaths, licenseInfos) + createFullLicenseOverview(licensePaths) } -cleanLicenseInfo(licensePaths, licenseInfos) -createFullLicenseOverview(licensePaths) \ No newline at end of file +main(process.argv.slice(2)) diff --git a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts index 2a06069ca0..c13d8d5825 100644 --- a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts +++ b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts @@ -11,6 +11,7 @@ import {ExtractImages} from "../../../../Models/ThemeConfig/Conversion/FixImages import * as cyclofix from "../../../../assets/generated/themes/cyclofix.json" import {Tag} from "../../../../Logic/Tags/Tag"; import {DesugaringContext} from "../../../../Models/ThemeConfig/Conversion/Conversion"; +import {And} from "../../../../Logic/Tags/And"; const themeConfigJson: LayoutConfigJson = { @@ -52,7 +53,7 @@ describe("PrepareTheme", () => { let themeConfigJsonPrepared = prepareStep.convert(theme, "test").result const themeConfig = new LayoutConfig(themeConfigJsonPrepared); const layerUnderTest = themeConfig.layers.find(l => l.id === "public_bookcase") - expect(layerUnderTest.source.osmTags).deep.eq(new Tag("amenity","public_bookcase")) + expect(layerUnderTest.source.osmTags).deep.eq(new And([new Tag("amenity","public_bookcase")])) }) diff --git a/test/UI/ValidatedTextFieldTranslations.ts b/test/UI/ValidatedTextFieldTranslations.ts index f9082ce2eb..48234d2cd6 100644 --- a/test/UI/ValidatedTextFieldTranslations.ts +++ b/test/UI/ValidatedTextFieldTranslations.ts @@ -1,14 +1,17 @@ import {describe} from 'mocha' -import {expect} from 'chai' -import Translations from "../../UI/i18n/Translations"; import ValidatedTextField from "../../UI/Input/ValidatedTextField"; +import {fail} from "assert"; +import Translations from "../../UI/i18n/Translations"; describe("ValidatedTextFields", () => { - - it("should all have description in the translations", () => { - const ts = Translations.t.validation; - const missingTranslations = Array.from(ValidatedTextField.allTypes.keys()) - .filter(key => ts[key] === undefined || ts[key].description === undefined) - expect(missingTranslations, "These validated text fields don't have a type name defined in en.json. (Did you just add one? Run `npm run generate:translations`)").to.be.empty - }) + + it("should all have description in the translations", () => { + const ts = Translations.t.validation; + const missingTranslations = Array.from(ValidatedTextField.allTypes.keys()) + .filter(key => ts[key] === undefined || ts[key].description === undefined) + .filter(key => key !== "distance") + if (missingTranslations.length > 0) { + fail("The validated text fields don't have a description defined in en.json for "+missingTranslations.join(", ")+". (Did you just add one? Run `npm run generate:translations`)") + } + }) })