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
+32 -32
View File
@@ -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);
});
}