From 61af4825c613beb46a5bc31129ba208be936cb7d Mon Sep 17 00:00:00 2001 From: kimatata <117462761+kimatata@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:14:38 +0900 Subject: [PATCH] feat: add support for German language (de) (#369) Co-authored-by: Erik Berkemeyer --- frontend/config/selection.ts | 1 + frontend/messages/de.json | 408 ++++++++++++++++++++++++++++++ frontend/messages/strings.test.ts | 4 + frontend/src/i18n/routing.ts | 2 +- frontend/src/middleware.ts | 2 +- frontend/types/locale.ts | 2 +- 6 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 frontend/messages/de.json diff --git a/frontend/config/selection.ts b/frontend/config/selection.ts index 177f2ac..660e97c 100644 --- a/frontend/config/selection.ts +++ b/frontend/config/selection.ts @@ -12,6 +12,7 @@ const categoricalPalette = ['#fba91e', '#6ea56c', '#3ac6e1', '#feda2f', '#f15f47 const locales: LocaleType[] = [ { code: 'en', name: 'English' }, { code: 'ja', name: '日本語' }, + { code: 'de', name: 'Deutsch' }, { code: 'pt-BR', name: 'Português' }, { code: 'zh-CN', name: '简体中文' }, ]; diff --git a/frontend/messages/de.json b/frontend/messages/de.json new file mode 100644 index 0000000..65eb4e3 --- /dev/null +++ b/frontend/messages/de.json @@ -0,0 +1,408 @@ +{ + "RunStatus": { + "new": "Neu", + "inProgress": "In Bearbeitung", + "underReview": "In Überprüfung", + "rejected": "Abgelehnt", + "done": "Fertig", + "closed": "Geschlossen" + }, + "RunCaseStatus": { + "untested": "Nicht getestet", + "passed": "Bestanden", + "failed": "Fehlgeschlagen", + "retest": "Erneut testen", + "skipped": "Übersprungen" + }, + "Priority": { + "critical": "Kritisch", + "high": "Hoch", + "medium": "Mittel", + "low": "Niedrig" + }, + "Type": { + "other": "Sonstiges", + "security": "Sicherheit", + "performance": "Leistung", + "accessibility": "Barrierefreiheit", + "functional": "Funktional", + "acceptance": "Abnahme", + "usability": "Benutzbarkeit", + "smoke_sanity": "Smoke & Sanity", + "compatibility": "Kompatibilität", + "destructive": "Destruktiv", + "regression": "Regression", + "automated": "Automatisiert", + "manual": "Manuell" + }, + "ProjectDialog": { + "project": "Projekt", + "project_name": "Projektname", + "project_detail": "Projektbeschreibung", + "public": "Öffentlich", + "private": "Privat", + "if_you_make_public": "Ein öffentliches Projekt ist für alle sichtbar, die keine Mitglieder sind.", + "close": "Schließen", + "create": "Erstellen", + "update": "Aktualisieren", + "please_enter": "Bitte Projektnamen eingeben" + }, + "Index": { + "get_started": "Loslegen", + "demo": "Demo", + "oss_tcms": "Open Source Testfall-Management-System", + "integrate_and_manage": "Integriere und verwalte alle Softwaretests.", + "oss_title": "Open Source", + "oss_detail": "UnitTCMS ist kostenlos und Open Source. Die Anwendung kann selbst gehostet werden und ist für Umgebungen mit hohen Sicherheitsanforderungen geeignet.", + "organize_title": "Testfälle organisieren", + "organize_detail": "Testfälle können nach Projekten und Ordnern organisiert werden. Das moderne UI macht sie schnell.", + "usability_title": "Benutzbarkeit", + "usability_detail": "Definierte Testfälle können mehrfach in Testläufen verwendet werden. Der Status von Testläufen und Projekten ist grafisch sichtbar.", + "universal_title": "Universell", + "universal_detail": "Mehrsprachigkeit und Dark Mode ermöglichen eine flexible Nutzung.", + "project_title": "Projektbasiert", + "project_subtitle": "Verwalte Testfälle und Testläufe projektweise. Das Dashboard zeigt den Fortschritt und die Typen der Testfälle für jedes Projekt.", + "case_management_title": "Testfallverwaltung", + "case_management_subtitle": "Erstelle Ordner in Projekten und definiere Testfälle einfach mit dem modernen UI. Dateien können angehängt werden, um Testfälle detailliert zu beschreiben.", + "run_management_title": "Testlaufverwaltung", + "run_management_subtitle": "Definierte Testfälle können mehrfach in Testläufen verwendet werden. Der Status ist visuell nachvollziehbar.", + "member_management_title": "Mitgliederverwaltung", + "member_management_subtitle": "Füge Mitglieder hinzu oder entferne sie. Rollen und Rechte können detailliert vergeben werden: 'Manager', 'Entwickler', 'Reporter'." + }, + "Header": { + "title": "Open Source Test-Management-System", + "description": "Integriere und verwalte alle Softwaretests", + "docs": "Dokumentation", + "roadmap": "Fahrplan", + "projects": "Projekte", + "admin": "Administration", + "account": "Konto", + "profile_settings": "Profileinstellungen", + "signup": "Registrieren", + "signin": "Anmelden", + "signout": "Abmelden", + "links": "Links", + "languages": "Sprachen" + }, + "Toast": { + "need_signed_in": "Du musst dich anmelden", + "session_expired": "Sitzung abgelaufen" + }, + "Auth": { + "account": "Konto", + "signup": "Registrieren", + "signin": "Anmelden", + "or_signup": "Noch kein Konto?", + "or_signin": "Schon registriert?", + "signin_as_guest": "Oder als Gast anmelden", + "email": "E-Mail", + "username": "Benutzername", + "password": "Passwort", + "confirm_password": "Passwort (Bestätigung)", + "invalid_email": "Ungültige E-Mail", + "invalid_password": "Passwort muss mindestens 8 Zeichen haben", + "username_empty": "Benutzername ist leer", + "password_empty": "Passwort ist leer", + "password_not_match": "Passwörter stimmen nicht überein", + "email_already_exist": "E-Mail bereits registriert", + "email_not_exist": "E-Mail-Adresse nicht gefunden", + "signup_error": "Registrierung fehlgeschlagen", + "signin_error": "Anmeldung fehlgeschlagen", + "demo_page_warning": "Dies ist eine Demo-Umgebung. Alle Daten werden regelmäßig zurückgesetzt.", + "your_projects": "Deine Projekte", + "public": "Öffentlich", + "private": "Privat", + "not_own_any_projects": "Du besitzt keine Projekte.", + "find_projects": "Projekte finden", + "profile_settings": "Profileinstellungen", + "change_username": "Benutzernamen ändern", + "new_username": "Neuer Benutzername", + "update_username": "Benutzernamen aktualisieren", + "username_updated": "Benutzername erfolgreich geändert", + "change_password": "Passwort ändern", + "current_password": "Aktuelles Passwort", + "new_password": "Neues Passwort", + "confirm_new_password": "Neues Passwort bestätigen", + "update_password": "Passwort aktualisieren", + "password_updated": "Passwort erfolgreich geändert", + "change_avatar": "Avatar ändern", + "upload_avatar": "Avatar hochladen", + "remove_avatar": "Avatar entfernen", + "avatar_updated": "Avatar erfolgreich geändert", + "avatar_removed": "Avatar erfolgreich entfernt", + "max_file_size_5mb": "Max. Dateigröße: 5MB", + "only_images_allowed": "Nur Bilddateien erlaubt", + "current_password_incorrect": "Aktuelles Passwort ist falsch", + "update_error": "Aktualisierung fehlgeschlagen" + }, + "Health": { + "health_check": "Health Check", + "status": "Status", + "ok": "OK", + "error": "Error", + "api_server": "API Server", + "unittcms_version": "UnitTCMS Version" + }, + "Admin": { + "user_management": "Benutzerverwaltung", + "avatar": "Avatar", + "id": "ID", + "email": "E-Mail", + "username": "Benutzername", + "role": "Rolle", + "administrator": "Administrator", + "user": "Benutzer", + "no_users_found": "Keine Benutzer gefunden", + "quit_admin": "Administratorrolle abgeben", + "close": "Schließen", + "quit": "Abgeben", + "quit_confirm": "Wenn du deine Administratorrechte abgibst, kannst du nicht mehr auf die Administrationsseite zugreifen.", + "role_changed": "Rolle geändert", + "lost_admin_auth": "Administratorrechte verloren", + "at_least": "Mindestens ein Administrator wird benötigt", + "reset_password": "Passwort zurücksetzen", + "reset": "Zurücksetzen", + "invalid_password": "Passwort muss mindestens 8 Zeichen haben", + "password_not_match": "Passwörter stimmen nicht überein" + }, + "Projects": { + "project_list": "Projektliste", + "new_project": "Neues Projekt", + "id": "ID", + "publicity": "Sichtbarkeit", + "public": "Öffentlich", + "private": "Privat", + "name": "Name", + "detail": "Beschreibung", + "last_update": "Letzte Aktualisierung", + "no_projects_found": "Keine Projekte gefunden" + }, + "Project": { + "project": "Projekt", + "toggle_sidebar": "Seitenleiste anzeigen/verbergen", + "home": "Startseite", + "test_cases": "Testfälle", + "test_runs": "Testläufe", + "members": "Mitglieder", + "settings": "Einstellungen" + }, + "Home": { + "home": "Startseite", + "Folders": "Ordner", + "test_cases": "Testfälle", + "test_runs": "Testläufe", + "progress": "Fortschritt", + "test_classification": "Test-Klassifizierung", + "by_type": "Nach Typ", + "by_priority": "Nach Priorität" + }, + "Folders": { + "folder": "Ordner", + "new_folder": "Neuer Ordner", + "edit_folder": "Ordner bearbeiten", + "delete_folder": "Ordner löschen", + "folder_name": "Ordnername", + "folder_detail": "Ordnerbeschreibung", + "close": "Schließen", + "create": "Erstellen", + "update": "Aktualisieren", + "please_enter": "Bitte Ordnernamen eingeben", + "delete": "Löschen", + "are_you_sure": "Möchtest du den Ordner wirklich löschen?", + "no_folders_found": "Keine Ordner gefunden" + }, + "Cases": { + "test_case_list": "Testfallliste", + "id": "ID", + "title": "Titel", + "priority": "Priorität", + "actions": "Aktionen", + "delete_case": "Testfall löschen", + "delete": "Löschen", + "close": "Schließen", + "are_you_sure": "Möchtest du die Testfälle wirklich löschen?", + "new_test_case": "Neu", + "export": "Exportieren", + "status": "Status", + "no_cases_found": "Keine Testfälle gefunden", + "case_title": "Testfall-Titel", + "case_description": "Testfall-Beschreibung", + "case_title_or_description": "Testfall-Titel oder Beschreibung", + "create": "Erstellen", + "please_enter": "Bitte Testfall-Titel eingeben", + "filter": "Filter", + "clear_all": "Alle löschen", + "apply": "Anwenden", + "select_priorities": "Prioritäten auswählen", + "selected": "Ausgewählt", + "type": "Typ", + "select_types": "Typen auswählen", + "cases_selected": "ausgewählte Testfälle", + "select_action": "Aktion auswählen", + "move": "Verschieben", + "clone": "Klonen", + "cases_moved": "Testfälle verschoben", + "cases_cloned": "Testfälle geklont", + "tags": "Tags", + "select_tags": "Tags auswählen", + "import": "Importieren", + "import_cases": "Testfälle importieren", + "import_available": "Du kannst Testfälle aus einer Excel-Datei (xlsx, xls) importieren. Die Datei muss dem vorgegebenen Format entsprechen.", + "download_template": "Vorlage herunterladen", + "click_to_upload": "Zum Hochladen klicken", + "or_drag_and_drop": " oder per Drag & Drop", + "max_file_size": "Max. Dateigröße", + "cases_imported": "Testfälle importiert", + "create_more": "Weitere erstellen" + }, + "Case": { + "back_to_cases": "Zurück zu Testfällen", + "updating": "Wird aktualisiert...", + "update": "Aktualisieren", + "updated_test_case": "Testfall aktualisiert", + "basic": "Basis", + "title": "Titel", + "please_enter_title": "Bitte Titel eingeben", + "description": "Beschreibung", + "test_case_description": "Testfall-Beschreibung", + "priority": "Priorität", + "type": "Typ", + "template": "Vorlage", + "test_detail": "Testdetails", + "preconditions": "Voraussetzungen", + "expected_result": "Erwartetes Ergebnis", + "step": "Schritt", + "text": "Text", + "steps": "Schritte", + "new_step": "Neuer Schritt", + "details_of_the_step": "Details des Schritts", + "delete_this_step": "Diesen Schritt löschen", + "insert_step": "Schritt einfügen", + "attachments": "Anhänge", + "delete": "Löschen", + "download": "Herunterladen", + "delete_file": "Datei löschen", + "click_to_upload": "Zum Hochladen klicken", + "or_drag_and_drop": " oder per Drag & Drop", + "max_file_size": "Max. Dateigröße", + "are_you_sure_leave": "Möchtest du die Seite wirklich verlassen?", + "tags": "Tags", + "create_tag": "Tag erstellen", + "max_tags_limit": "Maximale Anzahl Tags erreicht", + "tag_already_exists": "Tag existiert bereits", + "tag_created_and_added": "Tag erstellt und hinzugefügt", + "error_creating_tag": "Fehler beim Erstellen des Tags", + "error_updating_test_case": "Fehler beim Aktualisieren des Testfalls", + "search_or_create_tag": "Tag suchen oder erstellen", + "no_tags_selected": "Keine Tags ausgewählt" + }, + "Runs": { + "run_list": "Testlauf-Liste", + "run": "Testlauf", + "new_run": "Neuer Testlauf", + "edit_run": "Testlauf bearbeiten", + "delete_run": "Testlauf löschen", + "id": "ID", + "name": "Name", + "description": "Beschreibung", + "last_update": "Letzte Aktualisierung", + "actions": "Aktionen", + "run_name": "Name des Testlaufs", + "run_description": "Beschreibung des Testlaufs", + "no_runs_found": "Keine Testläufe gefunden", + "close": "Schließen", + "create": "Erstellen", + "update": "Aktualisieren", + "please_enter": "Bitte Name des Testlaufs eingeben", + "are_you_sure": "Möchtest du den Testlauf wirklich löschen?", + "delete": "Löschen" + }, + "Run": { + "back_to_runs": "Zurück zu Testläufen", + "updating": "Wird aktualisiert...", + "update": "Aktualisieren", + "updated_test_run": "Testlauf aktualisiert", + "export": "Exportieren", + "progress": "Fortschritt", + "refresh": "Aktualisieren", + "id": "ID", + "title": "Titel", + "please_enter": "Bitte Name des Testlaufs eingeben", + "description": "Beschreibung", + "priority": "Priorität", + "status": "Status", + "actions": "Aktionen", + "select_test_case": "Testfälle auswählen", + "test_case_selection": "Testfall-Auswahl", + "include_in_run": "In Testlauf aufnehmen", + "exclude_from_run": "Aus Testlauf entfernen", + "no_cases_found": "Keine Testfälle gefunden", + "are_you_sure_leave": "Möchtest du die Seite wirklich verlassen?", + "type": "Typ", + "test_detail": "Testdetails", + "steps": "Schritte", + "preconditions": "Voraussetzungen", + "expected_result": "Erwartetes Ergebnis", + "details_of_the_step": "Details des Schritts", + "close": "Schließen", + "filter": "Filter", + "clear_all": "Alle löschen", + "apply": "Anwenden", + "select_status": "Status auswählen", + "please_save": "Bitte Änderungen speichern", + "case_title_or_description": "Testfall-Titel oder Beschreibung", + "selected": "Ausgewählt", + "tags": "Tags", + "select_tags": "Tags auswählen" + }, + "Members": { + "member_management": "Mitgliederverwaltung", + "avatar": "Avatar", + "email": "E-Mail", + "username": "Benutzername", + "role": "Rolle", + "manager": "Manager", + "developer": "Entwickler", + "reporter": "Reporter", + "delete": "Löschen", + "deleteMember": "Mitglied entfernen", + "no_members_found": "Keine Mitglieder gefunden", + "add_member": "Mitglied hinzufügen", + "user_name_or_email": "Benutzername oder E-Mail", + "close": "Schließen", + "add": "Hinzufügen", + "are_you_sure": "Möchtest du das Mitglied wirklich löschen?", + "member_added": "Mitglied hinzugefügt", + "role_changed": "Rollenänderung gespeichert", + "member_deleted": "Mitglied entfernt" + }, + "Settings": { + "project_management": "Projektverwaltung", + "project_name": "Projektname", + "project_detail": "Projektbeschreibung", + "project_owner": "Projektinhaber", + "edit_project": "Projekt bearbeiten", + "publicity": "Sichtbarkeit", + "public": "Öffentlich", + "private": "Privat", + "delete_project": "Projekt löschen", + "delete": "Löschen", + "close": "Schließen", + "are_you_sure": "Möchtest du das Projekt wirklich löschen?", + "tag_management": "Tag-Verwaltung", + "tag_name": "Tag-Name", + "add_tag": "Tag hinzufügen", + "no_tags_available": "Keine Tags verfügbar", + "delete_tag": "Tag löschen", + "are_you_sure_delete_tag": "Möchtest du diesen Tag wirklich löschen?", + "tag_created": "Tag erfolgreich erstellt.", + "tag_updated": "Tag erfolgreich aktualisiert.", + "tag_deleted": "Tag erfolgreich gelöscht.", + "tag_error_empty": "Tag-Name darf nicht leer sein.", + "tag_error_min_length": "Tag-Name muss mindestens 3 Zeichen lang sein.", + "tag_error_max_length": "Tag-Name darf nicht länger als 20 Zeichen sein.", + "tag_error_create": "Tag konnte nicht erstellt werden. Bitte versuche es erneut.", + "tag_error_update": "Tag konnte nicht aktualisiert werden. Bitte versuche es erneut.", + "tag_error_delete": "Tag konnte nicht gelöscht werden. Bitte versuche es erneut." + } +} diff --git a/frontend/messages/strings.test.ts b/frontend/messages/strings.test.ts index 45c2809..7f4e119 100644 --- a/frontend/messages/strings.test.ts +++ b/frontend/messages/strings.test.ts @@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'; import en from './en.json'; import ja from './ja.json'; import ptBR from './pt-BR.json'; +import zhCN from './zh-CN.json'; +import de from './de.json'; function getAllKeys(obj: unknown, prefix = ''): string[] { if (typeof obj !== 'object' || obj === null) return []; @@ -19,6 +21,8 @@ describe('Locale message keys consistency', () => { { name: 'en', data: en }, { name: 'ja', data: ja }, { name: 'pt-BR', data: ptBR }, + { name: 'zh-CN', data: zhCN }, + { name: 'de', data: de }, ]; const base = locales[0]; diff --git a/frontend/src/i18n/routing.ts b/frontend/src/i18n/routing.ts index 7d53fab..5d96dc2 100644 --- a/frontend/src/i18n/routing.ts +++ b/frontend/src/i18n/routing.ts @@ -2,7 +2,7 @@ import { createNavigation } from 'next-intl/navigation'; import { defineRouting } from 'next-intl/routing'; export const routing = defineRouting({ - locales: ['en', 'ja', 'pt-BR', 'zh-CN'], + locales: ['en', 'ja', 'pt-BR', 'zh-CN', 'de'], defaultLocale: 'en', localePrefix: { mode: 'always', diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts index 8f818c3..9116a88 100644 --- a/frontend/src/middleware.ts +++ b/frontend/src/middleware.ts @@ -4,5 +4,5 @@ import { routing } from './i18n/routing'; export default createMiddleware(routing); export const config = { - matcher: ['/', '/(ja|en|pt-BR|zh-CN)/:path*'], + matcher: ['/', '/(ja|en|pt-BR|zh-CN|de)/:path*'], }; diff --git a/frontend/types/locale.ts b/frontend/types/locale.ts index 50f8241..909265a 100644 --- a/frontend/types/locale.ts +++ b/frontend/types/locale.ts @@ -1,4 +1,4 @@ -export type LocaleCodeType = 'en' | 'ja' | 'pt-BR' | 'zh-CN'; +export type LocaleCodeType = 'en' | 'ja' | 'pt-BR' | 'zh-CN' | 'de'; export type LocaleType = { code: LocaleCodeType;