diff --git a/.gitignore b/.gitignore index 073ca9257..d8e8f299c 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,7 @@ error_changeset_* public/*.webmanifest public/assets/generated/ public/assets/langs/* + +android/ +dist-full/ +public/assets/icons/*.webp diff --git a/langs/de.json b/langs/de.json index eabf6ad49..9524196f3 100644 --- a/langs/de.json +++ b/langs/de.json @@ -596,10 +596,33 @@ "seeNearby": "Bilder in der Nähe durchsuchen", "title": "Straßenbilder in der Nähe" }, + "panoramax": { + "deletionRequested": "Der Bericht wurde abgeschickt. Ein Moderator wird sich in Kürze darum kümmern", + "freeform": "Gibt es weitere relevante Informationen?", + "otherFreeform": "Bitte gib an, warum dieses Bild entfernt werden soll:", + "placeholder": "Erkläre, warum das Bild gelöscht werden sollte", + "report": { + "blur_excess": "Zu viel ist unscharf und macht das Bild unbrauchbar", + "blur_missing": "Ein Gesicht oder Nummernschild ist auf diesem Bild nicht unscharf", + "copyright": "Das Bild enthält urheberrechtlich geschützte Inhalte", + "inappropriate": "Dieses Bild ist unangemessen (es enthält Nacktheit, ruft nach Hass oder ist nicht Streetview)", + "mislocated": "Das Bild ist von einem anderen Ort", + "other": "Ein weiterer Grund, bitte angeben", + "picture_low_quality": "Das Bild ist von geringer Qualität", + "privacy": "Das Bild zeigt eine private Immobilie" + }, + "requestDeletion": "Bildlöschung beantragen", + "title": "Warum sollte dieses Bild dauerhaft gelöscht werden?" + }, "pleaseLogin": "Bitte anmelden, um ein Bild hinzuzufügen", "processing": "Der Server verarbeitet das Bild", "respectPrivacy": "Laden Sie keine Bilder von Google Maps, Google Streetview oder anderen urheberrechtlich geschützten Quellen hoch.", "toBig": "Ihr Bild ist mit {actual_size} zu groß. Die maximale Bildgröße ist {max_size}", + "unlink": { + "button": "Bild entkoppeln", + "explanation": "Wenn du die Verknüpfung dieses Bildes aufhebst, wird das Bild nicht mehr mit diesem Objekt angezeigt. Es wird aber weiterhin in den Nachbarschaftsbildern und möglicherweise in anderen Objekten angezeigt.", + "title": "Dieses Bild entkoppeln?" + }, "upload": { "failReasons": "Keine Internetverbindung", "failReasonsAdvanced": "Alternativ dazu können Sie einstellen, dass Ihr Browser und Ihre Erweiterungen die APIs von Drittanbietern nicht blockieren.", @@ -898,4 +921,4 @@ "startsWithQ": "Ein Wikidata-Identifikator beginnt mit Q und wird von einer Zahl gefolgt" } } -} \ No newline at end of file +} diff --git a/langs/el.json b/langs/el.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/langs/el.json @@ -0,0 +1 @@ +{} diff --git a/langs/es.json b/langs/es.json index 50e7c7313..d2630b9b9 100644 --- a/langs/es.json +++ b/langs/es.json @@ -596,10 +596,33 @@ "seeNearby": "Ver imágenes cercanas", "title": "Imágenes a nivel calle cercanas" }, + "panoramax": { + "deletionRequested": "El informe ha sido enviado. Un moderador lo examinará en breve", + "freeform": "¿Hay alguna otra información pertinente?", + "otherFreeform": "Por favor, especifique por qué debe eliminarse esta imagen:", + "placeholder": "Explicar por qué la imagen debe ser eliminada", + "report": { + "blur_excess": "Demasiado borrosa, haciendo la imagen inútil", + "blur_missing": "Una cara o placa de matrícula no está borrosa en esta imagen", + "copyright": "La imagen contiene contenido protegido por derechos de autor", + "inappropriate": "Esta imagen es inapropiada (contiene desnudez, incita al odio o no es streetview)", + "mislocated": "La imagen es de una ubicación diferente", + "other": "Otra razón, por favor especifíquese", + "picture_low_quality": "La imagen es de baja calidad", + "privacy": "La imagen muestra una propiedad privada" + }, + "requestDeletion": "Solicitar la eliminación de una imagen", + "title": "¿Por qué debería esta imagen ser eliminada permanentemente?" + }, "pleaseLogin": "Por favor, inicia sesión para agregar una imagen", "processing": "El servidor está procesando tu imagen", "respectPrivacy": "No subas imágenes de Google Maps, Google Street View u otras fuentes con derechos de autor.", "toBig": "Tu imagen es demasiado grande, tiene {actual_size}. Por favor, usa imágenes de como máximo {max_size}", + "unlink": { + "button": "Desvincular imagen", + "explanation": "Al desvincular esta imagen, esta imagen ya no se mostrará con este objeto. Todavía aparecerá en las imágenes cercanas y posiblemente otros objetos.", + "title": "¿Desvincular esta imagen?" + }, "upload": { "failReasons": "Podrías haber perdido la conexión a internet", "failReasonsAdvanced": "Alternativamente, asegúrate de que tu navegador y extensiones no bloqueen las API de terceros.", @@ -781,7 +804,7 @@ "title": "{count} reseñas", "title_singular": "Una reseña", "too_long": "Se permiten como máximo {max} caracteres. Tu reseña tiene {amount} caracteres.", - "tos": "Si creas una reseña, aceptas los T&C y la política de privacidad de Mangrove.reviews", + "tos": "Si creas una revisión, aceptas los TOS y la política de privacidad de Mangrove.reviews", "write_a_comment": "Deja una reseña…", "your_reviews": "Tus reseñas anteriores", "your_reviews_empty": "No pudimos encontrar ninguna de tus reseñas anteriores" @@ -898,4 +921,4 @@ "startsWithQ": "Un identificador de Wikidata comienza con Q y le sigue un número" } } -} \ No newline at end of file +} diff --git a/langs/hu.json b/langs/hu.json index 882c51202..9e7685a03 100644 --- a/langs/hu.json +++ b/langs/hu.json @@ -596,10 +596,33 @@ "seeNearby": "Közeli képek böngészése", "title": "Közeli utcakép" }, + "panoramax": { + "deletionRequested": "A jelentés be lett küldve; egy moderátor rövidesen megvizsgálja", + "freeform": "Van-e további lényeges információ?", + "otherFreeform": "Írd le, miért kellene eltávolítani ezt a képet:", + "placeholder": "Fejtsd ki, miért kellene törölni ezt a képet", + "report": { + "blur_excess": "A kép használhatatlan, mert túl nagy része homályos", + "blur_missing": "A képen nincs elhomályosítva egy arc vagy egy rendszámtábla", + "copyright": "A kép szerzői jog hatálya alá tartozó tartalmat jelenít meg", + "inappropriate": "A kép nem megfelelő (meztelenséget tartalmaz, gyűlöletre hív vagy nem utcakép)", + "mislocated": "A kép egy másik helyet ábrázol", + "other": "Más ok, kérjük, fejtsd ki", + "picture_low_quality": "A kép gyatra minőségű", + "privacy": "A kép magánterületet ábrázol" + }, + "requestDeletion": "Képtörlés kérése", + "title": "Miért kellene ezt a képet véglegesen törölni?" + }, "pleaseLogin": "Kép hozzáadásához be kell jelentkezni", "processing": "A szerver feldolgozza a képet", "respectPrivacy": "Ne tölts fel képet a Google térképről, a Google Streetview-ról (utcaképről) és egyéb szerzői jog által védett forrásokból.", "toBig": "A kép túl nagy ({actual_size}). Kérjük, legfeljebb {max_size} méretű képeket használj", + "unlink": { + "button": "Kép leválasztása", + "explanation": "A kép belinkeltségének feloldásával ez a kép nem jelenik meg többé ezzel az objektummal együtt. A közeli képek között és esetleg más objektumokban azonban továbbra is meg fog jelenni.", + "title": "Megszűnjék a kép belinkelése?" + }, "upload": { "failReasons": "Lehet, hogy megszakadt az internetkapcsolatod", "failReasonsAdvanced": "Másik lehetőségként győződj meg arról, hogy a böngésződ vagy bővítményei nem blokkolnak-e harmadik féltől származó API-kat.", @@ -898,4 +921,4 @@ "startsWithQ": "A Wikidata-azonosító Q-val kezdődik, amelyet egy szám követ" } } -} \ No newline at end of file +} diff --git a/langs/layers/ca.json b/langs/layers/ca.json index dd5b6f42e..46963065c 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -9594,4 +9594,4 @@ "render": "Turbina eòlica" } } -} \ No newline at end of file +} diff --git a/langs/layers/cs.json b/langs/layers/cs.json index f7e82a036..2618f40f1 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -10187,4 +10187,4 @@ "render": "větrná turbína" } } -} \ No newline at end of file +} diff --git a/langs/layers/de.json b/langs/layers/de.json index b4d685a0a..4b96e4e4f 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -1492,6 +1492,17 @@ } }, "tagRenderings": { + "automated": { + "mappings": { + "0": { + "then": "Dies ist eine manuelle Fahrradwaschanlage" + }, + "1": { + "then": "Dies ist eine automatische Fahrradwaschanlage" + } + }, + "question": "Ist dieser Fahrradreinigungsdienst automatisiert?" + }, "bike_cleaning-charge": { "mappings": { "0": { @@ -1515,6 +1526,17 @@ }, "question": "Wie viel kostet die Nutzung des Reinigungsdienstes?", "render": "Der Reinigungsservice kostet {service:bicycle:cleaning:charge}" + }, + "self_service": { + "mappings": { + "0": { + "then": "Die Reinigung erfolgt selbständig" + }, + "1": { + "then": "Dieser Reinigungsdienst wird von einem Angestellten betrieben" + } + }, + "question": "Muss die Reinigung selbständig erfolgen?" } }, "title": { @@ -3208,6 +3230,10 @@ "1": { "description": "Eine öffentlich sichtbare Uhr an einer Wand", "title": "eine an der Wand montierte Uhr" + }, + "2": { + "description": "Eine öffentlich sichtbare Uhr, die direkt an einer Wand angebracht ist", + "title": "eine Wanduhr, die direkt an der Wand angebracht ist" } }, "tagRenderings": { @@ -3288,13 +3314,29 @@ }, "question": "Zeigt diese Uhr auch die Luftfeuchtigkeit an?" }, + "indoor": { + "override": { + "mappings": { + "0": { + "then": "Diese Uhr befindet sich in Innenräumen" + }, + "1": { + "then": "Diese Uhr ist im Freien" + } + }, + "question": "Befindet sich diese Uhr in Innenräumen?" + } + }, "support": { "mappings": { "0": { "then": "Diese Uhr ist auf einem Mast montiert" }, "1": { - "then": "Diese Uhr ist an einer Wand montiert" + "then": "Diese Uhr wird an der Wand befestigt, in der Regel durch einen Träger, der senkrecht zur Wand steht" + }, + "2": { + "then": "Diese Uhr ist direkt an einer Wand montiert" }, "3": { "then": "Diese Uhr ist Teil einer Werbetafel" @@ -3493,6 +3535,57 @@ } }, "question": "Gibt die Ampel ein Vibrationssignal, um das Überqueren zu erleichtern? (in der Regel am unteren Ende der Ampeltaste)" + }, + "markings": { + "mappings": { + "0": { + "then": "Diese Kreuzung hat keine Markierungen" + }, + "1": { + "then": "Dieser Übergang ist mit Zebrastreifen markiert" + }, + "10": { + "then": "Dieser Übergang hat Zebrastreifen in wechselnden Farben" + }, + "11": { + "then": "Dieser Übergang hat doppelte Zebrastreifen" + }, + "12": { + "then": "Diese Kreuzung hat Piktogramme auf der Straße" + }, + "13": { + "then": "Diese Kreuzung hat Linien auf beiden Seiten der Kreuzung und Balken, die sie verbinden, mit einer Unterbrechung in jedem Balken" + }, + "14": { + "then": "Dieser Übergang hat doppelte Linien auf beiden Seiten des Übergangs" + }, + "2": { + "then": "Dieser Übergang weist Markierungen unbekannter Art auf" + }, + "3": { + "then": "Dieser Übergang hat Linien auf beiden Seiten des Übergangs" + }, + "4": { + "then": "Diese Kreuzung hat Linien auf beiden Seiten der Kreuzung, zusammen mit Stangen, die sie verbinden" + }, + "5": { + "then": "Dieser Übergang hat gestrichelte Linien auf beiden Seiten des Übergangs" + }, + "6": { + "then": "Dieser Übergang hat gestrichelte Linien auf beiden Seiten des Übergangs" + }, + "7": { + "then": "Dieser Übergang wird durch eine andersfarbige Oberfläche gekennzeichnet" + }, + "8": { + "then": "Diese Kreuzung hat Linien auf beiden Seiten der Kreuzung, zusammen mit abgewinkelten Stangen, die sie verbinden" + }, + "9": { + "then": "Dieser Übergang hat Zebrastreifen mit einer Unterbrechung in jedem Balken" + } + }, + "question": "Welche Art von Markierungen gibt es an diesem Übergang?", + "render": "Dieser Übergang hat {crossing:markings} Markierungen" } }, "title": { @@ -3908,6 +4001,57 @@ "render": "Weg" } }, + "cyclist_waiting_aid": { + "description": "Verschiedene Infrastruktureinrichtungen, die Radfahrern helfen, während sie an einer Ampel warten.", + "name": "Radfahrer-Wartehilfen", + "presets": { + "0": { + "description": "Eine Fußstütze, ein Handlauf oder ein anderes Hilfsmittel zur Verbesserung des Komforts beim Warten an der Ampel", + "title": "eine Radfahrer-Wartehilfe" + } + }, + "tagRenderings": { + "direction": { + "mappings": { + "0": { + "then": "Diese Wartehilfe kann bei der Weiterfahrt auf diesem Weg genutzt werden" + }, + "1": { + "then": "Diese Wartehilfe kann beim Rückwärtsfahren auf diesem Weg benutzt werden" + } + }, + "render": "Diese Wartehilfe kann in Fahrtrichtung {direction} benutzt werden" + }, + "side": { + "mappings": { + "0": { + "then": "Diese Wartehilfe befindet sich auf der linken Seite" + }, + "1": { + "then": "Diese Wartehilfe befindet sich auf der rechten Seite" + }, + "2": { + "then": "Auf beiden Seiten der Straße gibt es Wartehilfen" + } + }, + "question": "Auf welcher Straßenseite befindet sich dies?" + }, + "type": { + "mappings": { + "0": { + "then": "Hier gibt es ein Brett oder einen Pflock zum Abstützen des Fußes" + }, + "1": { + "then": "Hier gibt es eine Schiene oder einen Griff zum Festhalten" + } + }, + "question": "Aus welchen Bestandteilen besteht diese Wartehilfe?" + } + }, + "title": { + "render": "Radfahrer-Wartehilfe" + } + }, "defibrillator": { "description": "Eine Ebene mit Defibrillatoren, die in Notfällen eingesetzt werden können. Diese Ebene enthält öffentliche Defibrillatoren, aber auch Defibrillatoren, bei denen möglicherweise Personal benötigt wird, um das Gerät zu holen", "name": "Defibrillatoren", @@ -7891,6 +8035,15 @@ "presets": { "0": { "title": "ein Briefkasten" + }, + "1": { + "title": "ein Briefkasten an einer Wand" + } + }, + "tagRenderings": { + "operator": { + "question": "Wer betreibt diesen Briefkasten?", + "render": "Dieser Briefkasten wird von {operator} betrieben" } }, "title": { @@ -8386,6 +8539,17 @@ }, "question": "Verkauft das Geschäft glutenfreie Produkte?" }, + "indoor": { + "mappings": { + "0": { + "then": "Dieses Objekt befindet sich in einem Innenraum" + }, + "1": { + "then": "Dieses Objekt befindet sich im Freien" + } + }, + "question": "Befindet sich das Objekt in einem Innenraum?" + }, "induction-loop": { "mappings": { "0": { @@ -10476,6 +10640,133 @@ "render": "Überwachungskamera" } }, + "tactile_map": { + "description": "Ebene mit taktilen Karten, die von sehbehinderten Menschen zur Navigation in der Stadt verwendet werden können.", + "name": "Taktile Karten", + "presets": { + "0": { + "description": "Eine taktile Karte, die durch Berühren gelesen werden kann. Im Gegensatz zu einem taktilen Modell ist diese Karte relativ flach und enthält keine dreidimensionalen Gebäude oder ähnliches.", + "title": "eine taktile Karte" + } + }, + "tagRenderings": { + "braille": { + "mappings": { + "0": { + "then": "Diese taktile Karte hat einen Text in Blindenschrift." + }, + "1": { + "then": "Diese taktile Karte hat keinen Text in Blindenschrift." + } + }, + "question": "Gibt es einen Text in Blindenschrift auf dieser taktilen Karte?" + }, + "braille_languages": { + "render": { + "special": { + "question": "In welchen Sprachen ist der Text in Blindenschrift auf dieser taktilen Karte?", + "render_list_item": "Diese Karte hat Text in Blindenschrift in {language}", + "render_single_language": "Diese Karte hat Text in Blindenschrift in {language}" + } + } + }, + "description": { + "freeform": { + "placeholder": "z.B. Taktile Karte des Stadtzentrums" + }, + "question": "Was zeigt diese taktile Karte?", + "render": "Beschreibung: {blind:description:en}." + }, + "embossed_letters": { + "mappings": { + "0": { + "then": "Diese taktile Karte hat geprägte Buchstaben." + }, + "1": { + "then": "Diese taktile Karte hat keine geprägten Buchstaben." + } + }, + "question": "Gibt es auf dieser taktilen Karte geprägte Buchstaben?" + }, + "embossed_letters_languages": { + "render": { + "special": { + "question": "In welchen Sprachen sind die geprägten Buchstaben auf dieser taktilen Karte?", + "render_list_item": "Diese Karte hat geprägte Buchstaben in {language}", + "render_single_language": "Diese Karte hat geprägte Buchstaben in {language}" + } + } + } + }, + "title": "Taktile Karte" + }, + "tactile_model": { + "description": "Ebene mit dreidimensionalen ertastbaren Modellen der Umgebung.", + "name": "Taktile Modelle", + "presets": { + "0": { + "description": "Ein taktiles Modell ist ein dreidimensionales Modell eines Gebiets, das es den Menschen ermöglicht, ein Gebiet durch Berührung zu erkunden/sehen.", + "title": "ein taktiles Modell" + } + }, + "tagRenderings": { + "braille": { + "mappings": { + "0": { + "then": "Es gibt eine Beschreibung in Blindenschrift." + }, + "1": { + "then": "Es gibt keine Beschreibung in Blindenschrift." + } + }, + "question": "Gibt es eine Beschreibung in Blindenschrift?" + }, + "braille_languages": { + "render": { + "special": { + "question": "In welchen Sprachen gibt es eine Beschreibung in Blindenschrift?", + "render_list_item": "Dieses Modell hat eine Beschreibung in Brailleschrift in {language()}", + "render_single_language": "Dieses Modell hat eine Beschreibung in Brailleschrift in {language}" + } + } + }, + "description": { + "freeform": { + "placeholder": "z.B. Taktiles Modell des Stadtzentrums" + }, + "question": "Was zeigt dieses taktile Modell?", + "render": "Beschreibung: {blind:description:en}." + }, + "embossed_letters": { + "mappings": { + "0": { + "then": "Das Modell ist mit geprägten Buchstaben beschrieben." + }, + "1": { + "then": "Es gibt keine eingeprägten Buchstaben, die das Modell beschreiben." + } + }, + "question": "Gibt es eingeprägte Buchstaben, die das Modell beschreiben?" + }, + "embossed_letters_languages": { + "render": { + "special": { + "question": "In welchen Sprachen gibt es geprägte Buchstaben?", + "render_list_item": "Dieses Modell hat geprägte Buchstaben in {language()}", + "render_single_language": "Dieses Modell hat geprägte Buchstaben in {language}" + } + } + }, + "scale": { + "freeform": { + "placeholder": "z.B. 1:1000" + }, + "question": "Welchen Maßstab hat das Modell?", + "render": "Der Maßstab dieses Modells ist {scale}." + } + }, + "title": "Taktiles Modell" + }, "tertiary_education": { "name": "Hochschulen und Universitäten", "presets": { @@ -12491,4 +12782,4 @@ "render": "Windrad" } } -} \ No newline at end of file +} diff --git a/langs/layers/el.json b/langs/layers/el.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/langs/layers/el.json @@ -0,0 +1 @@ +{} diff --git a/langs/layers/es.json b/langs/layers/es.json index 5de65be08..8d7698fcc 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -3294,12 +3294,15 @@ "then": "Este reloj está montado en un poste" }, "1": { - "then": "Este reloj está montado en una pared" + "then": "Este reloj está montado en una pared, generalmente a través de un soporte perpendicular a la pared" }, "2": { - "then": "Este reloj forma parte de una cartelera" + "then": "Este reloj está montado directamente en una pared" }, "3": { + "then": "Este reloj es parte de un cartel" + }, + "4": { "then": "Este reloj está en el suelo" }, "4": { diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 80858a1a8..f5cde9b37 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -7556,4 +7556,4 @@ "render": "éolienne" } } -} \ No newline at end of file +} diff --git a/langs/layers/pl.json b/langs/layers/pl.json index 5a99131fa..5aead3423 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -3707,4 +3707,4 @@ "render": "turbina wiatrowa" } } -} \ No newline at end of file +} diff --git a/langs/layers/uk.json b/langs/layers/uk.json index a229da42f..d93f178f1 100644 --- a/langs/layers/uk.json +++ b/langs/layers/uk.json @@ -291,6 +291,28 @@ } } }, + "assisted_repair": { + "tagRenderings": { + "item:repair": { + "mappings": { + "0": { + "then": "Тут ремонтують мобільні телефони" + } + } + } + } + }, + "atm": { + "tagRenderings": { + "speech_output": { + "mappings": { + "0": { + "then": "Цей банкомат має мовний вихід, зазвичай доступний через роз'єм для навушників" + } + } + } + } + }, "bench": { "tagRenderings": { "bench-armrest": { @@ -468,7 +490,8 @@ "question": "Яка електронна адреса оператора цієї велопарковки?" }, "operator_phone": { - "question": "Який номер телефону оператора цієї велопарковки?" + "question": "Який номер телефону оператора цієї велопарковки?", + "questionHint": "За цим номером можна буде зателефонувати у разі виникнення проблем, наприклад, щоб прибрати недоглянуті велосипеди" }, "operator_website": { "question": "Яка адреса веб-сайту оператора цієї велопарковки?" @@ -587,7 +610,37 @@ } }, "charging_station": { + "filter": { + "2": { + "options": { + "14": { + "question": "Має
USB для зарядки телефонів і малої електроніки
роз'єм" + } + } + } + }, "tagRenderings": { + "Auth phone": { + "question": "Який номер телефону для аутентифікаційного дзвінка або SMS?", + "render": "Авторизуйтесь, зателефонувавши або надіславши SMS на {authentication:phone_call:number}" + }, + "Authentication": { + "mappings": { + "2": { + "then": "Доступна автентифікація за допомогою телефонного дзвінка" + } + } + }, + "Available_charging_stations (generated)": { + "mappings": { + "26": { + "then": "USB для зарядки телефонів і дрібної електроніки" + }, + "27": { + "then": "USB для зарядки телефонів і дрібної електроніки" + } + } + }, "Network": { "mappings": { "0": { @@ -605,6 +658,19 @@ }, "email": { "question": "Яка електронна адреса оператора?" + }, + "phone": { + "question": "За яким номером можна зателефонувати, якщо виникла проблема з цією зарядною станцією?", + "render": "У разі виникнення проблем телефонуйте {phone}" + }, + "rewritten-questions": { + "rewrite": { + "into": { + "13": { + "2": "USB для зарядки телефонів і дрібної електроніки" + } + } + } } } }, @@ -644,6 +710,10 @@ "tagRenderings": { "defibrillator-access": { "render": "Доступ – {access}" + }, + "defibrillator-phone": { + "question": "Який номер телефону для запитань щодо цього дефібрилятора?", + "render": "Телефонуйте з питань щодо цього дефібрилятора: {phone}" } } }, @@ -896,6 +966,26 @@ } } }, + "icons": { + "tagRenderings": { + "phonelink": { + "mappings": { + "0": { + "then": { + "special": { + "arialabel": "телефон" + } + } + } + }, + "render": { + "special": { + "arialabel": "телефон" + } + } + } + } + }, "indoors": { "tagRenderings": { "name": { @@ -941,6 +1031,10 @@ "tagRenderings": { "Access tag": { "render": "Доступ до цього природного заповідника: {access:description}" + }, + "phone": { + "question": "За яким номером телефону можна звертатися з питаннями та проблемами, пов'язаними з цим заповідником?", + "questionHint": "Поважайте приватність - вказуйте особистий номер телефону лише в тому випадку, якщо він є загальнодоступним" } } }, @@ -981,7 +1075,8 @@ "question": "Яка електронна адреса доглядача дитячого майданчика?" }, "playground-phone": { - "question": "Який номер телефону доглядача дитячого майданчика?" + "question": "Який номер телефону доглядача дитячого майданчика?", + "render": "{phone}" }, "playground-surface": { "mappings": { @@ -1073,6 +1168,9 @@ "then": "Ця локація пропонує послуги для bpost" } } + }, + "post_offic_brand": { + "render": "Це поштове відділення {brand}" } } }, @@ -1151,7 +1249,43 @@ "question": "Які години роботи {title()}?", "render": "

Години роботи

{opening_hours_table(opening_hours)}" }, + "opening_hours_24_7": { + "override": { + "+mappings": { + "0": { + "then": "Працює 24/7 (включаючи святкові дні)" + } + } + } + }, + "payment-options": { + "mappings": { + "2": { + "then": "Оплата за допомогою QR-коду можлива тут" + } + }, + "question": "Які способи оплати тут приймаються?" + }, + "payment-options-split": { + "override": { + "mappings+": { + "0": { + "then": "Монети приймаються тут" + }, + "1": { + "then": "Тут приймаються банкноти" + }, + "2": { + "then": "Тут приймаються дебетові картки" + }, + "3": { + "then": "Тут приймаються кредитні картки" + } + } + } + }, "phone": { + "editButtonAriaLabel": "Редагувати номер телефону", "question": "Який номер телефону {title()}?" }, "qr_code": { @@ -1886,18 +2020,137 @@ }, "vending_machine": { "tagRenderings": { + "indoor": { + "mappings": { + "1": { + "then": "Цей торговий автомат знаходиться в приміщенні" + }, + "2": { + "then": "Цей торговий автомат знаходиться на відкритому повітрі" + } + }, + "question": "Цей торговий автомат знаходиться в приміщенні?" + }, "operational_status": { "mappings": { + "0": { + "then": "Цей торговий автомат працює" + }, + "1": { + "then": "Цей торговий автомат несправний" + }, + "2": { + "then": "Цей торговий автомат закрито" + }, "3": { "then": "Робочий стан - це {operational_status}" } - } + }, + "question": "Чи працює цей торговий автомат досі?" + }, + "operator": { + "question": "Хто керує цим автоматом?", + "render": "Цим торговим автоматом керує {operator}" }, "phone": { "override": { - "question": "Який номер телефону оператора цього торгового автомата?" + "question": "Який номер телефону оператора цього торгового автомата?", + "questionHint": "Це номер, за яким ви можете зателефонувати у разі виникнення проблем з торговим автоматом" } + }, + "vending": { + "mappings": { + "0": { + "then": "Напої" + }, + "1": { + "then": "Солодощі" + }, + "10": { + "then": "Хліб" + }, + "11": { + "then": "Яйця" + }, + "12": { + "then": "Морозиво" + }, + "13": { + "then": "Твердий сир" + }, + "14": { + "then": "Мед" + }, + "15": { + "then": "Картопля" + }, + "16": { + "then": "М'ясо" + }, + "17": { + "then": "Фрукти" + }, + "18": { + "then": "Полуниця" + }, + "19": { + "then": "Квіти" + }, + "2": { + "then": "Продукти харчування" + }, + "20": { + "then": "Паркувальні талони" + }, + "21": { + "then": "Пресовані монети" + }, + "22": { + "then": "Квитки на громадський транспорт" + }, + "23": { + "then": "Велосипедні ліхтарі" + }, + "24": { + "then": "Рукавички" + }, + "25": { + "then": "Набори для ремонту велосипедів" + }, + "26": { + "then": "Велосипедні насоси" + }, + "27": { + "then": "Велосипедні замки" + }, + "3": { + "then": "Сигарети" + }, + "4": { + "then": "Презервативи" + }, + "5": { + "then": "Кава" + }, + "6": { + "then": "Питна вода" + }, + "7": { + "then": "Газети" + }, + "8": { + "then": "Велосипедні внутрішні трубки" + }, + "9": { + "then": "Молоко" + } + }, + "question": "Що продає цей автомат?", + "render": "Цей торговий автомат продає {vending}" } + }, + "title": { + "render": "Торговий автомат" } }, "waste_disposal": { @@ -1947,4 +2200,4 @@ "render": "Утилізація відходів" } } -} \ No newline at end of file +} diff --git a/langs/nan.json b/langs/nan.json index 4b97ab695..465db8fdf 100644 --- a/langs/nan.json +++ b/langs/nan.json @@ -1,9 +1,10 @@ { "advanced": { - "title": "Advanced features" + "title": "高级功能" }, "centerMessage": { - "allFilteredAway": "当前视图中没有满足过滤条件的要素" + "allFilteredAway": "当前视图中没有满足过滤条件的要素", + "loadingData": "加载数据…" }, "validation": { "wikidata": { @@ -11,4 +12,4 @@ "startsWithQ": "A wikidata identifier starts with Q and is followed by a number" } } -} \ No newline at end of file +} diff --git a/langs/themes/el.json b/langs/themes/el.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/langs/themes/el.json @@ -0,0 +1 @@ +{} diff --git a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts index 7872d67bc..494d59e2d 100644 --- a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts @@ -49,14 +49,15 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { }, options?: { padToTiles?: Store - isActive?: Store + isActive?: Store, + ignoreZoom?: boolean } ) { this.state = state this._isActive = options?.isActive ?? new ImmutableStore(true) this.padToZoomLevel = options?.padToTiles const self = this - this._layersToDownload = state.zoom.map((zoom) => this.layersToDownload(zoom)) + this._layersToDownload = options?.ignoreZoom? new ImmutableStore(state.layers) : state.zoom.map((zoom) => this.layersToDownload(zoom)) state.bounds.mapD( (_) => { @@ -103,7 +104,7 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { * Download the relevant data from overpass. Attempt to use a different server if one fails; only downloads the relevant layers * @private */ - public async updateAsync(): Promise { + public async updateAsync(overrideBounds?: BBox): Promise { let data: any = undefined let lastUsed = 0 const start = new Date() @@ -122,7 +123,7 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { let bounds: BBox do { try { - bounds = this.state.bounds.data + bounds = overrideBounds ?? this.state.bounds.data ?.pad(this.state.widenFactor) ?.expandToTileBounds(this.padToZoomLevel?.data) if (!bounds) { diff --git a/src/Logic/FeatureSource/Sources/ThemeSource.ts b/src/Logic/FeatureSource/Sources/ThemeSource.ts index 80c85b44e..bc20862df 100644 --- a/src/Logic/FeatureSource/Sources/ThemeSource.ts +++ b/src/Logic/FeatureSource/Sources/ThemeSource.ts @@ -4,7 +4,7 @@ import { FeatureSource, UpdatableFeatureSource } from "../FeatureSource" import { Or } from "../../Tags/Or" import FeatureSwitchState from "../../State/FeatureSwitchState" import OverpassFeatureSource from "./OverpassFeatureSource" -import { Store, UIEventSource } from "../../UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import OsmFeatureSource from "./OsmFeatureSource" import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" import { BBox } from "../../BBox" @@ -28,6 +28,13 @@ export default class ThemeSource extends FeatureSourceMerger { public static readonly fromCacheZoomLevel = 15 + /** + * This source is _only_ triggered when the data is downloaded for CSV export + * @private + */ + private readonly _downloadAll: OverpassFeatureSource + private readonly _mapBounds: Store + constructor( layers: LayerConfig[], featureSwitches: FeatureSwitchState, @@ -35,7 +42,7 @@ export default class ThemeSource extends FeatureSourceMerger { backend: string, isDisplayed: (id: string) => Store, mvtAvailableLayers: Set, - fullNodeDatabaseSource?: FullNodeDatabaseSource + fullNodeDatabaseSource?: FullNodeDatabaseSource, ) { const supportsForceDownload: UpdatableFeatureSource[] = [] @@ -56,7 +63,7 @@ export default class ThemeSource extends FeatureSourceMerger { { isActive: isDisplayed(layer.id), maxAge: layer.maxAgeOfCache, - } + }, ) fromCache.set(layer.id, src) } @@ -75,7 +82,7 @@ export default class ThemeSource extends FeatureSourceMerger { zoom, backend, featureSwitches, - fullNodeDatabaseSource + fullNodeDatabaseSource, ) nonMvtSources.push(osmApiSource) @@ -84,13 +91,14 @@ export default class ThemeSource extends FeatureSourceMerger { console.log( "Layers ", nonMvtLayers.map((l) => l.id), - " cannot be fetched from the cache server, defaulting to overpass/OSM-api" + " cannot be fetched from the cache server, defaulting to overpass/OSM-api", ) overpassSource = ThemeSource.setupOverpass(osmLayers, bounds, zoom, featureSwitches) nonMvtSources.push(overpassSource) supportsForceDownload.push(overpassSource) } + function setIsLoading() { const loading = overpassSource?.runningQuery?.data || osmApiSource?.isRunning?.data isLoading.setData(loading) @@ -100,21 +108,40 @@ export default class ThemeSource extends FeatureSourceMerger { osmApiSource?.isRunning?.addCallbackAndRun(() => setIsLoading()) const geojsonSources: UpdatableFeatureSource[] = geojsonlayers.map((l) => - ThemeSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) + ThemeSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)), ) - super(...geojsonSources, ...Array.from(fromCache.values()), ...mvtSources, ...nonMvtSources) + const downloadAllBounds: UIEventSource = new UIEventSource(undefined) + const downloadAll= new OverpassFeatureSource({ + layers: layers.filter(l => l.isNormal()), + bounds: mapProperties.bounds, + zoom: mapProperties.zoom, + overpassUrl: featureSwitches.overpassUrl, + overpassTimeout: featureSwitches.overpassTimeout, + overpassMaxZoom: new ImmutableStore(99), + widenFactor: 0, + },{ + ignoreZoom: true + }) + + super(...geojsonSources, ...Array.from(fromCache.values()), ...mvtSources, ...nonMvtSources, downloadAll) this.isLoading = isLoading supportsForceDownload.push(...geojsonSources) supportsForceDownload.push(...mvtSources) // Non-mvt sources are handled by overpass + + + this._mapBounds = mapProperties.bounds + this._downloadAll = downloadAll + this.supportsForceDownload = supportsForceDownload + } private static setupMvtSource( layer: LayerConfig, mapProperties: { zoom: Store; bounds: Store }, - isActive?: Store + isActive?: Store, ): UpdatableFeatureSource { return new DynamicMvtileSource(layer, mapProperties, { isActive }) } @@ -122,12 +149,12 @@ export default class ThemeSource extends FeatureSourceMerger { private static setupGeojsonSource( layer: LayerConfig, mapProperties: { zoom: Store; bounds: Store }, - isActiveByFilter?: Store + isActiveByFilter?: Store, ): UpdatableFeatureSource { const source = layer.source const isActive = mapProperties.zoom.map( (z) => (isActiveByFilter?.data ?? true) && z >= layer.minzoom, - [isActiveByFilter] + [isActiveByFilter], ) if (source.geojsonZoomLevel === undefined) { // This is a 'load everything at once' geojson layer @@ -143,7 +170,7 @@ export default class ThemeSource extends FeatureSourceMerger { zoom: Store, backend: string, featureSwitches: FeatureSwitchState, - fullNodeDatabase: FullNodeDatabaseSource + fullNodeDatabase: FullNodeDatabaseSource, ): OsmFeatureSource | undefined { if (osmLayers.length == 0) { return undefined @@ -177,7 +204,7 @@ export default class ThemeSource extends FeatureSourceMerger { osmLayers: LayerConfig[], bounds: Store, zoom: Store, - featureSwitches: FeatureSwitchState + featureSwitches: FeatureSwitchState, ): OverpassFeatureSource | undefined { if (osmLayers.length == 0) { return undefined @@ -206,13 +233,14 @@ export default class ThemeSource extends FeatureSourceMerger { { padToTiles: zoom.map((zoom) => Math.min(15, zoom + 1)), isActive, - } + }, ) } public async downloadAll() { - console.log("Downloading all data") - await Promise.all(this.supportsForceDownload.map((i) => i.updateAsync())) + console.log("Downloading all data:") + await this._downloadAll.updateAsync(this._mapBounds.data) + // await Promise.all(this.supportsForceDownload.map((i) => i.updateAsync())) console.log("Done") } } diff --git a/src/Logic/Web/LinkedDataLoader.ts b/src/Logic/Web/LinkedDataLoader.ts index 6021c3061..c2eb21f69 100644 --- a/src/Logic/Web/LinkedDataLoader.ts +++ b/src/Logic/Web/LinkedDataLoader.ts @@ -371,6 +371,10 @@ export default class LinkedDataLoader { const match = maxstay.match(/P([0-9]+)D/) if (match) { const days = Number(match[1]) + if(days === 30){ + // 30 is the default which is set if velopark didn't know the actual value + return undefined + } if (days === 1) { return "1 day" } diff --git a/src/Logic/Web/VeloparkLoader.ts b/src/Logic/Web/VeloparkLoader.ts index 904f7ca25..dffaec485 100644 --- a/src/Logic/Web/VeloparkLoader.ts +++ b/src/Logic/Web/VeloparkLoader.ts @@ -17,7 +17,7 @@ export default class VeloparkLoader { private static readonly coder = new CountryCoder( Constants.countryCoderEndpoint, - Utils.downloadJson + Utils.downloadJson, ) public static convert(veloparkData: VeloparkData): Feature { @@ -46,14 +46,14 @@ export default class VeloparkLoader { if (veloparkData.contactPoint?.email) { properties["operator:email"] = VeloparkLoader.emailReformatting.reformat( - veloparkData.contactPoint?.email + veloparkData.contactPoint?.email, ) } if (veloparkData.contactPoint?.telephone) { properties["operator:phone"] = VeloparkLoader.phoneValidator.reformat( veloparkData.contactPoint?.telephone, - () => "be" + () => "be", ) } @@ -78,9 +78,12 @@ export default class VeloparkLoader { ) { const duration = g.maximumParkingDuration.substring( 1, - g.maximumParkingDuration.length - 1 + g.maximumParkingDuration.length - 1, ) - properties.maxstay = duration + " days" + if (duration !== "30") { + // We don't set maxstay if it is 30, they are the default value that velopark chose for "unknown" + properties.maxstay = duration + " days" + } } properties.access = g.publicAccess ?? "yes" ? "yes" : "no" const prefix = "http://schema.org/" @@ -94,11 +97,11 @@ export default class VeloparkLoader { const startHour = spec.opens const endHour = spec.closes === "23:59" ? "24:00" : spec.closes const merged = OH.MergeTimes( - OH.ParseRule(dayOfWeek + " " + startHour + "-" + endHour) + OH.ParseRule(dayOfWeek + " " + startHour + "-" + endHour), ) return OH.ToString(merged) }) - .join("; ") + .join("; "), ) properties.opening_hours = oh }