Merge branch 'master' into develop

This commit is contained in:
Pieter Vander Vennet 2024-11-12 20:03:15 +01:00
commit 34e7233498
19 changed files with 706 additions and 46 deletions

4
.gitignore vendored
View file

@ -49,3 +49,7 @@ error_changeset_*
public/*.webmanifest
public/assets/generated/
public/assets/langs/*
android/
dist-full/
public/assets/icons/*.webp

View file

@ -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.",

1
langs/el.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -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 <a href='https://mangrove.reviews/terms' target='_blank'>los T&C y la política de privacidad de Mangrove.reviews</a>",
"tos": "Si creas una revisión, aceptas <a href='https://mangrove.reviews/terms' target='_blank'>los TOS y la política de privacidad de Mangrove.reviews</a>",
"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"

View file

@ -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.",

View file

@ -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 <b>{operator}</b> 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": {

1
langs/layers/el.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -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": {

View file

@ -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": "Має <div style='display: inline-block'><b><b>USB</b> для зарядки телефонів і малої електроніки</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> роз'єм"
}
}
}
},
"tagRenderings": {
"Auth phone": {
"question": "Який номер телефону для аутентифікаційного дзвінка або SMS?",
"render": "Авторизуйтесь, зателефонувавши або надіславши SMS на <a href='tel:{authentication:phone_call:number}'>{authentication:phone_call:number}</a>"
},
"Authentication": {
"mappings": {
"2": {
"then": "Доступна автентифікація за допомогою телефонного дзвінка"
}
}
},
"Available_charging_stations (generated)": {
"mappings": {
"26": {
"then": "<b>USB</b> для зарядки телефонів і дрібної електроніки"
},
"27": {
"then": "<b>USB</b> для зарядки телефонів і дрібної електроніки"
}
}
},
"Network": {
"mappings": {
"0": {
@ -605,6 +658,19 @@
},
"email": {
"question": "Яка електронна адреса оператора?"
},
"phone": {
"question": "За яким номером можна зателефонувати, якщо виникла проблема з цією зарядною станцією?",
"render": "У разі виникнення проблем телефонуйте <a href='tel:{phone}'>{phone}</a>"
},
"rewritten-questions": {
"rewrite": {
"into": {
"13": {
"2": "<b>USB</b> для зарядки телефонів і дрібної електроніки"
}
}
}
}
}
},
@ -644,6 +710,10 @@
"tagRenderings": {
"defibrillator-access": {
"render": "Доступ {access}"
},
"defibrillator-phone": {
"question": "Який номер телефону для запитань щодо цього дефібрилятора?",
"render": "Телефонуйте з питань щодо цього дефібрилятора: <a href='tel:{phone}'>{phone}</a>"
}
}
},
@ -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": "<a href='tel:{phone}'>{phone}</a>"
},
"playground-surface": {
"mappings": {
@ -1073,6 +1168,9 @@
"then": "Ця локація пропонує послуги для bpost"
}
}
},
"post_offic_brand": {
"render": "Це поштове відділення {brand}"
}
}
},
@ -1151,7 +1249,43 @@
"question": "Які години роботи {title()}?",
"render": "<h3>Години роботи</h3>{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": "Робочий стан - це <i>{operational_status}</i>"
}
}
},
"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": {

View file

@ -1,9 +1,10 @@
{
"advanced": {
"title": "Advanced features"
"title": "高级功能"
},
"centerMessage": {
"allFilteredAway": "当前视图中没有满足过滤条件的要素"
"allFilteredAway": "当前视图中没有满足过滤条件的要素",
"loadingData": "加载数据…"
},
"validation": {
"wikidata": {

1
langs/themes/el.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -49,14 +49,15 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource {
},
options?: {
padToTiles?: Store<number>
isActive?: Store<boolean>
isActive?: Store<boolean>,
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<void> {
public async updateAsync(overrideBounds?: BBox): Promise<void> {
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) {

View file

@ -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<BBox>
constructor(
layers: LayerConfig[],
featureSwitches: FeatureSwitchState,
@ -35,7 +42,7 @@ export default class ThemeSource extends FeatureSourceMerger {
backend: string,
isDisplayed: (id: string) => Store<boolean>,
mvtAvailableLayers: Set<string>,
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<BBox> = new UIEventSource<BBox>(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<number>; bounds: Store<BBox> },
isActive?: Store<boolean>
isActive?: Store<boolean>,
): 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<number>; bounds: Store<BBox> },
isActiveByFilter?: Store<boolean>
isActiveByFilter?: Store<boolean>,
): 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<number>,
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<BBox>,
zoom: Store<number>,
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")
}
}

View file

@ -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"
}

View file

@ -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,10 +78,13 @@ export default class VeloparkLoader {
) {
const duration = g.maximumParkingDuration.substring(
1,
g.maximumParkingDuration.length - 1
g.maximumParkingDuration.length - 1,
)
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/"
if (g.openingHoursSpecification) {
@ -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
}