fix sonar findings and translation structure
This commit is contained in:
@@ -26,9 +26,13 @@ Die App richtet sich an Grundschulkinder und Menschen ohne technische Vorkenntni
|
||||
|
||||
## Texte und Übersetzungen
|
||||
|
||||
- Alle eigenen Bedienungstexte liegen zentral in der Klasse `WegwichtelTextsDe`
|
||||
- Alle eigenen Bedienungstexte liegen zentral in der Klasse `WegwichtelTexts`
|
||||
in `public/js/texts-de.js`. Keine neuen sichtbaren Texte direkt in HTML oder
|
||||
Funktionscode schreiben.
|
||||
- Das HTML enthält daraus erzeugte deutsche Standardtexte, damit Beschriftungen
|
||||
vor dem Laden der Skripte vorhanden sind. Nach Änderungen am Textkatalog
|
||||
`node scripts/localize-html.js` ausführen und `public/index.html` mit ablegen.
|
||||
Diese erzeugten Texte nicht von Hand ändern; die i18n-Attribute bleiben erhalten.
|
||||
- JavaScript bezieht Texte über `Wegwichtel.I18n.t(textKey, variables)`.
|
||||
HTML verwendet `data-i18n` bzw. `data-i18n-aria-label`, `data-i18n-title`,
|
||||
`data-i18n-placeholder` und `data-i18n-alt`.
|
||||
@@ -37,6 +41,14 @@ Die App richtet sich an Grundschulkinder und Menschen ohne technische Vorkenntni
|
||||
- Nutzerinhalte wie Streckennamen, Beschreibungen und Aufnahmen nicht automatisch
|
||||
übersetzen oder verändern. Interne Fehlerdiagnosen dürfen technisch bleiben;
|
||||
die sichtbare Meldung muss verständlich sein.
|
||||
- Zusätzliche Sprachen über `I18n.register` ergänzen. Fehlende Übersetzungen
|
||||
fallen auf Deutsch zurück. Sprachwechsel aktualisieren feste Beschriftungen;
|
||||
- Sprachdateien heißen `public/js/texts-<Sprache>.js` und verwenden dieselbe
|
||||
generische Klasse `WegwichtelTexts`. Sie registrieren sich in `TextCatalogs`.
|
||||
Die App lädt Deutsch und Englisch und wählt anhand von `navigator.languages`
|
||||
bzw. `navigator.language`. Fehlende Übersetzungen fallen auf den ersten
|
||||
geladenen Katalog zurück (in der App Deutsch).
|
||||
- Verwende kurze, stabile englische Funktionsschlüssel wie `app.no-route` oder
|
||||
`bootstrap-loader.error.start-error`. Schlüssel nicht aus dem deutschen Text
|
||||
ableiten. Startmeldungen in `bootstrap-loader.steps` nach Schritt gruppieren.
|
||||
- Deutsch und Englisch enthalten dieselben Schlüssel, Platzhalter und Pluralformen.
|
||||
Texte in beiden Sprachen einfach und sinngemäß schreiben. Sprachwechsel aktualisieren feste Beschriftungen;
|
||||
bereits erzeugte dynamische Ansichten bei Bedarf neu rendern.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Texte und Sprachen
|
||||
|
||||
Die Sprachdateien `public/js/texts-de.js` und `public/js/texts-en.js` enthalten
|
||||
jeweils die generische Klasse `WegwichtelTexts`. Beide Dateien verwenden dieselben
|
||||
Schlüssel und registrieren ihren Katalog mit der eigenen Sprachkennung in
|
||||
`Wegwichtel.TextCatalogs`. Es gibt keine sprachabhängigen Klassennamen.
|
||||
|
||||
Die App lädt beide Kataloge vor `i18n.js`. Beim Start wird die erste unterstützte
|
||||
Sprache aus `navigator.languages` gewählt; ohne diese Liste wird
|
||||
`navigator.language` verwendet. Regionale Varianten wie `en-GB` oder `de-DE`
|
||||
verwenden den passenden Grundkatalog. Passt keine Sprache, wird der erste
|
||||
geladene Katalog verwendet: in der App Deutsch. Einzelne fehlende Übersetzungen
|
||||
fallen ebenfalls auf diesen Katalog zurück. Wird nur eine Sprachdatei eingebunden,
|
||||
funktioniert sie allein und bildet den Fallback.
|
||||
|
||||
## Verwendung
|
||||
|
||||
Schlüssel beschreiben die Funktion, nicht den Wortlaut. Zusammengehörige Texte
|
||||
liegen in verschachtelten Abschnitten; der Aufruf verwendet Punkte als Trennung.
|
||||
|
||||
```js
|
||||
Wegwichtel.I18n.t('app.no-route');
|
||||
Wegwichtel.I18n.t('bootstrap-loader.error.start-error');
|
||||
Wegwichtel.I18n.t(`bootstrap-loader.steps.${step}`);
|
||||
Wegwichtel.I18n.t('media.image.count', { count: 2 });
|
||||
```
|
||||
|
||||
`bootstrap-loader.steps` ist nach den stabilen Schrittkennungen aufgebaut:
|
||||
`storage`, `jquery`, `jquery-ui`, `modules`, `server`, `routes` und `location`.
|
||||
Die Zuordnung benötigt keine zusätzliche Liste von Nachrichtenschlüsseln.
|
||||
|
||||
HTML verwendet `data-i18n`, `data-i18n-title`, `data-i18n-aria-label`,
|
||||
`data-i18n-placeholder` und `data-i18n-alt`. Deutsche Standardtexte werden mit
|
||||
`node scripts/localize-html.js` aus dem Katalog ins HTML übernommen. Dadurch
|
||||
stehen Beschriftungen bereits vor dem Laden der Skripte bereit. Nach Änderungen
|
||||
die erzeugte `public/index.html` mit ins Repository übernehmen.
|
||||
|
||||
Zum gezielten Prüfen kann `Wegwichtel.I18n.setLanguage('en')` aufgerufen werden.
|
||||
Das aktualisiert feste Beschriftungen. Bereits aufgebaute dynamische Ansichten
|
||||
müssen neu aufgebaut werden; die automatische Startauswahl erfolgt vor ihrem
|
||||
ersten Aufbau. Ein sichtbarer Sprachschalter ist nicht enthalten.
|
||||
|
||||
Die Tests prüfen beide Kataloge, Platzhalter und Pluralformen, Browserpräferenzen,
|
||||
den Betrieb mit nur einer Sprachdatei, deutsche HTML-Standardtexte und englische
|
||||
App-Meldungen. Nutzerinhalte wie Streckennamen werden nicht übersetzt.
|
||||
|
||||
SonarQube prüft auch die Sprachdateien auf Probleme und Testabdeckung. Nur die
|
||||
Duplikaterkennung nimmt `public/js/texts-*.js` aus: Übersetzungskataloge müssen
|
||||
dieselbe Struktur haben und sind keine mehrfach implementierte Programmlogik.
|
||||
+202
-201
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="theme-color" content="#315d3a">
|
||||
<title data-i18n="html.title.wegwichtel"></title>
|
||||
<title data-i18n="common.app-name">Wegwichtel</title>
|
||||
<link rel="stylesheet" href="css/bootstrap.css?v=0.12.14">
|
||||
<link rel="stylesheet" href="vendor/jquery-ui/jquery-ui-1.14.2.min.css?v=0.12.14">
|
||||
<link rel="stylesheet" href="css/app.css?v=0.12.14">
|
||||
@@ -12,13 +12,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="info-control">
|
||||
<button id="info-button" class="info-button" type="button" aria-label="" title="" aria-haspopup="true" aria-expanded="false" aria-controls="info-dropdown" data-i18n-title="legal-ui.informationen" data-i18n-aria-label="legal-ui.hilfe.und.informationen.offnen">
|
||||
<span aria-hidden="true" data-i18n="html.span.i"></span>
|
||||
<button id="info-button" class="info-button" type="button" aria-label="Hilfe und Informationen öffnen" title="Informationen" aria-haspopup="true" aria-expanded="false" aria-controls="info-dropdown" data-i18n-title="info.title" data-i18n-aria-label="info.open">
|
||||
<span aria-hidden="true" data-i18n="info.icon">i</span>
|
||||
</button>
|
||||
<nav id="info-dropdown" class="info-dropdown" aria-label="" hidden data-i18n-aria-label="html.info-dropdown.hilfe.und.informationen">
|
||||
<button type="button" data-info-target="privacy" data-i18n="legal-ui.datenschutzerklarung"></button>
|
||||
<button type="button" data-info-target="imprint" data-i18n="legal-ui.impressum"></button>
|
||||
<button type="button" data-info-target="technology" data-i18n="legal-ui.speichern.und.berechtigungen"></button>
|
||||
<nav id="info-dropdown" class="info-dropdown" aria-label="Hilfe und Informationen" hidden data-i18n-aria-label="info.dropdown-label">
|
||||
<button type="button" data-info-target="privacy" data-i18n="info.privacy">Datenschutzerklärung</button>
|
||||
<button type="button" data-info-target="imprint" data-i18n="info.imprint">Impressum</button>
|
||||
<button type="button" data-info-target="technology" data-i18n="info.storage">Speichern und Berechtigungen</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="bootstrap-screen" aria-live="polite" aria-busy="true">
|
||||
@@ -33,8 +33,8 @@
|
||||
<circle cx="80" cy="16" r="8" fill="#fff" stroke="#f4b400" stroke-width="5"></circle>
|
||||
</svg>
|
||||
<div>
|
||||
<h1 data-i18n="html.title.wegwichtel"></h1>
|
||||
<p data-i18n="html.p.gleich.kannst.du.mit.dem.wegwichtel.losziehen"></p>
|
||||
<h1 data-i18n="common.app-name">Wegwichtel</h1>
|
||||
<p data-i18n="start.intro">Gleich kannst du mit dem Wegwichtel losziehen.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -64,201 +64,201 @@
|
||||
<div class="bootstrap-action-slot">
|
||||
<output id="bootstrap-action-status" class="bootstrap-action-status" aria-live="polite">
|
||||
<span class="bootstrap-loading-dot" aria-hidden="true"></span>
|
||||
<span id="bootstrap-status-text" class="bootstrap-status-text" data-i18n="html.bootstrap-status-text.wegwichtel.wird.vorbereitet"></span>
|
||||
<span id="bootstrap-status-text" class="bootstrap-status-text" data-i18n="start.preparing">Wegwichtel wird vorbereitet …</span>
|
||||
</output>
|
||||
<button id="bootstrap-enter" class="bootstrap-enter" type="button" hidden>
|
||||
<span data-i18n="html.span.los.gehts"></span>
|
||||
<span data-i18n="common.start">Los geht’s</span>
|
||||
<svg viewbox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m9 5 7 7-7 7"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p id="bootstrap-error" class="bootstrap-error" hidden></p>
|
||||
<button id="bootstrap-retry" class="bootstrap-retry" type="button" hidden data-i18n="html.bootstrap-retry.erneut.versuchen"></button>
|
||||
<p id="bootstrap-ready" class="bootstrap-ready" hidden data-i18n="html.bootstrap-ready.alles.ist.bereit"></p>
|
||||
<button id="bootstrap-retry" class="bootstrap-retry" type="button" hidden data-i18n="common.retry">Erneut versuchen</button>
|
||||
<p id="bootstrap-ready" class="bootstrap-ready" hidden data-i18n="bootstrap-loader.ready">Alles ist bereit.</p>
|
||||
|
||||
<div class="bootstrap-features" aria-label="" data-i18n-aria-label="html.div.wegwichtel.funktionen">
|
||||
<div class="bootstrap-features" aria-label="Wegwichtel Funktionen" data-i18n-aria-label="start.features-label">
|
||||
<div>
|
||||
<svg viewbox="0 0 32 32" aria-hidden="true" focusable="false"><path d="m3 7 8-3 10 4 8-3v20l-8 3-10-4-8 3Zm8-3v20m10-16v20"></path></svg>
|
||||
<span><span data-i18n="html.span.strecken"></span><br><span data-i18n="html.span.entdecken"></span></span>
|
||||
<span><span data-i18n="start.features.routes">Strecken</span><br><span data-i18n="start.features.explore">entdecken</span></span>
|
||||
</div>
|
||||
<div>
|
||||
<svg viewbox="0 0 32 32" aria-hidden="true" focusable="false"><path d="M7 18v-4a9 9 0 0 1 18 0v4M7 18H4v7h5v-7Zm18 0h3v7h-5v-7Z"></path></svg>
|
||||
<span><span data-i18n="html.span.spannende"></span><br><span data-i18n="html.span.geschichten"></span></span>
|
||||
<span><span data-i18n="start.features.exciting">Spannende</span><br><span data-i18n="start.features.stories">Geschichten</span></span>
|
||||
</div>
|
||||
<div>
|
||||
<svg viewbox="0 0 32 32" aria-hidden="true" focusable="false"><path d="m16 3 3.9 8 8.8 1.3-6.4 6.2 1.5 8.8-7.8-4.1-7.8 4.1 1.5-8.8-6.4-6.2 8.8-1.3Z"></path></svg>
|
||||
<span><span data-i18n="html.span.kindgerechte"></span><br><span data-i18n="html.span.erlebnisse"></span></span>
|
||||
<span><span data-i18n="start.features.child-friendly">Kindgerechte</span><br><span data-i18n="start.features.experiences">Erlebnisse</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="android-browser-hint" class="android-browser-hint" hidden></p>
|
||||
|
||||
<div class="privacy-summary">
|
||||
<p data-i18n="html.p.hier.gibt.es.keine.werbung.wir.verfolgen"></p>
|
||||
<label class="switch-row switch-disabled">
|
||||
<p data-i18n="privacy.no-tracking">Hier gibt es keine Werbung. Wir verfolgen nicht, welche anderen Seiten du besuchst.</p>
|
||||
<label for="cookie-toggle-1" class="switch-row switch-disabled">
|
||||
<span>
|
||||
<strong data-i18n="html.strong.optionale.cookies"></strong>
|
||||
<small data-i18n="html.small.diese.funktion.wird.nicht.verwendet"></small>
|
||||
<strong data-i18n="progress.cookies-label">Optionale Cookies</strong>
|
||||
<small data-i18n="progress.cookies-unused">Diese Funktion wird nicht verwendet.</small>
|
||||
</span>
|
||||
<input class="cookie-toggle" type="checkbox" role="switch" aria-checked="false" disabled>
|
||||
<input id="cookie-toggle-1" class="cookie-toggle" type="checkbox" role="switch" aria-checked="false" disabled>
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<label class="switch-row">
|
||||
<label for="progress-storage-toggle-1" class="switch-row">
|
||||
<span>
|
||||
<strong data-i18n="html.strong.meinen.fortschritt.merken"></strong>
|
||||
<small data-i18n="html.small.merkt.sich.auf.diesem.gerat.welche.stationen"></small>
|
||||
<strong data-i18n="progress.remember">Meinen Fortschritt merken</strong>
|
||||
<small data-i18n="progress.remember-hint">Merkt sich auf diesem Gerät, welche Stationen du schon besucht hast.</small>
|
||||
</span>
|
||||
<input class="progress-storage-toggle" type="checkbox" role="switch" aria-checked="false">
|
||||
<input id="progress-storage-toggle-1" class="progress-storage-toggle" type="checkbox" role="switch" aria-checked="false">
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<div class="offline-option-row">
|
||||
<label class="switch-row">
|
||||
<label for="offline-cache-toggle" class="switch-row">
|
||||
<span>
|
||||
<strong data-i18n="html.strong.app.ohne.internet.offnen"></strong>
|
||||
<small data-i18n="html.small.damit.kannst.du.wegwichtel.auch.ohne.internet"></small>
|
||||
<strong data-i18n="offline.open-app">App ohne Internet öffnen</strong>
|
||||
<small data-i18n="offline.open-app-hint">Damit kannst du Wegwichtel auch ohne Internet öffnen. Wähle danach die Strecken aus, die du mitnehmen möchtest.</small>
|
||||
</span>
|
||||
<input id="offline-cache-toggle" type="checkbox" role="switch" aria-checked="false" aria-describedby="offline-cache-status">
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<button id="offline-cache-help-button" class="offline-help-button" type="button" aria-label="" title="" aria-expanded="false" aria-controls="offline-cache-help" data-i18n-title="html.offline-cache-help-button.was.wird.fur.unterwegs.gespeichert" data-i18n-aria-label="html.offline-cache-help-button.hilfe.zum.speichern.fur.unterwegs">?</button>
|
||||
<button id="offline-cache-help-button" class="offline-help-button" type="button" aria-label="Hilfe zum Speichern für unterwegs" title="Was wird für unterwegs gespeichert?" aria-expanded="false" aria-controls="offline-cache-help" data-i18n-title="offline.help-title" data-i18n-aria-label="offline.help-label">?</button>
|
||||
</div>
|
||||
<section id="offline-cache-help" class="offline-help" aria-labelledby="offline-cache-help-title" hidden>
|
||||
<h2 id="offline-cache-help-title" data-i18n="html.offline-cache-help-button.was.wird.fur.unterwegs.gespeichert"></h2>
|
||||
<p data-i18n="html.p.mit.diesem.schalter.kannst.du.wegwichtel.auch"></p>
|
||||
<p data-i18n="html.p.offne.eine.strecke.und.tippe.auf.fur"></p>
|
||||
<p data-i18n="html.p.warte.vor.dem.losgehen.bis.alles.gespeichert"></p>
|
||||
<p data-i18n="html.p.alles.bleibt.auf.diesem.gerat.der.schalter"></p>
|
||||
<p data-i18n="html.p.wenn.du.die.gespeicherten.daten.von.wegwichtel"></p>
|
||||
<h2 id="offline-cache-help-title" data-i18n="offline.help-title">Was wird für unterwegs gespeichert?</h2>
|
||||
<p data-i18n="offline.help.app-only">Mit diesem Schalter kannst du Wegwichtel auch ohne Internet öffnen. Strecken werden dabei noch nicht gespeichert.</p>
|
||||
<p data-i18n="offline.help.save-route">Öffne eine Strecke und tippe auf „Für unterwegs speichern“. So nimmst du den Weg, die Stationen, Bilder und Tonaufnahmen mit.</p>
|
||||
<p data-i18n="offline.help.connection">Warte vor dem Losgehen, bis alles gespeichert ist. Zum Speichern und Erneuern brauchst du Internet.</p>
|
||||
<p data-i18n="offline.help.keep-routes">Alles bleibt auf diesem Gerät. Der Schalter zeichnet deinen Weg nicht auf. Beim Ausschalten bleiben deine gespeicherten Strecken und Entwürfe erhalten.</p>
|
||||
<p data-i18n="offline.help.storage-limits">Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetprogramm löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen.</p>
|
||||
</section>
|
||||
<output id="offline-cache-status" aria-live="polite"></output>
|
||||
<button id="offline-cache-refresh" type="button" hidden data-i18n="html.offline-cache-refresh.gespeicherte.app.erneuern"></button>
|
||||
<button id="offline-cache-refresh" type="button" hidden data-i18n="offline.renew-app">Gespeicherte App erneuern</button>
|
||||
</div>
|
||||
<section class="storage-usage" data-storage-usage aria-label="" data-i18n-aria-label="html.section.platz.fur.wegwichtel">
|
||||
<h3 data-i18n="html.section.platz.fur.wegwichtel"></h3>
|
||||
<meter data-storage-site-meter min="0" max="1" value="0" aria-label="" hidden data-i18n-aria-label="html.meter.belegter.platz.fur.app.bilder.und.ton"></meter>
|
||||
<p data-storage-site-text data-i18n="html.p.wir.schauen.wie.viel.platz.belegt.ist"></p>
|
||||
<meter data-storage-local-meter min="0" max="5242880" value="0" aria-label="" data-i18n-aria-label="html.meter.belegter.platz.fur.texte.und.einstellungen.der"></meter>
|
||||
<section class="storage-usage" data-storage-usage aria-label="Platz für Wegwichtel" data-i18n-aria-label="storage.title">
|
||||
<h3 data-i18n="storage.title">Platz für Wegwichtel</h3>
|
||||
<meter data-storage-site-meter min="0" max="1" value="0" aria-label="Belegter Platz für App, Bilder und Ton" hidden data-i18n-aria-label="storage.site-meter-label"></meter>
|
||||
<p data-storage-site-text data-i18n="storage.loading">Wir schauen, wie viel Platz belegt ist …</p>
|
||||
<meter data-storage-local-meter min="0" max="5242880" value="0" aria-label="Belegter Platz für Texte und Einstellungen; der Balken zeigt einen ungefähren Richtwert" data-i18n-aria-label="storage.local-meter-label"></meter>
|
||||
<p data-storage-local-text></p>
|
||||
<p data-i18n="html.p.der.erste.balken.zeigt.den.platz.fur"></p>
|
||||
<p data-i18n="storage.help">Der erste Balken zeigt den Platz für die App, Bilder und Ton. Der zweite zeigt den Platz für Texte und Einstellungen. Die Balken zeigen nur den Platz für Wegwichtel.</p>
|
||||
<output data-storage-warning hidden></output>
|
||||
<button data-storage-refresh type="button" data-i18n="html.button.platz.prufen"></button>
|
||||
<button data-storage-refresh type="button" data-i18n="storage.refresh">Platz prüfen</button>
|
||||
</section>
|
||||
<p id="storage-cleanup-status" class="storage-cleanup-status" hidden></p>
|
||||
|
||||
<progress id="bootstrap-progress" class="bootstrap-state-model" max="7" value="0" hidden></progress>
|
||||
<ol class="bootstrap-state-model" hidden>
|
||||
<li data-step="storage" data-i18n="html.li.gespeicherte.inhalte.prufen"></li>
|
||||
<li data-step="jquery" data-i18n="html.li.wegwichtel.vorbereiten"></li>
|
||||
<li data-step="jquery-ui" data-i18n="html.li.bedienung.vorbereiten"></li>
|
||||
<li data-step="modules" data-i18n="html.li.app.vorbereiten"></li>
|
||||
<li data-step="server" data-i18n="html.li.verbindung.prufen"></li>
|
||||
<li data-step="routes" data-i18n="html.li.strecken.laden"></li>
|
||||
<li data-step="location" data-i18n="html.li.deinen.standort.suchen"></li>
|
||||
<li data-step="storage" data-i18n="bootstrap-loader.steps.storage">Gespeicherte Inhalte prüfen …</li>
|
||||
<li data-step="jquery" data-i18n="bootstrap-loader.steps.jquery">Wegwichtel vorbereiten …</li>
|
||||
<li data-step="jquery-ui" data-i18n="bootstrap-loader.steps.jquery-ui">Die Bedienung vorbereiten …</li>
|
||||
<li data-step="modules" data-i18n="bootstrap-loader.steps.modules">Die App vorbereiten …</li>
|
||||
<li data-step="server" data-i18n="bootstrap-loader.steps.server">Verbindung prüfen …</li>
|
||||
<li data-step="routes" data-i18n="bootstrap-loader.steps.routes">Strecken laden …</li>
|
||||
<li data-step="location" data-i18n="bootstrap-loader.steps.location">Deinen Standort suchen …</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
<main id="app-shell" hidden>
|
||||
<div class="route-control">
|
||||
<div class="route-control-summary" aria-live="polite">
|
||||
<strong id="route-control-title" data-i18n="app.strecke.wahlen"></strong>
|
||||
<small id="route-control-next" data-i18n="app.strecken.fur.alle.und.meine.entwurfe"></small>
|
||||
<strong id="route-control-title" data-i18n="app.choose-route">Strecke wählen</strong>
|
||||
<small id="route-control-next" data-i18n="app.route-menu">Strecken für alle und meine Entwürfe</small>
|
||||
</div>
|
||||
<button id="route-control-button" class="route-control-button" type="button" aria-label="" title="" aria-haspopup="true" aria-expanded="false" aria-controls="route-control-dropdown" data-i18n-title="html.route-control-button.strecke.auswahlen.oder.wechseln" data-i18n-aria-label="html.route-control-button.strecke.auswahlen.oder.wechseln">
|
||||
<button id="route-control-button" class="route-control-button" type="button" aria-label="Strecke auswählen oder wechseln" title="Strecke auswählen oder wechseln" aria-haspopup="true" aria-expanded="false" aria-controls="route-control-dropdown" data-i18n-title="routes.open-menu" data-i18n-aria-label="routes.open-menu">
|
||||
<img src="images/icons/route-management.svg" alt="" aria-hidden="true">
|
||||
</button>
|
||||
<nav id="route-control-dropdown" class="route-control-dropdown" aria-label="" hidden data-i18n-aria-label="html.route-control-dropdown.strecken.auswahlen.und.bearbeiten">
|
||||
<nav id="route-control-dropdown" class="route-control-dropdown" aria-label="Strecken auswählen und bearbeiten" hidden data-i18n-aria-label="routes.menu-label">
|
||||
<section aria-labelledby="route-control-server-title">
|
||||
<h2 id="route-control-server-title" data-i18n="editor-ui.strecken.fur.alle"></h2>
|
||||
<h2 id="route-control-server-title" data-i18n="editor.public-routes">Strecken für alle</h2>
|
||||
<ul id="route-control-server-list" class="route-control-list" aria-live="polite"></ul>
|
||||
</section>
|
||||
<section aria-labelledby="route-control-local-title">
|
||||
<h2 id="route-control-local-title" data-i18n="editor-ui.meine.entwurfe"></h2>
|
||||
<h2 id="route-control-local-title" data-i18n="editor.drafts">Meine Entwürfe</h2>
|
||||
<ul id="route-control-local-list" class="route-control-list" aria-live="polite"></ul>
|
||||
</section>
|
||||
<div class="route-control-actions">
|
||||
<button id="editor-button" class="editor-button" type="button" aria-label="" title="" aria-haspopup="dialog" aria-expanded="false" aria-controls="editor-dialog" data-i18n-title="html.editor-button.strecken.bearbeiten" data-i18n-aria-label="html.editor-button.strecken.bearbeiten" data-i18n="html.editor-button.strecken.bearbeiten"></button>
|
||||
<button id="route-control-new" class="editor-primary-button" type="button" data-i18n="editor-ui.neue.strecke"></button>
|
||||
<button id="editor-button" class="editor-button" type="button" aria-label="Strecken bearbeiten" title="Strecken bearbeiten" aria-haspopup="dialog" aria-expanded="false" aria-controls="editor-dialog" data-i18n-title="editor.open" data-i18n-aria-label="editor.open" data-i18n="editor.open">Strecken bearbeiten</button>
|
||||
<button id="route-control-new" class="editor-primary-button" type="button" data-i18n="editor.new-route">Neue Strecke</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<section id="routes-page" class="app-page">
|
||||
<header>
|
||||
<h1 data-i18n="html.title.wegwichtel"></h1>
|
||||
<h1 data-i18n="common.app-name">Wegwichtel</h1>
|
||||
</header>
|
||||
<div class="content">
|
||||
<p id="location-hint" class="notice" data-i18n="html.location-hint.wir.suchen.strecken.in.deiner.nahe.dein"></p>
|
||||
<p id="location-hint" class="notice" data-i18n="routes.location-hint">Wir suchen Strecken in deiner Nähe. Dein Standort bleibt auf diesem Gerät.</p>
|
||||
|
||||
<section>
|
||||
<h2 data-i18n="html.h2.strecken.in.deiner.nahe"></h2>
|
||||
<h2 data-i18n="routes.nearby">Strecken in deiner Nähe</h2>
|
||||
<ul id="nearby-route-list" class="route-list" aria-live="polite">
|
||||
<li><p data-i18n="editor-ui.strecken.werden.geladen"></p></li>
|
||||
<li><p data-i18n="editor.loading-routes">Strecken werden geladen …</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2 data-i18n="html.h2.alle.strecken"></h2>
|
||||
<label for="route-search" data-i18n="html.label.strecken.durchsuchen"></label>
|
||||
<h2 data-i18n="routes.all">Alle Strecken</h2>
|
||||
<label for="route-search" data-i18n="routes.search-label">Strecken durchsuchen</label>
|
||||
<div class="search-row">
|
||||
<input id="route-search" type="search" autocomplete="off" inputmode="search" placeholder="" data-i18n-placeholder="html.route-search.name.schule.oder.beschreibung">
|
||||
<button id="clear-route-search" type="button" data-i18n="html.clear-route-search.suche.leeren"></button>
|
||||
<input id="route-search" type="search" autocomplete="off" inputmode="search" placeholder="Name, Schule oder Beschreibung" data-i18n-placeholder="routes.search-placeholder">
|
||||
<button id="clear-route-search" type="button" data-i18n="routes.clear-search">Suche leeren</button>
|
||||
</div>
|
||||
<p id="all-routes-summary" aria-live="polite" data-i18n="editor-ui.strecken.werden.geladen"></p>
|
||||
<p id="all-routes-summary" aria-live="polite" data-i18n="editor.loading-routes">Strecken werden geladen …</p>
|
||||
<ul id="all-route-list" class="route-list" aria-live="polite">
|
||||
<li><p data-i18n="editor-ui.strecken.werden.geladen"></p></li>
|
||||
<li><p data-i18n="editor.loading-routes">Strecken werden geladen …</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section aria-labelledby="local-route-title">
|
||||
<h2 id="local-route-title" data-i18n="editor-ui.meine.entwurfe"></h2>
|
||||
<p data-i18n="html.p.diese.strecken.hast.du.auf.diesem.gerat"></p>
|
||||
<h2 id="local-route-title" data-i18n="editor.drafts">Meine Entwürfe</h2>
|
||||
<p data-i18n="routes.drafts-hint">Diese Strecken hast du auf diesem Gerät angelegt. Andere können sie noch nicht sehen.</p>
|
||||
<ul id="local-route-list" class="route-list" aria-live="polite">
|
||||
<li><p data-i18n="html.p.meine.entwurfe.werden.geladen"></p></li>
|
||||
<li><p data-i18n="routes.loading-drafts">Meine Entwürfe werden geladen …</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<section id="route-page" class="app-page" hidden>
|
||||
<header class="has-back-button">
|
||||
<button class="back-button" type="button" data-page-target="#routes-page" aria-label="" title="" data-i18n-title="html.button.zuruck.zur.streckenauswahl" data-i18n-aria-label="html.button.zuruck.zur.streckenauswahl">
|
||||
<button class="back-button" type="button" data-page-target="#routes-page" aria-label="Zurück zur Streckenauswahl" title="Zurück zur Streckenauswahl" data-i18n-title="routes.back" data-i18n-aria-label="routes.back">
|
||||
<img src="images/icons/back.svg" alt="">
|
||||
</button>
|
||||
<h1 id="route-title" data-i18n="app.strecke"></h1>
|
||||
<h1 id="route-title" data-i18n="app.route">Strecke</h1>
|
||||
</header>
|
||||
<div class="content">
|
||||
<p id="route-description"></p>
|
||||
<section id="route-offline-controls" class="route-offline-controls" aria-label="" hidden data-i18n-aria-label="html.route-offline-controls.strecke.auf.dem.gerat.speichern">
|
||||
<button id="route-offline-save" type="button" data-i18n="offline-cache.fur.unterwegs.speichern"></button>
|
||||
<button id="route-offline-remove" type="button" hidden data-i18n="html.route-offline-remove.gespeicherte.strecke.entfernen"></button>
|
||||
<a id="route-gpx-download" download hidden data-i18n="html.route-gpx-download.weg.als.datei.herunterladen"></a>
|
||||
<section id="route-offline-controls" class="route-offline-controls" aria-label="Strecke auf dem Gerät speichern" hidden data-i18n-aria-label="offline.route-controls-label">
|
||||
<button id="route-offline-save" type="button" data-i18n="offline.save-route">Für unterwegs speichern</button>
|
||||
<button id="route-offline-remove" type="button" hidden data-i18n="offline.remove-route">Gespeicherte Strecke entfernen</button>
|
||||
<a id="route-gpx-download" download hidden data-i18n="offline.export-path">Weg als Datei herunterladen</a>
|
||||
<output id="route-offline-status" aria-live="polite"></output>
|
||||
<p data-i18n="html.p.fur.unterwegs.speichern.nimmt.diese.strecke.mit"></p>
|
||||
<p data-i18n="offline.route-actions-help">„Für unterwegs speichern“ nimmt diese Strecke mit Bildern und Ton mit. „Weg als Datei herunterladen“ speichert nur den Weg für eine andere Wander-App.</p>
|
||||
</section>
|
||||
<dl>
|
||||
<div><dt data-i18n="html.dt.lange"></dt><dd id="route-distance">–</dd></div>
|
||||
<div><dt data-i18n="html.dt.hohenmeter"></dt><dd id="route-elevation">–</dd></div>
|
||||
<div><dt data-i18n="html.dt.stationen"></dt><dd id="route-poi-count">–</dd></div>
|
||||
<div><dt data-i18n="route.length">Länge</dt><dd id="route-distance">–</dd></div>
|
||||
<div><dt data-i18n="route.elevation">Höhenmeter</dt><dd id="route-elevation">–</dd></div>
|
||||
<div><dt data-i18n="route.station-count">Stationen</dt><dd id="route-poi-count">–</dd></div>
|
||||
</dl>
|
||||
<fieldset>
|
||||
<legend data-i18n="html.legend.unterwegs"></legend>
|
||||
<button id="start-route" type="button" aria-label="" title="" data-i18n-title="app.strecke.starten" data-i18n-aria-label="app.strecke.starten">
|
||||
<img src="images/icons/play.svg" alt="" data-i18n-alt="app.strecke.starten">
|
||||
<legend data-i18n="route.controls-label">Unterwegs</legend>
|
||||
<button id="start-route" type="button" aria-label="Strecke starten" title="Strecke starten" data-i18n-title="app.start-route" data-i18n-aria-label="app.start-route">
|
||||
<img src="images/icons/play.svg" alt="Strecke starten" data-i18n-alt="app.start-route">
|
||||
</button>
|
||||
<button id="pause-route" type="button" aria-label="" title="" disabled data-i18n-title="html.pause-route.strecke.pausieren" data-i18n-aria-label="html.pause-route.strecke.pausieren">
|
||||
<img src="images/icons/pause.svg" alt="" data-i18n-alt="html.pause-route.strecke.pausieren">
|
||||
<button id="pause-route" type="button" aria-label="Strecke pausieren" title="Strecke pausieren" disabled data-i18n-title="route.pause" data-i18n-aria-label="route.pause">
|
||||
<img src="images/icons/pause.svg" alt="Strecke pausieren" data-i18n-alt="route.pause">
|
||||
</button>
|
||||
<button id="stop-route" type="button" aria-label="" title="" disabled data-i18n-title="html.stop-route.strecke.beenden" data-i18n-aria-label="html.stop-route.strecke.beenden">
|
||||
<img src="images/icons/stop.svg" alt="" data-i18n-alt="html.stop-route.strecke.beenden">
|
||||
<button id="stop-route" type="button" aria-label="Strecke beenden" title="Strecke beenden" disabled data-i18n-title="route.stop" data-i18n-aria-label="route.stop">
|
||||
<img src="images/icons/stop.svg" alt="Strecke beenden" data-i18n-alt="route.stop">
|
||||
</button>
|
||||
</fieldset>
|
||||
<p id="tracking-status" class="notice" hidden></p>
|
||||
<h2 data-i18n="html.dt.stationen"></h2>
|
||||
<h2 data-i18n="route.station-count">Stationen</h2>
|
||||
<ul id="poi-list" class="route-list"></ul>
|
||||
</div>
|
||||
</section>
|
||||
<section id="poi-page" class="app-page" hidden>
|
||||
<header class="has-back-button">
|
||||
<button class="back-button" type="button" data-page-target="#route-page" aria-label="" title="" data-i18n-title="html.button.zuruck.zur.strecke" data-i18n-aria-label="html.button.zuruck.zur.strecke">
|
||||
<button class="back-button" type="button" data-page-target="#route-page" aria-label="Zurück zur Strecke" title="Zurück zur Strecke" data-i18n-title="route.back" data-i18n-aria-label="route.back">
|
||||
<img src="images/icons/back.svg" alt="">
|
||||
</button>
|
||||
<h1 id="poi-title" data-i18n="html.poi-title.station"></h1>
|
||||
<h1 id="poi-title" data-i18n="station.title">Station</h1>
|
||||
</header>
|
||||
<div class="content">
|
||||
<div id="slideshow" aria-live="polite">
|
||||
@@ -266,21 +266,21 @@
|
||||
<img id="slide-image" alt="">
|
||||
</figure>
|
||||
<fieldset>
|
||||
<legend data-i18n="html.legend.bildnavigation"></legend>
|
||||
<button id="slide-prev" type="button" aria-label="" title="" data-i18n-title="html.slide-prev.vorheriges.bild" data-i18n-aria-label="html.slide-prev.vorheriges.bild">
|
||||
<img src="images/icons/chevron-left.svg" alt="" data-i18n-alt="html.slide-prev.vorheriges.bild">
|
||||
<legend data-i18n="slideshow.controls-label">Bildnavigation</legend>
|
||||
<button id="slide-prev" type="button" aria-label="Vorheriges Bild" title="Vorheriges Bild" data-i18n-title="slideshow.previous" data-i18n-aria-label="slideshow.previous">
|
||||
<img src="images/icons/chevron-left.svg" alt="Vorheriges Bild" data-i18n-alt="slideshow.previous">
|
||||
</button>
|
||||
<div>
|
||||
<p id="slide-caption"></p>
|
||||
<span id="slide-position">0 / 0</span>
|
||||
</div>
|
||||
<button id="slide-next" type="button" aria-label="" title="" data-i18n-title="html.slide-next.nachstes.bild" data-i18n-aria-label="html.slide-next.nachstes.bild">
|
||||
<img src="images/icons/chevron-right.svg" alt="" data-i18n-alt="html.slide-next.nachstes.bild">
|
||||
<button id="slide-next" type="button" aria-label="Nächstes Bild" title="Nächstes Bild" data-i18n-title="slideshow.next" data-i18n-aria-label="slideshow.next">
|
||||
<img src="images/icons/chevron-right.svg" alt="Nächstes Bild" data-i18n-alt="slideshow.next">
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p id="poi-description"></p>
|
||||
<audio id="poi-audio" controls preload="metadata" aria-label="" hidden data-i18n-aria-label="html.poi-audio.ton.zur.station" data-i18n="html.poi-audio.dieses.gerat.kann.den.ton.leider.nicht"></audio>
|
||||
<audio id="poi-audio" controls preload="metadata" aria-label="Ton zur Station" hidden data-i18n-aria-label="audio.station-label" data-i18n="audio.unsupported">Dieses Gerät kann den Ton leider nicht abspielen.</audio>
|
||||
</div>
|
||||
</section>
|
||||
<footer id="navigation-footer" hidden aria-live="polite">
|
||||
@@ -288,7 +288,7 @@
|
||||
<path d="M32 4 53 54 32 44 11 54Z"></path>
|
||||
</svg>
|
||||
<p>
|
||||
<span id="navigation-mode" data-i18n="html.navigation-mode.richtung.zum.nachsten.station"></span>
|
||||
<span id="navigation-mode" data-i18n="navigation.station-direction">Richtung zum nächsten Station</span>
|
||||
<strong id="navigation-distance">–</strong>
|
||||
</p>
|
||||
</footer>
|
||||
@@ -296,94 +296,94 @@
|
||||
<dialog id="info-dialog" class="info-dialog" aria-labelledby="info-dialog-title">
|
||||
<div class="info-dialog-shell">
|
||||
<header class="info-dialog-header">
|
||||
<h2 id="info-dialog-title" data-i18n="legal-ui.informationen"></h2>
|
||||
<button id="info-close" type="button" aria-label="" title="" data-i18n-title="legal-ui.schliessen" data-i18n-aria-label="legal-ui.informationsfenster.schliessen">×</button>
|
||||
<h2 id="info-dialog-title" data-i18n="info.title">Informationen</h2>
|
||||
<button id="info-close" type="button" aria-label="Informationsfenster schließen" title="Schließen" data-i18n-title="info.close" data-i18n-aria-label="info.close-label">×</button>
|
||||
</header>
|
||||
<div class="info-dialog-content">
|
||||
<article class="info-page" data-info-page="privacy" hidden>
|
||||
<p class="legal-warning"><strong data-i18n="html.strong.hinweis"></strong><span data-i18n="html.p.diese.angaben.sind.noch.nicht.vollstandig.die"></span></p>
|
||||
<h3 data-i18n="html.h3.1.verantwortliche.stelle.und.datenschutzkontakt"></h3>
|
||||
<p class="legal-warning"><strong data-i18n="common.notice">Hinweis:</strong><span data-i18n="privacy.incomplete-notice">Diese Angaben sind noch nicht vollständig. Die Kontakte stammen von der Grundschule Herpf. Wer die Website betreibt, welche Verbindungsdaten gespeichert werden und wie lange sie bleiben, muss noch ergänzt werden.</span></p>
|
||||
<h3 data-i18n="privacy.controller-title">1. Verantwortliche Stelle und Datenschutzkontakt</h3>
|
||||
<p>
|
||||
<strong data-i18n="html.strong.staatliche.grundschule.herpf"></strong><br><span data-i18n="html.p.zum.eichig.3"></span><br><span data-i18n="html.p.98617.meiningen.ot.herpf"></span><br><span data-i18n="html.p.telefon.036943.63722"></span><br><span data-i18n="html.p.e.mail.info.grundschule.herpf.de"></span></p>
|
||||
<p><span data-i18n="html.p.datenschutzangaben.der.schule"></span><br>
|
||||
<strong data-i18n="html.strong.staatliches.schulamt.sudthuringen"></strong><br><span data-i18n="html.p.datenschutzbeauftragter"></span><br><span data-i18n="html.p.holderlinstrae.1"></span><br><span data-i18n="html.p.98527.suhl"></span><br><span data-i18n="html.p.telefon.03681.7341.25"></span><br><span data-i18n="html.p.fax.03681.7341.09"></span><br><span data-i18n="html.p.e.mail.datenschutz.suedthueriungen.schulamt.thueringen.de"></span></p>
|
||||
<p data-i18n="html.p.der.datenschutzbeauftragte.kann.auerdem.uber.die.postadresse"></p>
|
||||
<h3 data-i18n="html.h3.2.wenn.du.wegwichtel.offnest"></h3>
|
||||
<p data-i18n="html.p.wenn.du.wegwichtel.offnest.konnen.verbindungsdaten.verarbeitet"></p>
|
||||
<p><span data-i18n="html.p.wer.die.website.bereitstellt.und.welche.verbindungsdaten"></span><strong data-i18n="html.strong.anbieter.anschrift.gespeicherte.daten.und.speicherdauer.erganzen"></strong></p>
|
||||
<h3 data-i18n="html.h3.3.standortdaten"></h3>
|
||||
<p data-i18n="html.p.du.entscheidest.ob.wegwichtel.deinen.standort.nutzen"></p>
|
||||
<h3 data-i18n="html.h3.4.deinen.fortschritt.merken"></h3>
|
||||
<p data-i18n="html.p.wenn.du.meinen.fortschritt.merken.einschaltest.speichert"></p>
|
||||
<p data-i18n="html.p.wenn.du.deinen.fortschritt.mindestens.einen.tag"></p>
|
||||
<label class="switch-row compact-switch">
|
||||
<strong data-i18n="contacts.school.name">Staatliche Grundschule Herpf</strong><br><span data-i18n="contacts.school.address">Zum Eichig 3</span><br><span data-i18n="contacts.school.city">98617 Meiningen / OT Herpf</span><br><span data-i18n="contacts.school.phone">Telefon: (036943) 63722</span><br><span data-i18n="contacts.school.email">E-Mail: info@grundschule-herpf.de</span></p>
|
||||
<p><span data-i18n="contacts.school.privacy-label">Datenschutzangaben der Schule:</span><br>
|
||||
<strong data-i18n="contacts.privacy.name">Staatliches Schulamt Südthüringen</strong><br><span data-i18n="contacts.privacy.role">Datenschutzbeauftragter</span><br><span data-i18n="contacts.privacy.address">Hölderlinstraße 1</span><br><span data-i18n="contacts.privacy.city">98527 Suhl</span><br><span data-i18n="contacts.privacy.phone">Telefon: 03681 / 7341 25</span><br><span data-i18n="contacts.privacy.fax">Fax: 03681 / 7341 09</span><br><span data-i18n="contacts.privacy.email">E-Mail: datenschutz.suedthueriungen@schulamt.thueringen.de</span></p>
|
||||
<p data-i18n="contacts.privacy.postal-hint">Der Datenschutzbeauftragte kann außerdem über die Postadresse der Schule mit dem Zusatz „der Datenschutzbeauftragte“ erreicht werden.</p>
|
||||
<h3 data-i18n="privacy.connection-title">2. Wenn du Wegwichtel öffnest</h3>
|
||||
<p data-i18n="privacy.connection-data">Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetprogramm und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.</p>
|
||||
<p><span data-i18n="privacy.provider-label">Wer die Website bereitstellt und welche Verbindungsdaten wie lange gespeichert werden:</span><strong data-i18n="privacy.provider-placeholder">[Anbieter, Anschrift, gespeicherte Daten und Speicherdauer ergänzen]</strong></p>
|
||||
<h3 data-i18n="privacy.location-title">3. Standortdaten</h3>
|
||||
<p data-i18n="privacy.location-data">Du entscheidest, ob Wegwichtel deinen Standort nutzen darf. Damit findest du Strecken in deiner Nähe und siehst den Weg zur nächsten Station. Dein laufender Standort wird auf deinem Gerät verarbeitet. Wegwichtel speichert daraus keinen Verlauf auf der Website.</p>
|
||||
<h3 data-i18n="privacy.progress-title">4. Deinen Fortschritt merken</h3>
|
||||
<p data-i18n="privacy.progress-data">Wenn du „Meinen Fortschritt merken“ einschaltest, speichert Wegwichtel deine gewählte Strecke, besuchte und angesehene Stationen sowie angehörte Tonaufnahmen. Auch ob du unterwegs bist, eine Pause machst oder fertig bist und der Zeitpunkt werden gespeichert. Diese Angaben bleiben auf deinem Gerät und werden nicht an die Website gesendet.</p>
|
||||
<p data-i18n="privacy.progress-deletion">Wenn du deinen Fortschritt mindestens einen Tag lang nicht verändert hast, wird er beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine eigenen und für unterwegs gespeicherten Strecken bleiben erhalten.</p>
|
||||
<label for="progress-storage-toggle-2" class="switch-row compact-switch">
|
||||
<span>
|
||||
<strong data-i18n="html.strong.meinen.fortschritt.merken"></strong>
|
||||
<small data-i18n="html.small.du.entscheidest.ob.sich.wegwichtel.deinen.fortschritt"></small>
|
||||
<strong data-i18n="progress.remember">Meinen Fortschritt merken</strong>
|
||||
<small data-i18n="progress.choice-hint">Du entscheidest, ob sich Wegwichtel deinen Fortschritt merkt.</small>
|
||||
</span>
|
||||
<input class="progress-storage-toggle" type="checkbox" role="switch" aria-checked="false">
|
||||
<input id="progress-storage-toggle-2" class="progress-storage-toggle" type="checkbox" role="switch" aria-checked="false">
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<h3 data-i18n="html.h3.5.deine.eigenen.strecken"></h3>
|
||||
<p data-i18n="html.p.neue.strecken.bleiben.zuerst.auf.deinem.gerat"></p>
|
||||
<p data-i18n="html.p.deine.entwurfe.bleiben.erhalten.wenn.alter.fortschritt"></p>
|
||||
<h3 data-i18n="html.h3.6.werbung.und.gespeicherte.einstellungen"></h3>
|
||||
<p data-i18n="html.p.wegwichtel.verwendet.keine.cookies.es.zahlt.keine"></p>
|
||||
<h3 data-i18n="html.h3.7.inhalte.von.schulerinnen.und.schulern"></h3>
|
||||
<p data-i18n="html.p.bilder.texte.und.tonaufnahmen.durfen.nur.geteilt"></p>
|
||||
<h3 data-i18n="html.h3.8.deine.rechte"></h3>
|
||||
<p data-i18n="html.p.du.kannst.fragen.welche.daten.uber.dich"></p>
|
||||
<p><span data-i18n="html.p.zustandige.datenschutzaufsicht"></span><br>
|
||||
<strong data-i18n="html.strong.thuringer.landesbeauftragter.fur.den.datenschutz.und.die"></strong><br><span data-i18n="html.p.postfach.90.04.55.99107.erfurt"></span><br><span data-i18n="html.p.halerstrae.8.99096.erfurt"></span><br><span data-i18n="html.p.telefon.0361.57.311.29.00"></span><br><span data-i18n="html.p.telefax.0361.57.311.29.04"></span><br><span data-i18n="html.p.e.mail.poststelle.datenschutz.thueringen.de"></span><br><span data-i18n="html.p.website.www.tlfdi.de"></span></p>
|
||||
<h3 data-i18n="privacy.drafts-title">5. Deine eigenen Strecken</h3>
|
||||
<p data-i18n="privacy.draft-data">Neue Strecken bleiben zuerst auf deinem Gerät. Gespeichert werden der Weg, die Orte der Stationen, Texte sowie ausgewählte Bilder und Tonaufnahmen.</p>
|
||||
<p data-i18n="privacy.draft-deletion">Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.</p>
|
||||
<h3 data-i18n="privacy.settings-title">6. Werbung und gespeicherte Einstellungen</h3>
|
||||
<p data-i18n="privacy.settings-data">Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.</p>
|
||||
<h3 data-i18n="privacy.children-title">7. Inhalte von Schülerinnen und Schülern</h3>
|
||||
<p data-i18n="privacy.children-permission">Bilder, Texte und Tonaufnahmen dürfen nur geteilt werden, wenn das erlaubt ist. Wenn Kinder auf Bildern oder in Aufnahmen erkannt werden können, müssen die Regeln der Schule beachtet werden. Auch die Eltern oder anderen Sorgeberechtigten müssen die nötige Erlaubnis geben.</p>
|
||||
<h3 data-i18n="privacy.rights-title">8. Deine Rechte</h3>
|
||||
<p data-i18n="privacy.rights-data">Du kannst fragen, welche Daten über dich gespeichert sind. Unter den gesetzlichen Voraussetzungen kannst du verlangen, dass sie berichtigt, gelöscht, nur noch eingeschränkt genutzt oder dir zur Weitergabe bereitgestellt werden. Du kannst der Nutzung widersprechen und eine erteilte Erlaubnis für die Zukunft zurücknehmen. Wenn du dich über den Umgang mit deinen Daten beschweren möchtest, kannst du dich an die folgende Datenschutzbehörde wenden.</p>
|
||||
<p><span data-i18n="contacts.authority.label">Zuständige Datenschutzaufsicht:</span><br>
|
||||
<strong data-i18n="contacts.authority.name">Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)</strong><br><span data-i18n="contacts.authority.postal-address">Postfach 90 04 55, 99107 Erfurt</span><br><span data-i18n="contacts.authority.address">Häßlerstraße 8, 99096 Erfurt</span><br><span data-i18n="contacts.authority.phone">Telefon: 0361 / 57 311 29 00</span><br><span data-i18n="contacts.authority.fax">Telefax: 0361 / 57 311 29 04</span><br><span data-i18n="contacts.authority.email">E-Mail: poststelle@datenschutz.thueringen.de</span><br><span data-i18n="contacts.authority.website">Website: www.tlfdi.de</span></p>
|
||||
</article>
|
||||
<article class="info-page" data-info-page="imprint" hidden>
|
||||
<p class="legal-warning"><strong data-i18n="html.strong.hinweis"></strong><span data-i18n="html.p.diese.angaben.stammen.von.der.grundschule.herpf"></span></p>
|
||||
<h3 data-i18n="html.h3.wer.fur.wegwichtel.verantwortlich.ist"></h3>
|
||||
<p class="legal-warning"><strong data-i18n="common.notice">Hinweis:</strong><span data-i18n="imprint.incomplete-notice">Diese Angaben stammen von der Grundschule Herpf. Vor der Veröffentlichung muss geprüft werden, ob für Wegwichtel noch weitere oder andere Angaben nötig sind.</span></p>
|
||||
<h3 data-i18n="imprint.controller-title">Wer für Wegwichtel verantwortlich ist</h3>
|
||||
<p>
|
||||
<strong data-i18n="html.strong.staatliche.grundschule.herpf"></strong><br><span data-i18n="html.p.zum.eichig.3"></span><br><span data-i18n="html.p.98617.meiningen.ot.herpf"></span></p>
|
||||
<h3 data-i18n="html.h3.kontakt"></h3>
|
||||
<p><span data-i18n="html.p.telefon.036943.63722"></span><br><span data-i18n="html.p.telefax.036943.63843"></span><br><span data-i18n="html.p.e.mail.info.grundschule.herpf.de"></span></p>
|
||||
<h3 data-i18n="html.h3.leitung.der.schule"></h3>
|
||||
<p data-i18n="html.p.frau.wichert.schulleiterin"></p>
|
||||
<h3 data-i18n="html.h3.schultrager"></h3>
|
||||
<strong data-i18n="contacts.school.name">Staatliche Grundschule Herpf</strong><br><span data-i18n="contacts.school.address">Zum Eichig 3</span><br><span data-i18n="contacts.school.city">98617 Meiningen / OT Herpf</span></p>
|
||||
<h3 data-i18n="imprint.contact-title">Kontakt</h3>
|
||||
<p><span data-i18n="contacts.school.phone">Telefon: (036943) 63722</span><br><span data-i18n="contacts.school.fax">Telefax: (036943) 63843</span><br><span data-i18n="contacts.school.email">E-Mail: info@grundschule-herpf.de</span></p>
|
||||
<h3 data-i18n="imprint.head-title">Leitung der Schule</h3>
|
||||
<p data-i18n="contacts.school.head">Frau Wichert, Schulleiterin</p>
|
||||
<h3 data-i18n="imprint.sponsor-title">Schulträger</h3>
|
||||
<p>
|
||||
<strong data-i18n="html.strong.landkreis.schmalkalden.meiningen"></strong><br><span data-i18n="html.p.landratsamt"></span><br><span data-i18n="html.p.obertshauser.platz.1"></span><br><span data-i18n="html.p.98617.meiningen"></span></p>
|
||||
<h3 data-i18n="html.h3.inhaltlich.verantwortlich"></h3>
|
||||
<p data-i18n="html.p.frau.wichert"></p>
|
||||
<h3 data-i18n="html.h3.weitere.pflichtangaben"></h3>
|
||||
<p data-i18n="html.p.je.nachdem.wer.wegwichtel.betreibt.konnen.weitere"></p>
|
||||
<h3 data-i18n="html.h3.haftung.fur.inhalte.und.verweise"></h3>
|
||||
<p data-i18n="html.p.die.inhalte.werden.sorgfaltig.erstellt.und.regelmaig"></p>
|
||||
<h3 data-i18n="html.h3.urheber.und.nutzungsrechte"></h3>
|
||||
<p data-i18n="html.p.wenn.nichts.anderes.angegeben.ist.liegen.die"></p>
|
||||
<strong data-i18n="contacts.sponsor.name">Landkreis Schmalkalden-Meiningen</strong><br><span data-i18n="contacts.sponsor.role">Landratsamt</span><br><span data-i18n="contacts.sponsor.address">Obertshäuser Platz 1</span><br><span data-i18n="contacts.sponsor.city">98617 Meiningen</span></p>
|
||||
<h3 data-i18n="imprint.content-title">Inhaltlich verantwortlich</h3>
|
||||
<p data-i18n="contacts.school.content-owner">Frau Wichert</p>
|
||||
<h3 data-i18n="imprint.required-title">Weitere Pflichtangaben</h3>
|
||||
<p data-i18n="imprint.required-data">Je nachdem, wer Wegwichtel betreibt, können weitere Angaben nötig sein. Dazu gehören zum Beispiel die Verantwortlichen für den Betrieb der Website. Diese Angaben müssen noch geprüft werden.</p>
|
||||
<h3 data-i18n="imprint.liability-title">Haftung für Inhalte und Verweise</h3>
|
||||
<p data-i18n="imprint.liability-data">Die Inhalte werden sorgfältig erstellt und regelmäßig geprüft. Falls etwas falsch, veraltet oder nicht erlaubt ist, melde dich beim Kontakt oben. Für andere verlinkte Seiten sind deren Betreiber verantwortlich.</p>
|
||||
<h3 data-i18n="imprint.copyright-title">Urheber- und Nutzungsrechte</h3>
|
||||
<p data-i18n="imprint.copyright-data">Wenn nichts anderes angegeben ist, liegen die Rechte bei der Grundschule Herpf. Du darfst Texte, Bilder, Tonaufnahmen und Wege nur so nutzen, wie es jeweils erlaubt wurde. Auch die Rechte der beteiligten Kinder, Familien, Lehrkräfte und anderen Personen bleiben bestehen.</p>
|
||||
</article>
|
||||
<article class="info-page" data-info-page="technology" hidden>
|
||||
<h3 data-i18n="html.h3.was.merkt.sich.wegwichtel"></h3>
|
||||
<p data-i18n="html.p.du.kannst.deinen.fortschritt.auf.diesem.gerat"></p>
|
||||
<p data-i18n="html.p.wenn.du.einen.tag.lang.nichts.mehr"></p>
|
||||
<label class="switch-row compact-switch">
|
||||
<h3 data-i18n="storage-help.progress-title">Was merkt sich Wegwichtel?</h3>
|
||||
<p data-i18n="storage-help.progress-data">Du kannst deinen Fortschritt auf diesem Gerät speichern. Dann weiß Wegwichtel beim nächsten Öffnen, welche Stationen du schon besucht oder angesehen hast und welche Tonaufnahmen du angehört hast.</p>
|
||||
<p data-i18n="storage-help.progress-deletion">Wenn du einen Tag lang nichts mehr daran geändert hast, wird dein Fortschritt beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine selbst angelegten und für unterwegs gespeicherten Strecken bleiben erhalten.</p>
|
||||
<label for="progress-storage-toggle-3" class="switch-row compact-switch">
|
||||
<span>
|
||||
<strong data-i18n="html.strong.meinen.fortschritt.merken"></strong>
|
||||
<small data-i18n="html.small.die.anderung.gilt.sofort.auf.diesem.gerat"></small>
|
||||
<strong data-i18n="progress.remember">Meinen Fortschritt merken</strong>
|
||||
<small data-i18n="progress.change-hint">Die Änderung gilt sofort auf diesem Gerät.</small>
|
||||
</span>
|
||||
<input class="progress-storage-toggle" type="checkbox" role="switch" aria-checked="false">
|
||||
<input id="progress-storage-toggle-3" class="progress-storage-toggle" type="checkbox" role="switch" aria-checked="false">
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<h3 data-i18n="html.h3.ohne.internet.unterwegs"></h3>
|
||||
<p data-i18n="html.p.mit.app.ohne.internet.offnen.speicherst.du"></p>
|
||||
<p data-i18n="html.p.mit.weg.als.datei.herunterladen.kannst.du"></p>
|
||||
<h3 data-i18n="html.h3.deine.eigenen.strecken"></h3>
|
||||
<p data-i18n="html.p.neue.strecken.bleiben.zuerst.auf.diesem.gerat"></p>
|
||||
<p data-i18n="html.p.deine.entwurfe.bleiben.gespeichert.bis.du.sie"></p>
|
||||
<h3 data-i18n="html.h3.welche.erlaubnisse.braucht.die.app"></h3>
|
||||
<h3 data-i18n="storage-help.offline-title">Ohne Internet unterwegs</h3>
|
||||
<p data-i18n="storage-help.offline-data">Mit „App ohne Internet öffnen“ speicherst du Wegwichtel auf diesem Gerät. Danach kannst du einzelne Strecken mit „Für unterwegs speichern“ mitnehmen. Warte vor dem Losgehen, bis alles gespeichert ist.</p>
|
||||
<p data-i18n="storage-help.export-data">Mit „Weg als Datei herunterladen“ kannst du den Weg in einer anderen Wander-App öffnen. Bilder und Tonaufnahmen sind in dieser Datei nicht enthalten.</p>
|
||||
<h3 data-i18n="storage-help.drafts-title">Deine eigenen Strecken</h3>
|
||||
<p data-i18n="storage-help.drafts-data">Neue Strecken bleiben zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ reichst du eine Strecke zur Freigabe ein. Erst nach der Freigabe können andere sie sehen.</p>
|
||||
<p data-i18n="storage-help.drafts-deletion">Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.</p>
|
||||
<h3 data-i18n="storage-help.permissions-title">Welche Erlaubnisse braucht die App?</h3>
|
||||
<ul>
|
||||
<li data-i18n="html.li.deinen.standort.um.strecken.in.deiner.nahe"></li>
|
||||
<li data-i18n="html.li.die.richtung.deines.gerats.damit.sich.der"></li>
|
||||
<li data-i18n="html.li.zugriff.auf.ausgewahlte.bilder.und.tonaufnahmen.wenn"></li>
|
||||
<li data-i18n="storage-help.permissions.location">Deinen Standort, um Strecken in deiner Nähe und den Weg zur nächsten Station zu zeigen.</li>
|
||||
<li data-i18n="storage-help.permissions.direction">Die Richtung deines Geräts, damit sich der Pfeil mitdrehen kann.</li>
|
||||
<li data-i18n="storage-help.permissions.files">Zugriff auf ausgewählte Bilder und Tonaufnahmen, wenn du sie zu einer Strecke hinzufügen möchtest.</li>
|
||||
</ul>
|
||||
<p data-i18n="html.p.du.kannst.diese.erlaubnisse.ablehnen.dann.fehlen"></p>
|
||||
<h3 data-i18n="html.h3.werbung.und.andere.anbieter"></h3>
|
||||
<p data-i18n="html.p.wegwichtel.zeigt.keine.werbung.und.verfolgt.nicht"></p>
|
||||
<p data-i18n="storage-help.permissions.optional">Du kannst diese Erlaubnisse ablehnen. Dann fehlen die jeweiligen Funktionen. Strecken und Stationen kannst du weiterhin selbst auswählen.</p>
|
||||
<h3 data-i18n="storage-help.providers-title">Werbung und andere Anbieter</h3>
|
||||
<p data-i18n="storage-help.providers-data">Wegwichtel zeigt keine Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Die App lädt ihre eigenen Programme und Bilder von Wegwichtel. Es werden keine Cookies verwendet.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@@ -391,58 +391,58 @@
|
||||
<dialog id="editor-dialog" class="editor-dialog" aria-labelledby="editor-dialog-title">
|
||||
<div class="editor-dialog-shell">
|
||||
<header class="editor-dialog-header">
|
||||
<h2 id="editor-dialog-title" data-i18n="html.editor-button.strecken.bearbeiten"></h2>
|
||||
<button id="editor-close" type="button" aria-label="" title="" data-i18n-title="legal-ui.schliessen" data-i18n-aria-label="html.editor-close.fenster.zum.bearbeiten.schlieen">×</button>
|
||||
<h2 id="editor-dialog-title" data-i18n="editor.open">Strecken bearbeiten</h2>
|
||||
<button id="editor-close" type="button" aria-label="Fenster zum Bearbeiten schließen" title="Schließen" data-i18n-title="info.close" data-i18n-aria-label="editor.close">×</button>
|
||||
</header>
|
||||
|
||||
<output id="editor-status" class="editor-status" aria-live="polite" hidden></output>
|
||||
<section id="editor-route-list-view" class="editor-view" aria-labelledby="editor-route-list-title">
|
||||
<div class="editor-toolbar">
|
||||
<h3 id="editor-route-list-title" data-i18n="html.editor-route-list-title.deine.strecken"></h3>
|
||||
<button id="editor-new-route" class="editor-round-button" type="button" aria-label="" title="" data-i18n-title="html.editor-new-route.neue.strecke.anlegen" data-i18n-aria-label="html.editor-new-route.neue.strecke.anlegen">
|
||||
<h3 id="editor-route-list-title" data-i18n="editor.routes-title">Deine Strecken</h3>
|
||||
<button id="editor-new-route" class="editor-round-button" type="button" aria-label="Neue Strecke anlegen" title="Neue Strecke anlegen" data-i18n-title="editor.create-route" data-i18n-aria-label="editor.create-route">
|
||||
<span aria-hidden="true">+</span>
|
||||
</button>
|
||||
</div>
|
||||
<section class="storage-usage" data-storage-usage aria-label="" data-i18n-aria-label="html.section.platz.fur.wegwichtel">
|
||||
<h3 data-i18n="html.section.platz.fur.wegwichtel"></h3>
|
||||
<meter data-storage-site-meter min="0" max="1" value="0" aria-label="" hidden data-i18n-aria-label="html.meter.belegter.platz.fur.app.bilder.und.ton"></meter>
|
||||
<p data-storage-site-text data-i18n="html.p.wir.schauen.wie.viel.platz.belegt.ist"></p>
|
||||
<meter data-storage-local-meter min="0" max="5242880" value="0" aria-label="" data-i18n-aria-label="html.meter.belegter.platz.fur.texte.und.einstellungen.der"></meter>
|
||||
<section class="storage-usage" data-storage-usage aria-label="Platz für Wegwichtel" data-i18n-aria-label="storage.title">
|
||||
<h3 data-i18n="storage.title">Platz für Wegwichtel</h3>
|
||||
<meter data-storage-site-meter min="0" max="1" value="0" aria-label="Belegter Platz für App, Bilder und Ton" hidden data-i18n-aria-label="storage.site-meter-label"></meter>
|
||||
<p data-storage-site-text data-i18n="storage.loading">Wir schauen, wie viel Platz belegt ist …</p>
|
||||
<meter data-storage-local-meter min="0" max="5242880" value="0" aria-label="Belegter Platz für Texte und Einstellungen; der Balken zeigt einen ungefähren Richtwert" data-i18n-aria-label="storage.local-meter-label"></meter>
|
||||
<p data-storage-local-text></p>
|
||||
<p data-i18n="html.p.der.erste.balken.zeigt.den.platz.fur"></p>
|
||||
<p data-i18n="storage.help">Der erste Balken zeigt den Platz für die App, Bilder und Ton. Der zweite zeigt den Platz für Texte und Einstellungen. Die Balken zeigen nur den Platz für Wegwichtel.</p>
|
||||
<output data-storage-warning hidden></output>
|
||||
<button data-storage-refresh type="button" data-i18n="html.button.platz.prufen"></button>
|
||||
<button data-storage-refresh type="button" data-i18n="storage.refresh">Platz prüfen</button>
|
||||
</section>
|
||||
<p class="editor-muted" data-i18n="html.p.hier.findest.du.strecken.fur.alle.und"></p>
|
||||
<p class="editor-muted" data-i18n="editor.routes-hint">Hier findest du Strecken für alle und deine eigenen Entwürfe.</p>
|
||||
<ul id="editor-route-list" class="editor-route-list" aria-live="polite"></ul>
|
||||
</section>
|
||||
<section id="editor-route-detail-view" class="editor-view" aria-labelledby="editor-route-detail-title" hidden>
|
||||
<div class="editor-toolbar editor-toolbar-back">
|
||||
<button id="editor-route-back" class="editor-secondary-button" type="button" data-i18n="html.editor-route-back.zuruck"></button>
|
||||
<h3 id="editor-route-detail-title" data-i18n="html.editor-route-detail-title.deine.strecke"></h3>
|
||||
<button id="editor-route-back" class="editor-secondary-button" type="button" data-i18n="common.back">Zurück</button>
|
||||
<h3 id="editor-route-detail-title" data-i18n="editor.route-title">Deine Strecke</h3>
|
||||
</div>
|
||||
<form id="editor-route-form" class="editor-form">
|
||||
<input id="editor-route-id" type="hidden">
|
||||
<label for="editor-route-name" data-i18n="html.label.name.der.strecke"></label>
|
||||
<label for="editor-route-name" data-i18n="editor.route-name">Name der Strecke</label>
|
||||
<input id="editor-route-name" type="text" autocomplete="off" required>
|
||||
|
||||
<label for="editor-route-description" data-i18n="html.label.beschreibung"></label>
|
||||
<label for="editor-route-description" data-i18n="editor.description">Beschreibung</label>
|
||||
<textarea id="editor-route-description" rows="4"></textarea>
|
||||
<div id="editor-route-gpx-group" class="editor-gpx-group">
|
||||
<label for="editor-route-gpx" data-i18n="html.label.weg.aus.einer.datei.ubernehmen"></label>
|
||||
<label for="editor-route-gpx" data-i18n="editor.import-path">Weg aus einer Datei übernehmen</label>
|
||||
<input id="editor-route-gpx" type="file" accept=".gpx,application/gpx+xml,text/xml,application/xml">
|
||||
</div>
|
||||
|
||||
<p id="editor-route-activated" class="editor-muted" data-i18n="html.editor-route-activated.deine.neue.strecke.bleibt.zuerst.auf.diesem"></p>
|
||||
<p id="editor-route-activated" class="editor-muted" data-i18n="editor.new-draft-hint">Deine neue Strecke bleibt zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ kannst du sie zur Freigabe einreichen.</p>
|
||||
|
||||
<div class="editor-form-actions">
|
||||
<button id="editor-route-save" class="editor-primary-button" type="submit" data-i18n="html.editor-route-save.strecke.speichern"></button>
|
||||
<button id="editor-route-upload" class="editor-secondary-button" type="button" hidden data-i18n="editor-ui.zur.prufung.senden"></button>
|
||||
<button id="editor-route-save" class="editor-primary-button" type="submit" data-i18n="editor.save-route">Strecke speichern</button>
|
||||
<button id="editor-route-upload" class="editor-secondary-button" type="button" hidden data-i18n="editor.submit">Zur Prüfung senden</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="editor-toolbar editor-poi-toolbar">
|
||||
<h3 data-i18n="html.dt.stationen"></h3>
|
||||
<button id="editor-add-poi" class="editor-round-button" type="button" aria-label="" title="" data-i18n-title="editor-ui.station.hinzufugen" data-i18n-aria-label="editor-ui.station.hinzufugen">
|
||||
<h3 data-i18n="route.station-count">Stationen</h3>
|
||||
<button id="editor-add-poi" class="editor-round-button" type="button" aria-label="Station hinzufügen" title="Station hinzufügen" data-i18n-title="editor.add-station" data-i18n-aria-label="editor.add-station">
|
||||
<span aria-hidden="true">+</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -450,49 +450,49 @@
|
||||
</section>
|
||||
<section id="editor-poi-form-view" class="editor-view" aria-labelledby="editor-poi-form-title" hidden>
|
||||
<div class="editor-toolbar editor-toolbar-back">
|
||||
<button id="editor-poi-cancel" class="editor-secondary-button" type="button" data-i18n="html.button.zuruck.zur.strecke"></button>
|
||||
<h3 id="editor-poi-form-title" data-i18n="editor-ui.station.bearbeiten"></h3>
|
||||
<button id="editor-poi-cancel" class="editor-secondary-button" type="button" data-i18n="route.back">Zurück zur Strecke</button>
|
||||
<h3 id="editor-poi-form-title" data-i18n="editor.edit-station">Station bearbeiten</h3>
|
||||
</div>
|
||||
|
||||
<form id="editor-poi-form" class="editor-form">
|
||||
<input id="editor-poi-id" type="hidden">
|
||||
<label for="editor-poi-title" data-i18n="html.label.name"></label>
|
||||
<label for="editor-poi-title" data-i18n="editor.station-name">Name</label>
|
||||
<input id="editor-poi-title" type="text" autocomplete="off" required>
|
||||
|
||||
<label for="editor-poi-description" data-i18n="html.label.text"></label>
|
||||
<label for="editor-poi-description" data-i18n="editor.station-text">Text</label>
|
||||
<textarea id="editor-poi-description" rows="6"></textarea>
|
||||
|
||||
<input id="editor-poi-lat" type="hidden">
|
||||
<input id="editor-poi-lon" type="hidden">
|
||||
<input id="editor-poi-radius" type="hidden" value="25">
|
||||
|
||||
<div class="editor-media-actions" aria-label="" data-i18n-aria-label="html.div.bilder.und.ton.zur.station.hinzufugen">
|
||||
<button id="editor-poi-audio-add" class="editor-media-button" type="button" title="" data-i18n-title="html.editor-poi-audio-add.ton.hinzufugen">
|
||||
<div class="editor-media-actions" aria-label="Bilder und Ton zur Station hinzufügen" data-i18n-aria-label="editor.media-actions-label">
|
||||
<button id="editor-poi-audio-add" class="editor-media-button" type="button" title="Ton hinzufügen" data-i18n-title="editor.add-audio">
|
||||
<svg class="editor-media-icon" viewbox="0 0 32 32" aria-hidden="true" focusable="false">
|
||||
<path d="M18 5v15.2a5 5 0 1 1-2-4V9l10-2v9.2a5 5 0 1 1-2-4V4.6z"></path>
|
||||
<path d="M6 4v6M3 7h6"></path>
|
||||
</svg>
|
||||
<span data-i18n="html.editor-poi-audio-add.ton.hinzufugen"></span>
|
||||
<span data-i18n="editor.add-audio">Ton hinzufügen</span>
|
||||
</button>
|
||||
<button id="editor-poi-images-add" class="editor-media-button" type="button" title="" data-i18n-title="html.editor-poi-images-add.bild.hinzufugen">
|
||||
<button id="editor-poi-images-add" class="editor-media-button" type="button" title="Bild hinzufügen" data-i18n-title="editor.add-image">
|
||||
<svg class="editor-media-icon" viewbox="0 0 32 32" aria-hidden="true" focusable="false">
|
||||
<path d="M6 10h4l2-3h8l2 3h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V12a2 2 0 0 1 2-2z"></path>
|
||||
<circle cx="16" cy="18" r="5"></circle>
|
||||
<path d="M6 3v6M3 6h6"></path>
|
||||
</svg>
|
||||
<span data-i18n="html.editor-poi-images-add.bild.hinzufugen"></span>
|
||||
<span data-i18n="editor.add-image">Bild hinzufügen</span>
|
||||
</button>
|
||||
</div>
|
||||
<input id="editor-poi-audio" type="file" accept="audio/*" hidden>
|
||||
<input id="editor-poi-images" type="file" accept="image/*" multiple hidden>
|
||||
<div id="editor-poi-media-preview" class="editor-media-preview" aria-label="" data-i18n-aria-label="html.editor-poi-media-preview.bilder.und.ton.ansehen"></div>
|
||||
<div id="editor-poi-media-preview" class="editor-media-preview" aria-label="Bilder und Ton ansehen" data-i18n-aria-label="editor.media-preview-label"></div>
|
||||
<p id="editor-poi-media-status" class="editor-muted" aria-live="polite"></p>
|
||||
|
||||
<div class="editor-form-actions">
|
||||
<button id="editor-poi-save" class="editor-primary-button" type="submit" data-i18n="html.editor-poi-save.station.speichern"></button>
|
||||
<button id="editor-poi-save" class="editor-primary-button" type="submit" data-i18n="editor.save-station">Station speichern</button>
|
||||
<button id="editor-poi-delete" class="editor-danger-button" type="button" hidden>
|
||||
<span class="editor-danger-icon" aria-hidden="true">−</span>
|
||||
<span data-i18n="html.span.station.entfernen"></span>
|
||||
<span data-i18n="editor.remove-station">Station entfernen</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -500,6 +500,7 @@
|
||||
</div>
|
||||
</dialog>
|
||||
<script src="js/texts-de.js?v=0.12.14"></script>
|
||||
<script src="js/texts-en.js?v=0.12.14"></script>
|
||||
<script src="js/i18n.js?v=0.12.14"></script>
|
||||
<script src="js/user-messages.js?v=0.12.14"></script>
|
||||
<script src="js/local-routes.js?v=0.12.14"></script>
|
||||
|
||||
+43
-43
@@ -58,7 +58,7 @@
|
||||
if (state.routeState === 'paused') {
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text(ns.I18n.t("app.du.kannst.dort.weitermachen.wo.du.aufgehort"));
|
||||
.text(ns.I18n.t("app.resume-hint"));
|
||||
} else {
|
||||
$('#tracking-status').prop('hidden', true).text('');
|
||||
}
|
||||
@@ -115,7 +115,7 @@
|
||||
const proximity = route.proximityM == null
|
||||
? ''
|
||||
: `<small>${escapeHtml(ns.Distance.format(route.proximityM))} entfernt</small>`;
|
||||
const sourceLabel = source === 'local' ? ("<small>" + escapeHtml(ns.I18n.t("app.eigener.entwurf")) + "</small>") : '';
|
||||
const sourceLabel = source === 'local' ? ("<small>" + escapeHtml(ns.I18n.t("app.draft")) + "</small>") : '';
|
||||
return `
|
||||
<li>
|
||||
<button type="button" data-route-select data-route-id="${escapeHtml(route.id)}" data-route-source="${source}">
|
||||
@@ -157,21 +157,21 @@
|
||||
|
||||
function updateRouteControlSummary() {
|
||||
if (!state.route) {
|
||||
setRouteControlSummary(ns.I18n.t("app.strecke.wahlen"), ns.I18n.t("app.strecken.fur.alle.und.meine.entwurfe"));
|
||||
setRouteControlSummary(ns.I18n.t("app.choose-route"), ns.I18n.t("app.route-menu"));
|
||||
return;
|
||||
}
|
||||
|
||||
const titleText = state.route.name || ns.I18n.t("app.strecke");
|
||||
const titleText = state.route.name || ns.I18n.t("app.route");
|
||||
const metric = nextPoiMetric();
|
||||
if (!metric) {
|
||||
const detailText = (state.route.pois || []).length
|
||||
? ns.I18n.t("app.alle.stationen.erreicht")
|
||||
: ns.I18n.t("app.keine.stationen.vorhanden");
|
||||
? ns.I18n.t("app.stations-complete")
|
||||
: ns.I18n.t("app.no-stations");
|
||||
setRouteControlSummary(titleText, detailText);
|
||||
return;
|
||||
}
|
||||
const distance = metric.distance == null ? '' : ` · ${Math.round(metric.distance)} m`;
|
||||
setRouteControlSummary(titleText, ns.I18n.t("app.nachste.station", { value1: metric.poi.title, value2: distance }));
|
||||
setRouteControlSummary(titleText, ns.I18n.t("app.next-station-summary", { value1: metric.poi.title, value2: distance }));
|
||||
}
|
||||
|
||||
function renderRouteControlList(selector, routes, source, emptyText) {
|
||||
@@ -187,7 +187,7 @@
|
||||
<li>
|
||||
<button type="button" data-route-select data-route-id="${escapeHtml(route.id)}" data-route-source="${source}" ${current ? 'aria-current="true"' : ''}>
|
||||
<strong>${escapeHtml(route.name)}</strong>
|
||||
<small>${source === 'local' ? ns.I18n.t("app.eigener.entwurf") : ns.I18n.t("app.veroffentlicht")}</small>
|
||||
<small>${source === 'local' ? ns.I18n.t("app.draft") : ns.I18n.t("app.published")}</small>
|
||||
</button>
|
||||
</li>
|
||||
`);
|
||||
@@ -198,22 +198,22 @@
|
||||
const routeControlLists = $('.route-control-list');
|
||||
routeControlLists.attr('aria-busy', 'true');
|
||||
|
||||
$('#route-control-server-title').text(ns.I18n.t("app.strecken.fur.alle", { value1: state.routes.length }));
|
||||
$('#route-control-local-title').text(ns.I18n.t("app.meine.entwurfe", { value1: state.localRoutes.length }));
|
||||
$('#local-route-title').text(ns.I18n.t("app.meine.entwurfe", { value1: state.localRoutes.length }));
|
||||
$('#route-control-server-title').text(ns.I18n.t("app.public-route-count", { value1: state.routes.length }));
|
||||
$('#route-control-local-title').text(ns.I18n.t("app.draft-count", { value1: state.localRoutes.length }));
|
||||
$('#local-route-title').text(ns.I18n.t("app.draft-count", { value1: state.localRoutes.length }));
|
||||
|
||||
updateRouteControlSummary();
|
||||
renderRouteControlList(
|
||||
'#route-control-server-list',
|
||||
state.routes,
|
||||
'server',
|
||||
ns.I18n.t("app.hier.gibt.es.noch.keine.strecken.fur")
|
||||
ns.I18n.t("app.no-public-route")
|
||||
);
|
||||
renderRouteControlList(
|
||||
'#route-control-local-list',
|
||||
state.localRoutes,
|
||||
'local',
|
||||
state.localRoutesError || ns.I18n.t("app.du.hast.noch.keine.eigene.strecke.angelegt")
|
||||
state.localRoutesError || ns.I18n.t("app.no-route")
|
||||
);
|
||||
|
||||
routeControlLists.attr('aria-busy', 'false');
|
||||
@@ -239,26 +239,26 @@
|
||||
.sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
||||
|
||||
const nearbyEmptyText = state.position
|
||||
? ns.I18n.t("app.keine.strecke.im.umkreis.von.km.gefunden", { value1: ns.Config.routeRadiusKm })
|
||||
: ns.I18n.t("app.erlaube.deinen.standort.um.strecken.in.deiner");
|
||||
? ns.I18n.t("app.no-nearby-route", { value1: ns.Config.routeRadiusKm })
|
||||
: ns.I18n.t("app.location-permission-hint");
|
||||
|
||||
renderRouteItems('#nearby-route-list', nearbyRoutes, nearbyEmptyText);
|
||||
renderRouteItems(
|
||||
'#all-route-list',
|
||||
allRoutes,
|
||||
searchTerm ? ns.I18n.t("app.keine.passende.strecke.gefunden.versuche.ein.anderes") : ns.I18n.t("app.hier.gibt.es.noch.keine.strecken.fur")
|
||||
searchTerm ? ns.I18n.t("app.no-search-result") : ns.I18n.t("app.no-public-route")
|
||||
);
|
||||
renderRouteItems(
|
||||
'#local-route-list',
|
||||
localRoutes,
|
||||
state.localRoutesError || ns.I18n.t("app.du.hast.noch.keine.eigene.strecke.angelegt"),
|
||||
state.localRoutesError || ns.I18n.t("app.no-route"),
|
||||
'local'
|
||||
);
|
||||
|
||||
$('#all-routes-summary').text(
|
||||
searchTerm
|
||||
? ns.I18n.t("app.von.strecken.gefunden", { value1: allRoutes.length, value2: routes.length })
|
||||
: ns.I18n.t("app.strecken.zur.auswahl", { value1: routes.length })
|
||||
? ns.I18n.t("app.search-result-count", { value1: allRoutes.length, value2: routes.length })
|
||||
: ns.I18n.t("app.route-count", { value1: routes.length })
|
||||
);
|
||||
renderRouteControl();
|
||||
}
|
||||
@@ -305,7 +305,7 @@
|
||||
function updateRouteControls() {
|
||||
const running = state.routeState === 'running';
|
||||
const paused = state.routeState === 'paused';
|
||||
const startLabel = paused ? ns.I18n.t("app.strecke.fortsetzen") : ns.I18n.t("app.strecke.starten");
|
||||
const startLabel = paused ? ns.I18n.t("app.resume-route") : ns.I18n.t("app.start-route");
|
||||
$('#start-route')
|
||||
.prop('disabled', running)
|
||||
.attr({ 'aria-label': startLabel, title: startLabel });
|
||||
@@ -333,7 +333,7 @@
|
||||
setActivePoi(null);
|
||||
hideNavigation();
|
||||
if (announce && wasActive && !returnToRoutes) {
|
||||
$('#tracking-status').prop('hidden', false).text(ns.I18n.t("app.strecke.beendet"));
|
||||
$('#tracking-status').prop('hidden', false).text(ns.I18n.t("app.route-finished"));
|
||||
} else {
|
||||
$('#tracking-status').prop('hidden', true).text('');
|
||||
}
|
||||
@@ -357,8 +357,8 @@
|
||||
ns.AudioPlayer.pause();
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text(ns.I18n.t("app.du.machst.eine.pause.die.app.sucht"));
|
||||
showNavigation(ns.I18n.t("app.strecke.pausiert"), $('#navigation-distance').text() || '–');
|
||||
.text(ns.I18n.t("app.pause-hint"));
|
||||
showNavigation(ns.I18n.t("app.route-paused"), $('#navigation-distance').text() || '–');
|
||||
updateRouteControls();
|
||||
updateRouteControlSummary();
|
||||
}
|
||||
@@ -384,7 +384,7 @@
|
||||
updateRouteControls();
|
||||
const list = $('#poi-list').empty();
|
||||
if (!route.pois.length) {
|
||||
list.append(("<li><p>" + escapeHtml(ns.I18n.t("app.diese.strecke.hat.noch.keine.stationen")) + "</p></li>"));
|
||||
list.append(("<li><p>" + escapeHtml(ns.I18n.t("app.empty-route")) + "</p></li>"));
|
||||
updateRouteControlSummary();
|
||||
return;
|
||||
}
|
||||
@@ -537,13 +537,13 @@
|
||||
$('#navigation-arrow').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
|
||||
showNavigation(
|
||||
heading == null
|
||||
? ns.I18n.t("app.norden.oben.nachste.station", { value1: metric.poi.title })
|
||||
: ns.I18n.t("app.nachste.station.2", { value1: metric.poi.title }),
|
||||
? ns.I18n.t("app.next-station-north", { value1: metric.poi.title })
|
||||
: ns.I18n.t("app.next-station", { value1: metric.poi.title }),
|
||||
`${distance} m`
|
||||
);
|
||||
$('#navigation-footer').attr(
|
||||
'aria-label',
|
||||
ns.I18n.t("app.nachste.station.in.metern", { value1: metric.poi.title, value2: distance })
|
||||
ns.I18n.t("app.next-station-distance", { value1: metric.poi.title, value2: distance })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -556,8 +556,8 @@
|
||||
|
||||
if (progress.reached) {
|
||||
$('#navigation-arrow').css('transform', 'rotate(0deg)');
|
||||
showNavigation(ns.I18n.t("app.alle.stationen.und.das.ziel.erreicht"), '0 m');
|
||||
$('#navigation-footer').attr('aria-label', ns.I18n.t("app.alle.stationen.und.das.ziel.erreicht"));
|
||||
showNavigation(ns.I18n.t("app.destination-reached"), '0 m');
|
||||
$('#navigation-footer').attr('aria-label', ns.I18n.t("app.destination-reached"));
|
||||
return;
|
||||
}
|
||||
const bearing = ns.Distance.bearing(position, progress.target);
|
||||
@@ -568,12 +568,12 @@
|
||||
const distance = Math.round(progress.distance);
|
||||
$('#navigation-arrow').css('transform', `rotate(${rotation.toFixed(1)}deg)`);
|
||||
showNavigation(
|
||||
heading == null ? ns.I18n.t("app.norden.oben.nachster.punkt.auf.dem.weg") : ns.I18n.t("app.nachster.punkt.auf.dem.weg"),
|
||||
heading == null ? ns.I18n.t("app.next-path-point-north") : ns.I18n.t("app.next-path-point"),
|
||||
`${distance} m`
|
||||
);
|
||||
$('#navigation-footer').attr(
|
||||
'aria-label',
|
||||
ns.I18n.t("app.nachster.punkt.auf.dem.weg.in.metern", { value1: distance })
|
||||
ns.I18n.t("app.next-path-point-distance", { value1: distance })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -608,8 +608,8 @@
|
||||
const nearest = nearestPendingPoi(metrics);
|
||||
updateNavigation(position);
|
||||
$('#tracking-status').text(
|
||||
ns.I18n.t("app.dein.standort.wird.gesucht.auf.etwa.m", { value1: Math.round(position.accuracy) })
|
||||
+ (nearest ? ns.I18n.t("app.nachste.station.m", { value1: Math.round(nearest.distance) }) : ns.I18n.t("app.alle.stationen.erreicht.2"))
|
||||
ns.I18n.t("app.location-accuracy", { value1: Math.round(position.accuracy) })
|
||||
+ (nearest ? ns.I18n.t("app.nearest-station-distance", { value1: Math.round(nearest.distance) }) : ns.I18n.t("app.stations-complete-suffix"))
|
||||
);
|
||||
if (nearest && nearest.distance <= nearest.poi.triggerRadiusM) {
|
||||
state.triggered.add(nearest.poi.id);
|
||||
@@ -636,11 +636,11 @@
|
||||
updateRouteControlSummary();
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text(resumed ? ns.I18n.t("app.strecke.wird.fortgesetzt") : ns.I18n.t("app.location.start"));
|
||||
showNavigation(resumed ? ns.I18n.t("app.strecke.wird.fortgesetzt.2") : ns.I18n.t("app.location.searching"));
|
||||
.text(resumed ? ns.I18n.t("app.resuming") : ns.I18n.t("app.location.start"));
|
||||
showNavigation(resumed ? ns.I18n.t("app.resuming-title") : ns.I18n.t("app.location.searching"));
|
||||
ns.Geo.start(
|
||||
evaluatePosition,
|
||||
() => $('#tracking-status').text(ns.I18n.t("app.dein.standort.konnte.nicht.gefunden.werden.prufe"))
|
||||
() => $('#tracking-status').text(ns.I18n.t("app.error.location"))
|
||||
);
|
||||
|
||||
const orientationAvailable = await ns.Orientation.start(updateHeading);
|
||||
@@ -648,12 +648,12 @@
|
||||
ns.Orientation.stop();
|
||||
return;
|
||||
}
|
||||
if (!orientationAvailable) $('#navigation-mode').text(ns.I18n.t("app.norden.oben.navigation"));
|
||||
if (!orientationAvailable) $('#navigation-mode').text(ns.I18n.t("app.navigation-north"));
|
||||
}
|
||||
|
||||
function routesFromResponse(response) {
|
||||
if (!Array.isArray(response?.routes)) {
|
||||
throw new TypeError(ns.I18n.t("app.die.strecken.konnten.nicht.geladen.werden.bitte"));
|
||||
throw new TypeError(ns.I18n.t("app.error.load-routes"));
|
||||
}
|
||||
return response.routes
|
||||
.filter(route => route?.id != null && route?.name != null)
|
||||
@@ -666,7 +666,7 @@
|
||||
state.localRoutesError = null;
|
||||
} catch (error) {
|
||||
state.localRoutes = [];
|
||||
state.localRoutesError = ns.UserMessages?.error(error, ns.I18n.t("app.deine.entwurfe.konnten.nicht.geoffnet.werden.bitte")) || ns.I18n.t("app.deine.entwurfe.konnten.nicht.geoffnet.werden.bitte");
|
||||
state.localRoutesError = ns.UserMessages?.error(error, ns.I18n.t("app.error.load-drafts")) || ns.I18n.t("app.error.load-drafts");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,12 +685,12 @@
|
||||
state.position = position;
|
||||
updateRouteProximity(position);
|
||||
$('#location-hint').text(
|
||||
ns.I18n.t("app.dein.standort.wurde.auf.etwa.m.genau", { value1: Math.round(position.accuracy) })
|
||||
+ ns.I18n.t("app.wir.zeigen.dir.strecken.bis.km.entfernung", { value1: ns.Config.routeRadiusKm })
|
||||
ns.I18n.t("app.location-found", { value1: Math.round(position.accuracy) })
|
||||
+ ns.I18n.t("app.search-radius", { value1: ns.Config.routeRadiusKm })
|
||||
);
|
||||
} catch {
|
||||
$('#location-hint').text(
|
||||
ns.I18n.t("app.dein.standort.konnte.nicht.gefunden.werden.du")
|
||||
ns.I18n.t("app.location-optional")
|
||||
);
|
||||
} finally {
|
||||
context.markStep('location', 'done');
|
||||
@@ -746,7 +746,7 @@
|
||||
try {
|
||||
await selectRoute(String(button.data('route-source') || 'server'), String(button.data('route-id')));
|
||||
} catch (error) {
|
||||
alert(ns.UserMessages?.error(error, ns.I18n.t("app.die.strecke.konnte.nicht.geoffnet.werden.prufe")) || ns.I18n.t("app.die.strecke.konnte.nicht.geoffnet.werden.bitte"));
|
||||
alert(ns.UserMessages?.error(error, ns.I18n.t("app.error.open-route")) || ns.I18n.t("app.error.open-draft"));
|
||||
} finally {
|
||||
button.prop('disabled', false);
|
||||
}
|
||||
|
||||
Vendored
+11
-19
@@ -3,15 +3,7 @@
|
||||
|
||||
const versions = Object.freeze({ app: '0.12.14', jquery: '4.0.0', jqueryUi: '1.14.2' });
|
||||
const steps = ['storage', 'jquery', 'jquery-ui', 'modules', 'server', 'routes', 'location'];
|
||||
const stepLabels = Object.freeze({
|
||||
get storage() { return globalThis.Wegwichtel.I18n.t("bootstrap-loader.gespeicherte.inhalte.prufen"); },
|
||||
get jquery() { return globalThis.Wegwichtel.I18n.t("bootstrap-loader.wegwichtel.vorbereiten"); },
|
||||
get 'jquery-ui'() { return globalThis.Wegwichtel.I18n.t("bootstrap-loader.die.bedienung.vorbereiten"); },
|
||||
get modules() { return globalThis.Wegwichtel.I18n.t("bootstrap-loader.die.app.vorbereiten"); },
|
||||
get server() { return globalThis.Wegwichtel.I18n.t("bootstrap-loader.verbindung.prufen"); },
|
||||
get routes() { return globalThis.Wegwichtel.I18n.t("bootstrap-loader.strecken.laden"); },
|
||||
get location() { return globalThis.Wegwichtel.I18n.t("bootstrap.location"); }
|
||||
});
|
||||
const stepLabel = step => globalThis.Wegwichtel.I18n.t(`bootstrap-loader.steps.${step}`);
|
||||
const screen = document.getElementById('bootstrap-screen');
|
||||
const progress = document.getElementById('bootstrap-progress');
|
||||
const errorBox = document.getElementById('bootstrap-error');
|
||||
@@ -43,8 +35,8 @@
|
||||
androidBrowserHint.hidden = false;
|
||||
androidBrowserHint.dataset.browser = isChrome ? 'chrome' : 'other';
|
||||
androidBrowserHint.textContent = isChrome
|
||||
? globalThis.Wegwichtel.I18n.t("bootstrap-loader.auf.diesem.gerat.kannst.du.bilder.und")
|
||||
: globalThis.Wegwichtel.I18n.t("bootstrap-loader.tipp.fur.android.mit.chrome.lassen.sich");
|
||||
? globalThis.Wegwichtel.I18n.t("bootstrap-loader.hints.chrome")
|
||||
: globalThis.Wegwichtel.I18n.t("bootstrap-loader.hints.android");
|
||||
}
|
||||
|
||||
function schedule(callback, delay = 0) {
|
||||
@@ -77,7 +69,7 @@
|
||||
}
|
||||
|
||||
function announceStep(step) {
|
||||
if (stepLabels[step]) setStatusText(stepLabels[step]);
|
||||
if (steps.includes(step)) setStatusText(stepLabel(step));
|
||||
}
|
||||
|
||||
function mark(step, state) {
|
||||
@@ -93,7 +85,7 @@
|
||||
if (progress) progress.value = completedSteps.size;
|
||||
|
||||
if (state === 'failed') {
|
||||
setStatusText(globalThis.Wegwichtel.I18n.t("bootstrap.step.failed", { value1: stepLabels[step] || step }));
|
||||
setStatusText(globalThis.Wegwichtel.I18n.t("bootstrap-loader.error.step-error", { value1: stepLabel(step) }));
|
||||
return;
|
||||
}
|
||||
if (state === 'done') {
|
||||
@@ -108,10 +100,10 @@
|
||||
script.src = versioned(src);
|
||||
script.async = false;
|
||||
script.onload = () => {
|
||||
if (test && !test()) reject(new Error(globalThis.Wegwichtel.I18n.t("bootstrap-loader.ein.teil.der.app.konnte.nicht.starten")));
|
||||
if (test && !test()) reject(new Error(globalThis.Wegwichtel.I18n.t("bootstrap-loader.error.start-error")));
|
||||
else resolve();
|
||||
};
|
||||
script.onerror = () => reject(new Error(globalThis.Wegwichtel.I18n.t("bootstrap-loader.ein.teil.der.app.konnte.nicht.geladen")));
|
||||
script.onerror = () => reject(new Error(globalThis.Wegwichtel.I18n.t("bootstrap-loader.error.load-error")));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
@@ -127,7 +119,7 @@
|
||||
}
|
||||
|
||||
function updateCleanupProgress(removed, total) {
|
||||
cleanupStatus.textContent = globalThis.Wegwichtel.I18n.t("bootstrap-loader.alten.fortschritt.loschen", { value1: removed, value2: total });
|
||||
cleanupStatus.textContent = globalThis.Wegwichtel.I18n.t("bootstrap-loader.cleanup-progress", { value1: removed, value2: total });
|
||||
cleanupStatus.hidden = false;
|
||||
}
|
||||
|
||||
@@ -161,7 +153,7 @@
|
||||
screen.classList.add('ready');
|
||||
readyText.hidden = false;
|
||||
|
||||
setStatusText(globalThis.Wegwichtel.I18n.t("bootstrap.ready"));
|
||||
setStatusText(globalThis.Wegwichtel.I18n.t("bootstrap-loader.ready"));
|
||||
actionStatus?.classList.add('is-leaving');
|
||||
enter.hidden = false;
|
||||
const showEnter = () => enter.classList.add('is-visible');
|
||||
@@ -194,7 +186,7 @@
|
||||
|
||||
try {
|
||||
await globalThis.Wegwichtel.OfflineCache?.initialize();
|
||||
setStatusText(stepLabels.storage, true);
|
||||
setStatusText(stepLabel('storage'), true);
|
||||
await cleanupOldProgress();
|
||||
mark('storage', 'done');
|
||||
|
||||
@@ -222,7 +214,7 @@
|
||||
console.error(error);
|
||||
const current = nextPendingStep();
|
||||
if (current) mark(current, 'failed');
|
||||
errorBox.textContent = globalThis.Wegwichtel.UserMessages?.error(error, globalThis.Wegwichtel.I18n.t("bootstrap-loader.wegwichtel.konnte.nicht.starten.prufe.deine.verbindung")) || globalThis.Wegwichtel.I18n.t("bootstrap-loader.wegwichtel.konnte.nicht.starten.bitte.versuche.es");
|
||||
errorBox.textContent = globalThis.Wegwichtel.UserMessages?.error(error, globalThis.Wegwichtel.I18n.t("bootstrap-loader.error.connection-error")) || globalThis.Wegwichtel.I18n.t("bootstrap-loader.error.init-error");
|
||||
errorBox.hidden = false;
|
||||
retry.hidden = false;
|
||||
}
|
||||
|
||||
+68
-68
@@ -141,17 +141,17 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
const next = highest + 1;
|
||||
if (!Number.isSafeInteger(next)) {
|
||||
throw new TypeError(ns.I18n.t("editor-ui.zu.dieser.strecke.kann.gerade.keine.weitere"));
|
||||
throw new TypeError(ns.I18n.t("editor.error.add-station"));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function routeName(route) {
|
||||
return text(route?.name || route?.title || ns.I18n.t("editor-ui.unbenannte.strecke"));
|
||||
return text(route?.name || route?.title || ns.I18n.t("editor.untitled-route"));
|
||||
}
|
||||
|
||||
function poiTitle(poi) {
|
||||
return text(poi?.title || poi?.name || ns.I18n.t("editor-ui.station.ohne.namen"));
|
||||
return text(poi?.title || poi?.name || ns.I18n.t("editor.untitled-station"));
|
||||
}
|
||||
|
||||
function apiBase() {
|
||||
@@ -196,9 +196,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function routeDialogTitle() {
|
||||
if (state.currentSource === 'local') {
|
||||
return routeId(state.currentRoute) ? ns.I18n.t("editor-ui.eigene.strecke.bearbeiten") : ns.I18n.t("editor-ui.neue.strecke");
|
||||
return routeId(state.currentRoute) ? ns.I18n.t("editor.edit-draft") : ns.I18n.t("editor.new-route");
|
||||
}
|
||||
return ns.I18n.t("editor-ui.strecke.ansehen");
|
||||
return ns.I18n.t("editor.view-route");
|
||||
}
|
||||
|
||||
function showListView() {
|
||||
@@ -216,7 +216,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function showPoiView() {
|
||||
setDialogTitle(state.currentPoi ? ns.I18n.t("editor-ui.station.bearbeiten") : ns.I18n.t("editor-ui.station.hinzufugen"));
|
||||
setDialogTitle(state.currentPoi ? ns.I18n.t("editor.edit-station") : ns.I18n.t("editor.add-station"));
|
||||
setHidden(byId(ids.routeListView), true);
|
||||
setHidden(byId(ids.routeDetailView), true);
|
||||
setHidden(byId(ids.poiFormView), false);
|
||||
@@ -265,7 +265,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
async function handleResponse(response) {
|
||||
if (response.ok) return parseJson(response);
|
||||
const payload = await parseJson(response);
|
||||
const error = new Error(payload.message || ns.I18n.t("editor-ui.die.aktion.konnte.nicht.ausgefuhrt.werden.bitte"));
|
||||
const error = new Error(payload.message || ns.I18n.t("editor.error.action"));
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
}
|
||||
@@ -354,13 +354,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
const label = document.createElement('div');
|
||||
appendText(label, 'strong', routeName(route));
|
||||
appendText(label, 'small', routeSource(route) === 'local' ? ns.I18n.t("app.eigener.entwurf") : ns.I18n.t("editor-ui.veroffentlichte.strecke"));
|
||||
appendText(label, 'small', routeSource(route) === 'local' ? ns.I18n.t("app.draft") : ns.I18n.t("editor.published-route"));
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = classes.iconRow;
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor-ui.strecke.bearbeiten"), '✎', 'edit-route'));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor.edit-route"), '✎', 'edit-route'));
|
||||
if (routeSource(route) === 'local') {
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor-ui.strecke.loschen"), '×', 'delete-route', false, true));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor.delete-route"), '×', 'delete-route', false, true));
|
||||
}
|
||||
item.append(label, actions);
|
||||
return item;
|
||||
@@ -380,13 +380,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const title = poiTitle(poi);
|
||||
const label = document.createElement('div');
|
||||
appendText(label, 'strong', title);
|
||||
appendText(label, 'small', ns.I18n.t("editor-ui.station", { value1: index + 1 }));
|
||||
appendText(label, 'small', ns.I18n.t("editor.station-number", { value1: index + 1 }));
|
||||
const actions = document.createElement('div');
|
||||
actions.className = classes.iconRow;
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor.station.up", { value1: title }), '↑', 'move-poi-up', index === 0));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor.station.down", { value1: title }), '↓', 'move-poi-down', index === state.pois.length - 1));
|
||||
actions.appendChild(iconButton(`${title} bearbeiten`, '✎', 'edit-poi'));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor-ui.loschen", { value1: title }), '×', 'delete-poi', false, true));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor.delete-item", { value1: title }), '×', 'delete-poi', false, true));
|
||||
item.append(label, actions);
|
||||
return item;
|
||||
}
|
||||
@@ -409,8 +409,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
list.textContent = '';
|
||||
const serverRoutes = state.routes.filter(route => routeSource(route) === 'server');
|
||||
const localRoutes = state.routes.filter(route => routeSource(route) === 'local');
|
||||
renderRouteGroup(list, ns.I18n.t("editor-ui.strecken.fur.alle"), serverRoutes, ns.I18n.t("app.hier.gibt.es.noch.keine.strecken.fur"));
|
||||
renderRouteGroup(list, ns.I18n.t("editor-ui.meine.entwurfe"), localRoutes, ns.I18n.t("editor-ui.noch.kein.eigener.entwurf.vorhanden"));
|
||||
renderRouteGroup(list, ns.I18n.t("editor.public-routes"), serverRoutes, ns.I18n.t("app.no-public-route"));
|
||||
renderRouteGroup(list, ns.I18n.t("editor.drafts"), localRoutes, ns.I18n.t("editor.no-draft"));
|
||||
}
|
||||
|
||||
function renderPoiList() {
|
||||
@@ -420,7 +420,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (!state.pois.length) {
|
||||
const item = document.createElement('li');
|
||||
item.className = classes.poiItem;
|
||||
appendText(item, 'span', ns.I18n.t("editor-ui.noch.keine.stationen.angelegt"));
|
||||
appendText(item, 'span', ns.I18n.t("editor.no-stations"));
|
||||
list.appendChild(item);
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +437,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
async function loadRoutes() {
|
||||
setStatus(ns.I18n.t("editor-ui.strecken.werden.geladen"));
|
||||
setStatus(ns.I18n.t("editor.loading-routes"));
|
||||
const payload = await readJson('/routes?activatedOnly=true');
|
||||
const serverRoutes = routeArray(payload).map(route => ({ ...route, _source: 'server' }));
|
||||
let localRoutes = [];
|
||||
@@ -496,7 +496,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function fillRouteForm(route) {
|
||||
byId(ids.routeId).value = routeId(route);
|
||||
byId(ids.routeName).value = routeName(route) === ns.I18n.t("editor-ui.unbenannte.strecke") ? '' : routeName(route);
|
||||
byId(ids.routeName).value = routeName(route) === ns.I18n.t("editor.untitled-route") ? '' : routeName(route);
|
||||
byId(ids.routeDescription).value = text(route?.description);
|
||||
byId(ids.routeGpx).value = '';
|
||||
setHidden(byId(ids.routeGpxGroup), state.currentSource !== 'server');
|
||||
@@ -506,16 +506,16 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const uploaded = Boolean(route?.serverRouteId);
|
||||
setHidden(uploadButton, false);
|
||||
setDisabled(uploadButton, uploaded);
|
||||
if (uploadButton) uploadButton.textContent = uploaded ? ns.I18n.t("editor-ui.schon.zur.prufung.gesendet") : ns.I18n.t("editor-ui.zur.prufung.senden");
|
||||
if (uploadButton) uploadButton.textContent = uploaded ? ns.I18n.t("editor.submitted") : ns.I18n.t("editor.submit");
|
||||
byId(ids.routeActivated).textContent = uploaded
|
||||
? ns.I18n.t("editor-ui.diese.strecke.wurde.zur.prufung.gesendet.andere")
|
||||
: ns.I18n.t("editor-ui.dieser.entwurf.bleibt.auf.deinem.gerat.mit");
|
||||
? ns.I18n.t("editor.submitted-hint")
|
||||
: ns.I18n.t("editor.draft-hint");
|
||||
} else {
|
||||
setHidden(uploadButton, true);
|
||||
setDisabled(uploadButton, true);
|
||||
byId(ids.routeActivated).textContent = route?.activatedAt || route?.activated_at
|
||||
? ns.I18n.t("editor-ui.diese.strecke.ist.fur.alle.freigegeben.und")
|
||||
: ns.I18n.t("editor-ui.diese.strecke.wurde.noch.nicht.fur.alle");
|
||||
? ns.I18n.t("editor.read-only-hint")
|
||||
: ns.I18n.t("editor.unpublished-hint");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
select.dataset.mediaKey = key;
|
||||
select.type = 'button';
|
||||
select.setAttribute('aria-expanded', String(state.selectedMediaKey === key));
|
||||
select.setAttribute('aria-label', ns.I18n.t("editor-ui.knopf.zum.entfernen", { value1: label, value2: state.selectedMediaKey === key ? 'ausblenden' : 'anzeigen' }));
|
||||
select.setAttribute('aria-label', ns.I18n.t("editor.remove-media-label", { value1: label, value2: state.selectedMediaKey === key ? 'ausblenden' : 'anzeigen' }));
|
||||
|
||||
if (kind === 'image' && previewUrl) {
|
||||
const image = document.createElement('img');
|
||||
@@ -612,7 +612,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
preview.appendChild(mediaPreviewCard({
|
||||
key: 'pending-audio',
|
||||
kind: 'audio',
|
||||
label: state.pendingAudioFile.name || ns.I18n.t("editor-ui.neue.tonaufnahme"),
|
||||
label: state.pendingAudioFile.name || ns.I18n.t("editor.new-audio"),
|
||||
source: 'pending'
|
||||
}));
|
||||
}
|
||||
@@ -622,7 +622,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
preview.appendChild(mediaPreviewCard({
|
||||
key: `existing-image-${image.id ?? index}`,
|
||||
kind: 'image',
|
||||
label: image.name || image.caption || ns.I18n.t("editor-ui.bild", { value1: index + 1 }),
|
||||
label: image.name || image.caption || ns.I18n.t("editor.image-number", { value1: index + 1 }),
|
||||
mediaId: image.id ?? '',
|
||||
mediaIndex: index,
|
||||
previewUrl: imagePreviewUrl(image),
|
||||
@@ -634,7 +634,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
preview.appendChild(mediaPreviewCard({
|
||||
key: pending.key,
|
||||
kind: 'image',
|
||||
label: pending.file?.name || ns.I18n.t("editor-ui.neues.bild", { value1: index + 1 }),
|
||||
label: pending.file?.name || ns.I18n.t("editor.new-image", { value1: index + 1 }),
|
||||
mediaIndex: index,
|
||||
previewUrl: pending.previewUrl || null,
|
||||
source: 'pending'
|
||||
@@ -655,8 +655,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (imageCount) parts.push(ns.I18n.t('media.image.count', { count: imageCount }));
|
||||
|
||||
status.textContent = parts.length
|
||||
? ns.I18n.t("editor-ui.tippe.auf.ein.bild.oder.eine.tonaufnahme", { value1: parts.join(' · ') })
|
||||
: ns.I18n.t("editor-ui.noch.keine.bilder.oder.tonaufnahmen.vorhanden");
|
||||
? ns.I18n.t("editor.remove-media-hint", { value1: parts.join(' · ') })
|
||||
: ns.I18n.t("editor.no-media");
|
||||
}
|
||||
|
||||
function openFilePicker(inputId) {
|
||||
@@ -668,12 +668,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (kind === 'audio') {
|
||||
return {
|
||||
multiple: false,
|
||||
types: [{ description: ns.I18n.t("editor-ui.tonaufnahmen"), accept: { 'audio/*': ['.mp3', '.m4a', '.wav', '.ogg', '.webm'] } }]
|
||||
types: [{ description: ns.I18n.t("editor.audio-section"), accept: { 'audio/*': ['.mp3', '.m4a', '.wav', '.ogg', '.webm'] } }]
|
||||
};
|
||||
}
|
||||
return {
|
||||
multiple: true,
|
||||
types: [{ description: ns.I18n.t("editor-ui.bilder"), accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.webp'] } }]
|
||||
types: [{ description: ns.I18n.t("editor.image-section"), accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.webp'] } }]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -721,7 +721,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
function fillPoiForm(poi) {
|
||||
resetPendingMedia();
|
||||
byId(ids.poiId).value = poiId(poi);
|
||||
byId(ids.poiTitle).value = poiTitle(poi) === ns.I18n.t("editor-ui.station.ohne.namen") ? '' : poiTitle(poi);
|
||||
byId(ids.poiTitle).value = poiTitle(poi) === ns.I18n.t("editor.untitled-station") ? '' : poiTitle(poi);
|
||||
byId(ids.poiDescription).value = text(poi?.description || poi?.text);
|
||||
byId(ids.poiLat).value = text(poi?.lat ?? poi?.latitude);
|
||||
byId(ids.poiLon).value = text(poi?.lon ?? poi?.longitude);
|
||||
@@ -764,7 +764,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
if (state.currentSource === 'local') {
|
||||
const routePackage = await ns.LocalRoutes.getWithMedia(id, { requestPermission: true });
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor-ui.dein.entwurf.wurde.nicht.gefunden"));
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor.error.draft-missing"));
|
||||
state.currentRoute = {
|
||||
...routePackage.route,
|
||||
id: routePackage.id,
|
||||
@@ -792,7 +792,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
gpx: ns.LocalRoutes.emptyGpx(ns.I18n.t("editor-ui.neue.strecke")),
|
||||
gpx: ns.LocalRoutes.emptyGpx(ns.I18n.t("editor.new-route")),
|
||||
_source: 'local'
|
||||
};
|
||||
state.currentPoi = null;
|
||||
@@ -800,7 +800,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
fillRouteForm(state.currentRoute);
|
||||
renderPoiList();
|
||||
showRouteView();
|
||||
setStatus(ns.I18n.t("editor-ui.deine.neue.strecke.bleibt.zuerst.auf.diesem"));
|
||||
setStatus(ns.I18n.t("editor.draft-status"));
|
||||
}
|
||||
|
||||
function formDataFromRoute(values) {
|
||||
@@ -838,12 +838,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
state.pois = saved.pois;
|
||||
await loadRoutes();
|
||||
await loadRoute({ id: saved.id, _source: 'local' });
|
||||
setStatus(ns.I18n.t("editor-ui.gespeichert"));
|
||||
setStatus(ns.I18n.t("editor.saved"));
|
||||
}
|
||||
|
||||
async function saveServerRoute(values) {
|
||||
const id = routeId(state.currentRoute);
|
||||
if (!id) throw new Error(ns.I18n.t("editor-ui.lege.eine.neue.strecke.zuerst.auf.diesem"));
|
||||
if (!id) throw new Error(ns.I18n.t("editor.error.create-locally"));
|
||||
const file = byId(ids.routeGpx)?.files?.[0];
|
||||
const path = `/routes/${encodeURIComponent(id)}`;
|
||||
const payload = file
|
||||
@@ -853,12 +853,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
state.currentRoute = { ...saved, _source: 'server' };
|
||||
await loadRoutes();
|
||||
await loadRoute({ id, _source: 'server' });
|
||||
setStatus(ns.I18n.t("editor-ui.gespeichert"));
|
||||
setStatus(ns.I18n.t("editor.saved"));
|
||||
}
|
||||
|
||||
async function saveRoute() {
|
||||
const values = routeFromForm();
|
||||
if (!values.name) throw new Error(ns.I18n.t("editor-ui.gib.deiner.strecke.einen.namen"));
|
||||
if (!values.name) throw new Error(ns.I18n.t("editor.error.route-name"));
|
||||
if (state.currentSource === 'server') await saveServerRoute(values);
|
||||
else await saveLocalRoute(values);
|
||||
}
|
||||
@@ -877,7 +877,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function localRouteUploadForm(routePackage) {
|
||||
const data = new FormData();
|
||||
data.append('name', routePackage.route?.name || ns.I18n.t("editor-ui.unbenannte.strecke"));
|
||||
data.append('name', routePackage.route?.name || ns.I18n.t("editor.untitled-route"));
|
||||
data.append('description', routePackage.route?.description || '');
|
||||
const uploadGpx = ns.LocalRoutes.gpxForUpload(routePackage);
|
||||
const gpx = new Blob([uploadGpx], { type: 'application/gpx+xml' });
|
||||
@@ -888,7 +888,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
async function storedMediaBlob(media) {
|
||||
const blob = await ns.LocalRoutes.mediaBlob(media, { requestPermission: true });
|
||||
if (!blob) {
|
||||
throw new Error(ns.I18n.t("editor-ui.die.datei.konnte.nicht.gesendet.werden.bitte", { value1: media?.name || '' }).trim());
|
||||
throw new Error(ns.I18n.t("editor.error.send-file", { value1: media?.name || '' }).trim());
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
@@ -918,12 +918,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const payload = await sendForm('/routes', 'POST', localRouteUploadForm(routePackage));
|
||||
const serverRoute = payload.route || payload;
|
||||
createdRouteId = routeId(serverRoute);
|
||||
if (!createdRouteId) throw new Error(ns.I18n.t("editor-ui.die.strecke.konnte.nicht.gesendet.werden.bitte"));
|
||||
if (!createdRouteId) throw new Error(ns.I18n.t("editor.error.send-route"));
|
||||
|
||||
const orderedPois = [...(routePackage.pois || [])]
|
||||
.sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
||||
for (const [index, poi] of orderedPois.entries()) {
|
||||
const poiTitle = poi.title || ns.I18n.t("editor-ui.station", { value1: index + 1 });
|
||||
const poiTitle = poi.title || ns.I18n.t("editor.station-number", { value1: index + 1 });
|
||||
const poiPayload = await sendJson(`/routes/${encodeURIComponent(createdRouteId)}/pois`, 'POST', {
|
||||
description: poi.description || '',
|
||||
lat: poi.lat,
|
||||
@@ -934,7 +934,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
});
|
||||
const serverPoi = poiPayload.poi || poiPayload;
|
||||
const serverPoiId = poiId(serverPoi);
|
||||
if (!serverPoiId) throw new Error(ns.I18n.t("editor-ui.die.station.konnte.nicht.gesendet.werden.bitte", { value1: poiTitle }));
|
||||
if (!serverPoiId) throw new Error(ns.I18n.t("editor.error.send-station", { value1: poiTitle }));
|
||||
await uploadLocalPoiMedia(createdRouteId, serverPoiId, poi);
|
||||
}
|
||||
return createdRouteId;
|
||||
@@ -951,11 +951,11 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
async function uploadLocalRoute() {
|
||||
if (state.currentSource !== 'local') throw new Error(ns.I18n.t("editor-ui.du.kannst.nur.deine.eigenen.entwurfe.zur"));
|
||||
if (state.currentSource !== 'local') throw new Error(ns.I18n.t("editor.error.submit-own-draft"));
|
||||
const values = routeFromForm();
|
||||
if (!values.name) throw new Error(ns.I18n.t("editor-ui.gib.deiner.strecke.einen.namen"));
|
||||
if (!values.name) throw new Error(ns.I18n.t("editor.error.route-name"));
|
||||
if (state.currentRoute?.serverRouteId) {
|
||||
setStatus(ns.I18n.t("editor-ui.diese.strecke.wurde.schon.zur.prufung.gesendet"));
|
||||
setStatus(ns.I18n.t("editor.error.already-submitted"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -963,10 +963,10 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
setDisabled(uploadButton, true);
|
||||
try {
|
||||
await saveLocalRoute(values);
|
||||
setStatus(ns.I18n.t("editor-ui.deine.strecke.wird.gesendet"));
|
||||
setStatus(ns.I18n.t("editor.submitting"));
|
||||
const localId = routeId(state.currentRoute);
|
||||
const routePackage = ns.LocalRoutes.get(localId);
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor-ui.dein.entwurf.wurde.nicht.gefunden"));
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor.error.draft-missing"));
|
||||
|
||||
const serverRouteId = await createServerRouteFromLocal(routePackage);
|
||||
const uploadedAt = new Date().toISOString();
|
||||
@@ -982,7 +982,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
state.pois = saved.pois;
|
||||
fillRouteForm(state.currentRoute);
|
||||
await loadRoutes();
|
||||
setStatus(ns.I18n.t("editor-ui.zur.prufung.gesendet"));
|
||||
setStatus(ns.I18n.t("editor.submit-success"));
|
||||
} finally {
|
||||
if (!state.currentRoute?.serverRouteId) setDisabled(uploadButton, false);
|
||||
}
|
||||
@@ -997,7 +997,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const lat = Number(position?.lat);
|
||||
const lon = Number(position?.lon);
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
|
||||
setStatus(ns.I18n.t("editor-ui.fur.eine.neue.station.brauchen.wir.deinen"), true);
|
||||
setStatus(ns.I18n.t("editor.error.location-required"), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1010,20 +1010,20 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
lon,
|
||||
triggerRadiusM: Number.isFinite(accuracy) && accuracy > 0 ? accuracy : 25
|
||||
});
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor-ui.station.hinzufugen");
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor.add-station");
|
||||
showPoiView();
|
||||
}
|
||||
|
||||
function editPoi(poi) {
|
||||
state.currentPoi = poi;
|
||||
fillPoiForm(poi);
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor-ui.station.bearbeiten");
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor.edit-station");
|
||||
showPoiView();
|
||||
}
|
||||
|
||||
async function mediaFromLocalForm(existingPoi, poiIdentifier) {
|
||||
const routeIdentifier = routeId(state.currentRoute);
|
||||
if (!routeIdentifier) throw new Error(ns.I18n.t("editor-ui.speichere.zuerst.die.strecke"));
|
||||
if (!routeIdentifier) throw new Error(ns.I18n.t("editor.error.save-route-first"));
|
||||
|
||||
const storedNow = [];
|
||||
const replacedAfterSave = [];
|
||||
@@ -1063,7 +1063,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
async function persistLocalCurrentRoute() {
|
||||
const id = routeId(state.currentRoute);
|
||||
if (!id) throw new Error(ns.I18n.t("editor-ui.speichere.zuerst.die.strecke"));
|
||||
if (!id) throw new Error(ns.I18n.t("editor.error.save-route-first"));
|
||||
const currentPoiId = poiId(state.currentPoi);
|
||||
const saved = ns.LocalRoutes.save({
|
||||
id,
|
||||
@@ -1082,7 +1082,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
async function saveLocalPoi(values) {
|
||||
if (!Number.isFinite(values.lat) || !Number.isFinite(values.lon)) {
|
||||
throw new TypeError(ns.I18n.t("editor-ui.der.ort.dieser.station.fehlt.erlaube.deinen"));
|
||||
throw new TypeError(ns.I18n.t("editor.error.station-location"));
|
||||
}
|
||||
const currentId = numericPoiId(byId(ids.poiId).value);
|
||||
const identifier = currentId ?? nextLocalPoiId();
|
||||
@@ -1109,7 +1109,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
await Promise.allSettled(media.replacedAfterSave.map(ns.LocalRoutes.removeMedia));
|
||||
resetPendingMedia();
|
||||
setStatus(ns.I18n.t("editor-ui.station.mit.bildern.und.ton.auf.diesem"));
|
||||
setStatus(ns.I18n.t("editor.station-saved-with-media"));
|
||||
await loadRoute({ id: routeId(state.currentRoute), _source: 'local' });
|
||||
}
|
||||
|
||||
@@ -1132,7 +1132,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
async function saveServerPoi(values) {
|
||||
const currentRouteId = routeId(state.currentRoute);
|
||||
if (!currentRouteId) throw new Error(ns.I18n.t("editor-ui.speichere.zuerst.die.strecke"));
|
||||
if (!currentRouteId) throw new Error(ns.I18n.t("editor.error.save-route-first"));
|
||||
const currentPoiId = byId(ids.poiId).value;
|
||||
const path = currentPoiId
|
||||
? `/routes/${encodeURIComponent(currentRouteId)}/pois/${encodeURIComponent(currentPoiId)}`
|
||||
@@ -1142,13 +1142,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const saved = payload.poi || payload;
|
||||
const savedPoiId = poiId(saved) || currentPoiId;
|
||||
if (savedPoiId) await uploadPoiMedia(currentRouteId, savedPoiId);
|
||||
setStatus(ns.I18n.t("editor-ui.station.gespeichert"));
|
||||
setStatus(ns.I18n.t("editor.station-saved"));
|
||||
await loadRoute({ id: currentRouteId, _source: 'server' });
|
||||
}
|
||||
|
||||
async function savePoi() {
|
||||
const values = poiFromForm();
|
||||
if (!values.title) throw new Error(ns.I18n.t("editor-ui.gib.deiner.station.einen.namen"));
|
||||
if (!values.title) throw new Error(ns.I18n.t("editor.error.station-name"));
|
||||
if (state.currentSource === 'local') await saveLocalPoi(values);
|
||||
else await saveServerPoi(values);
|
||||
}
|
||||
@@ -1196,13 +1196,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
state.selectedMediaKey = null;
|
||||
renderPoiMediaPreview();
|
||||
updatePoiMediaStatus();
|
||||
setStatus(kind === 'audio' ? ns.I18n.t("editor.audio.removed") : ns.I18n.t("editor-ui.bild.entfernt"));
|
||||
setStatus(kind === 'audio' ? ns.I18n.t("editor.audio.removed") : ns.I18n.t("editor.image-removed"));
|
||||
}
|
||||
|
||||
async function removeExistingServerMedia(kind, mediaId) {
|
||||
const currentRouteId = routeId(state.currentRoute);
|
||||
const currentPoiId = poiId(state.currentPoi);
|
||||
if (!currentRouteId || !currentPoiId) throw new Error(ns.I18n.t("editor-ui.die.station.ist.noch.nicht.gespeichert"));
|
||||
if (!currentRouteId || !currentPoiId) throw new Error(ns.I18n.t("editor.error.station-unsaved"));
|
||||
const basePath = `/routes/${encodeURIComponent(currentRouteId)}/pois/${encodeURIComponent(currentPoiId)}`;
|
||||
const updated = { ...state.currentPoi };
|
||||
|
||||
@@ -1212,7 +1212,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
updated.audioUrl = null;
|
||||
updated.audio_path = null;
|
||||
} else {
|
||||
if (!mediaId) throw new Error(ns.I18n.t("editor-ui.das.bild.konnte.nicht.entfernt.werden.lade"));
|
||||
if (!mediaId) throw new Error(ns.I18n.t("editor.error.remove-image"));
|
||||
await sendDelete(`${basePath}/pictures/${encodeURIComponent(mediaId)}`);
|
||||
updated.images = (state.currentPoi.images || []).filter(image => text(image.id) !== text(mediaId));
|
||||
}
|
||||
@@ -1221,7 +1221,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
state.selectedMediaKey = null;
|
||||
renderPoiMediaPreview();
|
||||
updatePoiMediaStatus();
|
||||
setStatus(kind === 'audio' ? ns.I18n.t("editor.audio.removed") : ns.I18n.t("editor-ui.bild.entfernt"));
|
||||
setStatus(kind === 'audio' ? ns.I18n.t("editor.audio.removed") : ns.I18n.t("editor.image-removed"));
|
||||
}
|
||||
|
||||
async function removeMedia(control) {
|
||||
@@ -1266,13 +1266,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
await Promise.allSettled(removedMedia.map(ns.LocalRoutes.removeMedia));
|
||||
renderPoiList();
|
||||
showRouteView();
|
||||
setStatus(ns.I18n.t("editor-ui.station.entfernt.deine.strecke.wurde.gespeichert"));
|
||||
setStatus(ns.I18n.t("editor.station-removed-and-saved"));
|
||||
return;
|
||||
}
|
||||
|
||||
await sendDelete(`/routes/${encodeURIComponent(currentRouteId)}/pois/${encodeURIComponent(currentPoiId)}`);
|
||||
await loadRoute({ id: currentRouteId, _source: 'server' });
|
||||
setStatus(ns.I18n.t("editor-ui.station.entfernt"));
|
||||
setStatus(ns.I18n.t("editor.station-removed"));
|
||||
}
|
||||
|
||||
function reorderPois(index, offset) {
|
||||
@@ -1295,7 +1295,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (!currentRouteId) return;
|
||||
if (state.currentSource === 'local') {
|
||||
await persistLocalCurrentRoute();
|
||||
setStatus(ns.I18n.t("editor-ui.reihenfolge.der.stationen.gespeichert"));
|
||||
setStatus(ns.I18n.t("editor.station-order-saved"));
|
||||
return;
|
||||
}
|
||||
const jobs = state.pois.map((poi, index) => {
|
||||
@@ -1340,7 +1340,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
await loadRoutes();
|
||||
showListView();
|
||||
setStatus(ns.I18n.t("editor-ui.strecke.geloscht"));
|
||||
setStatus(ns.I18n.t("editor.route-deleted"));
|
||||
}
|
||||
|
||||
async function routeListAction(event) {
|
||||
@@ -1387,7 +1387,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
await ns.StorageUsage?.refresh();
|
||||
} catch (error) {
|
||||
ns.StorageUsage?.reportError(error);
|
||||
setStatus(ns.UserMessages?.error(error) || ns.I18n.t("editor-ui.das.hat.leider.nicht.geklappt.bitte.versuche"), true);
|
||||
setStatus(ns.UserMessages?.error(error) || ns.I18n.t("editor.error.generic"), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
function unsupportedError() {
|
||||
return new Error(ns.I18n.t("geolocation.dieses.gerat.kann.deinen.standort.nicht.finden"));
|
||||
return new Error(ns.I18n.t("location.error.unsupported"));
|
||||
}
|
||||
|
||||
ns.Geo = {
|
||||
|
||||
@@ -4,15 +4,15 @@ globalThis.Wegwichtel ??= {};
|
||||
'use strict';
|
||||
|
||||
const entries = Object.freeze([
|
||||
['🪵', "group-symbols.holz"],
|
||||
['🪨', "group-symbols.felsen"],
|
||||
['🏮', "group-symbols.laterne"],
|
||||
['🌼', "group-symbols.blume"],
|
||||
['🚬', "group-symbols.pfeife"],
|
||||
['🐦', "group-symbols.vogel"],
|
||||
['🍄', "group-symbols.pilz"],
|
||||
['🍓', "group-symbols.erdbeere"],
|
||||
['🎃', "group-symbols.kurbis"]
|
||||
['🪵', "symbols.wood"],
|
||||
['🪨', "symbols.rock"],
|
||||
['🏮', "symbols.lantern"],
|
||||
['🌼', "symbols.flower"],
|
||||
['🚬', "symbols.pipe"],
|
||||
['🐦', "symbols.bird"],
|
||||
['🍄', "symbols.mushroom"],
|
||||
['🍓', "symbols.strawberry"],
|
||||
['🎃', "symbols.pumpkin"]
|
||||
].map(([unicode, labelKey]) => Object.freeze({
|
||||
unicode,
|
||||
get label() { return ns.I18n.t(labelKey); },
|
||||
|
||||
+31
-9
@@ -3,11 +3,22 @@ globalThis.Wegwichtel ??= {};
|
||||
'use strict';
|
||||
|
||||
class WegwichtelI18n {
|
||||
static #languages = new Map([['de', ns.TextsDe.messages]]);
|
||||
static #language = 'de';
|
||||
static #languages = new Map(ns.TextCatalogs);
|
||||
static #fallbackLanguage = this.#languages.keys().next().value;
|
||||
static #language = this.#fallbackLanguage;
|
||||
|
||||
static get language() { return this.#language; }
|
||||
|
||||
static detectLanguage(preferences = globalThis.navigator?.languages ?? [globalThis.navigator?.language]) {
|
||||
for (const language of preferences) {
|
||||
const requested = String(language ?? '').toLowerCase();
|
||||
if (this.#languages.has(requested)) return requested;
|
||||
const base = requested.split('-')[0];
|
||||
if (this.#languages.has(base)) return base;
|
||||
}
|
||||
return this.#fallbackLanguage;
|
||||
}
|
||||
|
||||
static register(language, messages) {
|
||||
this.#languages.set(language.toLowerCase(), Object.freeze({ ...messages }));
|
||||
}
|
||||
@@ -15,7 +26,7 @@ globalThis.Wegwichtel ??= {};
|
||||
static setLanguage(language, root = globalThis.document) {
|
||||
const requested = String(language).toLowerCase();
|
||||
const base = requested.split('-')[0];
|
||||
this.#language = 'de';
|
||||
this.#language = this.#fallbackLanguage;
|
||||
if (this.#languages.has(base)) this.#language = base;
|
||||
if (this.#languages.has(requested)) this.#language = requested;
|
||||
this.apply(root);
|
||||
@@ -23,32 +34,43 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
static t(key, variables = {}) {
|
||||
let value = this.#languages.get(this.#language)?.[key] ?? ns.TextsDe.messages[key];
|
||||
let value = this.#lookup(this.#languages.get(this.#language), key)
|
||||
?? this.#lookup(this.#languages.get(this.#fallbackLanguage), key);
|
||||
if (value == null) throw new RangeError(`Missing translation: ${key}`);
|
||||
if (typeof value === 'object') {
|
||||
const plural = new Intl.PluralRules(this.#language).select(Number(variables.count));
|
||||
value = value[plural] ?? value.other;
|
||||
}
|
||||
return value.replace(/\{([a-zA-Z0-9_]+)\}/g, (placeholder, name) => {
|
||||
return value.replaceAll(/\{(\w+)\}/g, (placeholder, name) => {
|
||||
if (!Object.hasOwn(variables, name)) return placeholder;
|
||||
return String(variables[name] ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
static #lookup(catalog, key) {
|
||||
if (Object.hasOwn(catalog, key)) return catalog[key];
|
||||
let value = catalog;
|
||||
for (const part of key.split('.')) {
|
||||
if (!value || !Object.hasOwn(value, part)) return undefined;
|
||||
value = value[part];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static apply(root = globalThis.document) {
|
||||
if (!root?.querySelectorAll) return;
|
||||
if (root.documentElement) root.documentElement.lang = this.#language;
|
||||
for (const element of root.querySelectorAll('[data-i18n]')) {
|
||||
if (typeof element.getAttribute === 'function') element.textContent = this.t(element.getAttribute('data-i18n'));
|
||||
if (element.dataset?.i18n) element.textContent = this.t(element.dataset.i18n);
|
||||
}
|
||||
for (const attribute of ['title', 'aria-label', 'placeholder', 'alt']) {
|
||||
for (const [attribute, key] of [['title', 'i18nTitle'], ['aria-label', 'i18nAriaLabel'], ['placeholder', 'i18nPlaceholder'], ['alt', 'i18nAlt']]) {
|
||||
for (const element of root.querySelectorAll(`[data-i18n-${attribute}]`)) {
|
||||
if (typeof element.getAttribute === 'function' && typeof element.setAttribute === 'function') element.setAttribute(attribute, this.t(element.getAttribute(`data-i18n-${attribute}`)));
|
||||
if (element.dataset?.[key] && typeof element.setAttribute === 'function') element.setAttribute(attribute, this.t(element.dataset[key]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ns.I18n = WegwichtelI18n;
|
||||
WegwichtelI18n.apply();
|
||||
WegwichtelI18n.setLanguage(WegwichtelI18n.detectLanguage());
|
||||
}(globalThis.Wegwichtel));
|
||||
|
||||
+16
-16
@@ -5,14 +5,14 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
const DROPDOWN_ANIMATION_MS = 180;
|
||||
const pageTitles = Object.freeze({
|
||||
get privacy() { return ns.I18n.t("legal-ui.datenschutzerklarung"); },
|
||||
get imprint() { return ns.I18n.t("legal-ui.impressum"); },
|
||||
get technology() { return ns.I18n.t("legal-ui.speichern.und.berechtigungen"); }
|
||||
get privacy() { return ns.I18n.t("info.privacy"); },
|
||||
get imprint() { return ns.I18n.t("info.imprint"); },
|
||||
get technology() { return ns.I18n.t("info.storage"); }
|
||||
});
|
||||
function menuEntries() { return [
|
||||
['privacy', ns.I18n.t("legal-ui.datenschutzerklarung")],
|
||||
['imprint', ns.I18n.t("legal-ui.impressum")],
|
||||
['technology', ns.I18n.t("legal-ui.speichern.und.berechtigungen")]
|
||||
['privacy', ns.I18n.t("info.privacy")],
|
||||
['imprint', ns.I18n.t("info.imprint")],
|
||||
['technology', ns.I18n.t("info.storage")]
|
||||
]; }
|
||||
const selectors = Object.freeze({
|
||||
content: '.info-dialog-content',
|
||||
@@ -159,8 +159,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
id: 'info-button',
|
||||
className: 'info-button',
|
||||
type: 'button',
|
||||
'aria-label': ns.I18n.t("legal-ui.hilfe.und.informationen.offnen"),
|
||||
title: ns.I18n.t("legal-ui.informationen")
|
||||
'aria-label': ns.I18n.t("info.open"),
|
||||
title: ns.I18n.t("info.title")
|
||||
});
|
||||
append(infoButton, createElement('span', { 'aria-hidden': 'true' }, 'i'));
|
||||
append(control, infoButton);
|
||||
@@ -173,7 +173,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const dropdown = createElement('nav', {
|
||||
id: 'info-dropdown',
|
||||
className: 'info-dropdown',
|
||||
'aria-label': ns.I18n.t("legal-ui.rechtliche.und.technische.informationen")
|
||||
'aria-label': ns.I18n.t("info.menu-label")
|
||||
});
|
||||
append(control, dropdown);
|
||||
return dropdown;
|
||||
@@ -191,7 +191,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function setInfoButtonLabel(infoButton) {
|
||||
if (typeof infoButton.getAttribute === 'function' && infoButton.getAttribute('aria-label')) return;
|
||||
setAttribute(infoButton, 'aria-label', ns.I18n.t("legal-ui.hilfe.und.informationen.offnen"));
|
||||
setAttribute(infoButton, 'aria-label', ns.I18n.t("info.open"));
|
||||
}
|
||||
|
||||
function ensureInfoButtonIcon(infoButton) {
|
||||
@@ -201,7 +201,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function configureDropdown(dropdown) {
|
||||
if (!dropdown) return;
|
||||
setAttribute(dropdown, 'aria-label', ns.I18n.t("legal-ui.rechtliche.und.technische.informationen"));
|
||||
setAttribute(dropdown, 'aria-label', ns.I18n.t("info.menu-label"));
|
||||
setHidden(dropdown, true);
|
||||
menuEntries().forEach(([target, label]) => ensureMenuButton(dropdown, target, label));
|
||||
}
|
||||
@@ -277,7 +277,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function configureBackButton(backButton) {
|
||||
setAttribute(backButton, 'type', 'button');
|
||||
setAttribute(backButton, 'aria-label', ns.I18n.t("legal-ui.zuruck.zum.informationsmenu"));
|
||||
setAttribute(backButton, 'aria-label', ns.I18n.t("info.back"));
|
||||
setHidden(backButton, true);
|
||||
return backButton;
|
||||
}
|
||||
@@ -293,8 +293,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const closeButton = createElement('button', {
|
||||
id: 'info-close',
|
||||
type: 'button',
|
||||
'aria-label': ns.I18n.t("legal-ui.informationsfenster.schliessen"),
|
||||
title: ns.I18n.t("legal-ui.schliessen")
|
||||
'aria-label': ns.I18n.t("info.close-label"),
|
||||
title: ns.I18n.t("info.close")
|
||||
}, '×');
|
||||
append(header, closeButton);
|
||||
return configureCloseButton(closeButton);
|
||||
@@ -302,7 +302,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function configureCloseButton(closeButton) {
|
||||
setAttribute(closeButton, 'type', 'button');
|
||||
setAttribute(closeButton, 'aria-label', ns.I18n.t("legal-ui.informationsfenster.schliessen"));
|
||||
setAttribute(closeButton, 'aria-label', ns.I18n.t("info.close-label"));
|
||||
if (closeButton && !String(closeButton.textContent ?? '').trim()) closeButton.textContent = '×';
|
||||
return closeButton;
|
||||
}
|
||||
@@ -436,7 +436,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function updateDialogTitle(pageName) {
|
||||
if (elements?.title) elements.title.textContent = pageTitles[pageName] ?? ns.I18n.t("legal-ui.informationen");
|
||||
if (elements?.title) elements.title.textContent = pageTitles[pageName] ?? ns.I18n.t("info.title");
|
||||
}
|
||||
|
||||
function focusSafely(element) {
|
||||
|
||||
+29
-29
@@ -15,7 +15,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function storage() {
|
||||
const target = globalThis.localStorage;
|
||||
if (!target) throw new Error(ns.I18n.t("local-routes.deine.strecke.kann.auf.diesem.gerat.gerade"));
|
||||
if (!target) throw new Error(ns.I18n.t("local.error.route-storage"));
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (mediaDatabasePromise) return mediaDatabasePromise;
|
||||
const indexedDb = globalThis.indexedDB;
|
||||
if (!indexedDb || typeof indexedDb.open !== 'function') {
|
||||
return Promise.reject(new Error(ns.I18n.t("local-routes.bilder.und.ton.konnen.auf.diesem.gerat")));
|
||||
return Promise.reject(new Error(ns.I18n.t("local.error.media-storage")));
|
||||
}
|
||||
|
||||
mediaDatabasePromise = new Promise((resolve, reject) => {
|
||||
@@ -41,11 +41,11 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => {
|
||||
mediaDatabasePromise = null;
|
||||
reject(request.error || new Error(ns.I18n.t("local-routes.gespeicherte.bilder.und.ton.konnten.nicht.geoffnet")));
|
||||
reject(request.error || new Error(ns.I18n.t("local.error.media-read")));
|
||||
};
|
||||
request.onblocked = () => {
|
||||
mediaDatabasePromise = null;
|
||||
reject(new Error(ns.I18n.t("local-routes.schliesse.andere.fenster.mit.wegwichtel.und.versuche")));
|
||||
reject(new Error(ns.I18n.t("local.error.other-tabs")));
|
||||
};
|
||||
});
|
||||
return mediaDatabasePromise;
|
||||
@@ -59,8 +59,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const transaction = database.transaction(MEDIA_STORE_NAME, mode);
|
||||
request = operation(transaction.objectStore(MEDIA_STORE_NAME));
|
||||
request.onsuccess = () => resolve(request.result ?? null);
|
||||
request.onerror = () => reject(request.error || new Error(ns.I18n.t("local-routes.bilder.und.ton.konnten.nicht.geoffnet.oder")));
|
||||
transaction.onabort = () => reject(transaction.error || new Error(ns.I18n.t("local-routes.das.speichern.von.bildern.und.ton.wurde")));
|
||||
request.onerror = () => reject(request.error || new Error(ns.I18n.t("local.error.media-access")));
|
||||
transaction.onabort = () => reject(transaction.error || new Error(ns.I18n.t("local.error.media-cancelled")));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
};
|
||||
} catch (error) {
|
||||
if (error?.name === 'QuotaExceededError') {
|
||||
throw new Error(ns.I18n.t("local-routes.der.platz.fur.bilder.und.ton.ist"), { cause: error });
|
||||
throw new Error(ns.I18n.t("local.error.media-quota-hint"), { cause: error });
|
||||
}
|
||||
// Manche Browser können FileSystemFileHandle noch nicht in IndexedDB serialisieren.
|
||||
// In diesem Fall wird die Datei als Blob in IndexedDB gespeichert.
|
||||
@@ -111,9 +111,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
await putMediaRecord({ ...metadata, storage: 'indexeddb', blob: file });
|
||||
} catch (error) {
|
||||
const prefix = error?.name === 'QuotaExceededError'
|
||||
? ns.I18n.t("local-routes.der.platz.fur.bilder.und.ton.ist.2")
|
||||
: ns.I18n.t("local-routes.das.bild.oder.die.tonaufnahme.konnte.nicht");
|
||||
throw new Error(ns.I18n.t("local-routes.entferne.bilder.tonaufnahmen.oder.eigene.strecken.die", { value1: prefix }), { cause: error });
|
||||
? ns.I18n.t("local.error.media-quota")
|
||||
: ns.I18n.t("local.error.media-save");
|
||||
throw new Error(ns.I18n.t("local.error.media-cleanup-hint", { value1: prefix }), { cause: error });
|
||||
}
|
||||
return {
|
||||
id: String(mediaId),
|
||||
@@ -135,12 +135,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
permission = await handle.requestPermission({ mode: 'read' });
|
||||
}
|
||||
if (permission !== 'granted') {
|
||||
throw new Error(ns.I18n.t("local-routes.erlaube.noch.einmal.den.zugriff.auf.das"));
|
||||
throw new Error(ns.I18n.t("local.error.file-permission"));
|
||||
}
|
||||
try {
|
||||
return await handle.getFile();
|
||||
} catch (error) {
|
||||
throw new Error(ns.I18n.t("local-routes.ein.ausgewahltes.bild.oder.eine.tonaufnahme.wurde"), { cause: error });
|
||||
throw new Error(ns.I18n.t("local.error.file-missing"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (typeof cryptoApi?.getRandomValues === 'function') {
|
||||
return `local-${uuidFromRandomValues(cryptoApi)}`;
|
||||
}
|
||||
throw new Error(ns.I18n.t("local-routes.auf.diesem.gerat.kann.gerade.keine.neue"));
|
||||
throw new Error(ns.I18n.t("local.error.create-route"));
|
||||
}
|
||||
|
||||
function createPoiId() {
|
||||
@@ -255,7 +255,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function emptyGpx(name = ns.I18n.t("editor-ui.neue.strecke")) {
|
||||
function emptyGpx(name = ns.I18n.t("editor.new-route")) {
|
||||
const safeName = escapeXml(name);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n`
|
||||
+ `<gpx version="1.1" creator="Wegwichtel" xmlns="${GPX_NAMESPACE}" xmlns:wegwichtel="${POI_NAMESPACE}">\n`
|
||||
@@ -268,7 +268,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const parser = new DOMParser();
|
||||
const document = parser.parseFromString(String(gpx || ''), 'application/xml');
|
||||
if (document.querySelector('parsererror') || document.documentElement?.localName !== 'gpx') {
|
||||
throw new Error(ns.I18n.t("local-routes.diese.datei.enthalt.keinen.lesbaren.weg.wahle"));
|
||||
throw new Error(ns.I18n.t("local.error.invalid-track"));
|
||||
}
|
||||
return document;
|
||||
}
|
||||
@@ -300,7 +300,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const document = parseXml(gpx);
|
||||
return Array.from(document.getElementsByTagNameNS('*', 'wpt')).map((node, index) => ({
|
||||
id: waypointId(node),
|
||||
title: textOfChild(node, 'name') || ns.I18n.t("editor-ui.station", { value1: index + 1 }),
|
||||
title: textOfChild(node, 'name') || ns.I18n.t("editor.station-number", { value1: index + 1 }),
|
||||
description: textOfChild(node, 'desc'),
|
||||
lat: numberAttribute(node, 'lat'),
|
||||
lon: numberAttribute(node, 'lon'),
|
||||
@@ -360,7 +360,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function syncGpx(gpx, route, pois) {
|
||||
const routeName = route?.name || ns.I18n.t("editor-ui.neue.strecke");
|
||||
const routeName = route?.name || ns.I18n.t("editor.new-route");
|
||||
const document = parseXml(gpx || emptyGpx(routeName));
|
||||
updateMetadataName(document, routeName);
|
||||
|
||||
@@ -374,9 +374,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const segment = ensureTrackSegment(document, routeName);
|
||||
const orderedPois = [...(pois || [])].sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
||||
orderedPois.forEach((poi, index) => {
|
||||
const title = poi.title || ns.I18n.t("editor-ui.station", { value1: index + 1 });
|
||||
const title = poi.title || ns.I18n.t("editor.station-number", { value1: index + 1 });
|
||||
if (!Number.isFinite(Number(poi.lat)) || !Number.isFinite(Number(poi.lon))) {
|
||||
throw new TypeError(ns.I18n.t("local-routes.der.ort.von.fehlt", { value1: title }));
|
||||
throw new TypeError(ns.I18n.t("local.error.station-location", { value1: title }));
|
||||
}
|
||||
|
||||
const gpxPoint = createElement(document, 'trkpt');
|
||||
@@ -469,9 +469,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
ns.StorageUsage?.reportError(error);
|
||||
const name = error?.name === 'QuotaExceededError'
|
||||
? ns.I18n.t("local-routes.der.platz.fur.deine.strecken.ist.voll")
|
||||
: ns.I18n.t("local-routes.deine.strecke.konnte.nicht.gespeichert.werden");
|
||||
throw new Error(ns.I18n.t("local-routes.entferne.eigene.strecken.die.du.nicht.mehr", { value1: name }), { cause: error });
|
||||
? ns.I18n.t("local.error.route-quota")
|
||||
: ns.I18n.t("local.error.save-route");
|
||||
throw new Error(ns.I18n.t("local.error.route-cleanup-hint", { value1: name }), { cause: error });
|
||||
}
|
||||
|
||||
notify(id, 'saved');
|
||||
@@ -514,9 +514,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function gpxForUpload(routePackage) {
|
||||
if (!routePackage?.gpx) throw new Error(ns.I18n.t("local-routes.der.weg.deiner.strecke.fehlt"));
|
||||
if (!routePackage?.gpx) throw new Error(ns.I18n.t("local.error.path-missing"));
|
||||
if (!routePoints(routePackage.gpx).length) {
|
||||
throw new Error(ns.I18n.t("local-routes.deine.strecke.hat.noch.keinen.aufgezeichneten.weg"));
|
||||
throw new Error(ns.I18n.t("local.error.path-empty"));
|
||||
}
|
||||
return routePackage.gpx;
|
||||
}
|
||||
@@ -577,7 +577,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
function appPoi(poi) {
|
||||
return {
|
||||
id: Number(poi.id),
|
||||
title: poi.title || ns.I18n.t("editor-ui.station.ohne.namen"),
|
||||
title: poi.title || ns.I18n.t("editor.untitled-station"),
|
||||
description: poi.description || '',
|
||||
lat: Number(poi.lat),
|
||||
lon: Number(poi.lon),
|
||||
@@ -604,7 +604,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
id: String(routePackage.id),
|
||||
source: 'local',
|
||||
local: true,
|
||||
name: routePackage.route?.name || ns.I18n.t("editor-ui.unbenannte.strecke"),
|
||||
name: routePackage.route?.name || ns.I18n.t("editor.untitled-route"),
|
||||
description: routePackage.route?.description || '',
|
||||
schoolName: ns.I18n.t("local.device"),
|
||||
status: 'local',
|
||||
@@ -625,7 +625,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
async function getAppRoute(id, options = {}) {
|
||||
const routePackage = await withMedia(readPackage(id), options);
|
||||
if (!routePackage) throw new Error(ns.I18n.t("local-routes.deine.strecke.wurde.nicht.gefunden"));
|
||||
if (!routePackage) throw new Error(ns.I18n.t("local.error.route-missing"));
|
||||
return toAppRoute(routePackage);
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
function readFileAsText(file) {
|
||||
if (!file) return Promise.resolve('');
|
||||
if (typeof file.text !== 'function') {
|
||||
return Promise.reject(new TypeError(ns.I18n.t("local-routes.diese.datei.kann.auf.deinem.gerat.nicht")));
|
||||
return Promise.reject(new TypeError(ns.I18n.t("local.error.file-unsupported")));
|
||||
}
|
||||
return file.text();
|
||||
}
|
||||
@@ -647,7 +647,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
if (typeof reader.result !== 'string') {
|
||||
reject(new TypeError(ns.I18n.t("local-routes.diese.datei.konnte.nicht.geoffnet.werden.wahle")));
|
||||
reject(new TypeError(ns.I18n.t("local.error.file-read")));
|
||||
return;
|
||||
}
|
||||
resolve({
|
||||
|
||||
+25
-25
@@ -44,7 +44,7 @@ globalThis.Wegwichtel ??= {};
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
navigator.serviceWorker.removeEventListener('controllerchange', changed);
|
||||
reject(new Error(ns.I18n.t("offline-cache.das.speichern.fur.unterwegs.hat.nicht.geklappt")));
|
||||
reject(new Error(ns.I18n.t("offline.error.download")));
|
||||
}, 10000);
|
||||
function changed() {
|
||||
clearTimeout(timeout);
|
||||
@@ -64,32 +64,32 @@ globalThis.Wegwichtel ??= {};
|
||||
let stage = null;
|
||||
try {
|
||||
await worker();
|
||||
announce(ns.I18n.t("offline-cache.wegwichtel.wird.fur.unterwegs.vorbereitet"));
|
||||
announce(ns.I18n.t("offline.preparing"));
|
||||
// Persistent storage is best-effort; a denial must not block downloads.
|
||||
await navigator.storage?.persist?.().catch(() => false);
|
||||
const options = { cache: 'no-store', headers: { 'X-Wegwichtel-Offline-Download': 'true' }, signal };
|
||||
const manifestResponse = await fetch('/api/offline-manifest', options);
|
||||
if (!manifestResponse.ok) throw new Error(ns.I18n.t("offline-cache.wegwichtel.konnte.nicht.fur.unterwegs.geladen.werden"));
|
||||
if (!manifestResponse.ok) throw new Error(ns.I18n.t("offline.error.connection"));
|
||||
const manifest = await manifestResponse.json();
|
||||
if (!Array.isArray(manifest.resources) || !manifest.resources.length) throw new TypeError(ns.I18n.t("offline-cache.wegwichtel.konnte.nicht.fur.unterwegs.geladen.werden.2"));
|
||||
if (!Array.isArray(manifest.resources) || !manifest.resources.length) throw new TypeError(ns.I18n.t("offline.error.prepare"));
|
||||
stage = await ns.OfflineStore.create();
|
||||
const urls = [...new Set(manifest.resources.map(url => ns.OfflineStore.key(url)))];
|
||||
for (const [index, url] of urls.entries()) {
|
||||
signal.throwIfAborted();
|
||||
announce(ns.I18n.t("offline-cache.fur.unterwegs.speichern.von", { value1: index + 1, value2: urls.length }));
|
||||
announce(ns.I18n.t("offline.download-progress", { value1: index + 1, value2: urls.length }));
|
||||
const response = await fetch(url, options);
|
||||
await ns.OfflineStore.put(stage, url, response);
|
||||
}
|
||||
signal.throwIfAborted();
|
||||
const saved = await ns.OfflineStore.commit(stage);
|
||||
announce(ns.I18n.t("offline-cache.wegwichtel.ist.fur.unterwegs.bereit"));
|
||||
announce(ns.I18n.t("offline.ready"));
|
||||
return saved;
|
||||
} catch (error) {
|
||||
if (stage) await ns.OfflineStore.remove(stage.id);
|
||||
ns.StorageUsage?.reportError(error);
|
||||
const saved = await ns.OfflineStore.state();
|
||||
if (error.name === 'AbortError') announce(ns.I18n.t("offline-cache.speichern.abgebrochen"));
|
||||
else announce(`${ns.UserMessages?.error(error) || ns.I18n.t("offline-cache.das.speichern.hat.nicht.geklappt")} ${saved ? ns.I18n.t("offline.previous.kept") : ns.I18n.t("offline-cache.wegwichtel.ist.noch.nicht.ohne.internet.bereit")}`);
|
||||
if (error.name === 'AbortError') announce(ns.I18n.t("offline.cancelled"));
|
||||
else announce(`${ns.UserMessages?.error(error) || ns.I18n.t("offline.error.save")} ${saved ? ns.I18n.t("offline.previous.kept") : ns.I18n.t("offline.not-ready")}`);
|
||||
throw error;
|
||||
} finally {
|
||||
task = null;
|
||||
@@ -109,7 +109,7 @@ globalThis.Wegwichtel ??= {};
|
||||
if (task) await task.catch(() => {});
|
||||
await ns.OfflineStore.clear();
|
||||
await updateControls();
|
||||
announce(ns.I18n.t("offline-cache.die.gespeicherte.app.wurde.entfernt.deine.strecken"));
|
||||
announce(ns.I18n.t("offline.app-removed"));
|
||||
await ns.StorageUsage?.refresh();
|
||||
}
|
||||
|
||||
@@ -126,12 +126,12 @@ globalThis.Wegwichtel ??= {};
|
||||
const message = document.getElementById('route-offline-status');
|
||||
if (save) {
|
||||
save.disabled = !supported() || Boolean(routeTask);
|
||||
save.textContent = saved ? ns.I18n.t("offline-cache.gespeicherte.strecke.erneuern") : ns.I18n.t("offline-cache.fur.unterwegs.speichern");
|
||||
save.textContent = saved ? ns.I18n.t("offline.renew-route") : ns.I18n.t("offline.save-route");
|
||||
}
|
||||
if (remove) { remove.hidden = !saved; remove.disabled = Boolean(routeTask); }
|
||||
if (message && !routeTask) message.textContent = saved
|
||||
? ns.I18n.t("offline-cache.diese.strecke.ist.fur.unterwegs.bereit")
|
||||
: ns.I18n.t("offline-cache.nimm.diese.strecke.mit.bildern.und.tonaufnahmen");
|
||||
? ns.I18n.t("offline.route-ready")
|
||||
: ns.I18n.t("offline.route-hint");
|
||||
updateGpxDownload(route);
|
||||
}
|
||||
|
||||
@@ -147,9 +147,9 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
async function saveRoute(route) {
|
||||
if (routeTask) throw new Error(ns.I18n.t("offline-cache.bitte.warte.bis.die.andere.strecke.gespeichert"));
|
||||
if (!supported()) throw new Error(ns.I18n.t("offline-cache.auf.diesem.gerat.kannst.du.strecken.gerade"));
|
||||
if (!route || route.source === 'local') throw new Error(ns.I18n.t("offline-cache.wahle.zuerst.eine.strecke.die.fur.alle"));
|
||||
if (routeTask) throw new Error(ns.I18n.t("offline.error.busy"));
|
||||
if (!supported()) throw new Error(ns.I18n.t("offline.error.route-unsupported"));
|
||||
if (!route || route.source === 'local') throw new Error(ns.I18n.t("offline.error.select-route"));
|
||||
routeTask = (async () => {
|
||||
let stage = null;
|
||||
const message = document.getElementById('route-offline-status');
|
||||
@@ -159,10 +159,10 @@ globalThis.Wegwichtel ??= {};
|
||||
const url = `/api/routes/${encodeURIComponent(route.id)}`;
|
||||
const options = { cache: 'no-store', headers: { 'X-Wegwichtel-Offline-Download': 'true' } };
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) throw new Error(ns.I18n.t("offline-cache.die.strecke.konnte.nicht.geladen.werden"));
|
||||
if (!response.ok) throw new Error(ns.I18n.t("offline.error.load-route"));
|
||||
const fresh = await response.clone().json();
|
||||
if (String(fresh.id) !== String(route.id) || !fresh.activatedAt) throw new Error(ns.I18n.t("offline-cache.du.kannst.nur.freigegebene.strecken.fur.unterwegs"));
|
||||
if (!fresh.gpxUrl) throw new Error(ns.I18n.t("offline-cache.der.weg.dieser.strecke.fehlt.sie.kann"));
|
||||
if (String(fresh.id) !== String(route.id) || !fresh.activatedAt) throw new Error(ns.I18n.t("offline.error.published-only"));
|
||||
if (!fresh.gpxUrl) throw new Error(ns.I18n.t("offline.error.path-missing"));
|
||||
const resources = [fresh.gpxUrl];
|
||||
for (const poi of fresh.pois || []) {
|
||||
if (poi.audioUrl) resources.push(poi.audioUrl);
|
||||
@@ -172,7 +172,7 @@ globalThis.Wegwichtel ??= {};
|
||||
stage = await ns.OfflineStore.create();
|
||||
await ns.OfflineStore.put(stage, url, response);
|
||||
for (const [index, resource] of urls.entries()) {
|
||||
if (message && selectedRoute === route) message.textContent = ns.I18n.t("offline-cache.strecke.speichern.von", { value1: index + 1, value2: urls.length });
|
||||
if (message && selectedRoute === route) message.textContent = ns.I18n.t("offline.route-progress", { value1: index + 1, value2: urls.length });
|
||||
await ns.OfflineStore.put(stage, resource, await fetch(resource, options));
|
||||
await ns.StorageUsage?.refresh();
|
||||
}
|
||||
@@ -193,7 +193,7 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
async function removeRoute(route) {
|
||||
if (routeTask) throw new Error(ns.I18n.t("offline-cache.bitte.warte.bis.das.speichern.fertig.ist"));
|
||||
if (routeTask) throw new Error(ns.I18n.t("offline.error.wait"));
|
||||
await ns.OfflineStore.clearRoute(route.id);
|
||||
await renderRoute(selectedRoute);
|
||||
await ns.StorageUsage?.refresh();
|
||||
@@ -206,7 +206,7 @@ globalThis.Wegwichtel ??= {};
|
||||
return action(route).catch(error => {
|
||||
if (selectedRoute !== route) return;
|
||||
const message = document.getElementById('route-offline-status');
|
||||
if (message) message.textContent = ns.I18n.t("offline.error.previous.kept", { value1: ns.UserMessages?.error(error) || ns.I18n.t("offline-cache.das.speichern.hat.nicht.geklappt") });
|
||||
if (message) message.textContent = ns.I18n.t("offline.error.previous.kept", { value1: ns.UserMessages?.error(error) || ns.I18n.t("offline.error.save") });
|
||||
});
|
||||
};
|
||||
document.getElementById('route-offline-save')?.addEventListener('click', () => run(saveRoute));
|
||||
@@ -226,7 +226,7 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
if (!supported()) {
|
||||
if (toggle) toggle.disabled = true;
|
||||
announce(ns.I18n.t("offline-cache.auf.diesem.gerat.lasst.sich.wegwichtel.gerade"));
|
||||
announce(ns.I18n.t("offline.error.unsupported"));
|
||||
return;
|
||||
}
|
||||
// Remove old v1 snapshots that also included all routes automatically.
|
||||
@@ -236,9 +236,9 @@ globalThis.Wegwichtel ??= {};
|
||||
if ((await ns.OfflineStore.routes()).length) await worker();
|
||||
if (saved) {
|
||||
await worker();
|
||||
announce(ns.I18n.t("offline-cache.wegwichtel.ist.fur.unterwegs.bereit"));
|
||||
announce(ns.I18n.t("offline.ready"));
|
||||
} else {
|
||||
announce(ns.I18n.t("offline-cache.speichere.zuerst.die.app.danach.kannst.du"));
|
||||
announce(ns.I18n.t("offline.error.save-app-first"));
|
||||
}
|
||||
toggle?.addEventListener('change', () => {
|
||||
toggle.setAttribute('aria-checked', String(toggle.checked));
|
||||
@@ -249,7 +249,7 @@ globalThis.Wegwichtel ??= {};
|
||||
|
||||
ns.OfflineCache = Object.freeze({
|
||||
initialize() {
|
||||
initialized ??= setup().catch(error => { announce(ns.UserMessages?.error(error) || ns.I18n.t("offline-cache.das.speichern.fur.unterwegs.hat.nicht.geklappt")); });
|
||||
initialized ??= setup().catch(error => { announce(ns.UserMessages?.error(error) || ns.I18n.t("offline.error.download")); });
|
||||
return initialized;
|
||||
},
|
||||
download,
|
||||
|
||||
@@ -10,7 +10,7 @@ globalThis.Wegwichtel ??= {};
|
||||
|
||||
function key(value) {
|
||||
const url = new URL(value, globalThis.location.origin);
|
||||
if (url.origin !== globalThis.location.origin) throw new TypeError(ns.I18n.t("offline-storage.inhalte.von.anderen.seiten.konnen.hier.nicht"));
|
||||
if (url.origin !== globalThis.location.origin) throw new TypeError(ns.I18n.t("offline-storage.error.foreign-origin"));
|
||||
url.searchParams.delete('v');
|
||||
if (url.pathname.startsWith('/api/')) url.searchParams.delete('_');
|
||||
url.hash = '';
|
||||
@@ -60,7 +60,7 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
async function put(stage, url, response) {
|
||||
if (!response.ok || response.type === 'opaque') throw new Error(ns.I18n.t("offline-storage.ein.teil.konnte.nicht.gespeichert.werden.prufe"));
|
||||
if (!response.ok || response.type === 'opaque') throw new Error(ns.I18n.t("offline-storage.error.response"));
|
||||
const normalized = key(url);
|
||||
if (stage.mode === 'cache') {
|
||||
const cache = await caches.open(stage.id);
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
const image = images[index];
|
||||
$('#slide-image')
|
||||
.attr('src', mediaUrl(image.url))
|
||||
.attr('alt', image.caption || ns.I18n.t("slideshow.bild.zur.station"))
|
||||
.attr('alt', image.caption || ns.I18n.t("slideshow.image-label"))
|
||||
.off('error.wegwichtel')
|
||||
.on('error.wegwichtel', function () {
|
||||
$(this).removeAttr('src').attr('alt', ns.I18n.t("slideshow.bild.konnte.nicht.geladen.werden"));
|
||||
$(this).removeAttr('src').attr('alt', ns.I18n.t("slideshow.error.load-image"));
|
||||
});
|
||||
$('#slide-caption').text(image.caption || '');
|
||||
$('#slide-position').text(`${index + 1} / ${images.length}`);
|
||||
|
||||
@@ -34,15 +34,15 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
function siteDescription(usage, quota, known) {
|
||||
if (!known) return ns.I18n.t("storage-usage.der.freie.platz.konnte.nicht.gemessen.werden");
|
||||
const warning = usage / quota >= .9 ? ns.I18n.t("storage-usage.fast.voll.entferne.inhalte.die.du.nicht") : '';
|
||||
return ns.I18n.t("storage-usage.app.bilder.und.ton.des.verfugbaren.platzes", { value1: Math.round(usage / quota * 100), value2: format(Math.max(0, quota - usage)), value3: warning });
|
||||
if (!known) return ns.I18n.t("storage.error.estimate");
|
||||
const warning = usage / quota >= .9 ? ns.I18n.t("storage.site-warning") : '';
|
||||
return ns.I18n.t("storage.site-usage", { value1: Math.round(usage / quota * 100), value2: format(Math.max(0, quota - usage)), value3: warning });
|
||||
}
|
||||
|
||||
function localDescription(local) {
|
||||
if (local == null) return ns.I18n.t("storage-usage.texte.und.einstellungen.konnen.hier.gerade.nicht");
|
||||
const warning = local >= LOCAL_REFERENCE * .9 ? ns.I18n.t("storage-usage.der.balken.ist.fast.voll.grosse.bilder") : '';
|
||||
return ns.I18n.t("storage-usage.texte.und.einstellungen.ungefahr.belegt.der.balken", { value1: format(local), value2: warning });
|
||||
if (local == null) return ns.I18n.t("storage.error.local-unavailable");
|
||||
const warning = local >= LOCAL_REFERENCE * .9 ? ns.I18n.t("storage.local-warning") : '';
|
||||
return ns.I18n.t("storage.local-usage", { value1: format(local), value2: warning });
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
@@ -69,7 +69,7 @@ globalThis.Wegwichtel ??= {};
|
||||
let current = error;
|
||||
while (current) {
|
||||
if (current.name === 'QuotaExceededError') {
|
||||
lastError = ns.I18n.t("storage-usage.der.platz.ist.voll.entferne.gespeicherte.strecken");
|
||||
lastError = ns.I18n.t("storage.error.quota");
|
||||
break;
|
||||
}
|
||||
current = current.cause;
|
||||
|
||||
+524
-411
@@ -2,418 +2,531 @@ globalThis.Wegwichtel ??= {};
|
||||
(function (ns) {
|
||||
'use strict';
|
||||
|
||||
class WegwichtelTextsDe {
|
||||
class WegwichtelTexts {
|
||||
static language = 'de';
|
||||
static messages = Object.freeze({
|
||||
"app.du.kannst.dort.weitermachen.wo.du.aufgehort": "Du kannst dort weitermachen, wo du aufgehört hast.",
|
||||
"app.eigener.entwurf": "Eigener Entwurf",
|
||||
"app.strecke.wahlen": "Strecke wählen",
|
||||
"app.strecken.fur.alle.und.meine.entwurfe": "Strecken für alle und meine Entwürfe",
|
||||
"app.strecke": "Strecke",
|
||||
"app.alle.stationen.erreicht": "Alle Stationen erreicht",
|
||||
"app.keine.stationen.vorhanden": "Keine Stationen vorhanden",
|
||||
"app.nachste.station": "Nächste Station: {value1}{value2}",
|
||||
"app.veroffentlicht": "Veröffentlicht",
|
||||
"app.strecken.fur.alle": "Strecken für alle ({value1})",
|
||||
"app.meine.entwurfe": "Meine Entwürfe ({value1})",
|
||||
"app.hier.gibt.es.noch.keine.strecken.fur": "Hier gibt es noch keine Strecken für alle.",
|
||||
"app.du.hast.noch.keine.eigene.strecke.angelegt": "Du hast noch keine eigene Strecke angelegt.",
|
||||
"app.keine.strecke.im.umkreis.von.km.gefunden": "Keine Strecke im Umkreis von {value1} km gefunden. Wähle unten eine beliebige Strecke aus.",
|
||||
"app.erlaube.deinen.standort.um.strecken.in.deiner": "Erlaube deinen Standort, um Strecken in deiner Nähe zu finden. Unten kannst du alle Strecken auswählen.",
|
||||
"app.keine.passende.strecke.gefunden.versuche.ein.anderes": "Keine passende Strecke gefunden. Versuche ein anderes Wort.",
|
||||
"app.von.strecken.gefunden": "{value1} von {value2} Strecken gefunden.",
|
||||
"app.strecken.zur.auswahl": "{value1} Strecken zur Auswahl.",
|
||||
"app.strecke.fortsetzen": "Strecke fortsetzen",
|
||||
"app.strecke.starten": "Strecke starten",
|
||||
"app.strecke.beendet": "Strecke beendet.",
|
||||
"app.du.machst.eine.pause.die.app.sucht": "Du machst eine Pause. Die App sucht gerade nicht nach deinem Standort.",
|
||||
"app.strecke.pausiert": "Strecke pausiert",
|
||||
"app.diese.strecke.hat.noch.keine.stationen": "Diese Strecke hat noch keine Stationen.",
|
||||
"app.norden.oben.nachste.station": "Norden oben · nächste Station: {value1}",
|
||||
"app.nachste.station.2": "Nächste Station: {value1}",
|
||||
"app.nachste.station.in.metern": "Nächste Station {value1} in {value2} Metern",
|
||||
"app.alle.stationen.und.das.ziel.erreicht": "Alle Stationen und das Ziel erreicht",
|
||||
"app.norden.oben.nachster.punkt.auf.dem.weg": "Norden oben · nächster Punkt auf dem Weg",
|
||||
"app.nachster.punkt.auf.dem.weg": "Nächster Punkt auf dem Weg",
|
||||
"app.nachster.punkt.auf.dem.weg.in.metern": "Nächster Punkt auf dem Weg in {value1} Metern",
|
||||
"app.dein.standort.wird.gesucht.auf.etwa.m": "Dein Standort wird gesucht · auf etwa {value1} m genau",
|
||||
"app.nachste.station.m": " · nächste Station {value1} m",
|
||||
"app.alle.stationen.erreicht.2": " · alle Stationen erreicht",
|
||||
"app.strecke.wird.fortgesetzt": "Strecke wird fortgesetzt.",
|
||||
"app.strecke.wird.fortgesetzt.2": "Strecke wird fortgesetzt",
|
||||
"app.dein.standort.konnte.nicht.gefunden.werden.prufe": "Dein Standort konnte nicht gefunden werden. Prüfe die Standort-Erlaubnis auf deinem Gerät.",
|
||||
"app.norden.oben.navigation": "Norden oben · Navigation",
|
||||
"app.die.strecken.konnten.nicht.geladen.werden.bitte": "Die Strecken konnten nicht geladen werden. Bitte versuche es noch einmal.",
|
||||
"app.deine.entwurfe.konnten.nicht.geoffnet.werden.bitte": "Deine Entwürfe konnten nicht geöffnet werden. Bitte versuche es noch einmal.",
|
||||
"app.dein.standort.wurde.auf.etwa.m.genau": "Dein Standort wurde auf etwa {value1} m genau gefunden. ",
|
||||
"app.wir.zeigen.dir.strecken.bis.km.entfernung": "Wir zeigen dir Strecken bis {value1} km Entfernung.",
|
||||
"app.dein.standort.konnte.nicht.gefunden.werden.du": "Dein Standort konnte nicht gefunden werden. Du kannst trotzdem alle Strecken auswählen.",
|
||||
"app.die.strecke.konnte.nicht.geoffnet.werden.prufe": "Die Strecke konnte nicht geöffnet werden. Prüfe deine Verbindung und versuche es noch einmal.",
|
||||
"app.die.strecke.konnte.nicht.geoffnet.werden.bitte": "Die Strecke konnte nicht geöffnet werden. Bitte versuche es noch einmal.",
|
||||
"bootstrap-loader.gespeicherte.inhalte.prufen": "Gespeicherte Inhalte prüfen …",
|
||||
"bootstrap-loader.wegwichtel.vorbereiten": "Wegwichtel vorbereiten …",
|
||||
"bootstrap-loader.die.bedienung.vorbereiten": "Die Bedienung vorbereiten …",
|
||||
"bootstrap-loader.die.app.vorbereiten": "Die App vorbereiten …",
|
||||
"bootstrap-loader.verbindung.prufen": "Verbindung prüfen …",
|
||||
"bootstrap-loader.strecken.laden": "Strecken laden …",
|
||||
"bootstrap-loader.auf.diesem.gerat.kannst.du.bilder.und": "Auf diesem Gerät kannst du Bilder und Tonaufnahmen direkt auswählen.",
|
||||
"bootstrap-loader.tipp.fur.android.mit.chrome.lassen.sich": "Tipp für Android: Mit Chrome lassen sich Bilder und Tonaufnahmen auswählen, ohne sie noch einmal zu speichern.",
|
||||
"bootstrap-loader.ein.teil.der.app.konnte.nicht.starten": "Ein Teil der App konnte nicht starten. Bitte versuche es noch einmal.",
|
||||
"bootstrap-loader.ein.teil.der.app.konnte.nicht.geladen": "Ein Teil der App konnte nicht geladen werden. Prüfe deine Internetverbindung und versuche es noch einmal.",
|
||||
"bootstrap-loader.alten.fortschritt.loschen": "Alten Fortschritt löschen ({value1} / {value2})",
|
||||
"bootstrap-loader.wegwichtel.konnte.nicht.starten.prufe.deine.verbindung": "Wegwichtel konnte nicht starten. Prüfe deine Verbindung und versuche es noch einmal.",
|
||||
"bootstrap-loader.wegwichtel.konnte.nicht.starten.bitte.versuche.es": "Wegwichtel konnte nicht starten. Bitte versuche es noch einmal.",
|
||||
"editor-ui.zu.dieser.strecke.kann.gerade.keine.weitere": "Zu dieser Strecke kann gerade keine weitere Station hinzugefügt werden.",
|
||||
"editor-ui.unbenannte.strecke": "Unbenannte Strecke",
|
||||
"editor-ui.station.ohne.namen": "Station ohne Namen",
|
||||
"editor-ui.eigene.strecke.bearbeiten": "Eigene Strecke bearbeiten",
|
||||
"editor-ui.neue.strecke": "Neue Strecke",
|
||||
"editor-ui.strecke.ansehen": "Strecke ansehen",
|
||||
"editor-ui.station.bearbeiten": "Station bearbeiten",
|
||||
"editor-ui.station.hinzufugen": "Station hinzufügen",
|
||||
"editor-ui.die.aktion.konnte.nicht.ausgefuhrt.werden.bitte": "Die Aktion konnte nicht ausgeführt werden. Bitte versuche es noch einmal.",
|
||||
"editor-ui.veroffentlichte.strecke": "Veröffentlichte Strecke",
|
||||
"editor-ui.strecke.bearbeiten": "Strecke bearbeiten",
|
||||
"editor-ui.strecke.loschen": "Strecke löschen",
|
||||
"editor-ui.station": "Station {value1}",
|
||||
"editor-ui.loschen": "{value1} löschen",
|
||||
"editor-ui.strecken.fur.alle": "Strecken für alle",
|
||||
"editor-ui.meine.entwurfe": "Meine Entwürfe",
|
||||
"editor-ui.noch.kein.eigener.entwurf.vorhanden": "Noch kein eigener Entwurf vorhanden.",
|
||||
"editor-ui.noch.keine.stationen.angelegt": "Noch keine Stationen angelegt.",
|
||||
"editor-ui.strecken.werden.geladen": "Strecken werden geladen …",
|
||||
"editor-ui.schon.zur.prufung.gesendet": "Schon zur Prüfung gesendet",
|
||||
"editor-ui.zur.prufung.senden": "Zur Prüfung senden",
|
||||
"editor-ui.diese.strecke.wurde.zur.prufung.gesendet.andere": "Diese Strecke wurde zur Prüfung gesendet. Andere können sie erst nach der Freigabe sehen.",
|
||||
"editor-ui.dieser.entwurf.bleibt.auf.deinem.gerat.mit": "Dieser Entwurf bleibt auf deinem Gerät. Mit „Zur Prüfung senden“ reichst du ihn zur Freigabe ein.",
|
||||
"editor-ui.diese.strecke.ist.fur.alle.freigegeben.und": "Diese Strecke ist für alle freigegeben und kann hier nicht mehr verändert werden.",
|
||||
"editor-ui.diese.strecke.wurde.noch.nicht.fur.alle": "Diese Strecke wurde noch nicht für alle freigegeben.",
|
||||
"editor-ui.knopf.zum.entfernen": "{value1}: Knopf zum Entfernen {value2}",
|
||||
"editor-ui.neue.tonaufnahme": "Neue Tonaufnahme",
|
||||
"editor-ui.bild": "Bild {value1}",
|
||||
"editor-ui.neues.bild": "Neues Bild {value1}",
|
||||
"editor-ui.bild.2": "{value1} Bild{value2}",
|
||||
"editor-ui.tippe.auf.ein.bild.oder.eine.tonaufnahme": "{value1}. Tippe auf ein Bild oder eine Tonaufnahme, um sie zu entfernen.",
|
||||
"editor-ui.noch.keine.bilder.oder.tonaufnahmen.vorhanden": "Noch keine Bilder oder Tonaufnahmen vorhanden.",
|
||||
"editor-ui.tonaufnahmen": "Tonaufnahmen",
|
||||
"editor-ui.bilder": "Bilder",
|
||||
"editor-ui.dein.entwurf.wurde.nicht.gefunden": "Dein Entwurf wurde nicht gefunden.",
|
||||
"editor-ui.deine.neue.strecke.bleibt.zuerst.auf.diesem": "Deine neue Strecke bleibt zuerst auf diesem Gerät.",
|
||||
"editor-ui.gespeichert": "Gespeichert.",
|
||||
"editor-ui.lege.eine.neue.strecke.zuerst.auf.diesem": "Lege eine neue Strecke zuerst auf diesem Gerät an.",
|
||||
"editor-ui.gib.deiner.strecke.einen.namen": "Gib deiner Strecke einen Namen.",
|
||||
"editor-ui.die.datei.konnte.nicht.gesendet.werden.bitte": "Die Datei {value1} konnte nicht gesendet werden. Bitte wähle sie noch einmal aus.",
|
||||
"editor-ui.die.strecke.konnte.nicht.gesendet.werden.bitte": "Die Strecke konnte nicht gesendet werden. Bitte versuche es noch einmal.",
|
||||
"editor-ui.die.station.konnte.nicht.gesendet.werden.bitte": "Die Station {value1} konnte nicht gesendet werden. Bitte versuche es noch einmal.",
|
||||
"editor-ui.du.kannst.nur.deine.eigenen.entwurfe.zur": "Du kannst nur deine eigenen Entwürfe zur Prüfung senden.",
|
||||
"editor-ui.diese.strecke.wurde.schon.zur.prufung.gesendet": "Diese Strecke wurde schon zur Prüfung gesendet.",
|
||||
"editor-ui.deine.strecke.wird.gesendet": "Deine Strecke wird gesendet …",
|
||||
"editor-ui.zur.prufung.gesendet": "Zur Prüfung gesendet.",
|
||||
"editor-ui.fur.eine.neue.station.brauchen.wir.deinen": "Für eine neue Station brauchen wir deinen Standort. Erlaube den Standort und versuche es noch einmal.",
|
||||
"editor-ui.speichere.zuerst.die.strecke": "Speichere zuerst die Strecke.",
|
||||
"editor-ui.der.ort.dieser.station.fehlt.erlaube.deinen": "Der Ort dieser Station fehlt. Erlaube deinen Standort und versuche es noch einmal.",
|
||||
"editor-ui.station.mit.bildern.und.ton.auf.diesem": "Station mit Bildern und Ton auf diesem Gerät gespeichert.",
|
||||
"editor-ui.station.gespeichert": "Station gespeichert.",
|
||||
"editor-ui.gib.deiner.station.einen.namen": "Gib deiner Station einen Namen.",
|
||||
"editor-ui.bild.entfernt": "Bild entfernt.",
|
||||
"editor-ui.die.station.ist.noch.nicht.gespeichert": "Die Station ist noch nicht gespeichert.",
|
||||
"editor-ui.das.bild.konnte.nicht.entfernt.werden.lade": "Das Bild konnte nicht entfernt werden. Lade die Strecke noch einmal.",
|
||||
"editor-ui.station.entfernt.deine.strecke.wurde.gespeichert": "Station entfernt. Deine Strecke wurde gespeichert.",
|
||||
"editor-ui.station.entfernt": "Station entfernt.",
|
||||
"editor-ui.reihenfolge.der.stationen.gespeichert": "Reihenfolge der Stationen gespeichert.",
|
||||
"editor-ui.strecke.geloscht": "Strecke gelöscht.",
|
||||
"editor-ui.das.hat.leider.nicht.geklappt.bitte.versuche": "Das hat leider nicht geklappt. Bitte versuche es noch einmal.",
|
||||
"geolocation.dieses.gerat.kann.deinen.standort.nicht.finden": "Dieses Gerät kann deinen Standort nicht finden.",
|
||||
"group-symbols.holz": "Holz",
|
||||
"group-symbols.felsen": "Felsen",
|
||||
"group-symbols.laterne": "Laterne",
|
||||
"group-symbols.blume": "Blume",
|
||||
"group-symbols.pfeife": "Pfeife",
|
||||
"group-symbols.vogel": "Vogel",
|
||||
"group-symbols.pilz": "Pilz",
|
||||
"group-symbols.erdbeere": "Erdbeere",
|
||||
"group-symbols.kurbis": "Kürbis",
|
||||
"legal-ui.datenschutzerklarung": "Datenschutzerklärung",
|
||||
"legal-ui.impressum": "Impressum",
|
||||
"legal-ui.speichern.und.berechtigungen": "Speichern und Berechtigungen",
|
||||
"legal-ui.hilfe.und.informationen.offnen": "Hilfe und Informationen öffnen",
|
||||
"legal-ui.informationen": "Informationen",
|
||||
"legal-ui.rechtliche.und.technische.informationen": "Rechtliche und technische Informationen",
|
||||
"legal-ui.zuruck.zum.informationsmenu": "Zurück zum Informationsmenü",
|
||||
"legal-ui.informationsfenster.schliessen": "Informationsfenster schließen",
|
||||
"legal-ui.schliessen": "Schließen",
|
||||
"local-routes.deine.strecke.kann.auf.diesem.gerat.gerade": "Deine Strecke kann auf diesem Gerät gerade nicht gespeichert werden.",
|
||||
"local-routes.bilder.und.ton.konnen.auf.diesem.gerat": "Bilder und Ton können auf diesem Gerät gerade nicht gespeichert werden.",
|
||||
"local-routes.gespeicherte.bilder.und.ton.konnten.nicht.geoffnet": "Gespeicherte Bilder und Ton konnten nicht geöffnet werden.",
|
||||
"local-routes.schliesse.andere.fenster.mit.wegwichtel.und.versuche": "Schließe andere Fenster mit Wegwichtel und versuche es noch einmal.",
|
||||
"local-routes.bilder.und.ton.konnten.nicht.geoffnet.oder": "Bilder und Ton konnten nicht geöffnet oder gespeichert werden.",
|
||||
"local-routes.das.speichern.von.bildern.und.ton.wurde": "Das Speichern von Bildern und Ton wurde abgebrochen.",
|
||||
"local-routes.der.platz.fur.bilder.und.ton.ist": "Der Platz für Bilder und Ton ist voll. Entferne Bilder, Tonaufnahmen oder eigene Strecken, die du nicht mehr brauchst.",
|
||||
"local-routes.der.platz.fur.bilder.und.ton.ist.2": "Der Platz für Bilder und Ton ist voll.",
|
||||
"local-routes.das.bild.oder.die.tonaufnahme.konnte.nicht": "Das Bild oder die Tonaufnahme konnte nicht gespeichert werden.",
|
||||
"local-routes.entferne.bilder.tonaufnahmen.oder.eigene.strecken.die": "{value1} Entferne Bilder, Tonaufnahmen oder eigene Strecken, die du nicht mehr brauchst.",
|
||||
"local-routes.erlaube.noch.einmal.den.zugriff.auf.das": "Erlaube noch einmal den Zugriff auf das ausgewählte Bild oder die Tonaufnahme.",
|
||||
"local-routes.ein.ausgewahltes.bild.oder.eine.tonaufnahme.wurde": "Ein ausgewähltes Bild oder eine Tonaufnahme wurde verschoben oder gelöscht. Wähle die Datei noch einmal aus.",
|
||||
"local-routes.auf.diesem.gerat.kann.gerade.keine.neue": "Auf diesem Gerät kann gerade keine neue Strecke angelegt werden.",
|
||||
"local-routes.diese.datei.enthalt.keinen.lesbaren.weg.wahle": "Diese Datei enthält keinen lesbaren Weg. Wähle eine andere Datei.",
|
||||
"local-routes.der.ort.von.fehlt": "Der Ort von {value1} fehlt.",
|
||||
"local-routes.der.platz.fur.deine.strecken.ist.voll": "Der Platz für deine Strecken ist voll.",
|
||||
"local-routes.deine.strecke.konnte.nicht.gespeichert.werden": "Deine Strecke konnte nicht gespeichert werden.",
|
||||
"local-routes.entferne.eigene.strecken.die.du.nicht.mehr": "{value1} Entferne eigene Strecken, die du nicht mehr brauchst.",
|
||||
"local-routes.der.weg.deiner.strecke.fehlt": "Der Weg deiner Strecke fehlt.",
|
||||
"local-routes.deine.strecke.hat.noch.keinen.aufgezeichneten.weg": "Deine Strecke hat noch keinen aufgezeichneten Weg.",
|
||||
"local-routes.deine.strecke.wurde.nicht.gefunden": "Deine Strecke wurde nicht gefunden.",
|
||||
"local-routes.diese.datei.kann.auf.deinem.gerat.nicht": "Diese Datei kann auf deinem Gerät nicht gelesen werden.",
|
||||
"local-routes.diese.datei.konnte.nicht.geoffnet.werden.wahle": "Diese Datei konnte nicht geöffnet werden. Wähle sie noch einmal aus.",
|
||||
"offline-cache.das.speichern.fur.unterwegs.hat.nicht.geklappt": "Das Speichern für unterwegs hat nicht geklappt. Bitte versuche es noch einmal.",
|
||||
"offline-cache.wegwichtel.wird.fur.unterwegs.vorbereitet": "Wegwichtel wird für unterwegs vorbereitet …",
|
||||
"offline-cache.wegwichtel.konnte.nicht.fur.unterwegs.geladen.werden": "Wegwichtel konnte nicht für unterwegs geladen werden. Prüfe deine Internetverbindung.",
|
||||
"offline-cache.wegwichtel.konnte.nicht.fur.unterwegs.geladen.werden.2": "Wegwichtel konnte nicht für unterwegs geladen werden. Bitte versuche es noch einmal.",
|
||||
"offline-cache.fur.unterwegs.speichern.von": "Für unterwegs speichern: {value1} von {value2}",
|
||||
"offline-cache.wegwichtel.ist.fur.unterwegs.bereit": "Wegwichtel ist für unterwegs bereit.",
|
||||
"offline-cache.speichern.abgebrochen": "Speichern abgebrochen.",
|
||||
"offline-cache.das.speichern.hat.nicht.geklappt": "Das Speichern hat nicht geklappt.",
|
||||
"offline-cache.wegwichtel.ist.noch.nicht.ohne.internet.bereit": "Wegwichtel ist noch nicht ohne Internet bereit.",
|
||||
"offline-cache.die.gespeicherte.app.wurde.entfernt.deine.strecken": "Die gespeicherte App wurde entfernt. Deine Strecken bleiben erhalten.",
|
||||
"offline-cache.gespeicherte.strecke.erneuern": "Gespeicherte Strecke erneuern",
|
||||
"offline-cache.fur.unterwegs.speichern": "Für unterwegs speichern",
|
||||
"offline-cache.diese.strecke.ist.fur.unterwegs.bereit": "Diese Strecke ist für unterwegs bereit.",
|
||||
"offline-cache.nimm.diese.strecke.mit.bildern.und.tonaufnahmen": "Nimm diese Strecke mit Bildern und Tonaufnahmen mit.",
|
||||
"offline-cache.bitte.warte.bis.die.andere.strecke.gespeichert": "Bitte warte, bis die andere Strecke gespeichert ist.",
|
||||
"offline-cache.auf.diesem.gerat.kannst.du.strecken.gerade": "Auf diesem Gerät kannst du Strecken gerade nicht für unterwegs speichern.",
|
||||
"offline-cache.wahle.zuerst.eine.strecke.die.fur.alle": "Wähle zuerst eine Strecke, die für alle freigegeben ist.",
|
||||
"offline-cache.die.strecke.konnte.nicht.geladen.werden": "Die Strecke konnte nicht geladen werden.",
|
||||
"offline-cache.du.kannst.nur.freigegebene.strecken.fur.unterwegs": "Du kannst nur freigegebene Strecken für unterwegs speichern.",
|
||||
"offline-cache.der.weg.dieser.strecke.fehlt.sie.kann": "Der Weg dieser Strecke fehlt. Sie kann noch nicht für unterwegs gespeichert werden.",
|
||||
"offline-cache.strecke.speichern.von": "Strecke speichern: {value1} von {value2}",
|
||||
"offline-cache.bitte.warte.bis.das.speichern.fertig.ist": "Bitte warte, bis das Speichern fertig ist.",
|
||||
"offline-cache.auf.diesem.gerat.lasst.sich.wegwichtel.gerade": "Auf diesem Gerät lässt sich Wegwichtel gerade nicht für unterwegs speichern. Du kannst es mit Internet weiter nutzen.",
|
||||
"offline-cache.speichere.zuerst.die.app.danach.kannst.du": "Speichere zuerst die App. Danach kannst du die Strecken auswählen, die du mitnehmen möchtest.",
|
||||
"offline-storage.inhalte.von.anderen.seiten.konnen.hier.nicht": "Inhalte von anderen Seiten können hier nicht gespeichert werden.",
|
||||
"offline-storage.ein.teil.konnte.nicht.gespeichert.werden.prufe": "Ein Teil konnte nicht gespeichert werden. Prüfe deine Internetverbindung und versuche es noch einmal.",
|
||||
"slideshow.bild.zur.station": "Bild zur Station",
|
||||
"slideshow.bild.konnte.nicht.geladen.werden": "Bild konnte nicht geladen werden",
|
||||
"storage-usage.der.freie.platz.konnte.nicht.gemessen.werden": "Der freie Platz konnte nicht gemessen werden.",
|
||||
"storage-usage.fast.voll.entferne.inhalte.die.du.nicht": " Fast voll: Entferne Inhalte, die du nicht mehr brauchst.",
|
||||
"storage-usage.app.bilder.und.ton.des.verfugbaren.platzes": "App, Bilder und Ton: {value1} % des verfügbaren Platzes belegt. Ungefähr {value2} frei.{value3}",
|
||||
"storage-usage.texte.und.einstellungen.konnen.hier.gerade.nicht": "Texte und Einstellungen können hier gerade nicht gespeichert werden.",
|
||||
"storage-usage.der.balken.ist.fast.voll.grosse.bilder": " Der Balken ist fast voll. Große Bilder können das Speichern verhindern.",
|
||||
"storage-usage.texte.und.einstellungen.ungefahr.belegt.der.balken": "Texte und Einstellungen: ungefähr {value1} belegt. Der Balken zeigt einen Richtwert von etwa 5 MB. Wie viel wirklich hineinpasst, hängt von deinem Internetprogramm ab.{value2}",
|
||||
"storage-usage.der.platz.ist.voll.entferne.gespeicherte.strecken": "Der Platz ist voll. Entferne gespeicherte Strecken, Entwürfe, Bilder oder Tonaufnahmen, die du nicht mehr brauchst. Auch bei einem fast leeren ersten Balken kann der Platz für Texte und Einstellungen voll sein.",
|
||||
"user-messages.diese.strecke.kann.nur.die.person.loschen": "Diese Strecke kann nur die Person löschen, die Wegwichtel betreut.",
|
||||
"user-messages.diese.strecke.ist.fur.alle.freigegeben.du": "Diese Strecke ist für alle freigegeben. Du kannst sie hier nicht mehr verändern.",
|
||||
"user-messages.die.datei.enthalt.keinen.lesbaren.weg.wahle": "Die Datei enthält keinen lesbaren Weg. Wähle eine andere Datei.",
|
||||
"user-messages.diese.station.wurde.nicht.gefunden.offne.die": "Diese Station wurde nicht gefunden. Öffne die Strecke noch einmal.",
|
||||
"user-messages.der.ort.dieser.station.fehlt.oder.konnte": "Der Ort dieser Station fehlt oder konnte nicht gelesen werden.",
|
||||
"user-messages.dieses.bild.kann.nicht.geoffnet.werden.wahle": "Dieses Bild kann nicht geöffnet werden. Wähle ein anderes Bild.",
|
||||
"user-messages.diese.tonaufnahme.kann.nicht.geoffnet.werden.wahle": "Diese Tonaufnahme kann nicht geöffnet werden. Wähle eine andere Aufnahme.",
|
||||
"user-messages.diese.datei.ist.zu.gross.wahle.eine": "Diese Datei ist zu groß. Wähle eine kleinere Datei.",
|
||||
"user-messages.du.darfst.diese.aktion.hier.nicht.ausfuhren": "Du darfst diese Aktion hier nicht ausführen.",
|
||||
"user-messages.der.platz.ist.voll.entferne.gespeicherte.strecken": "Der Platz ist voll. Entferne gespeicherte Strecken, Bilder oder Tonaufnahmen, die du nicht mehr brauchst.",
|
||||
"user-messages.wegwichtel.ist.gerade.nicht.erreichbar.bitte.versuche": "Wegwichtel ist gerade nicht erreichbar. Bitte versuche es später noch einmal.",
|
||||
"vibration.das.vibrationsmuster.muss.aus.nichtnegativen.millisekunden.bestehen": "Das Vibrationsmuster muss aus nichtnegativen Millisekunden bestehen.",
|
||||
"vibration.die.vibrationsdauer.muss.eine.nichtnegative.millisekundenangabe.sein": "Die Vibrationsdauer muss eine nichtnegative Millisekundenangabe sein.",
|
||||
"html.title.wegwichtel": "Wegwichtel",
|
||||
"html.span.i": "i",
|
||||
"html.info-dropdown.hilfe.und.informationen": "Hilfe und Informationen",
|
||||
"html.p.gleich.kannst.du.mit.dem.wegwichtel.losziehen": "Gleich kannst du mit dem Wegwichtel losziehen.",
|
||||
"html.bootstrap-status-text.wegwichtel.wird.vorbereitet": "Wegwichtel wird vorbereitet …",
|
||||
"html.span.los.gehts": "Los geht’s",
|
||||
"html.bootstrap-retry.erneut.versuchen": "Erneut versuchen",
|
||||
"html.bootstrap-ready.alles.ist.bereit": "Alles ist bereit.",
|
||||
"html.div.wegwichtel.funktionen": "Wegwichtel Funktionen",
|
||||
"html.span.strecken": "Strecken",
|
||||
"html.span.entdecken": "entdecken",
|
||||
"html.span.spannende": "Spannende",
|
||||
"html.span.geschichten": "Geschichten",
|
||||
"html.span.kindgerechte": "Kindgerechte",
|
||||
"html.span.erlebnisse": "Erlebnisse",
|
||||
"html.p.hier.gibt.es.keine.werbung.wir.verfolgen": "Hier gibt es keine Werbung. Wir verfolgen nicht, welche anderen Seiten du besuchst.",
|
||||
"html.strong.optionale.cookies": "Optionale Cookies",
|
||||
"html.small.diese.funktion.wird.nicht.verwendet": "Diese Funktion wird nicht verwendet.",
|
||||
"html.strong.meinen.fortschritt.merken": "Meinen Fortschritt merken",
|
||||
"html.small.merkt.sich.auf.diesem.gerat.welche.stationen": "Merkt sich auf diesem Gerät, welche Stationen du schon besucht hast.",
|
||||
"html.strong.app.ohne.internet.offnen": "App ohne Internet öffnen",
|
||||
"html.small.damit.kannst.du.wegwichtel.auch.ohne.internet": "Damit kannst du Wegwichtel auch ohne Internet öffnen. Wähle danach die Strecken aus, die du mitnehmen möchtest.",
|
||||
"html.offline-cache-help-button.was.wird.fur.unterwegs.gespeichert": "Was wird für unterwegs gespeichert?",
|
||||
"html.offline-cache-help-button.hilfe.zum.speichern.fur.unterwegs": "Hilfe zum Speichern für unterwegs",
|
||||
"html.p.mit.diesem.schalter.kannst.du.wegwichtel.auch": "Mit diesem Schalter kannst du Wegwichtel auch ohne Internet öffnen. Strecken werden dabei noch nicht gespeichert.",
|
||||
"html.p.offne.eine.strecke.und.tippe.auf.fur": "Öffne eine Strecke und tippe auf „Für unterwegs speichern“. So nimmst du den Weg, die Stationen, Bilder und Tonaufnahmen mit.",
|
||||
"html.p.warte.vor.dem.losgehen.bis.alles.gespeichert": "Warte vor dem Losgehen, bis alles gespeichert ist. Zum Speichern und Erneuern brauchst du Internet.",
|
||||
"html.p.alles.bleibt.auf.diesem.gerat.der.schalter": "Alles bleibt auf diesem Gerät. Der Schalter zeichnet deinen Weg nicht auf. Beim Ausschalten bleiben deine gespeicherten Strecken und Entwürfe erhalten.",
|
||||
"html.p.wenn.du.die.gespeicherten.daten.von.wegwichtel": "Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetprogramm löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen.",
|
||||
"html.offline-cache-refresh.gespeicherte.app.erneuern": "Gespeicherte App erneuern",
|
||||
"html.section.platz.fur.wegwichtel": "Platz für Wegwichtel",
|
||||
"html.meter.belegter.platz.fur.app.bilder.und.ton": "Belegter Platz für App, Bilder und Ton",
|
||||
"html.p.wir.schauen.wie.viel.platz.belegt.ist": "Wir schauen, wie viel Platz belegt ist …",
|
||||
"html.meter.belegter.platz.fur.texte.und.einstellungen.der": "Belegter Platz für Texte und Einstellungen; der Balken zeigt einen ungefähren Richtwert",
|
||||
"html.p.der.erste.balken.zeigt.den.platz.fur": "Der erste Balken zeigt den Platz für die App, Bilder und Ton. Der zweite zeigt den Platz für Texte und Einstellungen. Die Balken zeigen nur den Platz für Wegwichtel.",
|
||||
"html.button.platz.prufen": "Platz prüfen",
|
||||
"html.li.gespeicherte.inhalte.prufen": "Gespeicherte Inhalte prüfen",
|
||||
"html.li.wegwichtel.vorbereiten": "Wegwichtel vorbereiten",
|
||||
"html.li.bedienung.vorbereiten": "Bedienung vorbereiten",
|
||||
"html.li.app.vorbereiten": "App vorbereiten",
|
||||
"html.li.verbindung.prufen": "Verbindung prüfen",
|
||||
"html.li.strecken.laden": "Strecken laden",
|
||||
"html.li.deinen.standort.suchen": "Deinen Standort suchen",
|
||||
"html.route-control-button.strecke.auswahlen.oder.wechseln": "Strecke auswählen oder wechseln",
|
||||
"html.route-control-dropdown.strecken.auswahlen.und.bearbeiten": "Strecken auswählen und bearbeiten",
|
||||
"html.editor-button.strecken.bearbeiten": "Strecken bearbeiten",
|
||||
"html.location-hint.wir.suchen.strecken.in.deiner.nahe.dein": "Wir suchen Strecken in deiner Nähe. Dein Standort bleibt auf diesem Gerät.",
|
||||
"html.h2.strecken.in.deiner.nahe": "Strecken in deiner Nähe",
|
||||
"html.h2.alle.strecken": "Alle Strecken",
|
||||
"html.label.strecken.durchsuchen": "Strecken durchsuchen",
|
||||
"html.route-search.name.schule.oder.beschreibung": "Name, Schule oder Beschreibung",
|
||||
"html.clear-route-search.suche.leeren": "Suche leeren",
|
||||
"html.p.diese.strecken.hast.du.auf.diesem.gerat": "Diese Strecken hast du auf diesem Gerät angelegt. Andere können sie noch nicht sehen.",
|
||||
"html.p.meine.entwurfe.werden.geladen": "Meine Entwürfe werden geladen …",
|
||||
"html.button.zuruck.zur.streckenauswahl": "Zurück zur Streckenauswahl",
|
||||
"html.route-offline-controls.strecke.auf.dem.gerat.speichern": "Strecke auf dem Gerät speichern",
|
||||
"html.route-offline-remove.gespeicherte.strecke.entfernen": "Gespeicherte Strecke entfernen",
|
||||
"html.route-gpx-download.weg.als.datei.herunterladen": "Weg als Datei herunterladen",
|
||||
"html.p.fur.unterwegs.speichern.nimmt.diese.strecke.mit": "„Für unterwegs speichern“ nimmt diese Strecke mit Bildern und Ton mit. „Weg als Datei herunterladen“ speichert nur den Weg für eine andere Wander-App.",
|
||||
"html.dt.lange": "Länge",
|
||||
"html.dt.hohenmeter": "Höhenmeter",
|
||||
"html.dt.stationen": "Stationen",
|
||||
"html.legend.unterwegs": "Unterwegs",
|
||||
"html.pause-route.strecke.pausieren": "Strecke pausieren",
|
||||
"html.stop-route.strecke.beenden": "Strecke beenden",
|
||||
"html.button.zuruck.zur.strecke": "Zurück zur Strecke",
|
||||
"html.poi-title.station": "Station",
|
||||
"html.legend.bildnavigation": "Bildnavigation",
|
||||
"html.slide-prev.vorheriges.bild": "Vorheriges Bild",
|
||||
"html.slide-next.nachstes.bild": "Nächstes Bild",
|
||||
"html.poi-audio.ton.zur.station": "Ton zur Station",
|
||||
"html.poi-audio.dieses.gerat.kann.den.ton.leider.nicht": "Dieses Gerät kann den Ton leider nicht abspielen.",
|
||||
"html.navigation-mode.richtung.zum.nachsten.station": "Richtung zum nächsten Station",
|
||||
"html.p.diese.angaben.sind.noch.nicht.vollstandig.die": "Diese Angaben sind noch nicht vollständig. Die Kontakte stammen von der Grundschule Herpf. Wer die Website betreibt, welche Verbindungsdaten gespeichert werden und wie lange sie bleiben, muss noch ergänzt werden.",
|
||||
"html.strong.hinweis": "Hinweis:",
|
||||
"html.h3.1.verantwortliche.stelle.und.datenschutzkontakt": "1. Verantwortliche Stelle und Datenschutzkontakt",
|
||||
"html.p.zum.eichig.3": "Zum Eichig 3",
|
||||
"html.p.98617.meiningen.ot.herpf": "98617 Meiningen / OT Herpf",
|
||||
"html.p.telefon.036943.63722": "Telefon: (036943) 63722",
|
||||
"html.p.e.mail.info.grundschule.herpf.de": "E-Mail: info@grundschule-herpf.de",
|
||||
"html.strong.staatliche.grundschule.herpf": "Staatliche Grundschule Herpf",
|
||||
"html.p.datenschutzangaben.der.schule": "Datenschutzangaben der Schule:",
|
||||
"html.p.datenschutzbeauftragter": "Datenschutzbeauftragter",
|
||||
"html.p.holderlinstrae.1": "Hölderlinstraße 1",
|
||||
"html.p.98527.suhl": "98527 Suhl",
|
||||
"html.p.telefon.03681.7341.25": "Telefon: 03681 / 7341 25",
|
||||
"html.p.fax.03681.7341.09": "Fax: 03681 / 7341 09",
|
||||
"html.p.e.mail.datenschutz.suedthueriungen.schulamt.thueringen.de": "E-Mail: datenschutz.suedthueriungen@schulamt.thueringen.de",
|
||||
"html.strong.staatliches.schulamt.sudthuringen": "Staatliches Schulamt Südthüringen",
|
||||
"html.p.der.datenschutzbeauftragte.kann.auerdem.uber.die.postadresse": "Der Datenschutzbeauftragte kann außerdem über die Postadresse der Schule mit dem Zusatz „der Datenschutzbeauftragte“ erreicht werden.",
|
||||
"html.h3.2.wenn.du.wegwichtel.offnest": "2. Wenn du Wegwichtel öffnest",
|
||||
"html.p.wenn.du.wegwichtel.offnest.konnen.verbindungsdaten.verarbeitet": "Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetprogramm und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.",
|
||||
"html.p.wer.die.website.bereitstellt.und.welche.verbindungsdaten": "Wer die Website bereitstellt und welche Verbindungsdaten wie lange gespeichert werden:",
|
||||
"html.strong.anbieter.anschrift.gespeicherte.daten.und.speicherdauer.erganzen": "[Anbieter, Anschrift, gespeicherte Daten und Speicherdauer ergänzen]",
|
||||
"html.h3.3.standortdaten": "3. Standortdaten",
|
||||
"html.p.du.entscheidest.ob.wegwichtel.deinen.standort.nutzen": "Du entscheidest, ob Wegwichtel deinen Standort nutzen darf. Damit findest du Strecken in deiner Nähe und siehst den Weg zur nächsten Station. Dein laufender Standort wird auf deinem Gerät verarbeitet. Wegwichtel speichert daraus keinen Verlauf auf der Website.",
|
||||
"html.h3.4.deinen.fortschritt.merken": "4. Deinen Fortschritt merken",
|
||||
"html.p.wenn.du.meinen.fortschritt.merken.einschaltest.speichert": "Wenn du „Meinen Fortschritt merken“ einschaltest, speichert Wegwichtel deine gewählte Strecke, besuchte und angesehene Stationen sowie angehörte Tonaufnahmen. Auch ob du unterwegs bist, eine Pause machst oder fertig bist und der Zeitpunkt werden gespeichert. Diese Angaben bleiben auf deinem Gerät und werden nicht an die Website gesendet.",
|
||||
"html.p.wenn.du.deinen.fortschritt.mindestens.einen.tag": "Wenn du deinen Fortschritt mindestens einen Tag lang nicht verändert hast, wird er beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine eigenen und für unterwegs gespeicherten Strecken bleiben erhalten.",
|
||||
"html.small.du.entscheidest.ob.sich.wegwichtel.deinen.fortschritt": "Du entscheidest, ob sich Wegwichtel deinen Fortschritt merkt.",
|
||||
"html.h3.5.deine.eigenen.strecken": "5. Deine eigenen Strecken",
|
||||
"html.p.neue.strecken.bleiben.zuerst.auf.deinem.gerat": "Neue Strecken bleiben zuerst auf deinem Gerät. Gespeichert werden der Weg, die Orte der Stationen, Texte sowie ausgewählte Bilder und Tonaufnahmen.",
|
||||
"html.p.deine.entwurfe.bleiben.erhalten.wenn.alter.fortschritt": "Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.",
|
||||
"html.h3.6.werbung.und.gespeicherte.einstellungen": "6. Werbung und gespeicherte Einstellungen",
|
||||
"html.p.wegwichtel.verwendet.keine.cookies.es.zahlt.keine": "Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.",
|
||||
"html.h3.7.inhalte.von.schulerinnen.und.schulern": "7. Inhalte von Schülerinnen und Schülern",
|
||||
"html.p.bilder.texte.und.tonaufnahmen.durfen.nur.geteilt": "Bilder, Texte und Tonaufnahmen dürfen nur geteilt werden, wenn das erlaubt ist. Wenn Kinder auf Bildern oder in Aufnahmen erkannt werden können, müssen die Regeln der Schule beachtet werden. Auch die Eltern oder anderen Sorgeberechtigten müssen die nötige Erlaubnis geben.",
|
||||
"html.h3.8.deine.rechte": "8. Deine Rechte",
|
||||
"html.p.du.kannst.fragen.welche.daten.uber.dich": "Du kannst fragen, welche Daten über dich gespeichert sind. Unter den gesetzlichen Voraussetzungen kannst du verlangen, dass sie berichtigt, gelöscht, nur noch eingeschränkt genutzt oder dir zur Weitergabe bereitgestellt werden. Du kannst der Nutzung widersprechen und eine erteilte Erlaubnis für die Zukunft zurücknehmen. Wenn du dich über den Umgang mit deinen Daten beschweren möchtest, kannst du dich an die folgende Datenschutzbehörde wenden.",
|
||||
"html.p.zustandige.datenschutzaufsicht": "Zuständige Datenschutzaufsicht:",
|
||||
"html.p.postfach.90.04.55.99107.erfurt": "Postfach 90 04 55, 99107 Erfurt",
|
||||
"html.p.halerstrae.8.99096.erfurt": "Häßlerstraße 8, 99096 Erfurt",
|
||||
"html.p.telefon.0361.57.311.29.00": "Telefon: 0361 / 57 311 29 00",
|
||||
"html.p.telefax.0361.57.311.29.04": "Telefax: 0361 / 57 311 29 04",
|
||||
"html.p.e.mail.poststelle.datenschutz.thueringen.de": "E-Mail: poststelle@datenschutz.thueringen.de",
|
||||
"html.p.website.www.tlfdi.de": "Website: www.tlfdi.de",
|
||||
"html.strong.thuringer.landesbeauftragter.fur.den.datenschutz.und.die": "Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)",
|
||||
"html.p.diese.angaben.stammen.von.der.grundschule.herpf": "Diese Angaben stammen von der Grundschule Herpf. Vor der Veröffentlichung muss geprüft werden, ob für Wegwichtel noch weitere oder andere Angaben nötig sind.",
|
||||
"html.h3.wer.fur.wegwichtel.verantwortlich.ist": "Wer für Wegwichtel verantwortlich ist",
|
||||
"html.h3.kontakt": "Kontakt",
|
||||
"html.p.telefax.036943.63843": "Telefax: (036943) 63843",
|
||||
"html.h3.leitung.der.schule": "Leitung der Schule",
|
||||
"html.p.frau.wichert.schulleiterin": "Frau Wichert, Schulleiterin",
|
||||
"html.h3.schultrager": "Schulträger",
|
||||
"html.p.landratsamt": "Landratsamt",
|
||||
"html.p.obertshauser.platz.1": "Obertshäuser Platz 1",
|
||||
"html.p.98617.meiningen": "98617 Meiningen",
|
||||
"html.strong.landkreis.schmalkalden.meiningen": "Landkreis Schmalkalden-Meiningen",
|
||||
"html.h3.inhaltlich.verantwortlich": "Inhaltlich verantwortlich",
|
||||
"html.p.frau.wichert": "Frau Wichert",
|
||||
"html.h3.weitere.pflichtangaben": "Weitere Pflichtangaben",
|
||||
"html.p.je.nachdem.wer.wegwichtel.betreibt.konnen.weitere": "Je nachdem, wer Wegwichtel betreibt, können weitere Angaben nötig sein. Dazu gehören zum Beispiel die Verantwortlichen für den Betrieb der Website. Diese Angaben müssen noch geprüft werden.",
|
||||
"html.h3.haftung.fur.inhalte.und.verweise": "Haftung für Inhalte und Verweise",
|
||||
"html.p.die.inhalte.werden.sorgfaltig.erstellt.und.regelmaig": "Die Inhalte werden sorgfältig erstellt und regelmäßig geprüft. Falls etwas falsch, veraltet oder nicht erlaubt ist, melde dich beim Kontakt oben. Für andere verlinkte Seiten sind deren Betreiber verantwortlich.",
|
||||
"html.h3.urheber.und.nutzungsrechte": "Urheber- und Nutzungsrechte",
|
||||
"html.p.wenn.nichts.anderes.angegeben.ist.liegen.die": "Wenn nichts anderes angegeben ist, liegen die Rechte bei der Grundschule Herpf. Du darfst Texte, Bilder, Tonaufnahmen und Wege nur so nutzen, wie es jeweils erlaubt wurde. Auch die Rechte der beteiligten Kinder, Familien, Lehrkräfte und anderen Personen bleiben bestehen.",
|
||||
"html.h3.was.merkt.sich.wegwichtel": "Was merkt sich Wegwichtel?",
|
||||
"html.p.du.kannst.deinen.fortschritt.auf.diesem.gerat": "Du kannst deinen Fortschritt auf diesem Gerät speichern. Dann weiß Wegwichtel beim nächsten Öffnen, welche Stationen du schon besucht oder angesehen hast und welche Tonaufnahmen du angehört hast.",
|
||||
"html.p.wenn.du.einen.tag.lang.nichts.mehr": "Wenn du einen Tag lang nichts mehr daran geändert hast, wird dein Fortschritt beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine selbst angelegten und für unterwegs gespeicherten Strecken bleiben erhalten.",
|
||||
"html.small.die.anderung.gilt.sofort.auf.diesem.gerat": "Die Änderung gilt sofort auf diesem Gerät.",
|
||||
"html.h3.ohne.internet.unterwegs": "Ohne Internet unterwegs",
|
||||
"html.p.mit.app.ohne.internet.offnen.speicherst.du": "Mit „App ohne Internet öffnen“ speicherst du Wegwichtel auf diesem Gerät. Danach kannst du einzelne Strecken mit „Für unterwegs speichern“ mitnehmen. Warte vor dem Losgehen, bis alles gespeichert ist.",
|
||||
"html.p.mit.weg.als.datei.herunterladen.kannst.du": "Mit „Weg als Datei herunterladen“ kannst du den Weg in einer anderen Wander-App öffnen. Bilder und Tonaufnahmen sind in dieser Datei nicht enthalten.",
|
||||
"html.h3.deine.eigenen.strecken": "Deine eigenen Strecken",
|
||||
"html.p.neue.strecken.bleiben.zuerst.auf.diesem.gerat": "Neue Strecken bleiben zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ reichst du eine Strecke zur Freigabe ein. Erst nach der Freigabe können andere sie sehen.",
|
||||
"html.p.deine.entwurfe.bleiben.gespeichert.bis.du.sie": "Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.",
|
||||
"html.h3.welche.erlaubnisse.braucht.die.app": "Welche Erlaubnisse braucht die App?",
|
||||
"html.li.deinen.standort.um.strecken.in.deiner.nahe": "Deinen Standort, um Strecken in deiner Nähe und den Weg zur nächsten Station zu zeigen.",
|
||||
"html.li.die.richtung.deines.gerats.damit.sich.der": "Die Richtung deines Geräts, damit sich der Pfeil mitdrehen kann.",
|
||||
"html.li.zugriff.auf.ausgewahlte.bilder.und.tonaufnahmen.wenn": "Zugriff auf ausgewählte Bilder und Tonaufnahmen, wenn du sie zu einer Strecke hinzufügen möchtest.",
|
||||
"html.p.du.kannst.diese.erlaubnisse.ablehnen.dann.fehlen": "Du kannst diese Erlaubnisse ablehnen. Dann fehlen die jeweiligen Funktionen. Strecken und Stationen kannst du weiterhin selbst auswählen.",
|
||||
"html.h3.werbung.und.andere.anbieter": "Werbung und andere Anbieter",
|
||||
"html.p.wegwichtel.zeigt.keine.werbung.und.verfolgt.nicht": "Wegwichtel zeigt keine Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Die App lädt ihre eigenen Programme und Bilder von Wegwichtel. Es werden keine Cookies verwendet.",
|
||||
"html.editor-close.fenster.zum.bearbeiten.schlieen": "Fenster zum Bearbeiten schließen",
|
||||
"html.editor-route-list-title.deine.strecken": "Deine Strecken",
|
||||
"html.editor-new-route.neue.strecke.anlegen": "Neue Strecke anlegen",
|
||||
"html.p.hier.findest.du.strecken.fur.alle.und": "Hier findest du Strecken für alle und deine eigenen Entwürfe.",
|
||||
"html.editor-route-back.zuruck": "Zurück",
|
||||
"html.editor-route-detail-title.deine.strecke": "Deine Strecke",
|
||||
"html.label.name.der.strecke": "Name der Strecke",
|
||||
"html.label.beschreibung": "Beschreibung",
|
||||
"html.label.weg.aus.einer.datei.ubernehmen": "Weg aus einer Datei übernehmen",
|
||||
"html.editor-route-activated.deine.neue.strecke.bleibt.zuerst.auf.diesem": "Deine neue Strecke bleibt zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ kannst du sie zur Freigabe einreichen.",
|
||||
"html.editor-route-save.strecke.speichern": "Strecke speichern",
|
||||
"html.label.name": "Name",
|
||||
"html.label.text": "Text",
|
||||
"html.div.bilder.und.ton.zur.station.hinzufugen": "Bilder und Ton zur Station hinzufügen",
|
||||
"html.editor-poi-audio-add.ton.hinzufugen": "Ton hinzufügen",
|
||||
"html.editor-poi-images-add.bild.hinzufugen": "Bild hinzufügen",
|
||||
"html.editor-poi-media-preview.bilder.und.ton.ansehen": "Bilder und Ton ansehen",
|
||||
"html.editor-poi-save.station.speichern": "Station speichern",
|
||||
"html.span.station.entfernen": "Station entfernen",
|
||||
"media.audio.count": {
|
||||
"one": "{count} Tonaufnahme",
|
||||
"other": "{count} Tonaufnahmen"
|
||||
},
|
||||
"media.image.count": {
|
||||
"one": "{count} Bild",
|
||||
"other": "{count} Bilder"
|
||||
},
|
||||
"app.page.missing": "Diese Seite konnte nicht geöffnet werden.",
|
||||
"app.location.searching": "Deinen Standort suchen",
|
||||
"app.distance.spoken": "{value1} Meter entfernt",
|
||||
"app.distance.short": "{value1} m entfernt",
|
||||
"app.path.missing": "Der Weg dieser Strecke fehlt.",
|
||||
"app.location.start": "Los geht’s! Wir suchen deinen Standort.",
|
||||
"bootstrap.location": "Deinen Standort suchen …",
|
||||
"bootstrap.step.failed": "Das hat nicht geklappt: {value1}",
|
||||
"bootstrap.ready": "Alles ist bereit.",
|
||||
"editor.station.up": "{value1} nach oben schieben",
|
||||
"editor.station.down": "{value1} nach unten schieben",
|
||||
"editor.route.loading": "Deine Strecke wird geöffnet …",
|
||||
"editor.audio.removed": "Tonaufnahme entfernt.",
|
||||
"editor.order.saved": "Reihenfolge gespeichert.",
|
||||
"info.missing": "{value1}: Diese Information fehlt noch.",
|
||||
"local.device": "Auf diesem Gerät",
|
||||
"file.read.failed": "Die Datei konnte nicht geöffnet werden. Wähle sie noch einmal aus.",
|
||||
"offline.previous.kept": "Was du vorher gespeichert hast, bleibt erhalten.",
|
||||
"offline.error.previous.kept": "{value1} Was du vorher gespeichert hast, bleibt erhalten."
|
||||
});
|
||||
static messages = Object.freeze(
|
||||
{
|
||||
"app": {
|
||||
"resume-hint": "Du kannst dort weitermachen, wo du aufgehört hast.",
|
||||
"draft": "Eigener Entwurf",
|
||||
"choose-route": "Strecke wählen",
|
||||
"route-menu": "Strecken für alle und meine Entwürfe",
|
||||
"route": "Strecke",
|
||||
"stations-complete": "Alle Stationen erreicht",
|
||||
"no-stations": "Keine Stationen vorhanden",
|
||||
"next-station-summary": "Nächste Station: {value1}{value2}",
|
||||
"published": "Veröffentlicht",
|
||||
"public-route-count": "Strecken für alle ({value1})",
|
||||
"draft-count": "Meine Entwürfe ({value1})",
|
||||
"no-public-route": "Hier gibt es noch keine Strecken für alle.",
|
||||
"no-route": "Du hast noch keine eigene Strecke angelegt.",
|
||||
"no-nearby-route": "Keine Strecke im Umkreis von {value1} km gefunden. Wähle unten eine beliebige Strecke aus.",
|
||||
"location-permission-hint": "Erlaube deinen Standort, um Strecken in deiner Nähe zu finden. Unten kannst du alle Strecken auswählen.",
|
||||
"no-search-result": "Keine passende Strecke gefunden. Versuche ein anderes Wort.",
|
||||
"search-result-count": "{value1} von {value2} Strecken gefunden.",
|
||||
"route-count": "{value1} Strecken zur Auswahl.",
|
||||
"resume-route": "Strecke fortsetzen",
|
||||
"start-route": "Strecke starten",
|
||||
"route-finished": "Strecke beendet.",
|
||||
"pause-hint": "Du machst eine Pause. Die App sucht gerade nicht nach deinem Standort.",
|
||||
"route-paused": "Strecke pausiert",
|
||||
"empty-route": "Diese Strecke hat noch keine Stationen.",
|
||||
"next-station-north": "Norden oben · nächste Station: {value1}",
|
||||
"next-station": "Nächste Station: {value1}",
|
||||
"next-station-distance": "Nächste Station {value1} in {value2} Metern",
|
||||
"destination-reached": "Alle Stationen und das Ziel erreicht",
|
||||
"next-path-point-north": "Norden oben · nächster Punkt auf dem Weg",
|
||||
"next-path-point": "Nächster Punkt auf dem Weg",
|
||||
"next-path-point-distance": "Nächster Punkt auf dem Weg in {value1} Metern",
|
||||
"location-accuracy": "Dein Standort wird gesucht · auf etwa {value1} m genau",
|
||||
"nearest-station-distance": " · nächste Station {value1} m",
|
||||
"stations-complete-suffix": " · alle Stationen erreicht",
|
||||
"resuming": "Strecke wird fortgesetzt.",
|
||||
"resuming-title": "Strecke wird fortgesetzt",
|
||||
"error": {
|
||||
"location": "Dein Standort konnte nicht gefunden werden. Prüfe die Standort-Erlaubnis auf deinem Gerät.",
|
||||
"load-routes": "Die Strecken konnten nicht geladen werden. Bitte versuche es noch einmal.",
|
||||
"load-drafts": "Deine Entwürfe konnten nicht geöffnet werden. Bitte versuche es noch einmal.",
|
||||
"open-route": "Die Strecke konnte nicht geöffnet werden. Prüfe deine Verbindung und versuche es noch einmal.",
|
||||
"open-draft": "Die Strecke konnte nicht geöffnet werden. Bitte versuche es noch einmal."
|
||||
},
|
||||
"navigation-north": "Norden oben · Navigation",
|
||||
"location-found": "Dein Standort wurde auf etwa {value1} m genau gefunden. ",
|
||||
"search-radius": "Wir zeigen dir Strecken bis {value1} km Entfernung.",
|
||||
"location-optional": "Dein Standort konnte nicht gefunden werden. Du kannst trotzdem alle Strecken auswählen.",
|
||||
"page": {
|
||||
"missing": "Diese Seite konnte nicht geöffnet werden."
|
||||
},
|
||||
"location": {
|
||||
"searching": "Deinen Standort suchen",
|
||||
"start": "Los geht’s! Wir suchen deinen Standort."
|
||||
},
|
||||
"distance": {
|
||||
"spoken": "{value1} Meter entfernt",
|
||||
"short": "{value1} m entfernt"
|
||||
},
|
||||
"path": {
|
||||
"missing": "Der Weg dieser Strecke fehlt."
|
||||
}
|
||||
},
|
||||
"bootstrap-loader": {
|
||||
"steps": {
|
||||
"storage": "Gespeicherte Inhalte prüfen …",
|
||||
"jquery": "Wegwichtel vorbereiten …",
|
||||
"jquery-ui": "Die Bedienung vorbereiten …",
|
||||
"modules": "Die App vorbereiten …",
|
||||
"server": "Verbindung prüfen …",
|
||||
"routes": "Strecken laden …",
|
||||
"location": "Deinen Standort suchen …"
|
||||
},
|
||||
"hints": {
|
||||
"chrome": "Auf diesem Gerät kannst du Bilder und Tonaufnahmen direkt auswählen.",
|
||||
"android": "Tipp für Android: Mit Chrome lassen sich Bilder und Tonaufnahmen auswählen, ohne sie noch einmal zu speichern."
|
||||
},
|
||||
"error": {
|
||||
"start-error": "Ein Teil der App konnte nicht starten. Bitte versuche es noch einmal.",
|
||||
"load-error": "Ein Teil der App konnte nicht geladen werden. Prüfe deine Internetverbindung und versuche es noch einmal.",
|
||||
"connection-error": "Wegwichtel konnte nicht starten. Prüfe deine Verbindung und versuche es noch einmal.",
|
||||
"init-error": "Wegwichtel konnte nicht starten. Bitte versuche es noch einmal.",
|
||||
"step-error": "Das hat nicht geklappt: {value1}"
|
||||
},
|
||||
"cleanup-progress": "Alten Fortschritt löschen ({value1} / {value2})",
|
||||
"ready": "Alles ist bereit."
|
||||
},
|
||||
"editor": {
|
||||
"error": {
|
||||
"add-station": "Zu dieser Strecke kann gerade keine weitere Station hinzugefügt werden.",
|
||||
"action": "Die Aktion konnte nicht ausgeführt werden. Bitte versuche es noch einmal.",
|
||||
"draft-missing": "Dein Entwurf wurde nicht gefunden.",
|
||||
"create-locally": "Lege eine neue Strecke zuerst auf diesem Gerät an.",
|
||||
"route-name": "Gib deiner Strecke einen Namen.",
|
||||
"send-file": "Die Datei {value1} konnte nicht gesendet werden. Bitte wähle sie noch einmal aus.",
|
||||
"send-route": "Die Strecke konnte nicht gesendet werden. Bitte versuche es noch einmal.",
|
||||
"send-station": "Die Station {value1} konnte nicht gesendet werden. Bitte versuche es noch einmal.",
|
||||
"submit-own-draft": "Du kannst nur deine eigenen Entwürfe zur Prüfung senden.",
|
||||
"already-submitted": "Diese Strecke wurde schon zur Prüfung gesendet.",
|
||||
"location-required": "Für eine neue Station brauchen wir deinen Standort. Erlaube den Standort und versuche es noch einmal.",
|
||||
"save-route-first": "Speichere zuerst die Strecke.",
|
||||
"station-location": "Der Ort dieser Station fehlt. Erlaube deinen Standort und versuche es noch einmal.",
|
||||
"station-name": "Gib deiner Station einen Namen.",
|
||||
"station-unsaved": "Die Station ist noch nicht gespeichert.",
|
||||
"remove-image": "Das Bild konnte nicht entfernt werden. Lade die Strecke noch einmal.",
|
||||
"generic": "Das hat leider nicht geklappt. Bitte versuche es noch einmal."
|
||||
},
|
||||
"untitled-route": "Unbenannte Strecke",
|
||||
"untitled-station": "Station ohne Namen",
|
||||
"edit-draft": "Eigene Strecke bearbeiten",
|
||||
"new-route": "Neue Strecke",
|
||||
"view-route": "Strecke ansehen",
|
||||
"edit-station": "Station bearbeiten",
|
||||
"add-station": "Station hinzufügen",
|
||||
"published-route": "Veröffentlichte Strecke",
|
||||
"edit-route": "Strecke bearbeiten",
|
||||
"delete-route": "Strecke löschen",
|
||||
"station-number": "Station {value1}",
|
||||
"delete-item": "{value1} löschen",
|
||||
"public-routes": "Strecken für alle",
|
||||
"drafts": "Meine Entwürfe",
|
||||
"no-draft": "Noch kein eigener Entwurf vorhanden.",
|
||||
"no-stations": "Noch keine Stationen angelegt.",
|
||||
"loading-routes": "Strecken werden geladen …",
|
||||
"submitted": "Schon zur Prüfung gesendet",
|
||||
"submit": "Zur Prüfung senden",
|
||||
"submitted-hint": "Diese Strecke wurde zur Prüfung gesendet. Andere können sie erst nach der Freigabe sehen.",
|
||||
"draft-hint": "Dieser Entwurf bleibt auf deinem Gerät. Mit „Zur Prüfung senden“ reichst du ihn zur Freigabe ein.",
|
||||
"read-only-hint": "Diese Strecke ist für alle freigegeben und kann hier nicht mehr verändert werden.",
|
||||
"unpublished-hint": "Diese Strecke wurde noch nicht für alle freigegeben.",
|
||||
"remove-media-label": "{value1}: Knopf zum Entfernen {value2}",
|
||||
"new-audio": "Neue Tonaufnahme",
|
||||
"image-number": "Bild {value1}",
|
||||
"new-image": "Neues Bild {value1}",
|
||||
"remove-media-hint": "{value1}. Tippe auf ein Bild oder eine Tonaufnahme, um sie zu entfernen.",
|
||||
"no-media": "Noch keine Bilder oder Tonaufnahmen vorhanden.",
|
||||
"audio-section": "Tonaufnahmen",
|
||||
"image-section": "Bilder",
|
||||
"draft-status": "Deine neue Strecke bleibt zuerst auf diesem Gerät.",
|
||||
"saved": "Gespeichert.",
|
||||
"submitting": "Deine Strecke wird gesendet …",
|
||||
"submit-success": "Zur Prüfung gesendet.",
|
||||
"station-saved-with-media": "Station mit Bildern und Ton auf diesem Gerät gespeichert.",
|
||||
"station-saved": "Station gespeichert.",
|
||||
"image-removed": "Bild entfernt.",
|
||||
"station-removed-and-saved": "Station entfernt. Deine Strecke wurde gespeichert.",
|
||||
"station-removed": "Station entfernt.",
|
||||
"station-order-saved": "Reihenfolge der Stationen gespeichert.",
|
||||
"route-deleted": "Strecke gelöscht.",
|
||||
"open": "Strecken bearbeiten",
|
||||
"close": "Fenster zum Bearbeiten schließen",
|
||||
"routes-title": "Deine Strecken",
|
||||
"create-route": "Neue Strecke anlegen",
|
||||
"routes-hint": "Hier findest du Strecken für alle und deine eigenen Entwürfe.",
|
||||
"route-title": "Deine Strecke",
|
||||
"route-name": "Name der Strecke",
|
||||
"description": "Beschreibung",
|
||||
"import-path": "Weg aus einer Datei übernehmen",
|
||||
"new-draft-hint": "Deine neue Strecke bleibt zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ kannst du sie zur Freigabe einreichen.",
|
||||
"save-route": "Strecke speichern",
|
||||
"station-name": "Name",
|
||||
"station-text": "Text",
|
||||
"media-actions-label": "Bilder und Ton zur Station hinzufügen",
|
||||
"add-audio": "Ton hinzufügen",
|
||||
"add-image": "Bild hinzufügen",
|
||||
"media-preview-label": "Bilder und Ton ansehen",
|
||||
"save-station": "Station speichern",
|
||||
"remove-station": "Station entfernen",
|
||||
"station": {
|
||||
"up": "{value1} nach oben schieben",
|
||||
"down": "{value1} nach unten schieben"
|
||||
},
|
||||
"route": {
|
||||
"loading": "Deine Strecke wird geöffnet …"
|
||||
},
|
||||
"audio": {
|
||||
"removed": "Tonaufnahme entfernt."
|
||||
},
|
||||
"order": {
|
||||
"saved": "Reihenfolge gespeichert."
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"error": {
|
||||
"unsupported": "Dieses Gerät kann deinen Standort nicht finden."
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"wood": "Holz",
|
||||
"rock": "Felsen",
|
||||
"lantern": "Laterne",
|
||||
"flower": "Blume",
|
||||
"pipe": "Pfeife",
|
||||
"bird": "Vogel",
|
||||
"mushroom": "Pilz",
|
||||
"strawberry": "Erdbeere",
|
||||
"pumpkin": "Kürbis"
|
||||
},
|
||||
"info": {
|
||||
"privacy": "Datenschutzerklärung",
|
||||
"imprint": "Impressum",
|
||||
"storage": "Speichern und Berechtigungen",
|
||||
"open": "Hilfe und Informationen öffnen",
|
||||
"title": "Informationen",
|
||||
"menu-label": "Rechtliche und technische Informationen",
|
||||
"back": "Zurück zum Informationsmenü",
|
||||
"close-label": "Informationsfenster schließen",
|
||||
"close": "Schließen",
|
||||
"icon": "i",
|
||||
"dropdown-label": "Hilfe und Informationen",
|
||||
"missing": "{value1}: Diese Information fehlt noch."
|
||||
},
|
||||
"local": {
|
||||
"error": {
|
||||
"route-storage": "Deine Strecke kann auf diesem Gerät gerade nicht gespeichert werden.",
|
||||
"media-storage": "Bilder und Ton können auf diesem Gerät gerade nicht gespeichert werden.",
|
||||
"media-read": "Gespeicherte Bilder und Ton konnten nicht geöffnet werden.",
|
||||
"other-tabs": "Schließe andere Fenster mit Wegwichtel und versuche es noch einmal.",
|
||||
"media-access": "Bilder und Ton konnten nicht geöffnet oder gespeichert werden.",
|
||||
"media-cancelled": "Das Speichern von Bildern und Ton wurde abgebrochen.",
|
||||
"media-quota-hint": "Der Platz für Bilder und Ton ist voll. Entferne Bilder, Tonaufnahmen oder eigene Strecken, die du nicht mehr brauchst.",
|
||||
"media-quota": "Der Platz für Bilder und Ton ist voll.",
|
||||
"media-save": "Das Bild oder die Tonaufnahme konnte nicht gespeichert werden.",
|
||||
"media-cleanup-hint": "{value1} Entferne Bilder, Tonaufnahmen oder eigene Strecken, die du nicht mehr brauchst.",
|
||||
"file-permission": "Erlaube noch einmal den Zugriff auf das ausgewählte Bild oder die Tonaufnahme.",
|
||||
"file-missing": "Ein ausgewähltes Bild oder eine Tonaufnahme wurde verschoben oder gelöscht. Wähle die Datei noch einmal aus.",
|
||||
"create-route": "Auf diesem Gerät kann gerade keine neue Strecke angelegt werden.",
|
||||
"invalid-track": "Diese Datei enthält keinen lesbaren Weg. Wähle eine andere Datei.",
|
||||
"station-location": "Der Ort von {value1} fehlt.",
|
||||
"route-quota": "Der Platz für deine Strecken ist voll.",
|
||||
"save-route": "Deine Strecke konnte nicht gespeichert werden.",
|
||||
"route-cleanup-hint": "{value1} Entferne eigene Strecken, die du nicht mehr brauchst.",
|
||||
"path-missing": "Der Weg deiner Strecke fehlt.",
|
||||
"path-empty": "Deine Strecke hat noch keinen aufgezeichneten Weg.",
|
||||
"route-missing": "Deine Strecke wurde nicht gefunden.",
|
||||
"file-unsupported": "Diese Datei kann auf deinem Gerät nicht gelesen werden.",
|
||||
"file-read": "Diese Datei konnte nicht geöffnet werden. Wähle sie noch einmal aus."
|
||||
},
|
||||
"device": "Auf diesem Gerät"
|
||||
},
|
||||
"offline": {
|
||||
"error": {
|
||||
"download": "Das Speichern für unterwegs hat nicht geklappt. Bitte versuche es noch einmal.",
|
||||
"connection": "Wegwichtel konnte nicht für unterwegs geladen werden. Prüfe deine Internetverbindung.",
|
||||
"prepare": "Wegwichtel konnte nicht für unterwegs geladen werden. Bitte versuche es noch einmal.",
|
||||
"save": "Das Speichern hat nicht geklappt.",
|
||||
"busy": "Bitte warte, bis die andere Strecke gespeichert ist.",
|
||||
"route-unsupported": "Auf diesem Gerät kannst du Strecken gerade nicht für unterwegs speichern.",
|
||||
"select-route": "Wähle zuerst eine Strecke, die für alle freigegeben ist.",
|
||||
"load-route": "Die Strecke konnte nicht geladen werden.",
|
||||
"published-only": "Du kannst nur freigegebene Strecken für unterwegs speichern.",
|
||||
"path-missing": "Der Weg dieser Strecke fehlt. Sie kann noch nicht für unterwegs gespeichert werden.",
|
||||
"wait": "Bitte warte, bis das Speichern fertig ist.",
|
||||
"unsupported": "Auf diesem Gerät lässt sich Wegwichtel gerade nicht für unterwegs speichern. Du kannst es mit Internet weiter nutzen.",
|
||||
"save-app-first": "Speichere zuerst die App. Danach kannst du die Strecken auswählen, die du mitnehmen möchtest.",
|
||||
"previous": {
|
||||
"kept": "{value1} Was du vorher gespeichert hast, bleibt erhalten."
|
||||
}
|
||||
},
|
||||
"preparing": "Wegwichtel wird für unterwegs vorbereitet …",
|
||||
"download-progress": "Für unterwegs speichern: {value1} von {value2}",
|
||||
"ready": "Wegwichtel ist für unterwegs bereit.",
|
||||
"cancelled": "Speichern abgebrochen.",
|
||||
"not-ready": "Wegwichtel ist noch nicht ohne Internet bereit.",
|
||||
"app-removed": "Die gespeicherte App wurde entfernt. Deine Strecken bleiben erhalten.",
|
||||
"renew-route": "Gespeicherte Strecke erneuern",
|
||||
"save-route": "Für unterwegs speichern",
|
||||
"route-ready": "Diese Strecke ist für unterwegs bereit.",
|
||||
"route-hint": "Nimm diese Strecke mit Bildern und Tonaufnahmen mit.",
|
||||
"route-progress": "Strecke speichern: {value1} von {value2}",
|
||||
"open-app": "App ohne Internet öffnen",
|
||||
"open-app-hint": "Damit kannst du Wegwichtel auch ohne Internet öffnen. Wähle danach die Strecken aus, die du mitnehmen möchtest.",
|
||||
"help-title": "Was wird für unterwegs gespeichert?",
|
||||
"help-label": "Hilfe zum Speichern für unterwegs",
|
||||
"help": {
|
||||
"app-only": "Mit diesem Schalter kannst du Wegwichtel auch ohne Internet öffnen. Strecken werden dabei noch nicht gespeichert.",
|
||||
"save-route": "Öffne eine Strecke und tippe auf „Für unterwegs speichern“. So nimmst du den Weg, die Stationen, Bilder und Tonaufnahmen mit.",
|
||||
"connection": "Warte vor dem Losgehen, bis alles gespeichert ist. Zum Speichern und Erneuern brauchst du Internet.",
|
||||
"keep-routes": "Alles bleibt auf diesem Gerät. Der Schalter zeichnet deinen Weg nicht auf. Beim Ausschalten bleiben deine gespeicherten Strecken und Entwürfe erhalten.",
|
||||
"storage-limits": "Wenn du die gespeicherten Daten von Wegwichtel in deinem Internetprogramm löschst, gehen auch deine Strecken verloren. Bei wenig Platz kann das Gerät gespeicherte Inhalte entfernen. Karten von anderen Anbietern werden nicht mitgenommen."
|
||||
},
|
||||
"renew-app": "Gespeicherte App erneuern",
|
||||
"route-controls-label": "Strecke auf dem Gerät speichern",
|
||||
"remove-route": "Gespeicherte Strecke entfernen",
|
||||
"export-path": "Weg als Datei herunterladen",
|
||||
"route-actions-help": "„Für unterwegs speichern“ nimmt diese Strecke mit Bildern und Ton mit. „Weg als Datei herunterladen“ speichert nur den Weg für eine andere Wander-App.",
|
||||
"previous": {
|
||||
"kept": "Was du vorher gespeichert hast, bleibt erhalten."
|
||||
}
|
||||
},
|
||||
"offline-storage": {
|
||||
"error": {
|
||||
"foreign-origin": "Inhalte von anderen Seiten können hier nicht gespeichert werden.",
|
||||
"response": "Ein Teil konnte nicht gespeichert werden. Prüfe deine Internetverbindung und versuche es noch einmal."
|
||||
}
|
||||
},
|
||||
"slideshow": {
|
||||
"image-label": "Bild zur Station",
|
||||
"error": {
|
||||
"load-image": "Bild konnte nicht geladen werden"
|
||||
},
|
||||
"controls-label": "Bildnavigation",
|
||||
"previous": "Vorheriges Bild",
|
||||
"next": "Nächstes Bild"
|
||||
},
|
||||
"storage": {
|
||||
"error": {
|
||||
"estimate": "Der freie Platz konnte nicht gemessen werden.",
|
||||
"local-unavailable": "Texte und Einstellungen können hier gerade nicht gespeichert werden.",
|
||||
"quota": "Der Platz ist voll. Entferne gespeicherte Strecken, Entwürfe, Bilder oder Tonaufnahmen, die du nicht mehr brauchst. Auch bei einem fast leeren ersten Balken kann der Platz für Texte und Einstellungen voll sein."
|
||||
},
|
||||
"site-warning": " Fast voll: Entferne Inhalte, die du nicht mehr brauchst.",
|
||||
"site-usage": "App, Bilder und Ton: {value1} % des verfügbaren Platzes belegt. Ungefähr {value2} frei.{value3}",
|
||||
"local-warning": " Der Balken ist fast voll. Große Bilder können das Speichern verhindern.",
|
||||
"local-usage": "Texte und Einstellungen: ungefähr {value1} belegt. Der Balken zeigt einen Richtwert von etwa 5 MB. Wie viel wirklich hineinpasst, hängt von deinem Internetprogramm ab.{value2}",
|
||||
"title": "Platz für Wegwichtel",
|
||||
"site-meter-label": "Belegter Platz für App, Bilder und Ton",
|
||||
"loading": "Wir schauen, wie viel Platz belegt ist …",
|
||||
"local-meter-label": "Belegter Platz für Texte und Einstellungen; der Balken zeigt einen ungefähren Richtwert",
|
||||
"help": "Der erste Balken zeigt den Platz für die App, Bilder und Ton. Der zweite zeigt den Platz für Texte und Einstellungen. Die Balken zeigen nur den Platz für Wegwichtel.",
|
||||
"refresh": "Platz prüfen"
|
||||
},
|
||||
"errors": {
|
||||
"delete-published": "Diese Strecke kann nur die Person löschen, die Wegwichtel betreut.",
|
||||
"edit-published": "Diese Strecke ist für alle freigegeben. Du kannst sie hier nicht mehr verändern.",
|
||||
"invalid-track": "Die Datei enthält keinen lesbaren Weg. Wähle eine andere Datei.",
|
||||
"station-missing": "Diese Station wurde nicht gefunden. Öffne die Strecke noch einmal.",
|
||||
"station-location": "Der Ort dieser Station fehlt oder konnte nicht gelesen werden.",
|
||||
"image-format": "Dieses Bild kann nicht geöffnet werden. Wähle ein anderes Bild.",
|
||||
"audio-format": "Diese Tonaufnahme kann nicht geöffnet werden. Wähle eine andere Aufnahme.",
|
||||
"file-size": "Diese Datei ist zu groß. Wähle eine kleinere Datei.",
|
||||
"permission": "Du darfst diese Aktion hier nicht ausführen.",
|
||||
"quota": "Der Platz ist voll. Entferne gespeicherte Strecken, Bilder oder Tonaufnahmen, die du nicht mehr brauchst.",
|
||||
"unavailable": "Wegwichtel ist gerade nicht erreichbar. Bitte versuche es später noch einmal."
|
||||
},
|
||||
"vibration": {
|
||||
"error": {
|
||||
"pattern": "Das Vibrationsmuster muss aus nichtnegativen Millisekunden bestehen.",
|
||||
"duration": "Die Vibrationsdauer muss eine nichtnegative Millisekundenangabe sein."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"app-name": "Wegwichtel",
|
||||
"start": "Los geht’s",
|
||||
"retry": "Erneut versuchen",
|
||||
"notice": "Hinweis:",
|
||||
"back": "Zurück"
|
||||
},
|
||||
"start": {
|
||||
"intro": "Gleich kannst du mit dem Wegwichtel losziehen.",
|
||||
"preparing": "Wegwichtel wird vorbereitet …",
|
||||
"features-label": "Wegwichtel Funktionen",
|
||||
"features": {
|
||||
"routes": "Strecken",
|
||||
"explore": "entdecken",
|
||||
"exciting": "Spannende",
|
||||
"stories": "Geschichten",
|
||||
"child-friendly": "Kindgerechte",
|
||||
"experiences": "Erlebnisse"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"no-tracking": "Hier gibt es keine Werbung. Wir verfolgen nicht, welche anderen Seiten du besuchst.",
|
||||
"incomplete-notice": "Diese Angaben sind noch nicht vollständig. Die Kontakte stammen von der Grundschule Herpf. Wer die Website betreibt, welche Verbindungsdaten gespeichert werden und wie lange sie bleiben, muss noch ergänzt werden.",
|
||||
"controller-title": "1. Verantwortliche Stelle und Datenschutzkontakt",
|
||||
"connection-title": "2. Wenn du Wegwichtel öffnest",
|
||||
"connection-data": "Wenn du Wegwichtel öffnest, können Verbindungsdaten verarbeitet werden. Dazu gehören zum Beispiel die Internetadresse deines Geräts, Datum, Uhrzeit und Zeitzone, die aufgerufene Seite, übertragene Daten und ob der Aufruf geklappt hat. Auch die vorherige Seite und Angaben zu deinem Gerät, Internetprogramm und dessen Sprache können dazugehören. Diese Daten helfen dabei, die Website bereitzustellen, Fehler zu finden und sie zu schützen.",
|
||||
"provider-label": "Wer die Website bereitstellt und welche Verbindungsdaten wie lange gespeichert werden:",
|
||||
"provider-placeholder": "[Anbieter, Anschrift, gespeicherte Daten und Speicherdauer ergänzen]",
|
||||
"location-title": "3. Standortdaten",
|
||||
"location-data": "Du entscheidest, ob Wegwichtel deinen Standort nutzen darf. Damit findest du Strecken in deiner Nähe und siehst den Weg zur nächsten Station. Dein laufender Standort wird auf deinem Gerät verarbeitet. Wegwichtel speichert daraus keinen Verlauf auf der Website.",
|
||||
"progress-title": "4. Deinen Fortschritt merken",
|
||||
"progress-data": "Wenn du „Meinen Fortschritt merken“ einschaltest, speichert Wegwichtel deine gewählte Strecke, besuchte und angesehene Stationen sowie angehörte Tonaufnahmen. Auch ob du unterwegs bist, eine Pause machst oder fertig bist und der Zeitpunkt werden gespeichert. Diese Angaben bleiben auf deinem Gerät und werden nicht an die Website gesendet.",
|
||||
"progress-deletion": "Wenn du deinen Fortschritt mindestens einen Tag lang nicht verändert hast, wird er beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine eigenen und für unterwegs gespeicherten Strecken bleiben erhalten.",
|
||||
"drafts-title": "5. Deine eigenen Strecken",
|
||||
"draft-data": "Neue Strecken bleiben zuerst auf deinem Gerät. Gespeichert werden der Weg, die Orte der Stationen, Texte sowie ausgewählte Bilder und Tonaufnahmen.",
|
||||
"draft-deletion": "Deine Entwürfe bleiben erhalten, wenn alter Fortschritt gelöscht wird oder du „Meinen Fortschritt merken“ ausschaltest. Sie bleiben auf diesem Gerät, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Nur wenn du „Zur Prüfung senden“ wählst, wird deine Strecke an die Website gesendet.",
|
||||
"settings-title": "6. Werbung und gespeicherte Einstellungen",
|
||||
"settings-data": "Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.",
|
||||
"children-title": "7. Inhalte von Schülerinnen und Schülern",
|
||||
"children-permission": "Bilder, Texte und Tonaufnahmen dürfen nur geteilt werden, wenn das erlaubt ist. Wenn Kinder auf Bildern oder in Aufnahmen erkannt werden können, müssen die Regeln der Schule beachtet werden. Auch die Eltern oder anderen Sorgeberechtigten müssen die nötige Erlaubnis geben.",
|
||||
"rights-title": "8. Deine Rechte",
|
||||
"rights-data": "Du kannst fragen, welche Daten über dich gespeichert sind. Unter den gesetzlichen Voraussetzungen kannst du verlangen, dass sie berichtigt, gelöscht, nur noch eingeschränkt genutzt oder dir zur Weitergabe bereitgestellt werden. Du kannst der Nutzung widersprechen und eine erteilte Erlaubnis für die Zukunft zurücknehmen. Wenn du dich über den Umgang mit deinen Daten beschweren möchtest, kannst du dich an die folgende Datenschutzbehörde wenden."
|
||||
},
|
||||
"progress": {
|
||||
"cookies-label": "Optionale Cookies",
|
||||
"cookies-unused": "Diese Funktion wird nicht verwendet.",
|
||||
"remember": "Meinen Fortschritt merken",
|
||||
"remember-hint": "Merkt sich auf diesem Gerät, welche Stationen du schon besucht hast.",
|
||||
"choice-hint": "Du entscheidest, ob sich Wegwichtel deinen Fortschritt merkt.",
|
||||
"change-hint": "Die Änderung gilt sofort auf diesem Gerät."
|
||||
},
|
||||
"routes": {
|
||||
"open-menu": "Strecke auswählen oder wechseln",
|
||||
"menu-label": "Strecken auswählen und bearbeiten",
|
||||
"location-hint": "Wir suchen Strecken in deiner Nähe. Dein Standort bleibt auf diesem Gerät.",
|
||||
"nearby": "Strecken in deiner Nähe",
|
||||
"all": "Alle Strecken",
|
||||
"search-label": "Strecken durchsuchen",
|
||||
"search-placeholder": "Name, Schule oder Beschreibung",
|
||||
"clear-search": "Suche leeren",
|
||||
"drafts-hint": "Diese Strecken hast du auf diesem Gerät angelegt. Andere können sie noch nicht sehen.",
|
||||
"loading-drafts": "Meine Entwürfe werden geladen …",
|
||||
"back": "Zurück zur Streckenauswahl"
|
||||
},
|
||||
"route": {
|
||||
"length": "Länge",
|
||||
"elevation": "Höhenmeter",
|
||||
"station-count": "Stationen",
|
||||
"controls-label": "Unterwegs",
|
||||
"pause": "Strecke pausieren",
|
||||
"stop": "Strecke beenden",
|
||||
"back": "Zurück zur Strecke"
|
||||
},
|
||||
"station": {
|
||||
"title": "Station"
|
||||
},
|
||||
"audio": {
|
||||
"station-label": "Ton zur Station",
|
||||
"unsupported": "Dieses Gerät kann den Ton leider nicht abspielen."
|
||||
},
|
||||
"navigation": {
|
||||
"station-direction": "Richtung zum nächsten Station"
|
||||
},
|
||||
"contacts": {
|
||||
"school": {
|
||||
"address": "Zum Eichig 3",
|
||||
"city": "98617 Meiningen / OT Herpf",
|
||||
"phone": "Telefon: (036943) 63722",
|
||||
"email": "E-Mail: info@grundschule-herpf.de",
|
||||
"name": "Staatliche Grundschule Herpf",
|
||||
"privacy-label": "Datenschutzangaben der Schule:",
|
||||
"fax": "Telefax: (036943) 63843",
|
||||
"head": "Frau Wichert, Schulleiterin",
|
||||
"content-owner": "Frau Wichert"
|
||||
},
|
||||
"privacy": {
|
||||
"role": "Datenschutzbeauftragter",
|
||||
"address": "Hölderlinstraße 1",
|
||||
"city": "98527 Suhl",
|
||||
"phone": "Telefon: 03681 / 7341 25",
|
||||
"fax": "Fax: 03681 / 7341 09",
|
||||
"email": "E-Mail: datenschutz.suedthueriungen@schulamt.thueringen.de",
|
||||
"name": "Staatliches Schulamt Südthüringen",
|
||||
"postal-hint": "Der Datenschutzbeauftragte kann außerdem über die Postadresse der Schule mit dem Zusatz „der Datenschutzbeauftragte“ erreicht werden."
|
||||
},
|
||||
"authority": {
|
||||
"label": "Zuständige Datenschutzaufsicht:",
|
||||
"postal-address": "Postfach 90 04 55, 99107 Erfurt",
|
||||
"address": "Häßlerstraße 8, 99096 Erfurt",
|
||||
"phone": "Telefon: 0361 / 57 311 29 00",
|
||||
"fax": "Telefax: 0361 / 57 311 29 04",
|
||||
"email": "E-Mail: poststelle@datenschutz.thueringen.de",
|
||||
"website": "Website: www.tlfdi.de",
|
||||
"name": "Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)"
|
||||
},
|
||||
"sponsor": {
|
||||
"role": "Landratsamt",
|
||||
"address": "Obertshäuser Platz 1",
|
||||
"city": "98617 Meiningen",
|
||||
"name": "Landkreis Schmalkalden-Meiningen"
|
||||
}
|
||||
},
|
||||
"imprint": {
|
||||
"incomplete-notice": "Diese Angaben stammen von der Grundschule Herpf. Vor der Veröffentlichung muss geprüft werden, ob für Wegwichtel noch weitere oder andere Angaben nötig sind.",
|
||||
"controller-title": "Wer für Wegwichtel verantwortlich ist",
|
||||
"contact-title": "Kontakt",
|
||||
"head-title": "Leitung der Schule",
|
||||
"sponsor-title": "Schulträger",
|
||||
"content-title": "Inhaltlich verantwortlich",
|
||||
"required-title": "Weitere Pflichtangaben",
|
||||
"required-data": "Je nachdem, wer Wegwichtel betreibt, können weitere Angaben nötig sein. Dazu gehören zum Beispiel die Verantwortlichen für den Betrieb der Website. Diese Angaben müssen noch geprüft werden.",
|
||||
"liability-title": "Haftung für Inhalte und Verweise",
|
||||
"liability-data": "Die Inhalte werden sorgfältig erstellt und regelmäßig geprüft. Falls etwas falsch, veraltet oder nicht erlaubt ist, melde dich beim Kontakt oben. Für andere verlinkte Seiten sind deren Betreiber verantwortlich.",
|
||||
"copyright-title": "Urheber- und Nutzungsrechte",
|
||||
"copyright-data": "Wenn nichts anderes angegeben ist, liegen die Rechte bei der Grundschule Herpf. Du darfst Texte, Bilder, Tonaufnahmen und Wege nur so nutzen, wie es jeweils erlaubt wurde. Auch die Rechte der beteiligten Kinder, Familien, Lehrkräfte und anderen Personen bleiben bestehen."
|
||||
},
|
||||
"storage-help": {
|
||||
"progress-title": "Was merkt sich Wegwichtel?",
|
||||
"progress-data": "Du kannst deinen Fortschritt auf diesem Gerät speichern. Dann weiß Wegwichtel beim nächsten Öffnen, welche Stationen du schon besucht oder angesehen hast und welche Tonaufnahmen du angehört hast.",
|
||||
"progress-deletion": "Wenn du einen Tag lang nichts mehr daran geändert hast, wird dein Fortschritt beim nächsten Öffnen gelöscht. Beim Ausschalten wird er sofort gelöscht. Deine selbst angelegten und für unterwegs gespeicherten Strecken bleiben erhalten.",
|
||||
"offline-title": "Ohne Internet unterwegs",
|
||||
"offline-data": "Mit „App ohne Internet öffnen“ speicherst du Wegwichtel auf diesem Gerät. Danach kannst du einzelne Strecken mit „Für unterwegs speichern“ mitnehmen. Warte vor dem Losgehen, bis alles gespeichert ist.",
|
||||
"export-data": "Mit „Weg als Datei herunterladen“ kannst du den Weg in einer anderen Wander-App öffnen. Bilder und Tonaufnahmen sind in dieser Datei nicht enthalten.",
|
||||
"drafts-title": "Deine eigenen Strecken",
|
||||
"drafts-data": "Neue Strecken bleiben zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ reichst du eine Strecke zur Freigabe ein. Erst nach der Freigabe können andere sie sehen.",
|
||||
"drafts-deletion": "Deine Entwürfe bleiben gespeichert, bis du sie löschst oder die gespeicherten Daten von Wegwichtel in deinem Internetprogramm entfernst. Viele große Bilder brauchen viel Platz. Die Anzeige „Platz für Wegwichtel“ hilft dir, den belegten Platz im Blick zu behalten.",
|
||||
"permissions-title": "Welche Erlaubnisse braucht die App?",
|
||||
"permissions": {
|
||||
"location": "Deinen Standort, um Strecken in deiner Nähe und den Weg zur nächsten Station zu zeigen.",
|
||||
"direction": "Die Richtung deines Geräts, damit sich der Pfeil mitdrehen kann.",
|
||||
"files": "Zugriff auf ausgewählte Bilder und Tonaufnahmen, wenn du sie zu einer Strecke hinzufügen möchtest.",
|
||||
"optional": "Du kannst diese Erlaubnisse ablehnen. Dann fehlen die jeweiligen Funktionen. Strecken und Stationen kannst du weiterhin selbst auswählen."
|
||||
},
|
||||
"providers-title": "Werbung und andere Anbieter",
|
||||
"providers-data": "Wegwichtel zeigt keine Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Die App lädt ihre eigenen Programme und Bilder von Wegwichtel. Es werden keine Cookies verwendet."
|
||||
},
|
||||
"media": {
|
||||
"audio": {
|
||||
"count": {
|
||||
"one": "{count} Tonaufnahme",
|
||||
"other": "{count} Tonaufnahmen"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"count": {
|
||||
"one": "{count} Bild",
|
||||
"other": "{count} Bilder"
|
||||
}
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"read": {
|
||||
"failed": "Die Datei konnte nicht geöffnet werden. Wähle sie noch einmal aus."
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ns.TextsDe = WegwichtelTextsDe;
|
||||
ns.Texts = WegwichtelTexts;
|
||||
ns.TextCatalogs ??= new Map();
|
||||
ns.TextCatalogs.set(WegwichtelTexts.language, WegwichtelTexts.messages);
|
||||
}(globalThis.Wegwichtel));
|
||||
|
||||
@@ -0,0 +1,532 @@
|
||||
globalThis.Wegwichtel ??= {};
|
||||
(function (ns) {
|
||||
'use strict';
|
||||
|
||||
class WegwichtelTexts {
|
||||
static language = 'en';
|
||||
static messages = Object.freeze(
|
||||
{
|
||||
"app": {
|
||||
"resume-hint": "You can carry on where you left off.",
|
||||
"draft": "My draft",
|
||||
"choose-route": "Choose a trail",
|
||||
"route-menu": "Public trails and my drafts",
|
||||
"route": "Trail",
|
||||
"stations-complete": "All stops reached",
|
||||
"no-stations": "No stops yet",
|
||||
"next-station-summary": "Next stop: {value1}{value2}",
|
||||
"published": "Published",
|
||||
"public-route-count": "Public trails ({value1})",
|
||||
"draft-count": "My drafts ({value1})",
|
||||
"no-public-route": "There are no public trails yet.",
|
||||
"no-route": "You have not created a trail of your own yet.",
|
||||
"no-nearby-route": "No trail found within {value1} km. Choose any trail below.",
|
||||
"location-permission-hint": "Allow location access to find trails near you. You can choose from all trails below.",
|
||||
"no-search-result": "No matching trail found. Try a different word.",
|
||||
"search-result-count": "{value1} of {value2} trails found.",
|
||||
"route-count": "{value1} trails to choose from.",
|
||||
"resume-route": "Continue trail",
|
||||
"start-route": "Start trail",
|
||||
"route-finished": "Trail finished.",
|
||||
"pause-hint": "You are taking a break. The app is not checking your location right now.",
|
||||
"route-paused": "Trail paused",
|
||||
"empty-route": "This trail has no stops yet.",
|
||||
"next-station-north": "North at the top · next stop: {value1}",
|
||||
"next-station": "Next stop: {value1}",
|
||||
"next-station-distance": "Next stop {value1}, {value2} metres away",
|
||||
"destination-reached": "All stops and the finish reached",
|
||||
"next-path-point-north": "North at the top · next point on the path",
|
||||
"next-path-point": "Next point on the path",
|
||||
"next-path-point-distance": "Next point on the path, {value1} metres away",
|
||||
"location-accuracy": "Finding your location · accurate to about {value1} m",
|
||||
"nearest-station-distance": " · next stop {value1} m away",
|
||||
"stations-complete-suffix": " · all stops reached",
|
||||
"resuming": "Continuing your trail.",
|
||||
"resuming-title": "Continuing your trail",
|
||||
"error": {
|
||||
"location": "We could not find your location. Check that location access is allowed on your device.",
|
||||
"load-routes": "The trails could not be loaded. Please try again.",
|
||||
"load-drafts": "Your drafts could not be opened. Please try again.",
|
||||
"open-route": "The trail could not be opened. Check your connection and try again.",
|
||||
"open-draft": "The trail could not be opened. Please try again."
|
||||
},
|
||||
"navigation-north": "North at the top · directions",
|
||||
"location-found": "Your location was found with an accuracy of about {value1} m. ",
|
||||
"search-radius": "We show trails within {value1} km.",
|
||||
"location-optional": "We could not find your location. You can still choose from all trails.",
|
||||
"page": {
|
||||
"missing": "This page could not be opened."
|
||||
},
|
||||
"location": {
|
||||
"searching": "Finding your location",
|
||||
"start": "Let’s go! We are finding your location."
|
||||
},
|
||||
"distance": {
|
||||
"spoken": "{value1} metres away",
|
||||
"short": "{value1} m away"
|
||||
},
|
||||
"path": {
|
||||
"missing": "This trail has no path."
|
||||
}
|
||||
},
|
||||
"bootstrap-loader": {
|
||||
"steps": {
|
||||
"storage": "Checking saved content …",
|
||||
"jquery": "Getting Wegwichtel ready …",
|
||||
"jquery-ui": "Getting the controls ready …",
|
||||
"modules": "Getting the app ready …",
|
||||
"server": "Checking the connection …",
|
||||
"routes": "Loading trails …",
|
||||
"location": "Finding your location …"
|
||||
},
|
||||
"hints": {
|
||||
"chrome": "You can choose pictures and recordings directly on this device.",
|
||||
"android": "Tip for Android: Chrome lets you choose pictures and recordings without saving them again."
|
||||
},
|
||||
"error": {
|
||||
"start-error": "Part of the app could not start. Please try again.",
|
||||
"load-error": "Part of the app could not be loaded. Check your internet connection and try again.",
|
||||
"connection-error": "Wegwichtel could not start. Check your connection and try again.",
|
||||
"init-error": "Wegwichtel could not start. Please try again.",
|
||||
"step-error": "That did not work: {value1}"
|
||||
},
|
||||
"cleanup-progress": "Removing old progress ({value1} / {value2})",
|
||||
"ready": "Everything is ready."
|
||||
},
|
||||
"editor": {
|
||||
"error": {
|
||||
"add-station": "Another stop cannot be added to this trail right now.",
|
||||
"action": "This action could not be completed. Please try again.",
|
||||
"draft-missing": "Your draft was not found.",
|
||||
"create-locally": "Create a new trail on this device first.",
|
||||
"route-name": "Give your trail a name.",
|
||||
"send-file": "The file {value1} could not be sent. Please choose it again.",
|
||||
"send-route": "The trail could not be sent. Please try again.",
|
||||
"send-station": "The stop {value1} could not be sent. Please try again.",
|
||||
"submit-own-draft": "You can only send your own drafts for review.",
|
||||
"already-submitted": "This trail has already been sent for review.",
|
||||
"location-required": "We need your location to add a new stop. Allow location access and try again.",
|
||||
"save-route-first": "Save the trail first.",
|
||||
"station-location": "This stop has no location. Allow location access and try again.",
|
||||
"station-name": "Give your stop a name.",
|
||||
"station-unsaved": "This stop has not been saved yet.",
|
||||
"remove-image": "The picture could not be removed. Open the trail again.",
|
||||
"generic": "That did not work. Please try again."
|
||||
},
|
||||
"untitled-route": "Untitled trail",
|
||||
"untitled-station": "Untitled stop",
|
||||
"edit-draft": "Edit my draft",
|
||||
"new-route": "New trail",
|
||||
"view-route": "View trail",
|
||||
"edit-station": "Edit stop",
|
||||
"add-station": "Add stop",
|
||||
"published-route": "Published trail",
|
||||
"edit-route": "Edit trail",
|
||||
"delete-route": "Delete trail",
|
||||
"station-number": "Stop {value1}",
|
||||
"delete-item": "Delete {value1}",
|
||||
"public-routes": "Public trails",
|
||||
"drafts": "My drafts",
|
||||
"no-draft": "There are no drafts yet.",
|
||||
"no-stations": "There are no stops yet.",
|
||||
"loading-routes": "Loading trails …",
|
||||
"submitted": "Already sent for review",
|
||||
"submit": "Send for review",
|
||||
"submitted-hint": "This trail has been sent for review. Others can see it once it has been approved.",
|
||||
"draft-hint": "This draft stays on your device. Choose “Send for review” to submit it for approval.",
|
||||
"read-only-hint": "This trail is available to everyone and can no longer be changed here.",
|
||||
"unpublished-hint": "This trail has not been made available to everyone yet.",
|
||||
"remove-media-label": "{value1}: remove button {value2}",
|
||||
"new-audio": "New recording",
|
||||
"image-number": "Picture {value1}",
|
||||
"new-image": "New picture {value1}",
|
||||
"remove-media-hint": "{value1}. Tap a picture or recording to remove it.",
|
||||
"no-media": "There are no pictures or recordings yet.",
|
||||
"audio-section": "Recordings",
|
||||
"image-section": "Pictures",
|
||||
"draft-status": "Your new trail stays on this device for now.",
|
||||
"saved": "Saved.",
|
||||
"submitting": "Sending your trail …",
|
||||
"submit-success": "Sent for review.",
|
||||
"station-saved-with-media": "Stop saved on this device with pictures and recordings.",
|
||||
"station-saved": "Stop saved.",
|
||||
"image-removed": "Picture removed.",
|
||||
"station-removed-and-saved": "Stop removed. Your trail has been saved.",
|
||||
"station-removed": "Stop removed.",
|
||||
"station-order-saved": "Stop order saved.",
|
||||
"route-deleted": "Trail deleted.",
|
||||
"open": "Edit trails",
|
||||
"close": "Close the editing window",
|
||||
"routes-title": "Your trails",
|
||||
"create-route": "Create a new trail",
|
||||
"routes-hint": "Here you can find public trails and your own drafts.",
|
||||
"route-title": "Your trail",
|
||||
"route-name": "Trail name",
|
||||
"description": "Description",
|
||||
"import-path": "Import a path from a file",
|
||||
"new-draft-hint": "Your new trail stays on this device for now. Choose “Send for review” to submit it for approval.",
|
||||
"save-route": "Save trail",
|
||||
"station-name": "Name",
|
||||
"station-text": "Text",
|
||||
"media-actions-label": "Add pictures and recordings to this stop",
|
||||
"add-audio": "Add recording",
|
||||
"add-image": "Add picture",
|
||||
"media-preview-label": "View pictures and recordings",
|
||||
"save-station": "Save stop",
|
||||
"remove-station": "Remove stop",
|
||||
"station": {
|
||||
"up": "Move {value1} up",
|
||||
"down": "Move {value1} down"
|
||||
},
|
||||
"route": {
|
||||
"loading": "Opening your trail …"
|
||||
},
|
||||
"audio": {
|
||||
"removed": "Recording removed."
|
||||
},
|
||||
"order": {
|
||||
"saved": "Order saved."
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"error": {
|
||||
"unsupported": "This device cannot find your location."
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"wood": "Wood",
|
||||
"rock": "Rock",
|
||||
"lantern": "Lantern",
|
||||
"flower": "Flower",
|
||||
"pipe": "Pipe",
|
||||
"bird": "Bird",
|
||||
"mushroom": "Mushroom",
|
||||
"strawberry": "Strawberry",
|
||||
"pumpkin": "Pumpkin"
|
||||
},
|
||||
"info": {
|
||||
"privacy": "Privacy notice",
|
||||
"imprint": "Legal notice",
|
||||
"storage": "Saving and permissions",
|
||||
"open": "Open help and information",
|
||||
"title": "Information",
|
||||
"menu-label": "Legal information and app details",
|
||||
"back": "Back to the information menu",
|
||||
"close-label": "Close the information window",
|
||||
"close": "Close",
|
||||
"icon": "i",
|
||||
"dropdown-label": "Help and information",
|
||||
"missing": "{value1}: This information still needs to be added."
|
||||
},
|
||||
"local": {
|
||||
"error": {
|
||||
"route-storage": "Your trail cannot be saved on this device right now.",
|
||||
"media-storage": "Pictures and recordings cannot be saved on this device right now.",
|
||||
"media-read": "Saved pictures and recordings could not be opened.",
|
||||
"other-tabs": "Close other Wegwichtel windows and try again.",
|
||||
"media-access": "Pictures and recordings could not be opened or saved.",
|
||||
"media-cancelled": "Saving pictures and recordings was cancelled.",
|
||||
"media-quota-hint": "There is no room for more pictures or recordings. Remove pictures, recordings or your own trails that you no longer need.",
|
||||
"media-quota": "There is no room for more pictures or recordings.",
|
||||
"media-save": "The picture or recording could not be saved.",
|
||||
"media-cleanup-hint": "{value1} Remove pictures, recordings or your own trails that you no longer need.",
|
||||
"file-permission": "Allow access to the selected picture or recording again.",
|
||||
"file-missing": "A selected picture or recording was moved or deleted. Choose the file again.",
|
||||
"create-route": "A new trail cannot be created on this device right now.",
|
||||
"invalid-track": "This file does not contain a readable path. Choose another file.",
|
||||
"station-location": "The location of {value1} is missing.",
|
||||
"route-quota": "There is no room for more trails.",
|
||||
"save-route": "Your trail could not be saved.",
|
||||
"route-cleanup-hint": "{value1} Remove your own trails that you no longer need.",
|
||||
"path-missing": "Your trail has no path.",
|
||||
"path-empty": "Your trail has no recorded path yet.",
|
||||
"route-missing": "Your trail was not found.",
|
||||
"file-unsupported": "This file cannot be read on your device.",
|
||||
"file-read": "This file could not be opened. Choose it again."
|
||||
},
|
||||
"device": "On this device"
|
||||
},
|
||||
"offline": {
|
||||
"error": {
|
||||
"download": "Saving for your trip did not work. Please try again.",
|
||||
"connection": "Wegwichtel could not be saved for your trip. Check your internet connection.",
|
||||
"prepare": "Wegwichtel could not be saved for your trip. Please try again.",
|
||||
"save": "Saving did not work.",
|
||||
"busy": "Please wait until the other trail has been saved.",
|
||||
"route-unsupported": "Trails cannot be saved for your trip on this device right now.",
|
||||
"select-route": "Choose a trail that is available to everyone first.",
|
||||
"load-route": "The trail could not be loaded.",
|
||||
"published-only": "You can only save approved trails for your trip.",
|
||||
"path-missing": "This trail has no path. It cannot be saved for your trip yet.",
|
||||
"wait": "Please wait until saving is finished.",
|
||||
"unsupported": "Wegwichtel cannot be saved for your trip on this device right now. You can still use it with internet.",
|
||||
"save-app-first": "Save the app first. Then choose the trails you want to take with you.",
|
||||
"previous": {
|
||||
"kept": "{value1} Your previously saved content is kept."
|
||||
}
|
||||
},
|
||||
"preparing": "Getting Wegwichtel ready for your trip …",
|
||||
"download-progress": "Saving for your trip: {value1} of {value2}",
|
||||
"ready": "Wegwichtel is ready for your trip.",
|
||||
"cancelled": "Saving cancelled.",
|
||||
"not-ready": "Wegwichtel is not ready to use without internet yet.",
|
||||
"app-removed": "The saved app has been removed. Your trails are still saved.",
|
||||
"renew-route": "Update saved trail",
|
||||
"save-route": "Save for my trip",
|
||||
"route-ready": "This trail is ready for your trip.",
|
||||
"route-hint": "Take this trail with its pictures and recordings on your trip.",
|
||||
"route-progress": "Saving trail: {value1} of {value2}",
|
||||
"open-app": "Open the app without internet",
|
||||
"open-app-hint": "Open Wegwichtel without internet. Then choose the trails you want to take with you.",
|
||||
"help-title": "What is saved for my trip?",
|
||||
"help-label": "Help with saving for your trip",
|
||||
"help": {
|
||||
"app-only": "This switch lets you open Wegwichtel without internet. It does not save any trails yet.",
|
||||
"save-route": "Open a trail and tap “Save for my trip”. This saves the path, stops, pictures and recordings for your trip.",
|
||||
"connection": "Wait until everything is saved before you set off. You need internet to save and update content.",
|
||||
"keep-routes": "Everything stays on this device. This switch does not record your path. Turning it off keeps your saved trails and drafts.",
|
||||
"storage-limits": "If you clear Wegwichtel’s saved data in your internet app, your trails will be lost too. Your device may remove saved content when space is low. Maps from other providers are not saved for your trip."
|
||||
},
|
||||
"renew-app": "Update saved app",
|
||||
"route-controls-label": "Save this trail on your device",
|
||||
"remove-route": "Remove saved trail",
|
||||
"export-path": "Download the path as a file",
|
||||
"route-actions-help": "“Save for my trip” saves this trail with pictures and recordings. “Download the path as a file” saves only the path for another walking app.",
|
||||
"previous": {
|
||||
"kept": "Your previously saved content is kept."
|
||||
}
|
||||
},
|
||||
"offline-storage": {
|
||||
"error": {
|
||||
"foreign-origin": "Content from other websites cannot be saved here.",
|
||||
"response": "Some content could not be saved. Check your internet connection and try again."
|
||||
}
|
||||
},
|
||||
"slideshow": {
|
||||
"image-label": "Picture for this stop",
|
||||
"error": {
|
||||
"load-image": "Picture could not be loaded"
|
||||
},
|
||||
"controls-label": "Picture controls",
|
||||
"previous": "Previous picture",
|
||||
"next": "Next picture"
|
||||
},
|
||||
"storage": {
|
||||
"error": {
|
||||
"estimate": "The free space could not be measured.",
|
||||
"local-unavailable": "Text and settings cannot be saved here right now.",
|
||||
"quota": "There is no space left. Remove saved trails, drafts, pictures or recordings that you no longer need. Even when the first bar is nearly empty, the space for text and settings may be full."
|
||||
},
|
||||
"site-warning": " Almost full: Remove content that you no longer need.",
|
||||
"site-usage": "App, pictures and recordings: {value1}% of the available space used. About {value2} free.{value3}",
|
||||
"local-warning": " The bar is almost full. Large pictures may prevent saving.",
|
||||
"local-usage": "Text and settings: about {value1} used. The bar shows a guide of about 5 MB. How much it can actually hold depends on your internet app.{value2}",
|
||||
"title": "Space for Wegwichtel",
|
||||
"site-meter-label": "Space used by the app, pictures and recordings",
|
||||
"loading": "Checking how much space is used …",
|
||||
"local-meter-label": "Space used by text and settings; the bar shows a rough guide",
|
||||
"help": "The first bar shows the space for the app, pictures and recordings. The second shows the space for text and settings. The bars only show space used by Wegwichtel.",
|
||||
"refresh": "Check space"
|
||||
},
|
||||
"errors": {
|
||||
"delete-published": "Only the person who looks after Wegwichtel can delete this trail.",
|
||||
"edit-published": "This trail is available to everyone. You can no longer change it here.",
|
||||
"invalid-track": "The file does not contain a readable path. Choose another file.",
|
||||
"station-missing": "This stop was not found. Open the trail again.",
|
||||
"station-location": "This stop has no location, or its location could not be read.",
|
||||
"image-format": "This picture cannot be opened. Choose another picture.",
|
||||
"audio-format": "This recording cannot be opened. Choose another recording.",
|
||||
"file-size": "This file is too large. Choose a smaller file.",
|
||||
"permission": "You are not allowed to do this here.",
|
||||
"quota": "There is no space left. Remove saved trails, pictures or recordings that you no longer need.",
|
||||
"unavailable": "Wegwichtel cannot be reached right now. Please try again later."
|
||||
},
|
||||
"vibration": {
|
||||
"error": {
|
||||
"pattern": "The vibration pattern must contain durations of zero milliseconds or more.",
|
||||
"duration": "The vibration duration must be zero milliseconds or more."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"app-name": "Wegwichtel",
|
||||
"start": "Let’s go",
|
||||
"retry": "Try again",
|
||||
"notice": "Please note:",
|
||||
"back": "Back"
|
||||
},
|
||||
"start": {
|
||||
"intro": "You will soon be ready to explore with Wegwichtel.",
|
||||
"preparing": "Getting Wegwichtel ready …",
|
||||
"features-label": "Wegwichtel features",
|
||||
"features": {
|
||||
"routes": "Trails",
|
||||
"explore": "Explore",
|
||||
"exciting": "Exciting",
|
||||
"stories": "Stories",
|
||||
"child-friendly": "Child-friendly",
|
||||
"experiences": "Adventures"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"no-tracking": "There are no adverts here. We do not track which other websites you visit.",
|
||||
"incomplete-notice": "This information is not complete yet. The contact details are from Grundschule Herpf. The website operator, the connection data stored and how long it is kept still need to be added.",
|
||||
"controller-title": "1. Who is responsible and the privacy contact",
|
||||
"connection-title": "2. When you open Wegwichtel",
|
||||
"connection-data": "When you open Wegwichtel, connection data may be processed. This may include your device’s internet address, the date, time and time zone, the page you opened, data transferred and whether the request worked. It may also include the previous page and details of your device, internet app and its language. This data helps to provide the website, find problems and keep it safe.",
|
||||
"provider-label": "Who provides the website, which connection data is stored and how long it is kept:",
|
||||
"provider-placeholder": "[Add provider, address, stored data and retention period]",
|
||||
"location-title": "3. Location data",
|
||||
"location-data": "You decide whether Wegwichtel may use your location. This helps you find trails nearby and see the way to the next stop. Your current location is processed on your device. Wegwichtel does not save a history of your movements on the website.",
|
||||
"progress-title": "4. Remembering your progress",
|
||||
"progress-data": "When you turn on “Remember my progress”, Wegwichtel saves your selected trail, stops you have reached or viewed and recordings you have listened to. It also saves whether you are on the trail, taking a break or finished, and the time. This information stays on your device and is not sent to the website.",
|
||||
"progress-deletion": "If your progress has not changed for at least one day, it is deleted the next time you open the app. Turning the switch off deletes it straight away. Your own trails and trails saved for your trip are kept.",
|
||||
"drafts-title": "5. Your own trails",
|
||||
"draft-data": "New trails stay on your device for now. The path, stop locations, text and selected pictures and recordings are saved.",
|
||||
"draft-deletion": "Your drafts are kept when old progress is deleted or you turn off “Remember my progress”. They stay on this device until you delete them or clear Wegwichtel’s saved data in your internet app. Your trail is only sent to the website when you choose “Send for review”.",
|
||||
"settings-title": "6. Adverts and saved settings",
|
||||
"settings-data": "Wegwichtel does not use cookies. It does not count visits for advertising or track which other websites you visit. Your device saves whether you have turned on “Remember my progress”.",
|
||||
"children-title": "7. Content from schoolchildren",
|
||||
"children-permission": "Pictures, text and recordings may only be shared when permission has been given. If children can be recognised in pictures or recordings, the school’s rules must be followed. Parents or other people with parental responsibility must also give the necessary permission.",
|
||||
"rights-title": "8. Your rights",
|
||||
"rights-data": "You can ask what data about you is stored. Where the law allows, you can ask for it to be corrected, deleted, used only in a limited way or provided to you so you can pass it on. You can object to its use and withdraw permission for future use. If you want to complain about how your data is handled, you can contact the data protection authority below."
|
||||
},
|
||||
"progress": {
|
||||
"cookies-label": "Optional cookies",
|
||||
"cookies-unused": "This feature is not used.",
|
||||
"remember": "Remember my progress",
|
||||
"remember-hint": "Remembers which stops you have already visited on this device.",
|
||||
"choice-hint": "You choose whether Wegwichtel remembers your progress.",
|
||||
"change-hint": "The change takes effect straight away on this device."
|
||||
},
|
||||
"routes": {
|
||||
"open-menu": "Choose or switch trails",
|
||||
"menu-label": "Choose and edit trails",
|
||||
"location-hint": "Finding trails near you. Your location stays on this device.",
|
||||
"nearby": "Trails near you",
|
||||
"all": "All trails",
|
||||
"search-label": "Search trails",
|
||||
"search-placeholder": "Name, school or description",
|
||||
"clear-search": "Clear search",
|
||||
"drafts-hint": "You created these trails on this device. Others cannot see them yet.",
|
||||
"loading-drafts": "Loading my drafts …",
|
||||
"back": "Back to trail selection"
|
||||
},
|
||||
"route": {
|
||||
"length": "Length",
|
||||
"elevation": "Elevation gain",
|
||||
"station-count": "Stops",
|
||||
"controls-label": "On your trail",
|
||||
"pause": "Pause trail",
|
||||
"stop": "Finish trail",
|
||||
"back": "Back to the trail"
|
||||
},
|
||||
"station": {
|
||||
"title": "Stop"
|
||||
},
|
||||
"audio": {
|
||||
"station-label": "Recording for this stop",
|
||||
"unsupported": "This device cannot play the recording."
|
||||
},
|
||||
"navigation": {
|
||||
"station-direction": "Direction to the next stop"
|
||||
},
|
||||
"contacts": {
|
||||
"school": {
|
||||
"address": "Zum Eichig 3",
|
||||
"city": "98617 Meiningen / OT Herpf",
|
||||
"phone": "Phone: (036943) 63722",
|
||||
"email": "Email: info@grundschule-herpf.de",
|
||||
"name": "Staatliche Grundschule Herpf",
|
||||
"privacy-label": "The school’s privacy contact:",
|
||||
"fax": "Fax: (036943) 63843",
|
||||
"head": "Ms Wichert, headteacher",
|
||||
"content-owner": "Ms Wichert"
|
||||
},
|
||||
"privacy": {
|
||||
"role": "Data protection officer",
|
||||
"address": "Hölderlinstraße 1",
|
||||
"city": "98527 Suhl",
|
||||
"phone": "Phone: 03681 / 7341 25",
|
||||
"fax": "Fax: 03681 / 7341 09",
|
||||
"email": "Email: datenschutz.suedthueriungen@schulamt.thueringen.de",
|
||||
"name": "Staatliches Schulamt Südthüringen",
|
||||
"postal-hint": "You can also contact the data protection officer by writing to the school’s postal address and adding “der Datenschutzbeauftragte”."
|
||||
},
|
||||
"authority": {
|
||||
"label": "Responsible data protection authority:",
|
||||
"postal-address": "Postfach 90 04 55, 99107 Erfurt",
|
||||
"address": "Häßlerstraße 8, 99096 Erfurt",
|
||||
"phone": "Phone: 0361 / 57 311 29 00",
|
||||
"fax": "Fax: 0361 / 57 311 29 04",
|
||||
"email": "Email: poststelle@datenschutz.thueringen.de",
|
||||
"website": "Website: www.tlfdi.de",
|
||||
"name": "Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)"
|
||||
},
|
||||
"sponsor": {
|
||||
"role": "District administration",
|
||||
"address": "Obertshäuser Platz 1",
|
||||
"city": "98617 Meiningen",
|
||||
"name": "Landkreis Schmalkalden-Meiningen"
|
||||
}
|
||||
},
|
||||
"imprint": {
|
||||
"incomplete-notice": "This information comes from Grundschule Herpf. Before publication, check whether Wegwichtel needs any additional or different information.",
|
||||
"controller-title": "Who is responsible for Wegwichtel",
|
||||
"contact-title": "Contact",
|
||||
"head-title": "School leadership",
|
||||
"sponsor-title": "School authority",
|
||||
"content-title": "Responsible for content",
|
||||
"required-title": "Further required information",
|
||||
"required-data": "Depending on who operates Wegwichtel, further information may be required. This may include the people responsible for operating the website. These details still need to be checked.",
|
||||
"liability-title": "Responsibility for content and links",
|
||||
"liability-data": "The content is prepared carefully and checked regularly. If something is wrong, out of date or not allowed, please use the contact details above to let us know. Other website operators are responsible for their own linked pages.",
|
||||
"copyright-title": "Copyright and rights of use",
|
||||
"copyright-data": "Unless stated otherwise, the rights belong to Grundschule Herpf. You may only use text, pictures, recordings and paths in the ways permitted for each item. The rights of the children, families, teachers and other people involved remain in place."
|
||||
},
|
||||
"storage-help": {
|
||||
"progress-title": "What does Wegwichtel remember?",
|
||||
"progress-data": "You can save your progress on this device. Next time you open Wegwichtel, it will know which stops you have visited or viewed and which recordings you have listened to.",
|
||||
"progress-deletion": "If nothing has changed for one day, your progress is deleted the next time you open the app. Turning the switch off deletes it straight away. Your own trails and trails saved for your trip are kept.",
|
||||
"offline-title": "Out and about without internet",
|
||||
"offline-data": "“Open the app without internet” saves Wegwichtel on this device. You can then take individual trails with you using “Save for my trip”. Wait until everything is saved before you set off.",
|
||||
"export-data": "“Download the path as a file” lets you open the path in another walking app. Pictures and recordings are not included in this file.",
|
||||
"drafts-title": "Your own trails",
|
||||
"drafts-data": "New trails stay on this device for now. Choose “Send for review” to submit a trail for approval. Others can see it once it has been approved.",
|
||||
"drafts-deletion": "Your drafts are saved until you delete them or clear Wegwichtel’s saved data in your internet app. Lots of large pictures take up space. The “Space for Wegwichtel” display helps you keep an eye on the space used.",
|
||||
"permissions-title": "Which permissions does the app need?",
|
||||
"permissions": {
|
||||
"location": "Your location, to show trails near you and the way to the next stop.",
|
||||
"direction": "The direction your device is facing, so the arrow can turn with it.",
|
||||
"files": "Access to selected pictures and recordings when you want to add them to a trail.",
|
||||
"optional": "You can refuse these permissions. The matching features will then be unavailable. You can still choose trails and stops yourself."
|
||||
},
|
||||
"providers-title": "Adverts and other providers",
|
||||
"providers-data": "Wegwichtel does not show adverts or track which other websites you visit. The app loads its own programs and pictures from Wegwichtel. No cookies are used."
|
||||
},
|
||||
"media": {
|
||||
"audio": {
|
||||
"count": {
|
||||
"one": "{count} recording",
|
||||
"other": "{count} recordings"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"count": {
|
||||
"one": "{count} picture",
|
||||
"other": "{count} pictures"
|
||||
}
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"read": {
|
||||
"failed": "The file could not be opened. Choose it again."
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ns.Texts = WegwichtelTexts;
|
||||
ns.TextCatalogs ??= new Map();
|
||||
ns.TextCatalogs.set(WegwichtelTexts.language, WegwichtelTexts.messages);
|
||||
}(globalThis.Wegwichtel));
|
||||
+21
-14
@@ -14,35 +14,42 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
const explanations = [
|
||||
[/Freigegebene Strecken dürfen nur/, "user-messages.diese.strecke.kann.nur.die.person.loschen"],
|
||||
[/Freigegebene Strecken können/, "user-messages.diese.strecke.ist.fur.alle.freigegeben.du"],
|
||||
[/GPX|XML|Trackpunkte/, "user-messages.die.datei.enthalt.keinen.lesbaren.weg.wahle"],
|
||||
[/POI.*nicht gefunden/, "user-messages.diese.station.wurde.nicht.gefunden.offne.die"],
|
||||
[/POI-Koordinaten/, "user-messages.der.ort.dieser.station.fehlt.oder.konnte"],
|
||||
[/Bildformat|Bilddatei/, "user-messages.dieses.bild.kann.nicht.geoffnet.werden.wahle"],
|
||||
[/Audioformat|Audiodatei/, "user-messages.diese.tonaufnahme.kann.nicht.geoffnet.werden.wahle"]
|
||||
[/Freigegebene Strecken dürfen nur/, "errors.delete-published"],
|
||||
[/Freigegebene Strecken können/, "errors.edit-published"],
|
||||
[/GPX|XML|Trackpunkte/, "errors.invalid-track"],
|
||||
[/POI.*nicht gefunden/, "errors.station-missing"],
|
||||
[/POI-Koordinaten/, "errors.station-location"],
|
||||
[/Bildformat|Bilddatei/, "errors.image-format"],
|
||||
[/Audioformat|Audiodatei/, "errors.audio-format"]
|
||||
];
|
||||
const statusExplanations = new Map([
|
||||
[413, "user-messages.diese.datei.ist.zu.gross.wahle.eine"],
|
||||
[401, "user-messages.du.darfst.diese.aktion.hier.nicht.ausfuhren"],
|
||||
[403, "user-messages.du.darfst.diese.aktion.hier.nicht.ausfuhren"]
|
||||
[413, "errors.file-size"],
|
||||
[401, "errors.permission"],
|
||||
[403, "errors.permission"]
|
||||
]);
|
||||
|
||||
function readableMessage(message, fallback) {
|
||||
if (/\b(?:HTTP|API|HTTPS|JSON|IndexedDB|OPFS|CacheStorage|Base64|Blob|Server|sequence|dataUrl|PUT|POST)\b|https?:\/\/|[{}]/i.test(message)) return fallback;
|
||||
if (/\b(?:Failed|NetworkError|TypeError|AbortError|SecurityError|NotAllowedError|undefined)\b/i.test(message)) return fallback;
|
||||
if (message && /[äöüÄÖÜß]|\b(?:Die|Der|Das|Dein|Deine|Diese|Dieser|Dieses|Bitte|Wähle|Erlaube|Ein|Eine|Kein|Keine|Speicher|Strecke|Station|Wegwichtel|Gespeichert|Entferne)\b/.test(message)) return message;
|
||||
if (message && readablePatterns.some(pattern => pattern.test(message))) return message;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function errorMessage(error, fallback = ns.I18n.t("editor-ui.das.hat.leider.nicht.geklappt.bitte.versuche")) {
|
||||
const readablePatterns = [
|
||||
/[äöüÄÖÜß]/,
|
||||
/\b(?:The|This|Your|Please|Choose|Allow|Give|Save|Remove|Trail|Stop|There)\b/,
|
||||
/\b(?:Die|Der|Das|Dein|Deine|Diese|Dieser|Dieses|Bitte|Wähle|Erlaube)\b/,
|
||||
/\b(?:Ein|Eine|Kein|Keine|Speicher|Strecke|Station|Wegwichtel|Gespeichert|Entferne)\b/
|
||||
];
|
||||
|
||||
function errorMessage(error, fallback = ns.I18n.t("editor.error.generic")) {
|
||||
const message = messageText(error);
|
||||
if (error?.name === 'QuotaExceededError' || error?.cause?.name === 'QuotaExceededError') return ns.I18n.t("user-messages.der.platz.ist.voll.entferne.gespeicherte.strecken");
|
||||
if (error?.name === 'QuotaExceededError' || error?.cause?.name === 'QuotaExceededError') return ns.I18n.t("errors.quota");
|
||||
const explanation = explanations.find(([pattern]) => pattern.test(message));
|
||||
if (explanation) return ns.I18n.t(explanation[1]);
|
||||
const statusExplanation = statusExplanations.get(error?.status);
|
||||
if (statusExplanation) return ns.I18n.t(statusExplanation);
|
||||
if (error?.status >= 500) return ns.I18n.t("user-messages.wegwichtel.ist.gerade.nicht.erreichbar.bitte.versuche");
|
||||
if (error?.status >= 500) return ns.I18n.t("errors.unavailable");
|
||||
return readableMessage(message, fallback);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
if (Array.isArray(selected)) {
|
||||
const values = selected.map(Number);
|
||||
if (!values.length || values.some(value => !Number.isFinite(value) || value < 0)) {
|
||||
throw new TypeError(ns.I18n.t("vibration.das.vibrationsmuster.muss.aus.nichtnegativen.millisekunden.bestehen"));
|
||||
throw new TypeError(ns.I18n.t("vibration.error.pattern"));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
const duration = Number(selected);
|
||||
if (!Number.isFinite(duration) || duration < 0) {
|
||||
throw new TypeError(ns.I18n.t("vibration.die.vibrationsdauer.muss.eine.nichtnegative.millisekundenangabe.sein"));
|
||||
throw new TypeError(ns.I18n.t("vibration.error.duration"));
|
||||
}
|
||||
return [duration];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
// Serve app files and individually selected route snapshots independently (v2).
|
||||
importScripts('/js/texts-de.js', '/js/i18n.js', '/js/offline-storage.js');
|
||||
importScripts('/js/texts-de.js', '/js/texts-en.js', '/js/i18n.js', '/js/offline-storage.js');
|
||||
|
||||
globalThis.addEventListener('install', event => event.waitUntil(globalThis.skipWaiting()));
|
||||
globalThis.addEventListener('activate', event => event.waitUntil(globalThis.clients.claim()));
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const escapeText = value => value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
// Populate the checked-in German fallback while retaining all runtime i18n markers.
|
||||
export function localizeHtml(source, translate) {
|
||||
const result = source.replaceAll(/(<[^>]+\bdata-i18n="([^"]+)"[^>]*>)([^<]*)(<\/)/g,
|
||||
(_match, opening, key, _text, closing) => opening + escapeText(translate(key)) + closing);
|
||||
return result.replaceAll(/<[^>]+>/g, opening => {
|
||||
let translated = opening;
|
||||
for (const attribute of ['title', 'aria-label', 'placeholder', 'alt']) {
|
||||
const key = new RegExp(`data-i18n-${attribute}="([^"]+)"`).exec(opening)?.[1];
|
||||
if (!key) continue;
|
||||
const value = escapeText(translate(key)).replaceAll('"', '"');
|
||||
// Match an actual attribute, never the suffix of its data-i18n marker.
|
||||
translated = translated.replaceAll(new RegExp(`(\\s)${attribute}="[^"]*"`, 'g'), (_match, space) => `${space}${attribute}="${value}"`);
|
||||
}
|
||||
return translated;
|
||||
});
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
await import('../public/js/texts-de.js');
|
||||
await import('../public/js/i18n.js');
|
||||
const file = new URL('../public/index.html', import.meta.url);
|
||||
const source = await fs.readFile(file, 'utf8');
|
||||
const translated = localizeHtml(source, key => globalThis.Wegwichtel.I18n.t(key));
|
||||
await fs.writeFile(file, translated);
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
sonar.javascript.lcov.reportPaths=coverage/js/lcov.info
|
||||
sonar.python.coverage.reportPaths=coverage/python/coverage.xml
|
||||
# Language catalogs intentionally share keys and structure; keep code/coverage analysis.
|
||||
sonar.cpd.exclusions=public/js/texts-*.js
|
||||
|
||||
+3
-13
@@ -1,31 +1,21 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
import { localizeHtml } from '../scripts/localize-html.js';
|
||||
|
||||
const root = path.resolve(import.meta.dirname, '..');
|
||||
|
||||
export async function loadI18n(context) {
|
||||
for (const name of ['texts-de', 'i18n']) {
|
||||
for (const name of ['texts-de', 'texts-en', 'i18n']) {
|
||||
const filename = path.join(root, `public/js/${name}.js`);
|
||||
vm.runInNewContext(await fs.readFile(filename, 'utf8'), context, { filename });
|
||||
}
|
||||
return context.Wegwichtel.I18n;
|
||||
}
|
||||
|
||||
const escapeText = value => value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
export async function localizedHtml(source) {
|
||||
const i18n = await loadI18n({});
|
||||
let result = source.replace(/(<[^>]+\bdata-i18n="([^"]+)"[^>]*>)([^<]*)(<\/)/g,
|
||||
(_match, opening, key, _text, closing) => opening + escapeText(i18n.t(key)) + closing);
|
||||
result = result.replace(/<[^>]+>/g, opening => {
|
||||
let translated = opening;
|
||||
for (const attribute of ['title', 'aria-label', 'placeholder', 'alt']) {
|
||||
const key = new RegExp(`data-i18n-${attribute}="([^"]+)"`).exec(opening)?.[1];
|
||||
if (key) translated = translated.replace(new RegExp(`\\b${attribute}="[^"]*"`), `${attribute}="${escapeText(i18n.t(key)).replaceAll('"', '"')}"`);
|
||||
}
|
||||
return translated;
|
||||
});
|
||||
const result = localizeHtml(source, key => i18n.t(key));
|
||||
return result.replace(/\sdata-i18n(?:-[\w-]+)?="[^"]*"/g, '');
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ function route(id, name, source = 'server') {
|
||||
};
|
||||
}
|
||||
|
||||
async function loadApp() {
|
||||
async function loadApp({ language = 'de-DE' } = {}) {
|
||||
const sourcePath = path.join(root, 'public/js/app.js');
|
||||
const source = await fs.readFile(sourcePath, 'utf8');
|
||||
const harness = createHarness();
|
||||
@@ -299,6 +299,7 @@ async function loadApp() {
|
||||
|
||||
context.addEventListener = (name, handler) => harness.globalListeners.set(name, handler);
|
||||
context.globalThis = context;
|
||||
context.navigator = { language };
|
||||
|
||||
await loadI18n(context);
|
||||
|
||||
@@ -318,6 +319,17 @@ async function loadApp() {
|
||||
};
|
||||
}
|
||||
|
||||
test('English browser settings localize app controls and retain user-authored trail names', async () => {
|
||||
const { context, harness } = await loadApp({ language: 'en-GB' });
|
||||
await context.Wegwichtel.App.initialize({ markStep() {} });
|
||||
assert.equal(harness.node('#route-control-title').textValue, 'Choose a trail');
|
||||
assert.equal(harness.node('#route-control-server-title').textValue, 'Public trails (1)');
|
||||
assert.equal(harness.node('#route-control-local-title').textValue, 'My drafts (1)');
|
||||
assert.match(harness.node('#location-hint').textValue, /You can still choose from all trails/);
|
||||
assert.ok(harness.node('#all-route-list').appended.join('').includes('Serverweg'));
|
||||
assert.ok(harness.node('#local-route-list').appended.join('').includes('Lokaler Weg'));
|
||||
});
|
||||
|
||||
test('route control runtime loads server/local routes and supports switching without geolocation', async () => {
|
||||
const setup = await loadApp();
|
||||
const { context, harness, apiCalls, editor } = setup;
|
||||
|
||||
Vendored
+4
-6
@@ -36,10 +36,8 @@ test('bootstrap status text fades between loading steps and is replaced by the s
|
||||
assert.match(fadingRule, /opacity:\s*0/);
|
||||
assert.match(loader, /statusText\.classList\.add\('is-fading'\)/);
|
||||
assert.match(loader, /statusText\.classList\.remove\('is-fading'\)/);
|
||||
assert.match(loader, /get storage\(\) \{ return globalThis\.Wegwichtel\.I18n\.t\(/);
|
||||
assert.match(loader, /get server\(\) \{ return globalThis\.Wegwichtel\.I18n\.t\(/);
|
||||
assert.match(loader, /get routes\(\) \{ return globalThis\.Wegwichtel\.I18n\.t\(/);
|
||||
assert.match(loader, /get location\(\) \{ return globalThis\.Wegwichtel\.I18n\.t\(/);
|
||||
assert.match(loader, /const stepLabel = step => globalThis\.Wegwichtel\.I18n\.t\(`bootstrap-loader\.steps\.\$\{step\}`\)/);
|
||||
assert.match(loader, /steps\.includes\(step\)/);
|
||||
assert.match(loader, /actionStatus\?\.classList\.add\('is-leaving'\)/);
|
||||
assert.match(loader, /enter\.classList\.add\('is-visible'\)/);
|
||||
assert.match(loader, /const completedSteps = new Set\(\)/);
|
||||
@@ -59,6 +57,6 @@ test('Android start hint distinguishes Chrome from other Android browsers', asyn
|
||||
assert.match(loader, /getElementById\('android-browser-hint'\)/);
|
||||
assert.match(loader, /\/Android\/i/);
|
||||
assert.match(loader, /\/Chrome\\\//i);
|
||||
assert.match(loader, /I18n\.t\("bootstrap-loader\.auf\.diesem\.gerat\.kannst\.du\.bilder\.und"\)/);
|
||||
assert.match(loader, /I18n\.t\("bootstrap-loader\.tipp\.fur\.android\.mit\.chrome\.lassen\.sich"\)/);
|
||||
assert.match(loader, /I18n\.t\("bootstrap-loader\.hints\.chrome"\)/);
|
||||
assert.match(loader, /I18n\.t\("bootstrap-loader\.hints\.android"\)/);
|
||||
});
|
||||
|
||||
+97
-9
@@ -3,19 +3,44 @@ import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import vm from 'node:vm';
|
||||
import { localizeHtml } from '../scripts/localize-html.js';
|
||||
|
||||
const root = path.resolve(import.meta.dirname, '..');
|
||||
|
||||
test('checked-in HTML has synchronized German defaults and accessible headings, labels and links before scripts load', async () => {
|
||||
const i18n = await loadI18n({});
|
||||
const html = await fs.readFile(path.join(root, 'public/index.html'), 'utf8');
|
||||
assert.equal(localizeHtml(html, key => i18n.t(key)), html, 'Run node scripts/localize-html.js after editing German texts');
|
||||
for (const match of html.matchAll(/<h[1-6]\b[^>]*>([\s\S]*?)<\/h[1-6]>/g)) assert.ok(match[1].replaceAll(/<[^>]*>/g, '').trim(), match[0]);
|
||||
for (const match of html.matchAll(/\baria-label="([^"]*)"/g)) assert.ok(match[1].trim(), 'Empty accessible label');
|
||||
for (const match of html.matchAll(/<a\b[^>]*>([\s\S]*?)<\/a>/g)) assert.ok(match[1].replaceAll(/<[^>]*>/g, '').trim(), match[0]);
|
||||
for (const match of html.matchAll(/<label\b([^>]*)>/g)) {
|
||||
const control = /\bfor="([^"]+)"/.exec(match[1])?.[1];
|
||||
assert.ok(control, `Missing label association: ${match[0]}`);
|
||||
assert.ok(html.includes(`id="${control}"`), `Missing control: ${control}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('HTML generation escapes text and attributes, preserves i18n markers and updates existing defaults', () => {
|
||||
const source = '<h1 data-i18n="heading">Old title</h1><button aria-label="Old label" data-i18n-aria-label="label">?</button>';
|
||||
const messages = { heading: '<New & title>', label: 'A "label" & $&' };
|
||||
const translate = key => messages[key];
|
||||
const result = localizeHtml(source, translate);
|
||||
assert.equal(result, '<h1 data-i18n="heading"><New & title></h1><button aria-label="A "label" & $&" data-i18n-aria-label="label">?</button>');
|
||||
assert.equal(localizeHtml(result, translate), result);
|
||||
});
|
||||
|
||||
test('translations support placeholders, plurals and German fallback without treating text as HTML', async () => {
|
||||
const context = {};
|
||||
const i18n = await loadI18n(context);
|
||||
assert.equal(i18n.t('app.strecke.wahlen'), 'Strecke wählen');
|
||||
assert.equal(i18n.t('app.choose-route'), 'Strecke wählen');
|
||||
assert.equal(i18n.t('media.image.count', { count: 1 }), '1 Bild');
|
||||
assert.equal(i18n.t('media.image.count', { count: 2 }), '2 Bilder');
|
||||
assert.equal(i18n.t('app.nachste.station', { value1: '<script>', value2: ' · 3 m' }), 'Nächste Station: <script> · 3 m');
|
||||
i18n.register('en', { 'app.strecke.wahlen': 'Choose a trail', 'media.image.count': { one: '{count} picture', other: '{count} pictures' } });
|
||||
assert.equal(i18n.t('app.next-station-summary', { value1: '<script>', value2: ' · 3 m' }), 'Nächste Station: <script> · 3 m');
|
||||
i18n.register('en', { 'app.choose-route': 'Choose a trail', 'media.image.count': { one: '{count} picture', other: '{count} pictures' } });
|
||||
assert.equal(i18n.setLanguage('en-GB', null), 'en');
|
||||
assert.equal(i18n.t('app.strecke.wahlen'), 'Choose a trail');
|
||||
assert.equal(i18n.t('app.choose-route'), 'Choose a trail');
|
||||
assert.equal(i18n.t('media.image.count', { count: 0 }), '0 pictures');
|
||||
assert.equal(i18n.t('media.audio.count', { count: 1 }), '1 Tonaufnahme');
|
||||
assert.equal(i18n.setLanguage('fr', null), 'de');
|
||||
@@ -24,10 +49,15 @@ test('translations support placeholders, plurals and German fallback without tre
|
||||
|
||||
test('language changes translate visible and spoken labels through textContent and retain text markers', async () => {
|
||||
const i18n = await loadI18n({});
|
||||
const make = key => ({ textContent: '', attributes: new Map(key), getAttribute(name) { return this.attributes.get(name); }, setAttribute(name, value) { this.attributes.set(name, value); } });
|
||||
const text = make([['data-i18n', 'app.strecke.wahlen']]);
|
||||
const make = entries => ({
|
||||
textContent: '',
|
||||
attributes: new Map(entries),
|
||||
dataset: Object.fromEntries(entries.map(([name, value]) => [name.slice(5).replaceAll(/-([a-z])/g, (_match, letter) => letter.toUpperCase()), value])),
|
||||
setAttribute(name, value) { this.attributes.set(name, value); }
|
||||
});
|
||||
const text = make([['data-i18n', 'app.choose-route']]);
|
||||
const attributes = ['aria-label', 'title', 'placeholder', 'alt'];
|
||||
const labels = new Map(attributes.map(attribute => [attribute, make([[`data-i18n-${attribute}`, 'app.strecke.wahlen']])]));
|
||||
const labels = new Map(attributes.map(attribute => [attribute, make([[`data-i18n-${attribute}`, 'app.choose-route']])]));
|
||||
const root = { documentElement: {}, querySelectorAll(selector) {
|
||||
if (selector === '[data-i18n]') return [text];
|
||||
for (const [attribute, label] of labels) {
|
||||
@@ -38,12 +68,12 @@ test('language changes translate visible and spoken labels through textContent a
|
||||
i18n.apply(root);
|
||||
assert.equal(text.textContent, 'Strecke wählen');
|
||||
for (const [attribute, label] of labels) assert.equal(label.attributes.get(attribute), 'Strecke wählen');
|
||||
i18n.register('en', { 'app.strecke.wahlen': '<Choose a trail>' });
|
||||
i18n.register('en', { 'app.choose-route': '<Choose a trail>' });
|
||||
i18n.setLanguage('en', root);
|
||||
assert.equal(text.textContent, '<Choose a trail>');
|
||||
for (const [attribute, label] of labels) assert.equal(label.attributes.get(attribute), '<Choose a trail>');
|
||||
assert.equal(root.documentElement.lang, 'en');
|
||||
assert.equal(text.attributes.get('data-i18n'), 'app.strecke.wahlen');
|
||||
assert.equal(text.attributes.get('data-i18n'), 'app.choose-route');
|
||||
});
|
||||
|
||||
test('every static HTML and JavaScript text key exists in the central catalog and translations load before app modules', async () => {
|
||||
@@ -56,5 +86,63 @@ test('every static HTML and JavaScript text key exists in the central catalog an
|
||||
for (const match of source.matchAll(/I18n\.t\(["']([^"']+)["']/g)) assert.equal(typeof i18n.t(match[1], { count: 2 }), 'string', match[1]);
|
||||
}
|
||||
assert.ok(html.indexOf('js/texts-de.js') < html.indexOf('js/i18n.js'));
|
||||
assert.ok(html.indexOf('js/texts-en.js') > html.indexOf('js/texts-de.js'));
|
||||
assert.ok(html.indexOf('js/texts-en.js') < html.indexOf('js/i18n.js'));
|
||||
assert.ok(html.indexOf('js/i18n.js') < html.indexOf('js/local-routes.js'));
|
||||
});
|
||||
|
||||
test('browser language preferences select German or English, including regional variants and unavailable languages', async () => {
|
||||
const cases = [
|
||||
[{ languages: ['en-GB', 'de-DE'], language: 'de-DE' }, 'en', 'Choose a trail'],
|
||||
[{ languages: ['de-DE', 'en-US'] }, 'de', 'Strecke wählen'],
|
||||
[{ languages: ['fr-FR', 'en-US'] }, 'en', 'Choose a trail'],
|
||||
[{ language: 'EN-us' }, 'en', 'Choose a trail'],
|
||||
[{ languages: ['fr-FR'] }, 'de', 'Strecke wählen'],
|
||||
[{}, 'de', 'Strecke wählen']
|
||||
];
|
||||
for (const [navigator, language, title] of cases) {
|
||||
const i18n = await loadI18n({ navigator });
|
||||
assert.equal(i18n.language, language);
|
||||
assert.equal(i18n.t('app.choose-route'), title);
|
||||
assert.equal(i18n.t('bootstrap-loader.steps.routes'), language === 'en' ? 'Loading trails …' : 'Strecken laden …');
|
||||
}
|
||||
});
|
||||
|
||||
function leafMessages(node, prefix = '') {
|
||||
return Object.entries(node).flatMap(([key, value]) => {
|
||||
const path = prefix + key;
|
||||
return typeof value === 'string' ? [[path, value]] : leafMessages(value, `${path}.`);
|
||||
});
|
||||
}
|
||||
|
||||
test('German and English catalogs have the same semantic keys, placeholders and plural forms', async () => {
|
||||
const context = {};
|
||||
const i18n = await loadI18n(context);
|
||||
const de = new Map(leafMessages(context.Wegwichtel.TextCatalogs.get('de')));
|
||||
const en = new Map(leafMessages(context.Wegwichtel.TextCatalogs.get('en')));
|
||||
assert.deepEqual([...en.keys()].sort(), [...de.keys()].sort());
|
||||
for (const [key, text] of de) {
|
||||
assert.ok(en.get(key).trim(), key);
|
||||
const placeholders = value => [...value.matchAll(/\{(\w+)\}/g)].map(match => match[1]).sort();
|
||||
assert.deepEqual(placeholders(en.get(key)), placeholders(text), key);
|
||||
assert.match(key, /^[a-z][a-z0-9.-]*$/, key);
|
||||
assert.ok(key.split('.').length <= 4, key);
|
||||
}
|
||||
i18n.setLanguage('en', null);
|
||||
assert.equal(i18n.t('app.no-route'), 'You have not created a trail of your own yet.');
|
||||
assert.equal(i18n.t('media.audio.count', { count: 1 }), '1 recording');
|
||||
assert.equal(i18n.t('media.audio.count', { count: 2 }), '2 recordings');
|
||||
for (const step of ['storage', 'jquery', 'jquery-ui', 'modules', 'server', 'routes', 'location']) assert.ok(i18n.t(`bootstrap-loader.steps.${step}`));
|
||||
});
|
||||
|
||||
test('the generic text class and i18n can start with only the English language file', async () => {
|
||||
const context = { navigator: { language: 'en-GB' } };
|
||||
for (const name of ['texts-en', 'i18n']) {
|
||||
const filename = path.join(root, `public/js/${name}.js`);
|
||||
vm.runInNewContext(await fs.readFile(filename, 'utf8'), context, { filename });
|
||||
}
|
||||
assert.equal(context.Wegwichtel.Texts.name, 'WegwichtelTexts');
|
||||
assert.equal(context.Wegwichtel.I18n.language, 'en');
|
||||
assert.equal(context.Wegwichtel.I18n.t('app.choose-route'), 'Choose a trail');
|
||||
assert.equal(context.Wegwichtel.I18n.setLanguage('fr', null), 'en');
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ test('start screen exposes cookie status, optional local progress and deliberate
|
||||
assert.match(html, /role="switch"[^>]*aria-checked="false"/);
|
||||
assert.match(html, /id="storage-cleanup-status"/);
|
||||
assert.match(html, /id="bootstrap-enter"[^>]*hidden/);
|
||||
assert.match(loader, /I18n\.t\("bootstrap-loader\.alten\.fortschritt\.loschen", \{ value1: removed, value2: total \}\)/);
|
||||
assert.match(loader, /I18n\.t\("bootstrap-loader\.cleanup-progress", \{ value1: removed, value2: total \}\)/);
|
||||
assert.match(loader, /showReadyState\(\)/);
|
||||
assert.doesNotMatch(loader, /getElementById\('bootstrap-screen'\)\.hidden = true/);
|
||||
assert.match(loader, /event\.target\.closest\('button, input, label, a, dialog'\)/);
|
||||
|
||||
@@ -153,7 +153,7 @@ test('Stationen have live direction and metre indicators and take navigation pri
|
||||
assert.match(app, /function updatePoiIndicators\(position\)/);
|
||||
assert.match(app, /function nearestPendingPoi\(metrics\)/);
|
||||
assert.match(app, /if \(nextPoi\) showPoiNavigation\(position, nextPoi\)/);
|
||||
assert.match(app, /I18n\.t\("app\.nachste\.station\.2", \{ value1: metric\.poi\.title \}\)/);
|
||||
assert.match(app, /I18n\.t\("app\.next-station", \{ value1: metric\.poi\.title \}\)/);
|
||||
assert.match(app, /find\('small'\)\.text\(`\$\{distance\} m`\)/);
|
||||
assert.match(app, /find\('svg'\)\.css\('transform'/);
|
||||
assert.match(css, /\.poi-proximity[\s\S]*grid-column:\s*2/);
|
||||
|
||||
@@ -56,7 +56,7 @@ test('persistent route control displays the next unvisited Station and preserves
|
||||
const app = await read('public/js/app.js');
|
||||
|
||||
assert.match(app, /function nextPoiMetric\(/);
|
||||
assert.match(app, /I18n\.t\("app\.nachste\.station", \{ value1: metric\.poi\.title, value2: distance \}\)/);
|
||||
assert.match(app, /I18n\.t\("app\.next-station-summary", \{ value1: metric\.poi\.title, value2: distance \}\)/);
|
||||
assert.match(app, /state\.triggered\.has\(poi\.id\)/);
|
||||
assert.match(app, /function suspendCurrentRoute\(/);
|
||||
assert.match(app, /persistRouteProgress\(\)/);
|
||||
|
||||
@@ -47,3 +47,14 @@ test('public help and spoken labels omit implementation terms and preserve meani
|
||||
assert.match(text, /Fortschritt.*gelöscht/s);
|
||||
assert.match(text, /Meine Entwürfe/);
|
||||
});
|
||||
|
||||
test('English errors keep translated validation hints and hide technical replies', async () => {
|
||||
const context = { navigator: { language: 'en-GB' } };
|
||||
await loadI18n(context);
|
||||
vm.runInNewContext(await fs.readFile(path.join(root, 'public/js/user-messages.js'), 'utf8'), context);
|
||||
const { I18n, UserMessages } = context.Wegwichtel;
|
||||
assert.equal(UserMessages.error(new Error(I18n.t('editor.error.route-name'))), 'Give your trail a name.');
|
||||
assert.equal(UserMessages.error({ status: 413 }), 'This file is too large. Choose a smaller file.');
|
||||
assert.match(UserMessages.error({ message: 'Die GPX-Datei ist kein gültiges XML.' }), /readable path/);
|
||||
assert.equal(UserMessages.error(new Error('Failed to fetch')), 'That did not work. Please try again.');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user