Files
wordarr/static/i18n.js
T

487 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// UI strings for both languages. Keys are grouped by the view they belong to;
// {placeholders} are filled by t().
const STRINGS = {
de: {
"common.cancel": "Abbrechen",
"common.save": "Speichern",
"common.create": "Anlegen",
"common.request": "Anfragen",
"common.search": "Suchen",
"common.all": "Alle",
"common.none": "Keine",
"common.close": "Schließen",
"common.edit": "Bearbeiten",
"common.delete": "Löschen",
"common.link": "Verbinden",
"common.createAndLink": "Anlegen & verbinden",
"common.selectAll": "Alle auswählen",
"common.removeSelected": "Ausgewählte entfernen",
"common.removeSelectedN": "Ausgewählte entfernen ({n})",
"common.library": "Library",
"common.type": "Typ",
"common.title": "Titel",
"common.authors": "Autor(en)",
"common.author": "Autor",
"common.narrator": "Sprecher",
"common.series": "Serie",
"common.volume": "Band",
"common.year": "Jahr",
"common.name": "Name",
"common.path": "Pfad",
"common.language": "Sprache",
"common.score": "Score",
"common.status": "Status",
"common.noLibrary": "— keine Library —",
"common.nothingHere": "Nichts hier.",
"common.optionalForAll": "optional, für alle",
"common.optional": "optional",
"common.needLibraryFirst": "Erst eine Library für diesen Typ anlegen (Tab Libraries)",
"common.creating": "Lege an…",
"lang.german": "Deutsch",
"lang.english": "Englisch",
"nav.search": "Suche",
"nav.missing": "Missing",
"nav.imported": "Importiert",
"nav.import": "Import",
"nav.settings": "Libraries",
"nav.langToggle": "Switch to English",
"search.languageTitle": "Sprache der Ausgabe — filtert Ebook- und Audiobook-Suche",
"search.allLanguages": "Alle Sprachen",
"search.placeholder": "Titel, Autor oder ISBN…",
"search.manual": "Manuell anlegen",
"search.running": "Suche…",
"search.wholeSeries": "Ganze Serie…",
"search.requested": "✓ Angefragt",
"search.emptyEbook": "Keine Treffer. Tipp: ISBN ohne Bindestriche oder Titel + Autor versuchen — und ggf. „Alle Sprachen“ wählen.",
"search.emptyAudiobook": "Keine Treffer. Tipp: EAN/ISBN funktioniert bei Audible nicht — nach Titel/Autor suchen.",
"search.emptyComic": "Keine Treffer bei AniList (Manga). Westliche Comics per „Manuell anlegen“ erfassen.",
"manual.heading": "Manuell anfragen",
"manual.volumeTo": "bis Band (Bulk, optional)",
"manual.bulkHint": "Legt pro Band/Folge eine Anfrage an, z.B. 1 bis 300",
"manual.externalId": "ISBN/ID",
"manual.noLibraryChosen": "Keine Library gewählt",
"manual.needStartVolume": "Für Bulk bitte Start-Band angeben",
"manual.created": "Anfrage angelegt",
"manual.createdN": "{n} Anfragen angelegt",
"series.fallbackTitle": "Serie",
"series.from": "von Nr.",
"series.to": "bis Nr.",
"series.loading": "{unit} werden gesucht…",
"series.unitVolumes": "Bände",
"series.unitEpisodes": "Folgen",
"series.found": "{n} {unit} gefunden",
"series.range": " (Nr. {from}{to})",
"series.noteEbook": ". Bände ohne Ausgabe in der gewählten Sprache stehen mit ihrem Originaltitel da.",
"series.noteAudiobook": ". Audible hat keine Serien-Abfrage — einzelne Folgen können fehlen.",
"series.gapVolumes": "… {n} Bände nicht bei Hardcover gefunden ({from}{to})",
"series.gapVolume": "… {n} Band nicht bei Hardcover gefunden ({from}{to})",
"series.gapEpisodes": "… {n} Folgen nicht bei Audible gefunden ({from}{to})",
"series.gapEpisode": "… {n} Folge nicht bei Audible gefunden ({from}{to})",
"series.requestN": "{n} anfragen",
"series.needLibrary": "Erst eine {type}-Library anlegen (Tab Libraries)",
"series.createdN": "{n} Anfragen angelegt",
"series.skippedN": ", {n} bereits vorhanden",
"list.allTypes": "Alle Typen",
"list.allLibraries": "Alle Libraries",
"list.filterPlaceholder": "Filtern (Titel/Autor/Serie)…",
"list.view": "Ansicht",
"list.viewList": "Liste",
"list.viewSmall": "Kleine Symbole",
"list.viewLarge": "Große Symbole",
"list.confirmDeleteRequests": "{n} Anfrage(n) entfernen?",
"list.confirmDeleteImported": "{n} Eintrag/Einträge entfernen? Die importierten Dateien in der Library bleiben erhalten.",
"list.deletedN": "{n} entfernt",
"detail.statusMissing": "fehlt noch",
"detail.statusImported": "importiert",
"detail.externalId": "ISBN/ASIN",
"detail.retag": "Speichern & neu taggen",
"detail.relocate": "Speichern & neu ablegen",
"detail.relocateTitle": "Ordner und Dateinamen nach dem Schema der Library neu anlegen",
"detail.relocating": "Lege ab…",
"detail.relocated": "Neu abgelegt: {dest}",
"detail.alreadyPlaced": "Liegt bereits richtig",
"detail.retagging": "Tagge…",
"detail.retagged": "{n} Datei(en) neu getaggt",
"detail.saved": "Anfrage gespeichert",
"detail.duplicateConfirm": "Trotzdem anlegen?",
"import.scan": "Download-Ordner scannen",
"import.splitDirs": "Ordner als Einzeldateien behandeln (z.B. Folgen-Sammlungen)",
"import.scanning": "Scanne…",
"import.candidates": "{n} Kandidat(en) in {dir}",
"import.remaining": "{n} Kandidat(en) übrig — für den aktuellen Stand erneut scannen",
"import.filterPlaceholder": "Filtern (Name/Pfad)…",
"import.modeSuggested": "Nur mit Vorschlag",
"import.modeUnassigned": "Nur ohne Zuordnung",
"import.modeConflict": "Nur Konflikte",
"import.formatTitle": "Nur Einträge mit diesem Dateiformat zeigen",
"import.allFormats": "Alle Formate",
"import.sortScan": "Reihenfolge: Scan",
"import.sortScore": "Score absteigend",
"import.sortName": "Name AZ",
"import.selectSuggested": "Alle mit Vorschlag auswählen",
"import.selectAllTitle": "Alle Einträge der aktuellen Ansicht auswählen, auch ohne Zuordnung",
"import.selectPage": "Diese Seite auswählen",
"import.selectPageTitle": "Nur die Einträge dieser Seite auswählen, alle anderen abwählen",
"import.deselectAll": "Alle abwählen",
"import.merge": "Ausgewählte zusammenfassen",
"import.mergeTitle": "Mehrteiler: ausgewählte Einträge als ein Hörbuch importieren",
"import.fromNames": "Anfragen aus Ordnernamen",
"import.fromNamesTitle": "Für ausgewählte Einträge ohne Zuordnung Anfragen aus den Ordnernamen anlegen",
"import.colFile": "Datei/Ordner",
"import.colAssignment": "Zuordnung",
"import.prev": "← Zurück",
"import.next": "Weiter →",
"import.run": "Ausgewählte importieren",
"import.skip": "— überspringen —",
"import.appendGroup": "Bereits importiert — Teile anhängen",
"import.quickTitle": "Metadaten suchen und Anfrage direkt verbinden",
"import.unmerge": "Zusammenfassung wieder auflösen",
"import.splitDir": "Ordner in einzelne Titel aufteilen",
"import.parts": "{parts} Teile · {files} Dateien",
"import.maybeSeparate": "{n} Titel?",
"import.maybeSeparateTitle": "Die {n} Dateien tragen verschiedene Titel und sind je groß genug für ein ganzes Buch — mit ✂️ aufteilen",
"import.libraryCellTitle": "Ziel-Library dieser Anfrage ändern",
"import.conflict": "⚠ Konflikt",
"import.conflictTitle": "Mehrere Einträge zeigen auf dieselbe Anfrage",
"import.running": "läuft…",
"import.done": "✓ importiert",
"import.failed": "✗ Fehler",
"import.pageInfo": "Seite {page}/{pages} · {total} Einträge{filtered} · {selected} ausgewählt",
"import.filtered": " (gefiltert: {n})",
"import.movedToLibrary": "„{title}\" → {library}",
"import.selectedN": "{n} Einträge {what} ausgewählt",
"import.selectedWithout": ", davon {n} ohne Zuordnung",
"import.thisPage": "dieser Seite",
"import.inTotal": "insgesamt",
"import.nothingSelected": "Nichts ausgewählt",
"import.needTwo": "Mindestens zwei Einträge auswählen",
"import.sameTypeOnly": "Nur Einträge desselben Typs lassen sich zusammenfassen",
"import.differentFolders": "Die Einträge liegen in verschiedenen Ordnern",
"import.mergedN": "{n} Einträge als „{name}\" zusammengefasst ({files} Dateien)",
"import.splitN": "„{name}\" in {n} Einzeltitel aufgeteilt",
"import.nothingImported": "Nichts importiert: alle ausgewählten Einträge waren mehreren Anfragen zugeordnet — Filter „Nur Konflikte\" zeigt sie.",
"import.summary": "{n} importiert",
"import.summaryFailed": ", {n} Fehler",
"import.summarySkipped": "{n} Anfrage(n) waren mehreren Einträgen zugeordnet und wurden übersprungen — Filter „Nur Konflikte\" zeigt sie.",
"import.finished": "Import fertig",
"import.finishedFailed": "Import fertig, {n} Fehler",
"import.progressDone": "{total}/{total} · fertig",
"names.heading": "Anfragen aus Ordnernamen",
"names.intro": "{n} Einträge ohne Zuordnung ({type}). Titel und Folgennummer kommen aus dem Ordnernamen; Serie und Autor gelten für alle.",
"names.more": "… und {n} weitere",
"names.noneWithout": "Keine ausgewählten Einträge ohne Zuordnung",
"names.result": "{created} Anfragen angelegt{skipped}, {linked} Einträge verbunden",
"names.existed": ", {n} gab es schon",
"conflict.heading": "Mehrfach zugeordnete Anfragen",
"conflict.intro": "{n} Anfrage(n) sind mehreren Einträgen zugeordnet. Gehören die Einträge zusammen (Mehrteiler), lassen sie sich als ein Hörbuch importieren — sonst überspringst du sie, korrigierst die Zuordnung und importierst sie später.",
"conflict.merge": "Als je ein Hörbuch zusammenfassen",
"conflict.skip": "Konflikte überspringen, Rest importieren",
"quick.heading": "Suchen & Anfragen",
"quick.placeholder": "Titel/Serie suchen…",
"quick.targetLibrary": "Ziel-Library",
"quick.openRequests": "Offene Anfragen",
"quick.reqFilter": "nach Titel, Autor, Serie filtern…",
"quick.manualSummary": "Nicht bei Audible? Manuell anlegen",
"quick.editSummary": "Felder anpassen",
"quick.episodeVolume": "Folge/Band",
"quick.searching": "Suche läuft…",
"quick.episodeNote": "Folge {n} — nicht mitgesucht",
"quick.matchingOpen": "Passende offene Requests:",
"quick.hits": "{shown} von {total} Treffern ({open} offen)",
"quick.noHits": "Kein Treffer unter {open} offenen Anfragen",
"quick.linked": "Mit bestehendem Request verbunden",
"quick.editTitle": "Felder vor dem Anlegen anpassen (z.B. Serie)",
"quick.pickTitle": "Anfrage anlegen und mit diesem Eintrag verbinden",
"quick.basedOn": "Übernommen von Audible: „{title}\" — Sprecher, Cover, Jahr und ASIN bleiben erhalten.",
"quick.requestedAndLinked": "„{title}\" angefragt und verbunden",
"quick.needTitle": "Titel angeben",
"lib.heading": "Libraries",
"lib.folderTemplate": "Ordner-Schema",
"lib.fileTemplate": "Datei-Schema",
"lib.new": "Neue Library",
"lib.edit": "Library „{name}“ bearbeiten",
"lib.namePlaceholder": "Name (z.B. Kids)",
"lib.folderPlaceholder": "Ordner-Schema (optional)",
"lib.filePlaceholder": "Datei-Schema (optional)",
"lib.languageTitle": "Sprache der Ausgaben in dieser Library — filtert die Metadaten-Suche",
"lib.anyLanguage": "Sprache: egal",
"lib.placeholders": "Platzhalter: {Author} {Authors} {Narrator} {Narrators} {Title} {Year} {Series} {Volume} — {Author}/{Narrator} nennen den ersten Namen, {Authors}/{Narrators} alle. Leere Werte fallen samt Trenner weg.",
"lib.confirmDelete": "Library löschen?",
"lib.saved": "Library gespeichert — gilt für künftige Importe",
"lib.created": "Library angelegt",
},
en: {
"common.cancel": "Cancel",
"common.save": "Save",
"common.create": "Create",
"common.request": "Request",
"common.search": "Search",
"common.all": "All",
"common.none": "None",
"common.close": "Close",
"common.edit": "Edit",
"common.delete": "Delete",
"common.link": "Link",
"common.createAndLink": "Create & link",
"common.selectAll": "Select all",
"common.removeSelected": "Remove selected",
"common.removeSelectedN": "Remove selected ({n})",
"common.library": "Library",
"common.type": "Type",
"common.title": "Title",
"common.authors": "Author(s)",
"common.author": "Author",
"common.narrator": "Narrator",
"common.series": "Series",
"common.volume": "Volume",
"common.year": "Year",
"common.name": "Name",
"common.path": "Path",
"common.language": "Language",
"common.score": "Score",
"common.status": "Status",
"common.noLibrary": "— no library —",
"common.nothingHere": "Nothing here.",
"common.optionalForAll": "optional, for all",
"common.optional": "optional",
"common.needLibraryFirst": "Create a library for this type first (Libraries tab)",
"common.creating": "Creating…",
"lang.german": "German",
"lang.english": "English",
"nav.search": "Search",
"nav.missing": "Missing",
"nav.imported": "Imported",
"nav.import": "Import",
"nav.settings": "Libraries",
"nav.langToggle": "Auf Deutsch umschalten",
"search.languageTitle": "Edition language — filters the ebook and audiobook search",
"search.allLanguages": "All languages",
"search.placeholder": "Title, author or ISBN…",
"search.manual": "Add manually",
"search.running": "Searching…",
"search.wholeSeries": "Whole series…",
"search.requested": "✓ Requested",
"search.emptyEbook": "No hits. Try the ISBN without dashes, or title + author — and maybe pick “All languages”.",
"search.emptyAudiobook": "No hits. EAN/ISBN does not work on Audible — search by title/author.",
"search.emptyComic": "No hits on AniList (manga). Add western comics via “Add manually”.",
"manual.heading": "Request manually",
"manual.volumeTo": "to volume (bulk, optional)",
"manual.bulkHint": "Creates one request per volume/episode, e.g. 1 to 300",
"manual.externalId": "ISBN/ID",
"manual.noLibraryChosen": "No library chosen",
"manual.needStartVolume": "Bulk needs a starting volume",
"manual.created": "Request created",
"manual.createdN": "{n} requests created",
"series.fallbackTitle": "Series",
"series.from": "from no.",
"series.to": "to no.",
"series.loading": "Looking for {unit}…",
"series.unitVolumes": "volumes",
"series.unitEpisodes": "episodes",
"series.found": "{n} {unit} found",
"series.range": " (no. {from}{to})",
"series.noteEbook": ". Volumes without an edition in the chosen language keep their original title.",
"series.noteAudiobook": ". Audible has no series query — single episodes can be missing.",
"series.gapVolumes": "… {n} volumes not found on Hardcover ({from}{to})",
"series.gapVolume": "… {n} volume not found on Hardcover ({from}{to})",
"series.gapEpisodes": "… {n} episodes not found on Audible ({from}{to})",
"series.gapEpisode": "… {n} episode not found on Audible ({from}{to})",
"series.requestN": "Request {n}",
"series.needLibrary": "Create an {type} library first (Libraries tab)",
"series.createdN": "{n} requests created",
"series.skippedN": ", {n} already there",
"list.allTypes": "All types",
"list.allLibraries": "All libraries",
"list.filterPlaceholder": "Filter (title/author/series)…",
"list.view": "View",
"list.viewList": "List",
"list.viewSmall": "Small icons",
"list.viewLarge": "Large icons",
"list.confirmDeleteRequests": "Remove {n} request(s)?",
"list.confirmDeleteImported": "Remove {n} entr(y/ies)? The imported files stay in the library.",
"list.deletedN": "{n} removed",
"detail.statusMissing": "still missing",
"detail.statusImported": "imported",
"detail.externalId": "ISBN/ASIN",
"detail.retag": "Save & re-tag",
"detail.relocate": "Save & re-file",
"detail.relocateTitle": "Rebuild folder and file names from the library's scheme",
"detail.relocating": "Filing…",
"detail.relocated": "Re-filed: {dest}",
"detail.alreadyPlaced": "Already in the right place",
"detail.retagging": "Tagging…",
"detail.retagged": "{n} file(s) re-tagged",
"detail.saved": "Request saved",
"detail.duplicateConfirm": "Create anyway?",
"import.scan": "Scan download folder",
"import.splitDirs": "Treat folders as single files (e.g. episode collections)",
"import.scanning": "Scanning…",
"import.candidates": "{n} candidate(s) in {dir}",
"import.remaining": "{n} candidate(s) left — scan again for the current state",
"import.filterPlaceholder": "Filter (name/path)…",
"import.modeSuggested": "With suggestion only",
"import.modeUnassigned": "Unassigned only",
"import.modeConflict": "Conflicts only",
"import.formatTitle": "Show only entries with this file format",
"import.allFormats": "All formats",
"import.sortScan": "Order: scan",
"import.sortScore": "Score descending",
"import.sortName": "Name AZ",
"import.selectSuggested": "Select all with a suggestion",
"import.selectAllTitle": "Select every entry of the current view, unassigned ones too",
"import.selectPage": "Select this page",
"import.selectPageTitle": "Select only this page's entries, deselect all others",
"import.deselectAll": "Deselect all",
"import.merge": "Merge selected",
"import.mergeTitle": "Multi-part title: import the selected entries as one audiobook",
"import.fromNames": "Requests from folder names",
"import.fromNamesTitle": "Create requests from the folder names of the selected unassigned entries",
"import.colFile": "File/folder",
"import.colAssignment": "Assignment",
"import.prev": "← Back",
"import.next": "Next →",
"import.run": "Import selected",
"import.skip": "— skip —",
"import.appendGroup": "Already imported — append parts",
"import.quickTitle": "Search metadata and link the request right away",
"import.unmerge": "Undo the merge",
"import.splitDir": "Split the folder into single titles",
"import.parts": "{parts} parts · {files} files",
"import.maybeSeparate": "{n} titles?",
"import.maybeSeparateTitle": "The {n} files carry different titles and are each big enough for a whole book — split with ✂️",
"import.libraryCellTitle": "Change this request's target library",
"import.conflict": "⚠ Conflict",
"import.conflictTitle": "Several entries point at the same request",
"import.running": "running…",
"import.done": "✓ imported",
"import.failed": "✗ Error",
"import.pageInfo": "Page {page}/{pages} · {total} entries{filtered} · {selected} selected",
"import.filtered": " (filtered: {n})",
"import.movedToLibrary": "“{title}” → {library}",
"import.selectedN": "{n} entries {what} selected",
"import.selectedWithout": ", {n} of them unassigned",
"import.thisPage": "on this page",
"import.inTotal": "in total",
"import.nothingSelected": "Nothing selected",
"import.needTwo": "Select at least two entries",
"import.sameTypeOnly": "Only entries of the same type can be merged",
"import.differentFolders": "The entries live in different folders",
"import.mergedN": "{n} entries merged as “{name}” ({files} files)",
"import.splitN": "“{name}” split into {n} single titles",
"import.nothingImported": "Nothing imported: every selected entry was assigned to several requests — the “Conflicts only” filter shows them.",
"import.summary": "{n} imported",
"import.summaryFailed": ", {n} failed",
"import.summarySkipped": "{n} request(s) were assigned to several entries and were skipped — the “Conflicts only” filter shows them.",
"import.finished": "Import finished",
"import.finishedFailed": "Import finished, {n} failed",
"import.progressDone": "{total}/{total} · done",
"names.heading": "Requests from folder names",
"names.intro": "{n} unassigned entries ({type}). Title and episode number come from the folder name; series and author apply to all of them.",
"names.more": "… and {n} more",
"names.noneWithout": "No selected entries without an assignment",
"names.result": "{created} requests created{skipped}, {linked} entries linked",
"names.existed": ", {n} already existed",
"conflict.heading": "Requests assigned more than once",
"conflict.intro": "{n} request(s) are assigned to several entries. If those entries belong together (a multi-part title), they can be imported as one audiobook — otherwise skip them, fix the assignment and import them later.",
"conflict.merge": "Merge each into one audiobook",
"conflict.skip": "Skip conflicts, import the rest",
"quick.heading": "Search & request",
"quick.placeholder": "Search title/series…",
"quick.targetLibrary": "Target library",
"quick.openRequests": "Open requests",
"quick.reqFilter": "filter by title, author, series…",
"quick.manualSummary": "Not on Audible? Add manually",
"quick.editSummary": "Adjust the fields",
"quick.episodeVolume": "Episode/volume",
"quick.searching": "Searching…",
"quick.episodeNote": "Episode {n} — not part of the search",
"quick.matchingOpen": "Matching open requests:",
"quick.hits": "{shown} of {total} hits ({open} open)",
"quick.noHits": "No hit among {open} open requests",
"quick.linked": "Linked to the existing request",
"quick.editTitle": "Adjust the fields before creating (e.g. the series)",
"quick.pickTitle": "Create the request and link it to this entry",
"quick.basedOn": "Taken from Audible: “{title}” — narrator, cover, year and ASIN are kept.",
"quick.requestedAndLinked": "“{title}” requested and linked",
"quick.needTitle": "Enter a title",
"lib.heading": "Libraries",
"lib.folderTemplate": "Folder scheme",
"lib.fileTemplate": "File scheme",
"lib.new": "New library",
"lib.edit": "Edit library “{name}”",
"lib.namePlaceholder": "Name (e.g. Kids)",
"lib.folderPlaceholder": "Folder scheme (optional)",
"lib.filePlaceholder": "File scheme (optional)",
"lib.languageTitle": "Language of this library's editions — filters the metadata search",
"lib.anyLanguage": "Language: any",
"lib.placeholders": "Placeholders: {Author} {Authors} {Narrator} {Narrators} {Title} {Year} {Series} {Volume} — {Author}/{Narrator} name the first one, {Authors}/{Narrators} all of them. Empty values drop out with their separator.",
"lib.confirmDelete": "Delete library?",
"lib.saved": "Library saved — applies to future imports",
"lib.created": "Library created",
},
};
const LANG_KEY = "wordarr.lang";
function initialLang() {
try {
const saved = localStorage.getItem(LANG_KEY);
if (saved && STRINGS[saved]) return saved;
} catch {}
return (navigator.language || "en").startsWith("de") ? "de" : "en";
}
let lang = initialLang();
// {Placeholders} of the naming scheme must survive, so only known vars are replaced
function t(key, vars) {
let s = STRINGS[lang][key] ?? STRINGS.en[key] ?? key;
if (vars) {
for (const [name, value] of Object.entries(vars)) {
s = s.split("{" + name + "}").join(String(value));
}
}
return s;
}
const I18N_ATTRS = { "i18nPlaceholder": "placeholder", "i18nTitle": "title", "i18nAria": "aria-label" };
function applyI18n(root = document) {
root.querySelectorAll("[data-i18n]").forEach((el) => (el.textContent = t(el.dataset.i18n)));
for (const [data, attr] of Object.entries(I18N_ATTRS)) {
root.querySelectorAll(`[data-${data.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase())}]`)
.forEach((el) => el.setAttribute(attr, t(el.dataset[data])));
}
document.documentElement.lang = lang;
}
function setLang(next) {
lang = next;
try { localStorage.setItem(LANG_KEY, next); } catch {}
applyI18n();
if (typeof onLangChange === "function") onLangChange();
}