Merge: i18n fallback fix + missing projekte.* translations

This commit is contained in:
m
2026-04-20 17:06:38 +02:00

View File

@@ -728,6 +728,113 @@ const translations: Record<Lang, Record<string, string>> = {
"dezernat.add": "Hinzuf\u00fcgen",
"dezernat.remove": "Entfernen",
"dezernat.confirm_remove": "Mitglied entfernen?",
"projekte.title": "Projekte \u2014 Paliad",
"projekte.heading": "Projekte",
"projekte.subtitle": "Mandanten, Streitsachen, Patente und F\u00e4lle \u2014 hierarchisch organisiert.",
"projekte.new": "Neues Projekt",
"projekte.search.placeholder": "Titel, Referenz oder ClientMatter...",
"projekte.filter.type": "Typ",
"projekte.filter.type.all": "Alle Typen",
"projekte.filter.status": "Status",
"projekte.filter.status.all": "Alle Status",
"projekte.filter.status.active": "Aktiv",
"projekte.filter.status.archived": "Archiviert",
"projekte.filter.status.closed": "Abgeschlossen",
"projekte.filter.view": "Ansicht",
"projekte.view.flat": "Flache Liste",
"projekte.view.roots": "Nur Wurzeln",
"projekte.unavailable": "Projektverwaltung zurzeit nicht verf\u00fcgbar \u2014 bitte Administrator kontaktieren.",
"projekte.col.title": "Titel",
"projekte.col.type": "Typ",
"projekte.col.reference": "Referenz",
"projekte.col.clientmatter": "ClientMatter",
"projekte.col.status": "Status",
"projekte.col.updated": "Zuletzt ge\u00e4ndert",
"projekte.empty.title": "Noch kein Projekt angelegt",
"projekte.empty.hint": "Starten Sie \u00fcber \u201eNeues Projekt\u201c \u2014 legen Sie zuerst einen Mandanten an, darunter Streitsachen, Patente und F\u00e4lle.",
"projekte.empty.filtered": "Keine Treffer f\u00fcr diese Filter.",
"projekte.cancel": "Abbrechen",
"projekte.submit": "Projekt anlegen",
"projekte.neu.title": "Neues Projekt \u2014 Paliad",
"projekte.neu.heading": "Neues Projekt anlegen",
"projekte.neu.subtitle": "Mandant, Streitsache, Patent, Verfahren oder generisches Projekt \u2014 hierarchisch einordnen. Sichtbarkeit folgt dem Team (Sie werden als \u201eLead\u201c automatisch hinzugef\u00fcgt).",
"projekte.field.type": "Typ",
"projekte.field.parent": "\u00dcbergeordnetes Projekt",
"projekte.field.parent.placeholder": "Titel eingeben, um ein \u00dcberprojekt zu suchen...",
"projekte.field.parent.hint": "Leer lassen f\u00fcr ein Wurzel-Projekt (typisch: Mandant).",
"projekte.field.title": "Titel",
"projekte.field.title.placeholder": "z.B. Siemens AG | Siemens v. Huawei | EP 1 234 567",
"projekte.field.reference": "Interne Referenz (optional)",
"projekte.field.reference.placeholder": "z.B. HL-2026-0042",
"projekte.field.client_number": "Client-Nr. (7 Ziffern)",
"projekte.field.matter_number": "Matter-Nr. (7 Ziffern)",
"projekte.field.clientmatter.hint": "HLC-Billing-Nummern. Format CCCCCCC.MMMMMMM. Client-Nr. wird an Unterprojekte vererbt (\u00fcberschreibbar).",
"projekte.field.netdocuments_url": "netDocuments-URL (optional)",
"projekte.field.industry": "Branche",
"projekte.field.country": "Land (ISO-2)",
"projekte.field.billing_reference": "Billing-Referenz",
"projekte.field.patent_number": "Patentnummer",
"projekte.field.filing_date": "Anmeldetag",
"projekte.field.grant_date": "Erteilungstag",
"projekte.field.court": "Gericht",
"projekte.field.case_number": "Aktenzeichen (Gericht)",
"projekte.field.status": "Status",
"projekte.error.title_required": "Titel erforderlich",
"projekte.detail.title": "Projekt \u2014 Paliad",
"projekte.detail.back": "\u2190 Zur\u00fcck zur \u00dcbersicht",
"projekte.detail.loading": "L\u00e4dt\u2026",
"projekte.detail.notfound": "Projekt nicht gefunden oder keine Berechtigung.",
"projekte.detail.edit": "Bearbeiten",
"projekte.detail.save": "Speichern",
"projekte.detail.tab.verlauf": "Verlauf",
"projekte.detail.tab.team": "Team",
"projekte.detail.tab.kinder": "Untergeordnet",
"projekte.detail.tab.parteien": "Parteien",
"projekte.detail.tab.fristen": "Fristen",
"projekte.detail.tab.termine": "Termine",
"projekte.detail.tab.notizen": "Notizen",
"projekte.detail.tab.checklisten": "Checklisten",
"projekte.detail.verlauf.empty": "Noch keine Ereignisse aufgezeichnet.",
"projekte.detail.verlauf.loadMore": "Mehr laden",
"projekte.detail.team.form.user": "Benutzer",
"projekte.detail.team.form.role": "Rolle",
"projekte.detail.team.form.cancel": "Abbrechen",
"projekte.detail.team.form.submit": "Hinzuf\u00fcgen",
"projekte.detail.team.col.name": "Name",
"projekte.detail.team.col.role": "Rolle",
"projekte.detail.team.col.source": "Herkunft",
"projekte.detail.kinder.add": "Untervorhaben anlegen",
"projekte.detail.kinder.empty": "Keine untergeordneten Projekte.",
"projekte.detail.parteien.add": "Partei hinzuf\u00fcgen",
"projekte.detail.parteien.form.name": "Name",
"projekte.detail.parteien.form.role": "Rolle",
"projekte.detail.parteien.form.rep": "Vertreter (optional)",
"projekte.detail.parteien.form.cancel": "Abbrechen",
"projekte.detail.parteien.form.submit": "Hinzuf\u00fcgen",
"projekte.detail.parteien.role.claimant": "Kl\u00e4ger",
"projekte.detail.parteien.role.defendant": "Beklagter",
"projekte.detail.parteien.role.thirdparty": "Streitverk\u00fcndeter / Drittpartei",
"projekte.detail.parteien.col.name": "Name",
"projekte.detail.parteien.col.role": "Rolle",
"projekte.detail.parteien.col.rep": "Vertreter",
"projekte.detail.parteien.empty": "Noch keine Parteien eingetragen.",
"projekte.detail.fristen.add": "Frist hinzuf\u00fcgen",
"projekte.detail.fristen.empty": "F\u00fcr dieses Projekt sind noch keine Fristen erfasst.",
"projekte.detail.termine.add": "Termin hinzuf\u00fcgen",
"projekte.detail.termine.form.cancel": "Abbrechen",
"projekte.detail.termine.form.submit": "Hinzuf\u00fcgen",
"projekte.detail.termine.empty": "F\u00fcr dieses Projekt sind noch keine Termine erfasst.",
"projekte.detail.checklisten.empty": "F\u00fcr dieses Projekt sind noch keine Checklisten-Instanzen erfasst.",
"projekte.detail.checklisten.col.template": "Vorlage",
"projekte.detail.checklisten.col.name": "Name",
"projekte.detail.checklisten.col.progress": "Fortschritt",
"projekte.detail.checklisten.col.created": "Angelegt",
"projekte.detail.checklisten.hint": "Instanzen werden auf der Vorlagen-Seite unter Checklisten angelegt.",
"projekte.detail.delete": "Projekt archivieren",
"projekte.detail.delete.confirm.title": "Projekt wirklich archivieren?",
"projekte.detail.delete.confirm.body": "Das Projekt wird archiviert. Es kann nicht direkt wiederhergestellt werden.",
"projekte.detail.delete.confirm.cancel": "Abbrechen",
"projekte.detail.delete.confirm.ok": "Archivieren",
"projekte.type.client": "Mandant",
"projekte.type.litigation": "Streitsache",
"projekte.type.patent": "Patent",
@@ -1622,6 +1729,113 @@ const translations: Record<Lang, Record<string, string>> = {
"dezernat.add": "Add",
"dezernat.remove": "Remove",
"dezernat.confirm_remove": "Remove member?",
"projekte.title": "Projects \u2014 Paliad",
"projekte.heading": "Projects",
"projekte.subtitle": "Clients, litigations, patents and cases \u2014 organised hierarchically.",
"projekte.new": "New project",
"projekte.search.placeholder": "Title, reference or ClientMatter...",
"projekte.filter.type": "Type",
"projekte.filter.type.all": "All types",
"projekte.filter.status": "Status",
"projekte.filter.status.all": "All statuses",
"projekte.filter.status.active": "Active",
"projekte.filter.status.archived": "Archived",
"projekte.filter.status.closed": "Closed",
"projekte.filter.view": "View",
"projekte.view.flat": "Flat list",
"projekte.view.roots": "Roots only",
"projekte.unavailable": "Project management is currently unavailable \u2014 please contact an administrator.",
"projekte.col.title": "Title",
"projekte.col.type": "Type",
"projekte.col.reference": "Reference",
"projekte.col.clientmatter": "ClientMatter",
"projekte.col.status": "Status",
"projekte.col.updated": "Last modified",
"projekte.empty.title": "No projects yet",
"projekte.empty.hint": "Start via \u201cNew project\u201d \u2014 create a client first, then litigations, patents and cases underneath.",
"projekte.empty.filtered": "No matches for these filters.",
"projekte.cancel": "Cancel",
"projekte.submit": "Create project",
"projekte.neu.title": "New project \u2014 Paliad",
"projekte.neu.heading": "Create a new project",
"projekte.neu.subtitle": "Client, litigation, patent, case or generic project \u2014 place it in the hierarchy. Visibility follows the team (you are auto-added as \u201cLead\u201d).",
"projekte.field.type": "Type",
"projekte.field.parent": "Parent project",
"projekte.field.parent.placeholder": "Type to search for a parent project...",
"projekte.field.parent.hint": "Leave blank for a root project (typically a client).",
"projekte.field.title": "Title",
"projekte.field.title.placeholder": "e.g. Siemens AG | Siemens v. Huawei | EP 1 234 567",
"projekte.field.reference": "Internal reference (optional)",
"projekte.field.reference.placeholder": "e.g. HL-2026-0042",
"projekte.field.client_number": "Client no. (7 digits)",
"projekte.field.matter_number": "Matter no. (7 digits)",
"projekte.field.clientmatter.hint": "HLC billing numbers. Format CCCCCCC.MMMMMMM. Client no. is inherited by sub-projects (overridable).",
"projekte.field.netdocuments_url": "netDocuments URL (optional)",
"projekte.field.industry": "Industry",
"projekte.field.country": "Country (ISO-2)",
"projekte.field.billing_reference": "Billing reference",
"projekte.field.patent_number": "Patent number",
"projekte.field.filing_date": "Filing date",
"projekte.field.grant_date": "Grant date",
"projekte.field.court": "Court",
"projekte.field.case_number": "Case number (court)",
"projekte.field.status": "Status",
"projekte.error.title_required": "Title required",
"projekte.detail.title": "Project \u2014 Paliad",
"projekte.detail.back": "\u2190 Back to overview",
"projekte.detail.loading": "Loading\u2026",
"projekte.detail.notfound": "Project not found or no access.",
"projekte.detail.edit": "Edit",
"projekte.detail.save": "Save",
"projekte.detail.tab.verlauf": "Activity",
"projekte.detail.tab.team": "Team",
"projekte.detail.tab.kinder": "Sub-projects",
"projekte.detail.tab.parteien": "Parties",
"projekte.detail.tab.fristen": "Deadlines",
"projekte.detail.tab.termine": "Appointments",
"projekte.detail.tab.notizen": "Notes",
"projekte.detail.tab.checklisten": "Checklists",
"projekte.detail.verlauf.empty": "No events recorded yet.",
"projekte.detail.verlauf.loadMore": "Load more",
"projekte.detail.team.form.user": "User",
"projekte.detail.team.form.role": "Role",
"projekte.detail.team.form.cancel": "Cancel",
"projekte.detail.team.form.submit": "Add",
"projekte.detail.team.col.name": "Name",
"projekte.detail.team.col.role": "Role",
"projekte.detail.team.col.source": "Source",
"projekte.detail.kinder.add": "Create sub-project",
"projekte.detail.kinder.empty": "No sub-projects.",
"projekte.detail.parteien.add": "Add party",
"projekte.detail.parteien.form.name": "Name",
"projekte.detail.parteien.form.role": "Role",
"projekte.detail.parteien.form.rep": "Representative (optional)",
"projekte.detail.parteien.form.cancel": "Cancel",
"projekte.detail.parteien.form.submit": "Add",
"projekte.detail.parteien.role.claimant": "Claimant",
"projekte.detail.parteien.role.defendant": "Defendant",
"projekte.detail.parteien.role.thirdparty": "Third-party / intervenor",
"projekte.detail.parteien.col.name": "Name",
"projekte.detail.parteien.col.role": "Role",
"projekte.detail.parteien.col.rep": "Representative",
"projekte.detail.parteien.empty": "No parties recorded yet.",
"projekte.detail.fristen.add": "Add deadline",
"projekte.detail.fristen.empty": "No deadlines recorded for this project.",
"projekte.detail.termine.add": "Add appointment",
"projekte.detail.termine.form.cancel": "Cancel",
"projekte.detail.termine.form.submit": "Add",
"projekte.detail.termine.empty": "No appointments recorded for this project.",
"projekte.detail.checklisten.empty": "No checklist instances recorded for this project.",
"projekte.detail.checklisten.col.template": "Template",
"projekte.detail.checklisten.col.name": "Name",
"projekte.detail.checklisten.col.progress": "Progress",
"projekte.detail.checklisten.col.created": "Created",
"projekte.detail.checklisten.hint": "Instances are created on the template page under Checklists.",
"projekte.detail.delete": "Archive project",
"projekte.detail.delete.confirm.title": "Archive project?",
"projekte.detail.delete.confirm.body": "The project will be archived. It cannot be directly restored.",
"projekte.detail.delete.confirm.cancel": "Cancel",
"projekte.detail.delete.confirm.ok": "Archive",
"projekte.type.client": "Client",
"projekte.type.litigation": "Litigation",
"projekte.type.patent": "Patent",
@@ -1803,6 +2017,12 @@ export function t(key: string): string {
return translations[currentLang][key] ?? translations.de[key] ?? key;
}
// tOrEmpty returns the translation if present, else "" — so callers that
// want to fall back to the existing default text in the DOM can do so.
function tOrEmpty(key: string): string {
return translations[currentLang][key] ?? translations.de[key] ?? "";
}
export function getLang(): Lang {
return currentLang;
}
@@ -1822,17 +2042,22 @@ export function setLang(lang: Lang) {
}
function applyTranslations() {
// When a key is missing from every locale, preserve whatever static text
// the HTML was authored with — never overwrite with the raw key string.
document.querySelectorAll<HTMLElement>("[data-i18n]").forEach((el) => {
const key = el.getAttribute("data-i18n")!;
el.textContent = t(key);
const val = tOrEmpty(key);
if (val !== "") el.textContent = val;
});
document.querySelectorAll<HTMLElement>("[data-i18n-placeholder]").forEach((el) => {
const key = el.getAttribute("data-i18n-placeholder")!;
(el as HTMLInputElement).placeholder = t(key);
const val = tOrEmpty(key);
if (val !== "") (el as HTMLInputElement).placeholder = val;
});
document.querySelectorAll<HTMLElement>("[data-i18n-title]").forEach((el) => {
const key = el.getAttribute("data-i18n-title")!;
el.setAttribute("title", t(key));
const val = tOrEmpty(key);
if (val !== "") el.setAttribute("title", val);
});
}