From c8ad13ad72fde37d2042bb110b295c63631f811a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Thu, 10 Aug 2017 20:44:48 +0200 Subject: [PATCH 01/13] update popup content from CategoryOverpass --- src/CategoryOverpass.js | 20 +++++++++++++++++++- src/index.js | 5 +++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/CategoryOverpass.js b/src/CategoryOverpass.js index cef0b0a0..0f5fdc24 100644 --- a/src/CategoryOverpass.js +++ b/src/CategoryOverpass.js @@ -55,7 +55,6 @@ function CategoryOverpass (id, data) { } data.feature.appUrl = '#' + this.id + '/{{ id }}' - data.feature.body = (typeof data.feature.body === 'string' ? data.feature.body : '') + 'show details' this.layer = new OverpassLayer(data) @@ -86,6 +85,13 @@ function CategoryOverpass (id, data) { }.bind(this, ob.id) } }.bind(this) + this.layer.onUpdate = function (ob) { + if (!ob.popup || !ob.popup._contentNode) { + return + } + + this.updatePopupContent(ob, ob.popup) + }.bind(this) var p = document.createElement('div') p.className = 'loadingIndicator' @@ -162,5 +168,17 @@ CategoryOverpass.prototype.show = function (id, options, callback) { this.layer.show(id, options, callback) } +CategoryOverpass.prototype.notifyPopupOpen = function (object, popup) { + this.updatePopupContent(object, popup) +} + +CategoryOverpass.prototype.updatePopupContent = function (object, popup) { + var footer = document.createElement('div') + footer.className = 'footer' + var footerContent = 'show details' + footer.innerHTML = footerContent + popup._contentNode.appendChild(footer) +} + OpenStreetBrowserLoader.registerType('overpass', CategoryOverpass) module.exports = CategoryOverpass diff --git a/src/index.js b/src/index.js index 0adfcc45..696e4fcd 100644 --- a/src/index.js +++ b/src/index.js @@ -86,6 +86,11 @@ function onload2 () { if (location.hash !== url) { history.pushState(null, null, '#' + url) } + + OpenStreetBrowserLoader.getCategory(e.popup.object.layer_id, function (err, category) { + category.notifyPopupOpen(e.popup.object, e.popup) + + }) } }) map.on('popupclose', function (e) { From 0e9ea584bee6a87a5facf3417e3d8e38075d9ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sun, 6 Aug 2017 22:36:16 +0200 Subject: [PATCH 02/13] Define twig function 'tagsPrefix' --- README.md | 1 + src/index.js | 1 + src/twigFunctions.js | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 src/twigFunctions.js diff --git a/README.md b/README.md index 4e89d2dc..496590af 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ There are several extra functions defined for the TwigJS language: * function `tagTransList`: return the translations of the given tag for tags with multiple values separated by ';' (e.g. 'cuisine'). Parameters: key (required, e.g. 'cuisine'), value (required, e.g. 'kebab' or 'kebab;pizza;noodles;burger'). * function `localizedTag`: return a localized tag if available (e.g. 'name:de' for the german translation of the tag). Parameters: tags (the tags property), key prefix (e.g. 'name'). Which language will be returned depends on the "data language" which can be set via Options. If no localized tag is available, the tag value itself will be returned (e.g. value of 'name'). * function `trans`: return the translation of the given string (e.g. 'save', 'unknown', 'unnamed', ...). Parameters: string (the string to translate). +* function `tagsPrefix(tags, prefix)`: return all tags with the specified prefix. The result will be an array with `{ "en": "name:en", "de": "name:de" }` (for the input `{ "name": "foo", "name:en": "english foo", "name:de": "german foo" }` and the prefix "name:"). Notes: * Variables will automatically be HTML escaped, if not the filter raw is used, e.g.: {{ tags.name|raw }} diff --git a/src/index.js b/src/index.js index 696e4fcd..190d6861 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,7 @@ require('./language') require('./location') require('./overpassChooser') require('./fullscreen') +require('./twigFunctions') window.onload = function() { map = L.map('map') diff --git a/src/twigFunctions.js b/src/twigFunctions.js new file mode 100644 index 00000000..7717c23b --- /dev/null +++ b/src/twigFunctions.js @@ -0,0 +1,19 @@ +var OverpassLayer = require('overpass-layer') + +OverpassLayer.twig.extendFunction('tagsPrefix', function (tags, prefix) { + var ret = {} + var count = 0 + + for (var k in tags) { + if (k.substr(0, prefix.length) === prefix) { + ret[k.substr(prefix.length)] = k + count++ + } + } + + if (count == 0) { + return null + } + + return ret +}) From 1ccad6e80a164c4d2b4bf6de9b3b1c096b66273c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sat, 12 Aug 2017 06:44:45 +0200 Subject: [PATCH 03/13] Categories can define a load() function with callback --- src/CategoryOverpass.js | 4 ++++ src/OpenStreetBrowserLoader.js | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/CategoryOverpass.js b/src/CategoryOverpass.js index 0f5fdc24..8cfeadd8 100644 --- a/src/CategoryOverpass.js +++ b/src/CategoryOverpass.js @@ -109,6 +109,10 @@ function CategoryOverpass (id, data) { this.dom.appendChild(this.domStatus) } +CategoryOverpass.prototype.load = function (callback) { + callback(null) +} + CategoryOverpass.prototype.setMap = function (map) { CategoryBase.prototype.setMap.call(this, map) diff --git a/src/OpenStreetBrowserLoader.js b/src/OpenStreetBrowserLoader.js index d8e61faa..c498d04d 100644 --- a/src/OpenStreetBrowserLoader.js +++ b/src/OpenStreetBrowserLoader.js @@ -52,7 +52,13 @@ OpenStreetBrowserLoader.prototype.getCategoryFromData = function (id, data, call this.categories[id] = layer - callback(null, layer) + if ('load' in layer) { + layer.load(function (err) { + callback(err, layer) + }) + } else { + callback(null, layer) + } } } From d2e42e311436c0977ed34173c0736117c493a034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sat, 12 Aug 2017 06:45:34 +0200 Subject: [PATCH 04/13] OpenStreetBrowserLoader: getTemplate() function --- src/OpenStreetBrowserLoader.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/OpenStreetBrowserLoader.js b/src/OpenStreetBrowserLoader.js index c498d04d..3e87e5ed 100644 --- a/src/OpenStreetBrowserLoader.js +++ b/src/OpenStreetBrowserLoader.js @@ -1,6 +1,9 @@ +var OverpassLayer = require('overpass-layer') + function OpenStreetBrowserLoader () { this.types = {} this.categories = {} + this.templates = {} } OpenStreetBrowserLoader.prototype.setMap = function (map) { @@ -38,6 +41,30 @@ OpenStreetBrowserLoader.prototype.getCategory = function (id, callback) { } +OpenStreetBrowserLoader.prototype.getTemplate = function (id, callback) { + if (id in this.templates) { + callback(null, this.templates[id]) + return + } + + function reqListener (req) { + if (req.status !== 200) { + console.log(req) + return callback(req.statusText, null) + } + + this.templates[id] = OverpassLayer.twig.twig({ data: req.responseText, autoescape: true }) + + callback(null, this.templates[id]) + } + + var req = new XMLHttpRequest() + req.addEventListener("load", reqListener.bind(this, req)) + req.open("GET", config.categoriesDir + '/' + id + ".html?" + config.categoriesRev) + req.send() + +} + OpenStreetBrowserLoader.prototype.getCategoryFromData = function (id, data, callback) { if (!data.type) { callback('no type defined', null) From 39755cde62ee1ceaf0934be982b37df73e0b8f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sat, 12 Aug 2017 06:57:03 +0200 Subject: [PATCH 05/13] CategoryOverpass: load and render 'commonBody.html' template --- src/CategoryOverpass.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/CategoryOverpass.js b/src/CategoryOverpass.js index 8cfeadd8..308219d6 100644 --- a/src/CategoryOverpass.js +++ b/src/CategoryOverpass.js @@ -110,7 +110,15 @@ function CategoryOverpass (id, data) { } CategoryOverpass.prototype.load = function (callback) { - callback(null) + OpenStreetBrowserLoader.getTemplate('commonBody', function (err, template) { + if (err) { + console.log("can't load commonBody.html") + } else { + this.commonBodyTemplate = template + } + + callback(null) + }.bind(this)) } CategoryOverpass.prototype.setMap = function (map) { @@ -177,6 +185,15 @@ CategoryOverpass.prototype.notifyPopupOpen = function (object, popup) { } CategoryOverpass.prototype.updatePopupContent = function (object, popup) { + if (this.commonBodyTemplate) { + var commonBody = document.createElement('div') + commonBody.className = 'commonBody' + popup._contentNode.appendChild(commonBody) + + var data = this.layer.twigData(object.object) + commonBody.innerHTML = this.commonBodyTemplate.render(data) + } + var footer = document.createElement('div') footer.className = 'footer' var footerContent = 'show details' From 905ad90a129068eeb3ceb5ca2eb4326debf4579a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sat, 12 Aug 2017 21:40:35 +0200 Subject: [PATCH 06/13] Popup: Improve style for descriptions --- style.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/style.css b/style.css index 6282c5ec..298d72f4 100644 --- a/style.css +++ b/style.css @@ -409,3 +409,22 @@ a.showDetails { margin-left: 100%; } } + +.leaflet-popup-content ul { + margin: 0; + padding-left: 20px; +} +.leaflet-popup-content ul > li { + list-style: none; +} +.leaflet-popup-content ul > li > .symbol { + position: absolute; + margin-left: -15px; + padding-top: 2px; +} +.leaflet-popup-content ul > li > .key { + font-weight: bold; +} +.leaflet-popup-content ul > li > .key::after { + content: ':'; +} From 60afe2d990b96e8b61423b3eb5b84adb061309f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sun, 13 Aug 2017 20:40:15 +0200 Subject: [PATCH 07/13] Popups: Improve style --- style.css | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/style.css b/style.css index 298d72f4..10036aca 100644 --- a/style.css +++ b/style.css @@ -410,21 +410,57 @@ a.showDetails { } } +.leaflet-popup-content { + min-width: 200px; +} +.leaflet-popup-content > h1 { + margin: 0; +} + +.yes { + color: green; +} +.limited { + color: orange; +} +.no { + color: red; +} + +.body .description, +.leaflet-popup-content .description { + display: block; + text-align: right; + font-style: italic; +} +.body .description:after, +.leaflet-popup-content .description:after { + content: ''; + clear: right; +} + +.body ul, .leaflet-popup-content ul { margin: 0; padding-left: 20px; } +.body ul > li, .leaflet-popup-content ul > li { + position: relative; +} +.body ul > li.hasSymbol, +.leaflet-popup-content ul > li.hasSymbol { list-style: none; } -.leaflet-popup-content ul > li > .symbol { +.body ul > li.hasSymbol > i, +.leaflet-popup-content ul > li.hasSymbol > i, +.body ul > li.hasSymbol > img, +.leaflet-popup-content ul > li.hasSymbol > img { position: absolute; margin-left: -15px; padding-top: 2px; } +.body ul > li > .key, .leaflet-popup-content ul > li > .key { font-weight: bold; } -.leaflet-popup-content ul > li > .key::after { - content: ':'; -} From 911be13c2db74b712efcd44c388a88a42e6df348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Tue, 15 Aug 2017 08:22:15 +0200 Subject: [PATCH 08/13] Render detailsBody via category.renderTemplate() --- src/CategoryOverpass.js | 14 ++++++++++++++ src/index.js | 7 +++++++ style.css | 2 ++ 3 files changed, 23 insertions(+) diff --git a/src/CategoryOverpass.js b/src/CategoryOverpass.js index 308219d6..5c6ec4c4 100644 --- a/src/CategoryOverpass.js +++ b/src/CategoryOverpass.js @@ -201,5 +201,19 @@ CategoryOverpass.prototype.updatePopupContent = function (object, popup) { popup._contentNode.appendChild(footer) } +CategoryOverpass.prototype.renderTemplate = function (object, templateId, callback) { + OpenStreetBrowserLoader.getTemplate(templateId, function (err, template) { + if (err) { + err = "can't load " + templateId + ": " + err + return callback(err, null) + } + + var data = this.layer.twigData(object.object) + var result = template.render(data) + + callback(null, result) + }.bind(this)) +} + OpenStreetBrowserLoader.registerType('overpass', CategoryOverpass) module.exports = CategoryOverpass diff --git a/src/index.js b/src/index.js index 190d6861..057a9cde 100644 --- a/src/index.js +++ b/src/index.js @@ -194,6 +194,13 @@ function showDetails (data, category) { div.innerHTML = data.data.body dom.appendChild(div) + var div = document.createElement('div') + div.className = 'body' + dom.appendChild(div) + category.renderTemplate(data, 'detailsBody', function (div, err, result) { + div.innerHTML = result + }.bind(this, div)) + var h = document.createElement('h3') h.innerHTML = 'Attributes' dom.appendChild(h) diff --git a/style.css b/style.css index 10036aca..26fa476b 100644 --- a/style.css +++ b/style.css @@ -454,6 +454,8 @@ a.showDetails { } .body ul > li.hasSymbol > i, .leaflet-popup-content ul > li.hasSymbol > i, +.body ul > li.hasSymbol > .symbol, +.leaflet-popup-content ul > li.hasSymbol > .symbol, .body ul > li.hasSymbol > img, .leaflet-popup-content ul > li.hasSymbol > img { position: absolute; From e4f539a29faf2abbbdc473b2b6cb80fa65d7d2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Fri, 18 Aug 2017 07:35:29 +0200 Subject: [PATCH 09/13] Twig Function 'openingHoursState' --- README.md | 1 + lang/de.json | 2 ++ lang/en.json | 2 ++ package.json | 3 +++ src/twigFunctions.js | 6 ++++++ 5 files changed, 14 insertions(+) diff --git a/README.md b/README.md index 496590af..2e939b10 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ There are several extra functions defined for the TwigJS language: * function `localizedTag`: return a localized tag if available (e.g. 'name:de' for the german translation of the tag). Parameters: tags (the tags property), key prefix (e.g. 'name'). Which language will be returned depends on the "data language" which can be set via Options. If no localized tag is available, the tag value itself will be returned (e.g. value of 'name'). * function `trans`: return the translation of the given string (e.g. 'save', 'unknown', 'unnamed', ...). Parameters: string (the string to translate). * function `tagsPrefix(tags, prefix)`: return all tags with the specified prefix. The result will be an array with `{ "en": "name:en", "de": "name:de" }` (for the input `{ "name": "foo", "name:en": "english foo", "name:de": "german foo" }` and the prefix "name:"). +* function openingHoursState(opening_hours_definition): returns state of object as string: 'closed', 'open' or 'unknown'. Notes: * Variables will automatically be HTML escaped, if not the filter raw is used, e.g.: {{ tags.name|raw }} diff --git a/lang/de.json b/lang/de.json index 8bb3eee2..452993e5 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1,6 +1,8 @@ { + "closed": "geschlossen", "default": "Standard", "main:options": "Optionen", + "open": "geöffnet", "options:data_lang": "Datensprache", "options:data_lang:local": "Lokale Sprache", "options:overpassUrl": "OverpassAPI Adresse", diff --git a/lang/en.json b/lang/en.json index a044c8d9..cc54139d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,6 +1,8 @@ { + "closed": "closed", "default": "default", "main:options": "Options", + "open": "open", "options:data_lang": "Data language", "options:data_lang:desc": "Many map features have their name (and other tags) translated to different languages (e.g. with 'name:en', 'name:de'). Specify which language should be used for displaying, or 'Local language' so that always the untranslated value (e.g. 'name') will be used", "options:data_lang:local": "Local language", diff --git a/package.json b/package.json index 4f28b2b1..f4fae0db 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,13 @@ "license": "GPL-3.0", "dependencies": { "font-awesome": "^4.7.0", + "i18next-client": "^1.11.4", "ip-location": "^1.0.1", "leaflet": "^1.0.3", "leaflet-geosearch": "^2.4.0", "leaflet.locatecontrol": "^0.61.0", + "moment": "^2.18.1", + "opening_hours": "^3.5.0", "openstreetbrowser-categories-main": "https://github.com/plepe/openstreetbrowser-categories-main", "openstreetmap-tag-translations": "https://github.com/plepe/openstreetmap-tag-translations", "overpass-layer": "https://github.com/plepe/overpass-layer", diff --git a/src/twigFunctions.js b/src/twigFunctions.js index 7717c23b..e6ab60b8 100644 --- a/src/twigFunctions.js +++ b/src/twigFunctions.js @@ -1,4 +1,5 @@ var OverpassLayer = require('overpass-layer') +var OpeningHours = require('opening_hours') OverpassLayer.twig.extendFunction('tagsPrefix', function (tags, prefix) { var ret = {} @@ -17,3 +18,8 @@ OverpassLayer.twig.extendFunction('tagsPrefix', function (tags, prefix) { return ret }) + +OverpassLayer.twig.extendFunction('openingHoursState', function (opening_hours) { + var oh = new OpeningHours(opening_hours) + return oh.getStateString(new Date(), true) +}) From a57c41b6f8cd8ecda86a0d072e1ee73336029e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sun, 27 Aug 2017 07:34:34 +0200 Subject: [PATCH 10/13] Don't re-open popup when opening details --- src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 057a9cde..9a46a065 100644 --- a/src/index.js +++ b/src/index.js @@ -165,7 +165,9 @@ function show (id, options, callback) { return callback('error loading object "' + id[0] + '/' + id[1] +'": ' + err) } - data.feature.openPopup() + if (!map._popup || map._popup !== data.popup) { + data.feature.openPopup() + } if (options.showDetails) { showDetails(data, category) From e6702df2c4dfce12725b6ca67f610fcfad750629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sun, 27 Aug 2017 21:36:48 +0200 Subject: [PATCH 11/13] Integrate description (or popupDescription) in popups --- README.md | 3 ++- src/CategoryOverpass.js | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e939b10..a92cbee3 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ File: foo.json "markerSign": "{% if tags.highway == 'motorway_junction' %}↗{% elseif tags.highway == 'mini_roundabout' %}↻{% elseif tags.highway == 'crossing' %}▤{% endif %}", "title": "{{ localizedTag(tags, 'name') |default(localizedTag(tags, 'operator')) | default(localizedTag(tags, 'ref')) | default(trans('unnamed')) }}", "description": "{{ tagTrans('highway', tags.highway) }}", - "body": "{{ tagTrans('highway', tags.highway) }}
Foo value: {{ const.foo }}" + "body": "Foo value: {{ const.foo }}" }, "const": { "foo": "foo value" @@ -104,6 +104,7 @@ The following values are possible for categories (the only mandatory value is qu * title: the title of the feature popup, the object in the list and the details page. (default: localized tags for 'name', 'operator' or 'ref', default: 'unknown') * body: the body for the feature popup and the details page. * description: a short description shown in the list next to the title. + * popupDescription: like description, but an alternative if the description in popups should be different from the list. * markerSign: a HTML string which will be shown within the marker and in the list. (default: '') * priority: a numeric value by which the elements in the list will be sorted (lower values first) * const: an object variable which is available as prefix in twig functions. diff --git a/src/CategoryOverpass.js b/src/CategoryOverpass.js index 5c6ec4c4..d7258d53 100644 --- a/src/CategoryOverpass.js +++ b/src/CategoryOverpass.js @@ -185,6 +185,14 @@ CategoryOverpass.prototype.notifyPopupOpen = function (object, popup) { } CategoryOverpass.prototype.updatePopupContent = function (object, popup) { + if (object.data.popupDescription || object.data.description) { + var div = document.createElement('div') + div.className = 'description' + div.innerHTML = object.data.popupDescription || object.data.description + popup._contentNode.insertBefore(div, popup._contentNode.firstChild.nextSibling) + + } + if (this.commonBodyTemplate) { var commonBody = document.createElement('div') commonBody.className = 'commonBody' From 055cff94578598086bada1ce42567ab41d13f522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sun, 27 Aug 2017 21:40:19 +0200 Subject: [PATCH 12/13] Twig function openingHoursState: catch errors and return 'unknown' instead --- src/twigFunctions.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/twigFunctions.js b/src/twigFunctions.js index e6ab60b8..3f501278 100644 --- a/src/twigFunctions.js +++ b/src/twigFunctions.js @@ -20,6 +20,12 @@ OverpassLayer.twig.extendFunction('tagsPrefix', function (tags, prefix) { }) OverpassLayer.twig.extendFunction('openingHoursState', function (opening_hours) { - var oh = new OpeningHours(opening_hours) + try { + var oh = new OpeningHours(opening_hours) + } catch (err) { + console.log("Error in opening_hours: " + err) + return 'unknown' + } + return oh.getStateString(new Date(), true) }) From ef5f78b360a3b069beb47c94fb5ad4bc622dcc7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6sch-Plepelits?= Date: Sun, 27 Aug 2017 21:42:49 +0200 Subject: [PATCH 13/13] Translate 'facilities' (for commonBody) --- lang/de.json | 1 + lang/en.json | 1 + 2 files changed, 2 insertions(+) diff --git a/lang/de.json b/lang/de.json index 452993e5..574e4734 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1,6 +1,7 @@ { "closed": "geschlossen", "default": "Standard", + "facilities": "Einrichtungen", "main:options": "Optionen", "open": "geöffnet", "options:data_lang": "Datensprache", diff --git a/lang/en.json b/lang/en.json index cc54139d..8e6eec55 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,6 +1,7 @@ { "closed": "closed", "default": "default", + "facilities": "Facilities", "main:options": "Options", "open": "open", "options:data_lang": "Data language",