Internationalization
This commit is contained in:
+49
-49
@@ -58,7 +58,7 @@
|
||||
if (state.routeState === 'paused') {
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text('Gespeicherter Streckenfortschritt gefunden. Die Route kann fortgesetzt werden.');
|
||||
.text(ns.I18n.t("app.du.kannst.dort.weitermachen.wo.du.aufgehort"));
|
||||
} else {
|
||||
$('#tracking-status').prop('hidden', true).text('');
|
||||
}
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
function navigate(pageSelector, options = {}) {
|
||||
const target = $(pageSelector);
|
||||
if (!target.length) throw new Error(`Unbekannte Ansicht: ${pageSelector}`);
|
||||
if (!target.length) throw new Error(ns.I18n.t("app.page.missing"));
|
||||
|
||||
const previous = state.activePage;
|
||||
$('.app-page').attr('hidden', true);
|
||||
@@ -115,7 +115,7 @@
|
||||
const proximity = route.proximityM == null
|
||||
? ''
|
||||
: `<small>${escapeHtml(ns.Distance.format(route.proximityM))} entfernt</small>`;
|
||||
const sourceLabel = source === 'local' ? '<small>Lokaler Entwurf</small>' : '';
|
||||
const sourceLabel = source === 'local' ? ("<small>" + escapeHtml(ns.I18n.t("app.eigener.entwurf")) + "</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('Route wählen', 'Aktive und lokale Strecken');
|
||||
setRouteControlSummary(ns.I18n.t("app.strecke.wahlen"), ns.I18n.t("app.strecken.fur.alle.und.meine.entwurfe"));
|
||||
return;
|
||||
}
|
||||
|
||||
const titleText = state.route.name || 'Route';
|
||||
const titleText = state.route.name || ns.I18n.t("app.strecke");
|
||||
const metric = nextPoiMetric();
|
||||
if (!metric) {
|
||||
const detailText = (state.route.pois || []).length
|
||||
? 'Alle Streckenpunkte erreicht'
|
||||
: 'Keine Streckenpunkte vorhanden';
|
||||
? ns.I18n.t("app.alle.stationen.erreicht")
|
||||
: ns.I18n.t("app.keine.stationen.vorhanden");
|
||||
setRouteControlSummary(titleText, detailText);
|
||||
return;
|
||||
}
|
||||
const distance = metric.distance == null ? '' : ` · ${Math.round(metric.distance)} m`;
|
||||
setRouteControlSummary(titleText, `Nächster Streckenpunkt: ${metric.poi.title}${distance}`);
|
||||
setRouteControlSummary(titleText, ns.I18n.t("app.nachste.station", { 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' ? 'Lokaler Entwurf' : 'Veröffentlicht'}</small>
|
||||
<small>${source === 'local' ? ns.I18n.t("app.eigener.entwurf") : ns.I18n.t("app.veroffentlicht")}</small>
|
||||
</button>
|
||||
</li>
|
||||
`);
|
||||
@@ -198,22 +198,22 @@
|
||||
const routeControlLists = $('.route-control-list');
|
||||
routeControlLists.attr('aria-busy', 'true');
|
||||
|
||||
$('#route-control-server-title').text(`Aktive Strecken (${state.routes.length})`);
|
||||
$('#route-control-local-title').text(`Lokale Strecken (${state.localRoutes.length})`);
|
||||
$('#local-route-title').text(`Lokale Strecken (${state.localRoutes.length})`);
|
||||
$('#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 }));
|
||||
|
||||
updateRouteControlSummary();
|
||||
renderRouteControlList(
|
||||
'#route-control-server-list',
|
||||
state.routes,
|
||||
'server',
|
||||
'Keine veröffentlichte Strecke vorhanden.'
|
||||
ns.I18n.t("app.hier.gibt.es.noch.keine.strecken.fur")
|
||||
);
|
||||
renderRouteControlList(
|
||||
'#route-control-local-list',
|
||||
state.localRoutes,
|
||||
'local',
|
||||
state.localRoutesError || 'Noch keine lokale Strecke vorhanden.'
|
||||
state.localRoutesError || ns.I18n.t("app.du.hast.noch.keine.eigene.strecke.angelegt")
|
||||
);
|
||||
|
||||
routeControlLists.attr('aria-busy', 'false');
|
||||
@@ -239,26 +239,26 @@
|
||||
.sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
||||
|
||||
const nearbyEmptyText = state.position
|
||||
? `Keine Route im Umkreis von ${ns.Config.routeRadiusKm} km gefunden. Wähle unten eine beliebige Route aus.`
|
||||
: 'Für Empfehlungen in der Nähe ist ein Standort erforderlich. Alle Routen bleiben unten auswählbar.';
|
||||
? 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");
|
||||
|
||||
renderRouteItems('#nearby-route-list', nearbyRoutes, nearbyEmptyText);
|
||||
renderRouteItems(
|
||||
'#all-route-list',
|
||||
allRoutes,
|
||||
searchTerm ? 'Keine Route entspricht dem Suchbegriff.' : 'Es sind noch keine veröffentlichten Routen vorhanden.'
|
||||
searchTerm ? ns.I18n.t("app.keine.passende.strecke.gefunden.versuche.ein.anderes") : ns.I18n.t("app.hier.gibt.es.noch.keine.strecken.fur")
|
||||
);
|
||||
renderRouteItems(
|
||||
'#local-route-list',
|
||||
localRoutes,
|
||||
state.localRoutesError || 'Noch keine lokalen Strecken gespeichert.',
|
||||
state.localRoutesError || ns.I18n.t("app.du.hast.noch.keine.eigene.strecke.angelegt"),
|
||||
'local'
|
||||
);
|
||||
|
||||
$('#all-routes-summary').text(
|
||||
searchTerm
|
||||
? `${allRoutes.length} von ${routes.length} veröffentlichten Routen gefunden.`
|
||||
: `${routes.length} veröffentlichte Routen verfügbar.`
|
||||
? ns.I18n.t("app.von.strecken.gefunden", { value1: allRoutes.length, value2: routes.length })
|
||||
: ns.I18n.t("app.strecken.zur.auswahl", { value1: routes.length })
|
||||
);
|
||||
renderRouteControl();
|
||||
}
|
||||
@@ -288,7 +288,7 @@
|
||||
$('#app-shell').removeClass('has-navigation');
|
||||
}
|
||||
|
||||
function showNavigation(message = 'Position wird ermittelt', distance = '–') {
|
||||
function showNavigation(message = ns.I18n.t("app.location.searching"), distance = '–') {
|
||||
$('#navigation-mode').text(message);
|
||||
$('#navigation-distance').text(distance);
|
||||
$('#navigation-footer').prop('hidden', false);
|
||||
@@ -305,7 +305,7 @@
|
||||
function updateRouteControls() {
|
||||
const running = state.routeState === 'running';
|
||||
const paused = state.routeState === 'paused';
|
||||
const startLabel = paused ? 'Route fortsetzen' : 'Route starten';
|
||||
const startLabel = paused ? ns.I18n.t("app.strecke.fortsetzen") : ns.I18n.t("app.strecke.starten");
|
||||
$('#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('Route beendet.');
|
||||
$('#tracking-status').prop('hidden', false).text(ns.I18n.t("app.strecke.beendet"));
|
||||
} else {
|
||||
$('#tracking-status').prop('hidden', true).text('');
|
||||
}
|
||||
@@ -357,8 +357,8 @@
|
||||
ns.AudioPlayer.pause();
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text('Route pausiert. Die Standortverfolgung ist angehalten.');
|
||||
showNavigation('Route pausiert', $('#navigation-distance').text() || '–');
|
||||
.text(ns.I18n.t("app.du.machst.eine.pause.die.app.sucht"));
|
||||
showNavigation(ns.I18n.t("app.strecke.pausiert"), $('#navigation-distance').text() || '–');
|
||||
updateRouteControls();
|
||||
updateRouteControlSummary();
|
||||
}
|
||||
@@ -384,7 +384,7 @@
|
||||
updateRouteControls();
|
||||
const list = $('#poi-list').empty();
|
||||
if (!route.pois.length) {
|
||||
list.append('<li><p>Diese Route enthält noch keine Streckenpunkte.</p></li>');
|
||||
list.append(("<li><p>" + escapeHtml(ns.I18n.t("app.diese.strecke.hat.noch.keine.stationen")) + "</p></li>"));
|
||||
updateRouteControlSummary();
|
||||
return;
|
||||
}
|
||||
@@ -484,8 +484,8 @@
|
||||
const indicator = $(`#poi-list [data-poi-id="${metric.poi.id}"] .poi-proximity`);
|
||||
const distance = Math.round(metric.distance);
|
||||
indicator
|
||||
.attr('aria-label', `${distance} Meter entfernt`)
|
||||
.attr('title', `${distance} m entfernt`);
|
||||
.attr('aria-label', ns.I18n.t("app.distance.spoken", { value1: distance }))
|
||||
.attr('title', ns.I18n.t("app.distance.short", { value1: distance }));
|
||||
indicator.find('svg').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
|
||||
indicator.find('small').text(`${distance} m`);
|
||||
});
|
||||
@@ -537,27 +537,27 @@
|
||||
$('#navigation-arrow').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
|
||||
showNavigation(
|
||||
heading == null
|
||||
? `Norden oben · nächster Streckenpunkt: ${metric.poi.title}`
|
||||
: `Nächster Streckenpunkt: ${metric.poi.title}`,
|
||||
? ns.I18n.t("app.norden.oben.nachste.station", { value1: metric.poi.title })
|
||||
: ns.I18n.t("app.nachste.station.2", { value1: metric.poi.title }),
|
||||
`${distance} m`
|
||||
);
|
||||
$('#navigation-footer').attr(
|
||||
'aria-label',
|
||||
`Nächster Streckenpunkt ${metric.poi.title} in ${distance} Metern`
|
||||
ns.I18n.t("app.nachste.station.in.metern", { value1: metric.poi.title, value2: distance })
|
||||
);
|
||||
}
|
||||
|
||||
function showRoutePointNavigation(position, progress) {
|
||||
if (!progress) {
|
||||
$('#navigation-arrow').css('transform', 'rotate(0deg)');
|
||||
showNavigation('Keine Navigationspunkte vorhanden', '–');
|
||||
showNavigation(ns.I18n.t("app.path.missing"), '–');
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress.reached) {
|
||||
$('#navigation-arrow').css('transform', 'rotate(0deg)');
|
||||
showNavigation('Alle Streckenpunkte und das Routenziel erreicht', '0 m');
|
||||
$('#navigation-footer').attr('aria-label', 'Alle Streckenpunkte und das Routenziel erreicht');
|
||||
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"));
|
||||
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 ? 'Norden oben · nächster Routenpunkt' : 'Nächster Routenpunkt',
|
||||
heading == null ? ns.I18n.t("app.norden.oben.nachster.punkt.auf.dem.weg") : ns.I18n.t("app.nachster.punkt.auf.dem.weg"),
|
||||
`${distance} m`
|
||||
);
|
||||
$('#navigation-footer').attr(
|
||||
'aria-label',
|
||||
`Nächster Routenpunkt in ${distance} Metern`
|
||||
ns.I18n.t("app.nachster.punkt.auf.dem.weg.in.metern", { value1: distance })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -608,8 +608,8 @@
|
||||
const nearest = nearestPendingPoi(metrics);
|
||||
updateNavigation(position);
|
||||
$('#tracking-status').text(
|
||||
`GPS aktiv · Genauigkeit ${Math.round(position.accuracy)} m`
|
||||
+ (nearest ? ` · nächster Streckenpunkt ${Math.round(nearest.distance)} m` : ' · alle Streckenpunkte erreicht')
|
||||
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"))
|
||||
);
|
||||
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 ? 'Route wird fortgesetzt.' : 'Route wird gestartet. GPS wird aktiviert.');
|
||||
showNavigation(resumed ? 'Route wird fortgesetzt' : 'Position wird ermittelt');
|
||||
.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"));
|
||||
ns.Geo.start(
|
||||
evaluatePosition,
|
||||
error => $('#tracking-status').text(`GPS-Fehler: ${error.message}`)
|
||||
() => $('#tracking-status').text(ns.I18n.t("app.dein.standort.konnte.nicht.gefunden.werden.prufe"))
|
||||
);
|
||||
|
||||
const orientationAvailable = await ns.Orientation.start(updateHeading);
|
||||
@@ -648,12 +648,12 @@
|
||||
ns.Orientation.stop();
|
||||
return;
|
||||
}
|
||||
if (!orientationAvailable) $('#navigation-mode').text('Norden oben · Navigation');
|
||||
if (!orientationAvailable) $('#navigation-mode').text(ns.I18n.t("app.norden.oben.navigation"));
|
||||
}
|
||||
|
||||
function routesFromResponse(response) {
|
||||
if (!Array.isArray(response?.routes)) {
|
||||
throw new TypeError('Die Routen-API hat keine gültige Routenliste geliefert.');
|
||||
throw new TypeError(ns.I18n.t("app.die.strecken.konnten.nicht.geladen.werden.bitte"));
|
||||
}
|
||||
return response.routes
|
||||
.filter(route => route?.id != null && route?.name != null)
|
||||
@@ -666,7 +666,7 @@
|
||||
state.localRoutesError = null;
|
||||
} catch (error) {
|
||||
state.localRoutes = [];
|
||||
state.localRoutesError = error.message || 'Lokale Strecken konnten nicht gelesen werden.';
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,12 +685,12 @@
|
||||
state.position = position;
|
||||
updateRouteProximity(position);
|
||||
$('#location-hint').text(
|
||||
`Standort ermittelt (Genauigkeit ${Math.round(position.accuracy)} m). `
|
||||
+ `Nahe Routen werden bis ${ns.Config.routeRadiusKm} km empfohlen.`
|
||||
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 })
|
||||
);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
$('#location-hint').text(
|
||||
`Standort nicht verfügbar: ${error.message}. Alle Routen können weiterhin ausgewählt werden.`
|
||||
ns.I18n.t("app.dein.standort.konnte.nicht.gefunden.werden.du")
|
||||
);
|
||||
} 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(error.responseJSON?.message || error.statusText || error.message);
|
||||
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"));
|
||||
} finally {
|
||||
button.prop('disabled', false);
|
||||
}
|
||||
|
||||
Vendored
+15
-15
@@ -4,13 +4,13 @@
|
||||
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({
|
||||
storage: 'Lokalen Speicher prüfen …',
|
||||
jquery: 'jQuery laden …',
|
||||
'jquery-ui': 'jQuery UI laden …',
|
||||
modules: 'Anwendungsmodule laden …',
|
||||
server: 'Server prüfen …',
|
||||
routes: 'Routen laden …',
|
||||
location: 'Standort ermitteln …'
|
||||
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 screen = document.getElementById('bootstrap-screen');
|
||||
const progress = document.getElementById('bootstrap-progress');
|
||||
@@ -43,8 +43,8 @@
|
||||
androidBrowserHint.hidden = false;
|
||||
androidBrowserHint.dataset.browser = isChrome ? 'chrome' : 'other';
|
||||
androidBrowserHint.textContent = isChrome
|
||||
? 'Android mit Chrome erkannt. Lokale Bilder und Audiodateien können als Dateireferenzen verwendet werden.'
|
||||
: 'Android erkannt. Für lokale Bilder und Audiodateien bitte Chrome verwenden; dort können Dateireferenzen ohne Kopie in den Local Storage genutzt werden.';
|
||||
? 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");
|
||||
}
|
||||
|
||||
function schedule(callback, delay = 0) {
|
||||
@@ -93,7 +93,7 @@
|
||||
if (progress) progress.value = completedSteps.size;
|
||||
|
||||
if (state === 'failed') {
|
||||
setStatusText(`Fehler bei: ${stepLabels[step] || step}`);
|
||||
setStatusText(globalThis.Wegwichtel.I18n.t("bootstrap.step.failed", { value1: stepLabels[step] || step }));
|
||||
return;
|
||||
}
|
||||
if (state === 'done') {
|
||||
@@ -108,10 +108,10 @@
|
||||
script.src = versioned(src);
|
||||
script.async = false;
|
||||
script.onload = () => {
|
||||
if (test && !test()) reject(new Error(`Selbsttest fehlgeschlagen: ${src}`));
|
||||
if (test && !test()) reject(new Error(globalThis.Wegwichtel.I18n.t("bootstrap-loader.ein.teil.der.app.konnte.nicht.starten")));
|
||||
else resolve();
|
||||
};
|
||||
script.onerror = () => reject(new Error(`Datei konnte nicht geladen werden: ${src}`));
|
||||
script.onerror = () => reject(new Error(globalThis.Wegwichtel.I18n.t("bootstrap-loader.ein.teil.der.app.konnte.nicht.geladen")));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
@@ -127,7 +127,7 @@
|
||||
}
|
||||
|
||||
function updateCleanupProgress(removed, total) {
|
||||
cleanupStatus.textContent = `Entferne alte Streckendaten aus dem Speicher (${removed} / ${total})`;
|
||||
cleanupStatus.textContent = globalThis.Wegwichtel.I18n.t("bootstrap-loader.alten.fortschritt.loschen", { value1: removed, value2: total });
|
||||
cleanupStatus.hidden = false;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
screen.classList.add('ready');
|
||||
readyText.hidden = false;
|
||||
|
||||
setStatusText('Vorbereitung abgeschlossen.');
|
||||
setStatusText(globalThis.Wegwichtel.I18n.t("bootstrap.ready"));
|
||||
actionStatus?.classList.add('is-leaving');
|
||||
enter.hidden = false;
|
||||
const showEnter = () => enter.classList.add('is-visible');
|
||||
@@ -222,7 +222,7 @@
|
||||
console.error(error);
|
||||
const current = nextPendingStep();
|
||||
if (current) mark(current, 'failed');
|
||||
errorBox.textContent = error.message;
|
||||
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.hidden = false;
|
||||
retry.hidden = false;
|
||||
}
|
||||
|
||||
+77
-77
@@ -141,17 +141,17 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
const next = highest + 1;
|
||||
if (!Number.isSafeInteger(next)) {
|
||||
throw new TypeError('Für diese lokale Strecke kann keine weitere Streckenpunkt-ID vergeben werden.');
|
||||
throw new TypeError(ns.I18n.t("editor-ui.zu.dieser.strecke.kann.gerade.keine.weitere"));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function routeName(route) {
|
||||
return text(route?.name || route?.title || 'Unbenannte Strecke');
|
||||
return text(route?.name || route?.title || ns.I18n.t("editor-ui.unbenannte.strecke"));
|
||||
}
|
||||
|
||||
function poiTitle(poi) {
|
||||
return text(poi?.title || poi?.name || 'Unbenannter Streckenpunkt');
|
||||
return text(poi?.title || poi?.name || ns.I18n.t("editor-ui.station.ohne.namen"));
|
||||
}
|
||||
|
||||
function apiBase() {
|
||||
@@ -184,7 +184,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
function setStatus(message, isError = false) {
|
||||
const status = byId(ids.status);
|
||||
if (!status) return;
|
||||
status.textContent = message;
|
||||
status.textContent = isError ? (ns.UserMessages?.error(message) || message) : message;
|
||||
status.hidden = !message;
|
||||
status.dataset.kind = isError ? 'error' : 'notice';
|
||||
}
|
||||
@@ -196,9 +196,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function routeDialogTitle() {
|
||||
if (state.currentSource === 'local') {
|
||||
return routeId(state.currentRoute) ? 'Lokale Strecke bearbeiten' : 'Neue lokale Strecke';
|
||||
return routeId(state.currentRoute) ? ns.I18n.t("editor-ui.eigene.strecke.bearbeiten") : ns.I18n.t("editor-ui.neue.strecke");
|
||||
}
|
||||
return 'Veröffentlichte Strecke bearbeiten';
|
||||
return ns.I18n.t("editor-ui.strecke.ansehen");
|
||||
}
|
||||
|
||||
function showListView() {
|
||||
@@ -216,7 +216,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function showPoiView() {
|
||||
setDialogTitle(state.currentPoi ? 'Streckenpunkt bearbeiten' : 'Streckenpunkt hinzufügen');
|
||||
setDialogTitle(state.currentPoi ? ns.I18n.t("editor-ui.station.bearbeiten") : ns.I18n.t("editor-ui.station.hinzufugen"));
|
||||
setHidden(byId(ids.routeListView), true);
|
||||
setHidden(byId(ids.routeDetailView), true);
|
||||
setHidden(byId(ids.poiFormView), false);
|
||||
@@ -264,8 +264,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
async function handleResponse(response) {
|
||||
if (response.ok) return parseJson(response);
|
||||
const message = await response.text();
|
||||
const error = new Error(message || `HTTP ${response.status}`);
|
||||
const payload = await parseJson(response);
|
||||
const error = new Error(payload.message || ns.I18n.t("editor-ui.die.aktion.konnte.nicht.ausgefuhrt.werden.bitte"));
|
||||
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' ? 'Lokaler Entwurf' : 'Veröffentlichte Strecke');
|
||||
appendText(label, 'small', routeSource(route) === 'local' ? ns.I18n.t("app.eigener.entwurf") : ns.I18n.t("editor-ui.veroffentlichte.strecke"));
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = classes.iconRow;
|
||||
actions.appendChild(iconButton('Strecke bearbeiten', '✎', 'edit-route'));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor-ui.strecke.bearbeiten"), '✎', 'edit-route'));
|
||||
if (routeSource(route) === 'local') {
|
||||
actions.appendChild(iconButton('Strecke löschen', '×', 'delete-route', false, true));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor-ui.strecke.loschen"), '×', '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', `Streckenpunkt ${index + 1}`);
|
||||
appendText(label, 'small', ns.I18n.t("editor-ui.station", { value1: index + 1 }));
|
||||
const actions = document.createElement('div');
|
||||
actions.className = classes.iconRow;
|
||||
actions.appendChild(iconButton(`${title} nach oben schieben`, '↑', 'move-poi-up', index === 0));
|
||||
actions.appendChild(iconButton(`${title} nach unten schieben`, '↓', 'move-poi-down', index === state.pois.length - 1));
|
||||
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(`${title} löschen`, '×', 'delete-poi', false, true));
|
||||
actions.appendChild(iconButton(ns.I18n.t("editor-ui.loschen", { 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, 'Veröffentlichte Strecken', serverRoutes, 'Keine veröffentlichte Strecke vorhanden.');
|
||||
renderRouteGroup(list, 'Lokale Strecken', localRoutes, 'Noch kein lokaler Entwurf vorhanden.');
|
||||
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"));
|
||||
}
|
||||
|
||||
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', 'Noch keine Streckenpunkte angelegt.');
|
||||
appendText(item, 'span', ns.I18n.t("editor-ui.noch.keine.stationen.angelegt"));
|
||||
list.appendChild(item);
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +437,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
async function loadRoutes() {
|
||||
setStatus('Strecken werden geladen …');
|
||||
setStatus(ns.I18n.t("editor-ui.strecken.werden.geladen"));
|
||||
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) === 'Unbenannte Strecke' ? '' : routeName(route);
|
||||
byId(ids.routeName).value = routeName(route) === ns.I18n.t("editor-ui.unbenannte.strecke") ? '' : 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 ? 'Bereits übertragen' : 'An Server übertragen';
|
||||
if (uploadButton) uploadButton.textContent = uploaded ? ns.I18n.t("editor-ui.schon.zur.prufung.gesendet") : ns.I18n.t("editor-ui.zur.prufung.senden");
|
||||
byId(ids.routeActivated).textContent = uploaded
|
||||
? `Lokaler Entwurf. Bereits als unveröffentlichte Server-Strecke ${route.serverRouteId} zur Prüfung übertragen.`
|
||||
: 'Lokaler Entwurf: GPX, Texte und Medien bleiben im Browser, bis „An Server übertragen“ gewählt wird.';
|
||||
? ns.I18n.t("editor-ui.diese.strecke.wurde.zur.prufung.gesendet.andere")
|
||||
: ns.I18n.t("editor-ui.dieser.entwurf.bleibt.auf.deinem.gerat.mit");
|
||||
} else {
|
||||
setHidden(uploadButton, true);
|
||||
setDisabled(uploadButton, true);
|
||||
byId(ids.routeActivated).textContent = route?.activatedAt || route?.activated_at
|
||||
? `Veröffentlicht am ${route.activatedAt || route.activated_at}. Der Editor verändert die Freigabe nicht.`
|
||||
: 'Server-Strecke ohne Freigabedatum. Der Editor verändert activated_at nicht.';
|
||||
? ns.I18n.t("editor-ui.diese.strecke.ist.fur.alle.freigegeben.und")
|
||||
: ns.I18n.t("editor-ui.diese.strecke.wurde.noch.nicht.fur.alle");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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', `${label}: Entfernen-Schaltfläche ${state.selectedMediaKey === key ? 'ausblenden' : 'anzeigen'}`);
|
||||
select.setAttribute('aria-label', ns.I18n.t("editor-ui.knopf.zum.entfernen", { value1: label, value2: state.selectedMediaKey === key ? 'ausblenden' : 'anzeigen' }));
|
||||
|
||||
if (kind === 'image' && previewUrl) {
|
||||
const image = document.createElement('img');
|
||||
@@ -603,7 +603,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
preview.appendChild(mediaPreviewCard({
|
||||
key: 'existing-audio',
|
||||
kind: 'audio',
|
||||
label: poi?.audio?.name || 'Audioaufnahme',
|
||||
label: poi?.audio?.name || 'Tonaufnahme',
|
||||
source: 'existing'
|
||||
}));
|
||||
}
|
||||
@@ -612,7 +612,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
preview.appendChild(mediaPreviewCard({
|
||||
key: 'pending-audio',
|
||||
kind: 'audio',
|
||||
label: state.pendingAudioFile.name || 'Neue Audioaufnahme',
|
||||
label: state.pendingAudioFile.name || ns.I18n.t("editor-ui.neue.tonaufnahme"),
|
||||
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 || `Bild ${index + 1}`,
|
||||
label: image.name || image.caption || ns.I18n.t("editor-ui.bild", { 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 || `Neues Bild ${index + 1}`,
|
||||
label: pending.file?.name || ns.I18n.t("editor-ui.neues.bild", { value1: index + 1 }),
|
||||
mediaIndex: index,
|
||||
previewUrl: pending.previewUrl || null,
|
||||
source: 'pending'
|
||||
@@ -651,12 +651,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const imageCount = existingImages + state.pendingImages.length;
|
||||
const parts = [];
|
||||
|
||||
if (audioCount) parts.push(`${audioCount} Audio${audioCount === 1 ? 'aufnahme' : 'aufnahmen'}`);
|
||||
if (imageCount) parts.push(`${imageCount} Bild${imageCount === 1 ? '' : 'er'}`);
|
||||
if (audioCount) parts.push(ns.I18n.t('media.audio.count', { count: audioCount }));
|
||||
if (imageCount) parts.push(ns.I18n.t('media.image.count', { count: imageCount }));
|
||||
|
||||
status.textContent = parts.length
|
||||
? `${parts.join(' · ')}. Vorschau antippen, um die Entfernen-Schaltfläche einzublenden.`
|
||||
: 'Noch keine Medien vorhanden.';
|
||||
? 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");
|
||||
}
|
||||
|
||||
function openFilePicker(inputId) {
|
||||
@@ -668,12 +668,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (kind === 'audio') {
|
||||
return {
|
||||
multiple: false,
|
||||
types: [{ description: 'Audio', accept: { 'audio/*': ['.mp3', '.m4a', '.wav', '.ogg', '.webm'] } }]
|
||||
types: [{ description: ns.I18n.t("editor-ui.tonaufnahmen"), accept: { 'audio/*': ['.mp3', '.m4a', '.wav', '.ogg', '.webm'] } }]
|
||||
};
|
||||
}
|
||||
return {
|
||||
multiple: true,
|
||||
types: [{ description: 'Bilder', accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.webp'] } }]
|
||||
types: [{ description: ns.I18n.t("editor-ui.bilder"), 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) === 'Unbenannter Streckenpunkt' ? '' : poiTitle(poi);
|
||||
byId(ids.poiTitle).value = poiTitle(poi) === ns.I18n.t("editor-ui.station.ohne.namen") ? '' : 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);
|
||||
@@ -759,12 +759,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
async function loadRoute(route) {
|
||||
const id = routeId(route);
|
||||
if (!id) return;
|
||||
setStatus('Streckendetails werden geladen …');
|
||||
setStatus(ns.I18n.t("editor.route.loading"));
|
||||
state.currentSource = routeSource(route);
|
||||
|
||||
if (state.currentSource === 'local') {
|
||||
const routePackage = await ns.LocalRoutes.getWithMedia(id, { requestPermission: true });
|
||||
if (!routePackage) throw new Error('Der lokale Entwurf wurde nicht gefunden.');
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor-ui.dein.entwurf.wurde.nicht.gefunden"));
|
||||
state.currentRoute = {
|
||||
...routePackage.route,
|
||||
id: routePackage.id,
|
||||
@@ -792,7 +792,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
gpx: ns.LocalRoutes.emptyGpx('Neue Strecke'),
|
||||
gpx: ns.LocalRoutes.emptyGpx(ns.I18n.t("editor-ui.neue.strecke")),
|
||||
_source: 'local'
|
||||
};
|
||||
state.currentPoi = null;
|
||||
@@ -800,7 +800,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
fillRouteForm(state.currentRoute);
|
||||
renderPoiList();
|
||||
showRouteView();
|
||||
setStatus('Neue Strecke wird als lokaler GPX-/JSON-Entwurf angelegt.');
|
||||
setStatus(ns.I18n.t("editor-ui.deine.neue.strecke.bleibt.zuerst.auf.diesem"));
|
||||
}
|
||||
|
||||
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('Speichern erfolgreich.');
|
||||
setStatus(ns.I18n.t("editor-ui.gespeichert"));
|
||||
}
|
||||
|
||||
async function saveServerRoute(values) {
|
||||
const id = routeId(state.currentRoute);
|
||||
if (!id) throw new Error('Neue Strecken werden ausschließlich lokal angelegt.');
|
||||
if (!id) throw new Error(ns.I18n.t("editor-ui.lege.eine.neue.strecke.zuerst.auf.diesem"));
|
||||
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('Speichern erfolgreich.');
|
||||
setStatus(ns.I18n.t("editor-ui.gespeichert"));
|
||||
}
|
||||
|
||||
async function saveRoute() {
|
||||
const values = routeFromForm();
|
||||
if (!values.name) throw new Error('Der Name der Strecke ist erforderlich.');
|
||||
if (!values.name) throw new Error(ns.I18n.t("editor-ui.gib.deiner.strecke.einen.namen"));
|
||||
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 || 'Unbenannte Strecke');
|
||||
data.append('name', routePackage.route?.name || ns.I18n.t("editor-ui.unbenannte.strecke"));
|
||||
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(`Die Mediendatei ${media?.name || ''} konnte nicht für den Upload vorbereitet werden.`.trim());
|
||||
throw new Error(ns.I18n.t("editor-ui.die.datei.konnte.nicht.gesendet.werden.bitte", { 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('Der Server hat keine Strecken-ID für den Upload zurückgegeben.');
|
||||
if (!createdRouteId) throw new Error(ns.I18n.t("editor-ui.die.strecke.konnte.nicht.gesendet.werden.bitte"));
|
||||
|
||||
const orderedPois = [...(routePackage.pois || [])]
|
||||
.sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
||||
for (const [index, poi] of orderedPois.entries()) {
|
||||
const poiTitle = poi.title || `Streckenpunkt ${index + 1}`;
|
||||
const poiTitle = poi.title || ns.I18n.t("editor-ui.station", { 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(`Für ${poiTitle} wurde keine Server-ID zurückgegeben.`);
|
||||
if (!serverPoiId) throw new Error(ns.I18n.t("editor-ui.die.station.konnte.nicht.gesendet.werden.bitte", { 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('Nur lokale Strecken können hochgeladen werden.');
|
||||
if (state.currentSource !== 'local') throw new Error(ns.I18n.t("editor-ui.du.kannst.nur.deine.eigenen.entwurfe.zur"));
|
||||
const values = routeFromForm();
|
||||
if (!values.name) throw new Error('Der Name der Strecke ist erforderlich.');
|
||||
if (!values.name) throw new Error(ns.I18n.t("editor-ui.gib.deiner.strecke.einen.namen"));
|
||||
if (state.currentRoute?.serverRouteId) {
|
||||
setStatus('Diese Strecke wurde bereits an den Server übertragen.');
|
||||
setStatus(ns.I18n.t("editor-ui.diese.strecke.wurde.schon.zur.prufung.gesendet"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -963,10 +963,10 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
setDisabled(uploadButton, true);
|
||||
try {
|
||||
await saveLocalRoute(values);
|
||||
setStatus('Übertragung läuft …');
|
||||
setStatus(ns.I18n.t("editor-ui.deine.strecke.wird.gesendet"));
|
||||
const localId = routeId(state.currentRoute);
|
||||
const routePackage = ns.LocalRoutes.get(localId);
|
||||
if (!routePackage) throw new Error('Der lokale Streckenentwurf wurde nicht gefunden.');
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor-ui.dein.entwurf.wurde.nicht.gefunden"));
|
||||
|
||||
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('Upload erfolgreich.');
|
||||
setStatus(ns.I18n.t("editor-ui.zur.prufung.gesendet"));
|
||||
} 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('Für einen neuen Streckenpunkt muss zuerst eine aktuelle GPS-Position verfügbar sein.', true);
|
||||
setStatus(ns.I18n.t("editor-ui.fur.eine.neue.station.brauchen.wir.deinen"), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1010,20 +1010,20 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
lon,
|
||||
triggerRadiusM: Number.isFinite(accuracy) && accuracy > 0 ? accuracy : 25
|
||||
});
|
||||
byId(ids.poiFormTitle).textContent = 'Streckenpunkt hinzufügen';
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor-ui.station.hinzufugen");
|
||||
showPoiView();
|
||||
}
|
||||
|
||||
function editPoi(poi) {
|
||||
state.currentPoi = poi;
|
||||
fillPoiForm(poi);
|
||||
byId(ids.poiFormTitle).textContent = 'Streckenpunkt bearbeiten';
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor-ui.station.bearbeiten");
|
||||
showPoiView();
|
||||
}
|
||||
|
||||
async function mediaFromLocalForm(existingPoi, poiIdentifier) {
|
||||
const routeIdentifier = routeId(state.currentRoute);
|
||||
if (!routeIdentifier) throw new Error('Speichere zuerst die Strecke.');
|
||||
if (!routeIdentifier) throw new Error(ns.I18n.t("editor-ui.speichere.zuerst.die.strecke"));
|
||||
|
||||
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('Speichere zuerst die Strecke.');
|
||||
if (!id) throw new Error(ns.I18n.t("editor-ui.speichere.zuerst.die.strecke"));
|
||||
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('Für einen Streckenpunkt sind gültige Breiten- und Längengrade erforderlich.');
|
||||
throw new TypeError(ns.I18n.t("editor-ui.der.ort.dieser.station.fehlt.erlaube.deinen"));
|
||||
}
|
||||
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('Streckenpunkt und zugehörige Mediendaten lokal gespeichert.');
|
||||
setStatus(ns.I18n.t("editor-ui.station.mit.bildern.und.ton.auf.diesem"));
|
||||
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('Speichere zuerst die Strecke.');
|
||||
if (!currentRouteId) throw new Error(ns.I18n.t("editor-ui.speichere.zuerst.die.strecke"));
|
||||
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('Streckenpunkt gespeichert.');
|
||||
setStatus(ns.I18n.t("editor-ui.station.gespeichert"));
|
||||
await loadRoute({ id: currentRouteId, _source: 'server' });
|
||||
}
|
||||
|
||||
async function savePoi() {
|
||||
const values = poiFromForm();
|
||||
if (!values.title) throw new Error('Der Titel des Streckenpunkts ist erforderlich.');
|
||||
if (!values.title) throw new Error(ns.I18n.t("editor-ui.gib.deiner.station.einen.namen"));
|
||||
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' ? 'Audioaufnahme entfernt.' : 'Bild entfernt.');
|
||||
setStatus(kind === 'audio' ? ns.I18n.t("editor.audio.removed") : ns.I18n.t("editor-ui.bild.entfernt"));
|
||||
}
|
||||
|
||||
async function removeExistingServerMedia(kind, mediaId) {
|
||||
const currentRouteId = routeId(state.currentRoute);
|
||||
const currentPoiId = poiId(state.currentPoi);
|
||||
if (!currentRouteId || !currentPoiId) throw new Error('Der Streckenpunkt ist noch nicht gespeichert.');
|
||||
if (!currentRouteId || !currentPoiId) throw new Error(ns.I18n.t("editor-ui.die.station.ist.noch.nicht.gespeichert"));
|
||||
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('Das Bild besitzt keine Server-ID und kann nicht entfernt werden.');
|
||||
if (!mediaId) throw new Error(ns.I18n.t("editor-ui.das.bild.konnte.nicht.entfernt.werden.lade"));
|
||||
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' ? 'Audioaufnahme entfernt.' : 'Bild entfernt.');
|
||||
setStatus(kind === 'audio' ? ns.I18n.t("editor.audio.removed") : ns.I18n.t("editor-ui.bild.entfernt"));
|
||||
}
|
||||
|
||||
async function removeMedia(control) {
|
||||
@@ -1266,13 +1266,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
await Promise.allSettled(removedMedia.map(ns.LocalRoutes.removeMedia));
|
||||
renderPoiList();
|
||||
showRouteView();
|
||||
setStatus('Streckenpunkt entfernt und lokale Strecke aktualisiert.');
|
||||
setStatus(ns.I18n.t("editor-ui.station.entfernt.deine.strecke.wurde.gespeichert"));
|
||||
return;
|
||||
}
|
||||
|
||||
await sendDelete(`/routes/${encodeURIComponent(currentRouteId)}/pois/${encodeURIComponent(currentPoiId)}`);
|
||||
await loadRoute({ id: currentRouteId, _source: 'server' });
|
||||
setStatus('Streckenpunkt entfernt.');
|
||||
setStatus(ns.I18n.t("editor-ui.station.entfernt"));
|
||||
}
|
||||
|
||||
function reorderPois(index, offset) {
|
||||
@@ -1295,7 +1295,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (!currentRouteId) return;
|
||||
if (state.currentSource === 'local') {
|
||||
await persistLocalCurrentRoute();
|
||||
setStatus('Lokale Streckenpunkt-Reihenfolge gespeichert.');
|
||||
setStatus(ns.I18n.t("editor-ui.reihenfolge.der.stationen.gespeichert"));
|
||||
return;
|
||||
}
|
||||
const jobs = state.pois.map((poi, index) => {
|
||||
@@ -1305,7 +1305,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
return sendJson(path, 'PUT', { ...poi, sequence: index, sortOrder: index });
|
||||
}).filter(Boolean);
|
||||
await Promise.all(jobs);
|
||||
setStatus('Reihenfolge gespeichert.');
|
||||
setStatus(ns.I18n.t("editor.order.saved"));
|
||||
}
|
||||
|
||||
function routeForItem(target) {
|
||||
@@ -1340,7 +1340,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
await loadRoutes();
|
||||
showListView();
|
||||
setStatus('Strecke gelöscht.');
|
||||
setStatus(ns.I18n.t("editor-ui.strecke.geloscht"));
|
||||
}
|
||||
|
||||
async function routeListAction(event) {
|
||||
@@ -1387,7 +1387,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
await ns.StorageUsage?.refresh();
|
||||
} catch (error) {
|
||||
ns.StorageUsage?.reportError(error);
|
||||
setStatus(error.message || 'Die Aktion konnte nicht ausgeführt werden.', true);
|
||||
setStatus(ns.UserMessages?.error(error) || ns.I18n.t("editor-ui.das.hat.leider.nicht.geklappt.bitte.versuche"), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
function unsupportedError() {
|
||||
return new Error('Dieses Gerät unterstützt keine Geolokalisierung.');
|
||||
return new Error(ns.I18n.t("geolocation.dieses.gerat.kann.deinen.standort.nicht.finden"));
|
||||
}
|
||||
|
||||
ns.Geo = {
|
||||
|
||||
+11
-11
@@ -4,18 +4,18 @@ globalThis.Wegwichtel ??= {};
|
||||
'use strict';
|
||||
|
||||
const entries = Object.freeze([
|
||||
['🪵', 'Holz'],
|
||||
['🪨', 'Felsen'],
|
||||
['🏮', 'Laterne'],
|
||||
['🌼', 'Blume'],
|
||||
['🚬', 'Pfeife'],
|
||||
['🐦', 'Vogel'],
|
||||
['🍄', 'Pilz'],
|
||||
['🍓', 'Erdbeere'],
|
||||
['🎃', 'Kürbis']
|
||||
].map(([unicode, label]) => 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"]
|
||||
].map(([unicode, labelKey]) => Object.freeze({
|
||||
unicode,
|
||||
label,
|
||||
get label() { return ns.I18n.t(labelKey); },
|
||||
imageUrl: `/images/group-symbols/${unicode}.png`
|
||||
})));
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
globalThis.Wegwichtel ??= {};
|
||||
(function (ns) {
|
||||
'use strict';
|
||||
|
||||
class WegwichtelI18n {
|
||||
static #languages = new Map([['de', ns.TextsDe.messages]]);
|
||||
static #language = 'de';
|
||||
|
||||
static get language() { return this.#language; }
|
||||
|
||||
static register(language, messages) {
|
||||
this.#languages.set(language.toLowerCase(), Object.freeze({ ...messages }));
|
||||
}
|
||||
|
||||
static setLanguage(language, root = globalThis.document) {
|
||||
const requested = String(language).toLowerCase();
|
||||
const base = requested.split('-')[0];
|
||||
this.#language = 'de';
|
||||
if (this.#languages.has(base)) this.#language = base;
|
||||
if (this.#languages.has(requested)) this.#language = requested;
|
||||
this.apply(root);
|
||||
return this.#language;
|
||||
}
|
||||
|
||||
static t(key, variables = {}) {
|
||||
let value = this.#languages.get(this.#language)?.[key] ?? ns.TextsDe.messages[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) => {
|
||||
if (!Object.hasOwn(variables, name)) return placeholder;
|
||||
return String(variables[name] ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
for (const attribute of ['title', 'aria-label', 'placeholder', 'alt']) {
|
||||
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}`)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ns.I18n = WegwichtelI18n;
|
||||
WegwichtelI18n.apply();
|
||||
}(globalThis.Wegwichtel));
|
||||
+20
-20
@@ -5,15 +5,15 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
const DROPDOWN_ANIMATION_MS = 180;
|
||||
const pageTitles = Object.freeze({
|
||||
privacy: 'Datenschutzerklärung',
|
||||
imprint: 'Impressum',
|
||||
technology: 'Cookies und Technik'
|
||||
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"); }
|
||||
});
|
||||
const menuEntries = Object.freeze([
|
||||
['privacy', 'Datenschutzerklärung'],
|
||||
['imprint', 'Impressum'],
|
||||
['technology', 'Cookies und verwendete JavaScripts']
|
||||
]);
|
||||
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")]
|
||||
]; }
|
||||
const selectors = Object.freeze({
|
||||
content: '.info-dialog-content',
|
||||
cookieToggle: '.cookie-toggle',
|
||||
@@ -159,8 +159,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
id: 'info-button',
|
||||
className: 'info-button',
|
||||
type: 'button',
|
||||
'aria-label': 'Informationsmenü öffnen',
|
||||
title: 'Informationen'
|
||||
'aria-label': ns.I18n.t("legal-ui.hilfe.und.informationen.offnen"),
|
||||
title: ns.I18n.t("legal-ui.informationen")
|
||||
});
|
||||
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': 'Rechtliche und technische Informationen'
|
||||
'aria-label': ns.I18n.t("legal-ui.rechtliche.und.technische.informationen")
|
||||
});
|
||||
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', 'Informationsmenü öffnen');
|
||||
setAttribute(infoButton, 'aria-label', ns.I18n.t("legal-ui.hilfe.und.informationen.offnen"));
|
||||
}
|
||||
|
||||
function ensureInfoButtonIcon(infoButton) {
|
||||
@@ -201,9 +201,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function configureDropdown(dropdown) {
|
||||
if (!dropdown) return;
|
||||
setAttribute(dropdown, 'aria-label', 'Rechtliche und technische Informationen');
|
||||
setAttribute(dropdown, 'aria-label', ns.I18n.t("legal-ui.rechtliche.und.technische.informationen"));
|
||||
setHidden(dropdown, true);
|
||||
menuEntries.forEach(([target, label]) => ensureMenuButton(dropdown, target, label));
|
||||
menuEntries().forEach(([target, label]) => ensureMenuButton(dropdown, target, label));
|
||||
}
|
||||
|
||||
function ensureMenuButton(dropdown, target, label) {
|
||||
@@ -277,7 +277,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function configureBackButton(backButton) {
|
||||
setAttribute(backButton, 'type', 'button');
|
||||
setAttribute(backButton, 'aria-label', 'Zurück zum Informationsmenü');
|
||||
setAttribute(backButton, 'aria-label', ns.I18n.t("legal-ui.zuruck.zum.informationsmenu"));
|
||||
setHidden(backButton, true);
|
||||
return backButton;
|
||||
}
|
||||
@@ -293,8 +293,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const closeButton = createElement('button', {
|
||||
id: 'info-close',
|
||||
type: 'button',
|
||||
'aria-label': 'Informationsfenster schließen',
|
||||
title: 'Schließen'
|
||||
'aria-label': ns.I18n.t("legal-ui.informationsfenster.schliessen"),
|
||||
title: ns.I18n.t("legal-ui.schliessen")
|
||||
}, '×');
|
||||
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', 'Informationsfenster schließen');
|
||||
setAttribute(closeButton, 'aria-label', ns.I18n.t("legal-ui.informationsfenster.schliessen"));
|
||||
if (closeButton && !String(closeButton.textContent ?? '').trim()) closeButton.textContent = '×';
|
||||
return closeButton;
|
||||
}
|
||||
@@ -335,7 +335,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function createFallbackPage(content, name) {
|
||||
const page = createElement('article', { className: 'info-page', dataset: { infoPage: name } });
|
||||
append(page, createElement('p', {}, `${pageTitles[name]} ist noch nicht hinterlegt.`));
|
||||
append(page, createElement('p', {}, ns.I18n.t("info.missing", { value1: pageTitles[name] })));
|
||||
append(content, page);
|
||||
return page;
|
||||
}
|
||||
@@ -436,7 +436,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function updateDialogTitle(pageName) {
|
||||
if (elements?.title) elements.title.textContent = pageTitles[pageName] ?? 'Informationen';
|
||||
if (elements?.title) elements.title.textContent = pageTitles[pageName] ?? ns.I18n.t("legal-ui.informationen");
|
||||
}
|
||||
|
||||
function focusSafely(element) {
|
||||
|
||||
+32
-32
@@ -15,7 +15,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function storage() {
|
||||
const target = globalThis.localStorage;
|
||||
if (!target) throw new Error('Lokaler Browser-Speicher ist auf diesem Gerät nicht verfügbar.');
|
||||
if (!target) throw new Error(ns.I18n.t("local-routes.deine.strecke.kann.auf.diesem.gerat.gerade"));
|
||||
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('Der lokale Medienspeicher (IndexedDB) ist in diesem Browser nicht verfügbar.'));
|
||||
return Promise.reject(new Error(ns.I18n.t("local-routes.bilder.und.ton.konnen.auf.diesem.gerat")));
|
||||
}
|
||||
|
||||
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('Der lokale Medienspeicher konnte nicht geöffnet werden.'));
|
||||
reject(request.error || new Error(ns.I18n.t("local-routes.gespeicherte.bilder.und.ton.konnten.nicht.geoffnet")));
|
||||
};
|
||||
request.onblocked = () => {
|
||||
mediaDatabasePromise = null;
|
||||
reject(new Error('Der lokale Medienspeicher ist durch eine andere Browser-Sitzung blockiert.'));
|
||||
reject(new Error(ns.I18n.t("local-routes.schliesse.andere.fenster.mit.wegwichtel.und.versuche")));
|
||||
};
|
||||
});
|
||||
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('Der lokale Medienspeicher konnte nicht gelesen oder geschrieben werden.'));
|
||||
transaction.onabort = () => reject(transaction.error || new Error('Die Medien-Speicheroperation wurde abgebrochen.'));
|
||||
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")));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
};
|
||||
} catch (error) {
|
||||
if (error?.name === 'QuotaExceededError') {
|
||||
throw new Error('Der lokale Medienspeicher ist voll. Entferne nicht mehr benötigte lokale Medien oder Strecken.', { cause: error });
|
||||
throw new Error(ns.I18n.t("local-routes.der.platz.fur.bilder.und.ton.ist"), { 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'
|
||||
? 'Der lokale Medienspeicher ist voll.'
|
||||
: 'Die Mediendatei konnte nicht lokal gespeichert werden.';
|
||||
throw new Error(`${prefix} Entferne nicht mehr benötigte lokale Medien oder Strecken.`, { cause: error });
|
||||
? 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 });
|
||||
}
|
||||
return {
|
||||
id: String(mediaId),
|
||||
@@ -135,12 +135,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
permission = await handle.requestPermission({ mode: 'read' });
|
||||
}
|
||||
if (permission !== 'granted') {
|
||||
throw new Error('Der Zugriff auf eine lokale Mediendatei muss erneut erlaubt werden.');
|
||||
throw new Error(ns.I18n.t("local-routes.erlaube.noch.einmal.den.zugriff.auf.das"));
|
||||
}
|
||||
try {
|
||||
return await handle.getFile();
|
||||
} catch (error) {
|
||||
throw new Error('Eine referenzierte Mediendatei ist auf dem Gerät nicht mehr verfügbar.', { cause: error });
|
||||
throw new Error(ns.I18n.t("local-routes.ein.ausgewahltes.bild.oder.eine.tonaufnahme.wurde"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
if (typeof cryptoApi?.getRandomValues === 'function') {
|
||||
return `local-${uuidFromRandomValues(cryptoApi)}`;
|
||||
}
|
||||
throw new Error('Keine sichere Zufallsquelle für eine lokale Strecken-ID verfügbar.');
|
||||
throw new Error(ns.I18n.t("local-routes.auf.diesem.gerat.kann.gerade.keine.neue"));
|
||||
}
|
||||
|
||||
function createPoiId() {
|
||||
@@ -255,7 +255,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function emptyGpx(name = 'Neue Strecke') {
|
||||
function emptyGpx(name = ns.I18n.t("editor-ui.neue.strecke")) {
|
||||
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('Die GPX-Datei ist nicht gültig.');
|
||||
throw new Error(ns.I18n.t("local-routes.diese.datei.enthalt.keinen.lesbaren.weg.wahle"));
|
||||
}
|
||||
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') || `Streckenpunkt ${index + 1}`,
|
||||
title: textOfChild(node, 'name') || ns.I18n.t("editor-ui.station", { value1: index + 1 }),
|
||||
description: textOfChild(node, 'desc'),
|
||||
lat: numberAttribute(node, 'lat'),
|
||||
lon: numberAttribute(node, 'lon'),
|
||||
@@ -360,11 +360,11 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function syncGpx(gpx, route, pois) {
|
||||
const routeName = route?.name || 'Neue Strecke';
|
||||
const routeName = route?.name || ns.I18n.t("editor-ui.neue.strecke");
|
||||
const document = parseXml(gpx || emptyGpx(routeName));
|
||||
updateMetadataName(document, routeName);
|
||||
|
||||
// Wegwichtel verwaltet neue Streckenpunkte direkt als Punkte im GPX-Track.
|
||||
// Wegwichtel verwaltet neue Stationen direkt als Punkte im GPX-Track.
|
||||
// Fremde/importierte GPX-Punkte ohne Wegwichtel-ID bleiben erhalten.
|
||||
Array.from(document.getElementsByTagNameNS('*', 'wpt')).forEach(node => node.remove());
|
||||
Array.from(document.getElementsByTagNameNS('*', 'trkpt'))
|
||||
@@ -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 || `Streckenpunkt ${index + 1}`;
|
||||
const title = poi.title || ns.I18n.t("editor-ui.station", { value1: index + 1 });
|
||||
if (!Number.isFinite(Number(poi.lat)) || !Number.isFinite(Number(poi.lon))) {
|
||||
throw new TypeError(`Für ${title} fehlen gültige Koordinaten.`);
|
||||
throw new TypeError(ns.I18n.t("local-routes.der.ort.von.fehlt", { value1: title }));
|
||||
}
|
||||
|
||||
const gpxPoint = createElement(document, 'trkpt');
|
||||
@@ -469,9 +469,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
ns.StorageUsage?.reportError(error);
|
||||
const name = error?.name === 'QuotaExceededError'
|
||||
? 'Der lokale Speicher für Streckendaten ist voll.'
|
||||
: 'Die lokale Strecke konnte nicht gespeichert werden.';
|
||||
throw new Error(`${name} Entferne nicht mehr benötigte lokale Strecken.`, { cause: error });
|
||||
? 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 });
|
||||
}
|
||||
|
||||
notify(id, 'saved');
|
||||
@@ -514,9 +514,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function gpxForUpload(routePackage) {
|
||||
if (!routePackage?.gpx) throw new Error('Für die lokale Strecke ist keine GPX-Datei vorhanden.');
|
||||
if (!routePackage?.gpx) throw new Error(ns.I18n.t("local-routes.der.weg.deiner.strecke.fehlt"));
|
||||
if (!routePoints(routePackage.gpx).length) {
|
||||
throw new Error('Die lokale Strecke enthält noch keine Streckenpunkte im GPX-Track.');
|
||||
throw new Error(ns.I18n.t("local-routes.deine.strecke.hat.noch.keinen.aufgezeichneten.weg"));
|
||||
}
|
||||
return routePackage.gpx;
|
||||
}
|
||||
@@ -577,7 +577,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
function appPoi(poi) {
|
||||
return {
|
||||
id: Number(poi.id),
|
||||
title: poi.title || 'Unbenannter Streckenpunkt',
|
||||
title: poi.title || ns.I18n.t("editor-ui.station.ohne.namen"),
|
||||
description: poi.description || '',
|
||||
lat: Number(poi.lat),
|
||||
lon: Number(poi.lon),
|
||||
@@ -604,9 +604,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
id: String(routePackage.id),
|
||||
source: 'local',
|
||||
local: true,
|
||||
name: routePackage.route?.name || 'Unbenannte Strecke',
|
||||
name: routePackage.route?.name || ns.I18n.t("editor-ui.unbenannte.strecke"),
|
||||
description: routePackage.route?.description || '',
|
||||
schoolName: 'Lokal gespeichert',
|
||||
schoolName: ns.I18n.t("local.device"),
|
||||
status: 'local',
|
||||
activatedAt: null,
|
||||
points,
|
||||
@@ -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('Die lokale Strecke wurde nicht gefunden.');
|
||||
if (!routePackage) throw new Error(ns.I18n.t("local-routes.deine.strecke.wurde.nicht.gefunden"));
|
||||
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('Die ausgewählte Datei unterstützt Blob.text() nicht.'));
|
||||
return Promise.reject(new TypeError(ns.I18n.t("local-routes.diese.datei.kann.auf.deinem.gerat.nicht")));
|
||||
}
|
||||
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('Die Datei konnte nicht als Data-URL gelesen werden.'));
|
||||
reject(new TypeError(ns.I18n.t("local-routes.diese.datei.konnte.nicht.geoffnet.werden.wahle")));
|
||||
return;
|
||||
}
|
||||
resolve({
|
||||
@@ -657,7 +657,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
type: file.type || 'application/octet-stream'
|
||||
});
|
||||
}, { once: true });
|
||||
reader.addEventListener('error', () => reject(reader.error || new Error('Datei konnte nicht gelesen werden.')), { once: true });
|
||||
reader.addEventListener('error', () => reject(reader.error || new Error(ns.I18n.t("file.read.failed"))), { once: true });
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
+25
-25
@@ -44,7 +44,7 @@ globalThis.Wegwichtel ??= {};
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
navigator.serviceWorker.removeEventListener('controllerchange', changed);
|
||||
reject(new Error('Offline-Speicher konnte nicht aktiviert werden. Bitte erneut versuchen.'));
|
||||
reject(new Error(ns.I18n.t("offline-cache.das.speichern.fur.unterwegs.hat.nicht.geklappt")));
|
||||
}, 10000);
|
||||
function changed() {
|
||||
clearTimeout(timeout);
|
||||
@@ -64,32 +64,32 @@ globalThis.Wegwichtel ??= {};
|
||||
let stage = null;
|
||||
try {
|
||||
await worker();
|
||||
announce('Offline-Dateien werden vorbereitet …');
|
||||
announce(ns.I18n.t("offline-cache.wegwichtel.wird.fur.unterwegs.vorbereitet"));
|
||||
// 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('Die Liste der Offline-Dateien konnte nicht geladen werden.');
|
||||
if (!manifestResponse.ok) throw new Error(ns.I18n.t("offline-cache.wegwichtel.konnte.nicht.fur.unterwegs.geladen.werden"));
|
||||
const manifest = await manifestResponse.json();
|
||||
if (!Array.isArray(manifest.resources) || !manifest.resources.length) throw new TypeError('Ungültige Offline-Dateiliste.');
|
||||
if (!Array.isArray(manifest.resources) || !manifest.resources.length) throw new TypeError(ns.I18n.t("offline-cache.wegwichtel.konnte.nicht.fur.unterwegs.geladen.werden.2"));
|
||||
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(`Offline-Dateien speichern: ${index + 1} von ${urls.length}`);
|
||||
announce(ns.I18n.t("offline-cache.fur.unterwegs.speichern.von", { 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(`${saved.count} Dateien sind offline verfügbar.`);
|
||||
announce(ns.I18n.t("offline-cache.wegwichtel.ist.fur.unterwegs.bereit"));
|
||||
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('Download abgebrochen.');
|
||||
else announce(`${error.message} ${saved ? 'Der bisherige Offline-Stand bleibt erhalten.' : 'Die App ist noch nicht vollständig offline verfügbar.'}`);
|
||||
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")}`);
|
||||
throw error;
|
||||
} finally {
|
||||
task = null;
|
||||
@@ -109,7 +109,7 @@ globalThis.Wegwichtel ??= {};
|
||||
if (task) await task.catch(() => {});
|
||||
await ns.OfflineStore.clear();
|
||||
await updateControls();
|
||||
announce('App-Dateien wurden entfernt. Gespeicherte Strecken bleiben erhalten.');
|
||||
announce(ns.I18n.t("offline-cache.die.gespeicherte.app.wurde.entfernt.deine.strecken"));
|
||||
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 ? 'Offline-Strecke aktualisieren' : 'Strecke offline speichern';
|
||||
save.textContent = saved ? ns.I18n.t("offline-cache.gespeicherte.strecke.erneuern") : ns.I18n.t("offline-cache.fur.unterwegs.speichern");
|
||||
}
|
||||
if (remove) { remove.hidden = !saved; remove.disabled = Boolean(routeTask); }
|
||||
if (message && !routeTask) message.textContent = saved
|
||||
? `${saved.count} Dateien dieser Strecke sind auf dem Gerät gespeichert.`
|
||||
: 'Speichert nur diese Strecke mit GPX, Bildern und Audio für die App.';
|
||||
? ns.I18n.t("offline-cache.diese.strecke.ist.fur.unterwegs.bereit")
|
||||
: ns.I18n.t("offline-cache.nimm.diese.strecke.mit.bildern.und.tonaufnahmen");
|
||||
updateGpxDownload(route);
|
||||
}
|
||||
|
||||
@@ -147,9 +147,9 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
async function saveRoute(route) {
|
||||
if (routeTask) throw new Error('Eine Strecke wird bereits gespeichert.');
|
||||
if (!supported()) throw new Error('Offline-Speicherung benötigt HTTPS und Browser-Unterstützung.');
|
||||
if (!route || route.source === 'local') throw new Error('Bitte eine veröffentlichte Strecke auswählen.');
|
||||
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"));
|
||||
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('Die Strecke konnte nicht geladen werden.');
|
||||
if (!response.ok) throw new Error(ns.I18n.t("offline-cache.die.strecke.konnte.nicht.geladen.werden"));
|
||||
const fresh = await response.clone().json();
|
||||
if (String(fresh.id) !== String(route.id) || !fresh.activatedAt) throw new Error('Nur veröffentlichte Strecken können offline gespeichert werden.');
|
||||
if (!fresh.gpxUrl) throw new Error('Für diese Strecke ist keine GPX-Datei vorhanden.');
|
||||
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"));
|
||||
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 = `Strecke speichern: ${index + 1} von ${urls.length}`;
|
||||
if (message && selectedRoute === route) message.textContent = ns.I18n.t("offline-cache.strecke.speichern.von", { 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('Bitte den laufenden Download abwarten.');
|
||||
if (routeTask) throw new Error(ns.I18n.t("offline-cache.bitte.warte.bis.das.speichern.fertig.ist"));
|
||||
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 = `${error.message} Ein bisheriger Offline-Stand bleibt erhalten.`;
|
||||
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") });
|
||||
});
|
||||
};
|
||||
document.getElementById('route-offline-save')?.addEventListener('click', () => run(saveRoute));
|
||||
@@ -226,7 +226,7 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
if (!supported()) {
|
||||
if (toggle) toggle.disabled = true;
|
||||
announce('Offline-Speicherung ist in diesem Browser oder ohne HTTPS nicht verfügbar.');
|
||||
announce(ns.I18n.t("offline-cache.auf.diesem.gerat.lasst.sich.wegwichtel.gerade"));
|
||||
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(`${saved.count} Dateien sind offline verfügbar.`);
|
||||
announce(ns.I18n.t("offline-cache.wegwichtel.ist.fur.unterwegs.bereit"));
|
||||
} else {
|
||||
announce('Speichert nur die App-Dateien auf diesem Gerät. Strecken kannst du einzeln speichern.');
|
||||
announce(ns.I18n.t("offline-cache.speichere.zuerst.die.app.danach.kannst.du"));
|
||||
}
|
||||
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(error.message); });
|
||||
initialized ??= setup().catch(error => { announce(ns.UserMessages?.error(error) || ns.I18n.t("offline-cache.das.speichern.fur.unterwegs.hat.nicht.geklappt")); });
|
||||
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('Nur Ressourcen dieser App sind zulässig.');
|
||||
if (url.origin !== globalThis.location.origin) throw new TypeError(ns.I18n.t("offline-storage.inhalte.von.anderen.seiten.konnen.hier.nicht"));
|
||||
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(`Download fehlgeschlagen: ${url}`);
|
||||
if (!response.ok || response.type === 'opaque') throw new Error(ns.I18n.t("offline-storage.ein.teil.konnte.nicht.gespeichert.werden.prufe"));
|
||||
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 || 'Bild zum Streckenpunkt')
|
||||
.attr('alt', image.caption || ns.I18n.t("slideshow.bild.zur.station"))
|
||||
.off('error.wegwichtel')
|
||||
.on('error.wegwichtel', function () {
|
||||
$(this).removeAttr('src').attr('alt', 'Bild konnte nicht geladen werden');
|
||||
$(this).removeAttr('src').attr('alt', ns.I18n.t("slideshow.bild.konnte.nicht.geladen.werden"));
|
||||
});
|
||||
$('#slide-caption').text(image.caption || '');
|
||||
$('#slide-position').text(`${index + 1} / ${images.length}`);
|
||||
|
||||
@@ -7,7 +7,7 @@ globalThis.Wegwichtel ??= {};
|
||||
let refreshTask = null;
|
||||
|
||||
function format(bytes) {
|
||||
return `${(bytes / 1024 / 1024).toLocaleString('de-DE', { maximumFractionDigits: 1 })} MiB`;
|
||||
return `${(bytes / 1000000).toLocaleString(ns.I18n.language, { maximumFractionDigits: 1 })} MB`;
|
||||
}
|
||||
|
||||
function localBytes() {
|
||||
@@ -34,15 +34,15 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
function siteDescription(usage, quota, known) {
|
||||
if (!known) return 'Der Browser meldet keine Speicherkapazität.';
|
||||
const warning = usage / quota >= .9 ? ' Speicher fast voll.' : '';
|
||||
return `Website-Speicher: ${format(usage)} von ${format(quota)} belegt (${Math.round(usage / quota * 100)} %). Etwa ${format(Math.max(0, quota - usage))} verfügbar.${warning}`;
|
||||
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 });
|
||||
}
|
||||
|
||||
function localDescription(local) {
|
||||
if (local == null) return 'Local Storage ist nicht verfügbar.';
|
||||
const warning = local >= LOCAL_REFERENCE * .9 ? ' Richtwert fast erreicht: Große Bilder können das Speichern verhindern.' : '';
|
||||
return `Local Storage: ungefähr ${format(local)} belegt. Balken bezogen auf einen Richtwert von 5 MiB; die tatsächliche Grenze hängt vom Browser ab.${warning}`;
|
||||
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 });
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
@@ -69,7 +69,7 @@ globalThis.Wegwichtel ??= {};
|
||||
let current = error;
|
||||
while (current) {
|
||||
if (current.name === 'QuotaExceededError') {
|
||||
lastError = 'Speicher voll: Entferne nicht mehr benötigte Offline-Strecken oder lokale Entwürfe. Eine Browser-Quote oder die separate Local-Storage-Grenze kann erreicht sein.';
|
||||
lastError = ns.I18n.t("storage-usage.der.platz.ist.voll.entferne.gespeicherte.strecken");
|
||||
break;
|
||||
}
|
||||
current = current.cause;
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
globalThis.Wegwichtel ??= {};
|
||||
(function (ns) {
|
||||
'use strict';
|
||||
|
||||
class WegwichtelTextsDe {
|
||||
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."
|
||||
});
|
||||
}
|
||||
|
||||
ns.TextsDe = WegwichtelTextsDe;
|
||||
}(globalThis.Wegwichtel));
|
||||
@@ -0,0 +1,50 @@
|
||||
globalThis.Wegwichtel ??= {};
|
||||
(function (ns) {
|
||||
'use strict';
|
||||
|
||||
function messageText(error) {
|
||||
const value = error?.responseJSON?.message || error?.message || error;
|
||||
if (typeof value !== 'string') return '';
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return typeof parsed.message === 'string' ? parsed.message : '';
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
];
|
||||
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"]
|
||||
]);
|
||||
|
||||
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;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function errorMessage(error, fallback = ns.I18n.t("editor-ui.das.hat.leider.nicht.geklappt.bitte.versuche")) {
|
||||
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");
|
||||
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");
|
||||
return readableMessage(message, fallback);
|
||||
}
|
||||
|
||||
ns.UserMessages = Object.freeze({ error: errorMessage });
|
||||
}(globalThis.Wegwichtel));
|
||||
@@ -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('Das Vibrationsmuster muss aus nichtnegativen Millisekunden bestehen.');
|
||||
throw new TypeError(ns.I18n.t("vibration.das.vibrationsmuster.muss.aus.nichtnegativen.millisekunden.bestehen"));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
const duration = Number(selected);
|
||||
if (!Number.isFinite(duration) || duration < 0) {
|
||||
throw new TypeError('Die Vibrationsdauer muss eine nichtnegative Millisekundenangabe sein.');
|
||||
throw new TypeError(ns.I18n.t("vibration.die.vibrationsdauer.muss.eine.nichtnegative.millisekundenangabe.sein"));
|
||||
}
|
||||
return [duration];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user