From 1f45867eb5347560a90d4b8c5fa5622a32723e65 Mon Sep 17 00:00:00 2001 From: dream10201 Date: Wed, 10 Dec 2025 19:07:18 +0800 Subject: [PATCH 1/4] feat: add support for Chinese language (zh-CN) (#365) --- frontend/config/selection.ts | 1 + frontend/messages/zh-CN.json | 408 +++++++++++++++++++++++++++++++++++ frontend/src/i18n/routing.ts | 2 +- frontend/src/middleware.ts | 2 +- frontend/types/locale.ts | 2 +- 5 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 frontend/messages/zh-CN.json diff --git a/frontend/config/selection.ts b/frontend/config/selection.ts index a74fb6e..177f2ac 100644 --- a/frontend/config/selection.ts +++ b/frontend/config/selection.ts @@ -13,6 +13,7 @@ const locales: LocaleType[] = [ { code: 'en', name: 'English' }, { code: 'ja', name: '日本語' }, { code: 'pt-BR', name: 'Português' }, + { code: 'zh-CN', name: '简体中文' }, ]; // The status of each test run diff --git a/frontend/messages/zh-CN.json b/frontend/messages/zh-CN.json new file mode 100644 index 0000000..af2f603 --- /dev/null +++ b/frontend/messages/zh-CN.json @@ -0,0 +1,408 @@ +{ + "RunStatus": { + "new": "新建", + "inProgress": "进行中", + "underReview": "审核中", + "rejected": "已拒绝", + "done": "完成", + "closed": "已关闭" + }, + "RunCaseStatus": { + "untested": "未测试", + "passed": "通过", + "failed": "失败", + "retest": "重测", + "skipped": "跳过" + }, + "Priority": { + "critical": "严重", + "high": "高", + "medium": "中", + "low": "低" + }, + "Type": { + "other": "其他", + "security": "安全", + "performance": "性能", + "accessibility": "无障碍", + "functional": "功能", + "acceptance": "验收", + "usability": "易用性", + "smoke_sanity": "冒烟与健全性", + "compatibility": "兼容性", + "destructive": "破坏性", + "regression": "回归", + "automated": "自动化", + "manual": "手动" + }, + "ProjectDialog": { + "project": "项目", + "project_name": "项目名称", + "project_detail": "项目详情", + "public": "公开", + "private": "私有", + "if_you_make_public": "将项目设为公开将使其对非项目成员用户可见。", + "close": "关闭", + "create": "创建", + "update": "更新", + "please_enter": "请输入项目名称" + }, + "Index": { + "get_started": "开始使用", + "demo": "演示", + "oss_tcms": "开源测试用例管理系统", + "integrate_and_manage": "集成并管理您的所有软件测试。", + "oss_title": "开源", + "oss_detail": "UnitTCMS 是免费且开源的。应用程序支持自托管,可部署在有严格安全要求的环境中。", + "organize_title": "组织测试用例", + "organize_detail": "可以通过项目和文件夹来组织测试用例。现代化的 UI 框架使其运行迅速。", + "usability_title": "易用性", + "usability_detail": "定义好的测试用例可以在测试运行中反复使用。测试运行和项目的状态可以通过图表直观查看。", + "universal_title": "通用性", + "universal_detail": "多语言支持和深色模式让任何人都可以无障碍地使用该系统。", + "project_title": "基于项目", + "project_subtitle": "以项目为单位管理测试用例和测试运行。我们的仪表盘提供了每个项目中测试用例类型及其进度的概览。这使您能够实时监控项目状态并进行高效管理。", + "case_management_title": "测试用例管理", + "case_management_subtitle": "在项目中创建文件夹,利用我们现代化且直观的 UI 轻松定义测试用例。支持附件上传,可对测试用例进行详细说明,便于在整个团队中共享信息。", + "run_management_title": "测试运行管理", + "run_management_subtitle": "定义好的测试用例可以在测试运行中多次复用,实现高效的测试循环。此外,您还可以可视化地监控测试运行和项目的状态。", + "member_management_title": "成员管理", + "member_management_subtitle": "通过在项目中添加或移除成员来支持团队开发。您可以为每个成员分配角色并详细设置权限。我们提供三种主要角色:“管理者”管理整个项目,“开发者”设计测试,“报告者”执行测试。" + }, + "Header": { + "title": "开源测试用例管理系统", + "description": "集成并管理您的所有软件测试", + "docs": "文档", + "roadmap": "路线图", + "projects": "项目", + "admin": "管理后台", + "account": "账户", + "profile_settings": "个人设置", + "signup": "注册", + "signin": "登录", + "signout": "退出", + "links": "链接", + "languages": "语言" + }, + "Toast": { + "need_signed_in": "您需要先登录", + "session_expired": "会话已过期" + }, + "Auth": { + "account": "账户", + "signup": "注册", + "signin": "登录", + "or_signup": "没有账号?去注册", + "or_signin": "已有账号?去登录", + "signin_as_guest": "或以访客身份登录", + "email": "邮箱", + "username": "用户名", + "password": "密码", + "confirm_password": "确认密码", + "invalid_email": "无效的邮箱地址", + "invalid_password": "密码必须至少包含 8 个字符", + "username_empty": "用户名不能为空", + "password_empty": "密码不能为空", + "password_not_match": "密码不匹配", + "email_already_exist": "邮箱已被注册", + "email_not_exist": "未找到该邮箱地址", + "signup_error": "注册失败", + "signin_error": "登录失败", + "demo_page_warning": "这是一个演示环境,您输入的任何数据都将被定期重置。", + "your_projects": "您的项目", + "public": "公开", + "private": "私有", + "not_own_any_projects": "您没有任何项目。", + "find_projects": "发现项目", + "profile_settings": "个人设置", + "change_username": "修改用户名", + "new_username": "新用户名", + "update_username": "更新用户名", + "username_updated": "用户名更新成功", + "change_password": "修改密码", + "current_password": "当前密码", + "new_password": "新密码", + "confirm_new_password": "确认新密码", + "update_password": "更新密码", + "password_updated": "密码更新成功", + "change_avatar": "修改头像", + "upload_avatar": "上传头像", + "remove_avatar": "移除头像", + "avatar_updated": "头像更新成功", + "avatar_removed": "头像移除成功", + "max_file_size_5mb": "最大文件大小:5MB", + "only_images_allowed": "仅允许上传图片文件", + "current_password_incorrect": "当前密码不正确", + "update_error": "更新失败" + }, + "Health": { + "health_check": "健康检查", + "status": "状态", + "ok": "正常", + "error": "错误", + "api_server": "API 服务器", + "unittcms_version": "UnitTCMS 版本" + }, + "Admin": { + "user_management": "用户管理", + "avatar": "头像", + "id": "ID", + "email": "邮箱", + "username": "用户名", + "role": "角色", + "administrator": "管理员", + "user": "用户", + "no_users_found": "未找到用户", + "quit_admin": "放弃管理员权限", + "close": "关闭", + "quit": "放弃", + "quit_confirm": "如果您放弃管理员权限,您将无法再访问管理页面。", + "role_changed": "角色已变更", + "lost_admin_auth": "已失去管理员权限", + "at_least": "至少需要保留一名管理员", + "reset_password": "重置密码", + "reset": "重置", + "invalid_password": "密码必须至少包含 8 个字符", + "password_not_match": "密码不匹配" + }, + "Projects": { + "project_list": "项目列表", + "new_project": "新建项目", + "id": "ID", + "publicity": "公开性", + "public": "公开", + "private": "私有", + "name": "名称", + "detail": "详情", + "last_update": "最后更新", + "no_projects_found": "未找到项目" + }, + "Project": { + "project": "项目", + "toggle_sidebar": "切换侧边栏", + "home": "首页", + "test_cases": "测试用例", + "test_runs": "测试运行", + "members": "成员", + "settings": "设置" + }, + "Home": { + "home": "首页", + "Folders": "文件夹", + "test_cases": "测试用例", + "test_runs": "测试运行", + "progress": "进度", + "test_classification": "测试分类", + "by_type": "按测试类型", + "by_priority": "按测试优先级" + }, + "Folders": { + "folder": "文件夹", + "new_folder": "新建文件夹", + "edit_folder": "编辑文件夹", + "delete_folder": "删除文件夹", + "folder_name": "文件夹名称", + "folder_detail": "文件夹详情", + "close": "关闭", + "create": "创建", + "update": "更新", + "please_enter": "请输入文件夹名称", + "delete": "删除", + "are_you_sure": "您确定要删除该文件夹吗?", + "no_folders_found": "未找到文件夹" + }, + "Cases": { + "test_case_list": "测试用例列表", + "id": "ID", + "title": "标题", + "priority": "优先级", + "actions": "操作", + "delete_case": "删除测试用例", + "delete": "删除", + "close": "关闭", + "are_you_sure": "您确定要删除测试用例吗?", + "new_test_case": "新建", + "export": "导出", + "status": "状态", + "no_cases_found": "未找到测试用例", + "case_title": "测试用例标题", + "case_description": "测试用例描述", + "case_title_or_description": "测试用例标题或描述", + "create": "创建", + "please_enter": "请输入测试用例标题", + "filter": "筛选", + "clear_all": "清除所有", + "apply": "应用", + "select_priorities": "选择优先级", + "selected": "已选择", + "type": "类型", + "select_types": "选择类型", + "cases_selected": "个用例已选择", + "select_action": "选择操作", + "move": "移动", + "clone": "克隆", + "cases_moved": "测试用例已移动", + "cases_cloned": "测试用例已克隆", + "tags": "标签", + "select_tags": "选择标签", + "import": "导入", + "import_cases": "导入测试用例", + "import_available": "您可以从 Excel 文件 (xlsx, xls) 导入测试用例。导入的 Excel 文件必须遵循指定格式。", + "download_template": "下载模板", + "click_to_upload": "点击上传", + "or_drag_and_drop": " 或拖拽文件到此处", + "max_file_size": "最大文件大小", + "cases_imported": "测试用例已导入", + "create_more": "继续创建" + }, + "Case": { + "back_to_cases": "返回测试用例列表", + "updating": "更新中...", + "update": "更新", + "updated_test_case": "测试用例已更新", + "basic": "基本信息", + "title": "标题", + "please_enter_title": "请输入标题", + "description": "描述", + "test_case_description": "测试用例描述", + "priority": "优先级", + "type": "类型", + "template": "模板", + "test_detail": "测试详情", + "preconditions": "前置条件", + "expected_result": "预期结果", + "step": "步骤", + "text": "文本", + "steps": "步骤", + "new_step": "新步骤", + "details_of_the_step": "步骤详情", + "delete_this_step": "删除此步骤", + "insert_step": "插入步骤", + "attachments": "附件", + "delete": "删除", + "download": "下载", + "delete_file": "删除文件", + "click_to_upload": "点击上传", + "or_drag_and_drop": " 或拖拽文件到此处", + "max_file_size": "最大文件大小", + "are_you_sure_leave": "您确定要离开此页面吗?", + "tags": "标签", + "create_tag": "创建标签", + "max_tags_limit": "最大标签数量限制", + "tag_already_exists": "标签已存在", + "tag_created_and_added": "标签已创建并添加", + "error_creating_tag": "创建标签失败", + "error_updating_test_case": "更新测试用例失败", + "search_or_create_tag": "搜索或创建标签", + "no_tags_selected": "未选择标签" + }, + "Runs": { + "run_list": "测试运行列表", + "run": "运行", + "new_run": "新建运行", + "edit_run": "编辑运行", + "delete_run": "删除运行", + "id": "ID", + "name": "名称", + "description": "描述", + "last_update": "最后更新", + "actions": "操作", + "run_name": "运行名称", + "run_description": "运行描述", + "no_runs_found": "未找到测试运行", + "close": "关闭", + "create": "创建", + "update": "更新", + "please_enter": "请输入运行名称", + "are_you_sure": "您确定要删除该测试运行吗?", + "delete": "删除" + }, + "Run": { + "back_to_runs": "返回测试运行列表", + "updating": "更新中...", + "update": "更新", + "updated_test_run": "测试运行已更新", + "export": "导出", + "progress": "进度", + "refresh": "刷新", + "id": "ID", + "title": "标题", + "please_enter": "请输入运行名称", + "description": "描述", + "priority": "优先级", + "status": "状态", + "actions": "操作", + "select_test_case": "选择测试用例", + "test_case_selection": "测试用例选择", + "include_in_run": "包含在运行中", + "exclude_from_run": "从运行中排除", + "no_cases_found": "未找到用例", + "are_you_sure_leave": "您确定要离开此页面吗?", + "type": "类型", + "test_detail": "测试详情", + "steps": "步骤", + "preconditions": "前置条件", + "expected_result": "预期结果", + "details_of_the_step": "步骤详情", + "close": "关闭", + "filter": "筛选", + "clear_all": "清除所有", + "apply": "应用", + "select_status": "选择状态", + "please_save": "请保存更改", + "case_title_or_description": "测试用例标题或描述", + "selected": "已选择", + "tags": "标签", + "select_tags": "选择标签" + }, + "Members": { + "member_management": "成员管理", + "avatar": "头像", + "email": "邮箱", + "username": "用户名", + "role": "角色", + "manager": "管理者", + "developer": "开发者", + "reporter": "报告者", + "delete": "删除", + "deleteMember": "删除成员", + "no_members_found": "未找到成员", + "add_member": "添加成员", + "user_name_or_email": "用户名或邮箱", + "close": "关闭", + "add": "添加", + "are_you_sure": "您确定要删除该成员吗?", + "member_added": "成员已添加", + "role_changed": "成员角色已变更", + "member_deleted": "成员已删除" + }, + "Settings": { + "project_management": "项目管理", + "project_name": "项目名称", + "project_detail": "项目详情", + "project_owner": "项目所有者", + "edit_project": "编辑项目", + "publicity": "公开性", + "public": "公开", + "private": "私有", + "delete_project": "删除项目", + "delete": "删除", + "close": "关闭", + "are_you_sure": "您确定要删除该项目吗?", + "tag_management": "标签管理", + "tag_name": "标签名称", + "add_tag": "添加标签", + "no_tags_available": "暂无标签", + "delete_tag": "删除标签", + "are_you_sure_delete_tag": "您确定要删除该标签吗?", + "tag_created": "标签创建成功。", + "tag_updated": "标签更新成功。", + "tag_deleted": "标签删除成功。", + "tag_error_empty": "标签名称不能为空。", + "tag_error_min_length": "标签名称长度至少为 3 个字符。", + "tag_error_max_length": "标签名称不能超过 20 个字符。", + "tag_error_create": "标签创建失败,请重试。", + "tag_error_update": "标签更新失败,请重试。", + "tag_error_delete": "标签删除失败,请重试。" + } +} diff --git a/frontend/src/i18n/routing.ts b/frontend/src/i18n/routing.ts index e7fca45..7d53fab 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'], + locales: ['en', 'ja', 'pt-BR', 'zh-CN'], defaultLocale: 'en', localePrefix: { mode: 'always', diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts index e781684..8f818c3 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)/:path*'], + matcher: ['/', '/(ja|en|pt-BR|zh-CN)/:path*'], }; diff --git a/frontend/types/locale.ts b/frontend/types/locale.ts index 04ded07..50f8241 100644 --- a/frontend/types/locale.ts +++ b/frontend/types/locale.ts @@ -1,4 +1,4 @@ -export type LocaleCodeType = 'en' | 'ja' | 'pt-BR'; +export type LocaleCodeType = 'en' | 'ja' | 'pt-BR' | 'zh-CN'; export type LocaleType = { code: LocaleCodeType; 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 2/4] 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; From 0d87d0fa28f56044304db1748e0b685b8867d3db Mon Sep 17 00:00:00 2001 From: kimatata <117462761+kimatata@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:39:55 +0900 Subject: [PATCH 3/4] chore: language order (#370) --- README.md | 4 +++- frontend/config/selection.ts | 9 +++++++-- frontend/messages/strings.test.ts | 11 ++++++----- frontend/src/i18n/routing.ts | 2 +- frontend/src/middleware.ts | 2 +- frontend/types/locale.ts | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 29e9fac..7681815 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,10 @@ Support team development by adding or removing members from projects. You can as UnitTCMS currently supports the following languages: +- German (de) - English (en) -- Japanese (ja) - Portuguese (pt-BR) +- Chinese (zh-CN) +- Japanese (ja) If you would like to add support for another language, feel free to submit a pull request. For reference, you can see how Portuguese was added in [PR #260](https://github.com/kimatata/unittcms/pull/260). diff --git a/frontend/config/selection.ts b/frontend/config/selection.ts index 660e97c..8b11215 100644 --- a/frontend/config/selection.ts +++ b/frontend/config/selection.ts @@ -9,12 +9,17 @@ const memberRoles: MemberRoleType[] = [{ uid: 'manager' }, { uid: 'developer' }, const categoricalPalette = ['#fba91e', '#6ea56c', '#3ac6e1', '#feda2f', '#f15f47', '#244470', '#9c80bb', '#f595a6']; +/** + * Locales are grouped by script: Latin-based locales first, followed by CJK. + * Within Latin-based group, entries are sorted lexicographically by their BCP 47 codes. + * This matches common UI patterns. + */ const locales: LocaleType[] = [ - { code: 'en', name: 'English' }, - { code: 'ja', name: '日本語' }, { code: 'de', name: 'Deutsch' }, + { code: 'en', name: 'English' }, { code: 'pt-BR', name: 'Português' }, { code: 'zh-CN', name: '简体中文' }, + { code: 'ja', name: '日本語' }, ]; // The status of each test run diff --git a/frontend/messages/strings.test.ts b/frontend/messages/strings.test.ts index 7f4e119..ef269f7 100644 --- a/frontend/messages/strings.test.ts +++ b/frontend/messages/strings.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest'; +import de from './de.json'; 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'; +import ja from './ja.json'; function getAllKeys(obj: unknown, prefix = ''): string[] { if (typeof obj !== 'object' || obj === null) return []; @@ -18,14 +18,15 @@ function getAllKeys(obj: unknown, prefix = ''): string[] { describe('Locale message keys consistency', () => { const locales = [ + { name: 'de', data: de }, { name: 'en', data: en }, - { name: 'ja', data: ja }, { name: 'pt-BR', data: ptBR }, { name: 'zh-CN', data: zhCN }, - { name: 'de', data: de }, + { name: 'ja', data: ja }, ]; - const base = locales[0]; + const base = locales.find((locale) => locale.name === 'en'); + if (!base) throw new Error('Base locale not found'); const baseKeys = getAllKeys(base.data); for (const locale of locales.slice(1)) { diff --git a/frontend/src/i18n/routing.ts b/frontend/src/i18n/routing.ts index 5d96dc2..c09ac0e 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', 'de'], + locales: ['de', 'en', 'pt-BR', 'zh-CN', 'ja'], defaultLocale: 'en', localePrefix: { mode: 'always', diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts index 9116a88..fa9af8c 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|de)/:path*'], + matcher: ['/', '/(de|en|pt-BR|zh-CN|ja)/:path*'], }; diff --git a/frontend/types/locale.ts b/frontend/types/locale.ts index 909265a..e1d2270 100644 --- a/frontend/types/locale.ts +++ b/frontend/types/locale.ts @@ -1,4 +1,4 @@ -export type LocaleCodeType = 'en' | 'ja' | 'pt-BR' | 'zh-CN' | 'de'; +export type LocaleCodeType = 'de' | 'en' | 'pt-BR' | 'zh-CN' | 'ja'; export type LocaleType = { code: LocaleCodeType; From 6e7edb52ed7120f8483b5e2b1c351dfeede17df3 Mon Sep 17 00:00:00 2001 From: kimatata <117462761+kimatata@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:41:16 +0900 Subject: [PATCH 4/4] chore: update version (#371) --- frontend/src/app/[locale]/health/HealthPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/[locale]/health/HealthPage.tsx b/frontend/src/app/[locale]/health/HealthPage.tsx index 2f8aa63..1810cbe 100644 --- a/frontend/src/app/[locale]/health/HealthPage.tsx +++ b/frontend/src/app/[locale]/health/HealthPage.tsx @@ -62,7 +62,7 @@ export default function HealthPage({ messages, locale }: Props) { {messages.unittcms_version} - 1.0.0-beta.25 + 1.0.0-beta.26 {messages.api_server}