Internationalization
Sonarqube Scanner / Prepare environment (push) Successful in 20s
Sonarqube Scanner / Build and test (push) Failing after 36s
Sonarqube Scanner / SonarQube analysis (push) Skipped
Sonarqube Scanner / Deploy to Server (push) Successful in 9s

This commit is contained in:
Florian Zumpe
2026-10-01 09:41:01 +02:00
parent 607e5450e1
commit aff88eba1b
41 changed files with 1368 additions and 636 deletions
+25 -25
View File
@@ -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,