From dd21074c27c3fd7bd053f919c31e2291c0e04c50 Mon Sep 17 00:00:00 2001 From: jerryhuangyu Date: Sun, 5 Apr 2026 23:53:26 +0800 Subject: [PATCH] feat: Add Traditional Chinese (zh-TW) translations support --- client/src/components/Layout/DemoBanner.tsx | 64 + client/src/i18n/TranslationContext.tsx | 10 +- client/src/i18n/translations/zhTw.ts | 1545 +++++++++++++++++++ server/src/services/notifications.ts | 10 + 4 files changed, 1625 insertions(+), 4 deletions(-) create mode 100644 client/src/i18n/translations/zhTw.ts diff --git a/client/src/components/Layout/DemoBanner.tsx b/client/src/components/Layout/DemoBanner.tsx index fff46be..1bbc53c 100644 --- a/client/src/components/Layout/DemoBanner.tsx +++ b/client/src/components/Layout/DemoBanner.tsx @@ -118,6 +118,70 @@ const texts: Record = { selfHostLink: 'alójalo tú mismo', close: 'Entendido', }, + zh: { + titleBefore: '欢迎来到 ', + titleAfter: '', + title: '欢迎来到 TREK 演示版', + description: '你可以查看、编辑和创建旅行。所有更改都会在每小时自动重置。', + resetIn: '下次重置将在', + minutes: '分钟后', + uploadNote: '演示模式下已禁用文件上传(照片、文档、封面)。', + fullVersionTitle: '完整版本还包括:', + features: [ + '文件上传(照片、文档、封面)', + 'API 密钥管理(Google Maps、天气)', + '用户和权限管理', + '自动备份', + '附加组件管理(启用/禁用)', + 'OIDC / SSO 单点登录', + ], + addonsTitle: '模块化附加组件(完整版本可禁用)', + addons: [ + ['Vacay', '带日历、节假日和用户融合的假期规划器'], + ['Atlas', '带已访问国家和旅行统计的世界地图'], + ['Packing', '按旅行管理清单'], + ['Budget', '支持分摊的费用追踪'], + ['Documents', '将文件附加到旅行'], + ['Widgets', '货币换算和时区工具'], + ], + whatIs: '什么是 TREK?', + whatIsDesc: '一个支持实时协作、交互式地图、OIDC 登录和深色模式的自托管旅行规划器。', + selfHost: '开源项目 - ', + selfHostLink: '自行部署', + close: '知道了', + }, + 'zh-TW': { + titleBefore: '歡迎來到 ', + titleAfter: '', + title: '歡迎來到 TREK 展示版', + description: '你可以檢視、編輯和建立行程。所有變更都會在每小時自動重設。', + resetIn: '下次重設將在', + minutes: '分鐘後', + uploadNote: '展示模式下已停用檔案上傳(照片、文件、封面)。', + fullVersionTitle: '完整版本還包含:', + features: [ + '檔案上傳(照片、文件、封面)', + 'API 金鑰管理(Google Maps、天氣)', + '使用者與權限管理', + '自動備份', + '附加元件管理(啟用/停用)', + 'OIDC / SSO 單一登入', + ], + addonsTitle: '模組化附加元件(完整版本可停用)', + addons: [ + ['Vacay', '具備日曆、假日與使用者融合的假期規劃器'], + ['Atlas', '顯示已造訪國家與旅行統計的世界地圖'], + ['Packing', '依行程管理的檢查清單'], + ['Budget', '支援分攤的費用追蹤'], + ['Documents', '將檔案附加到行程'], + ['Widgets', '貨幣換算與時區工具'], + ], + whatIs: 'TREK 是什麼?', + whatIsDesc: '一個支援即時協作、互動式地圖、OIDC 登入和深色模式的自架旅行規劃器。', + selfHost: '開源專案 - ', + selfHostLink: '自行架設', + close: '知道了', + }, ar: { titleBefore: 'مرحبًا بك في ', titleAfter: '', diff --git a/client/src/i18n/TranslationContext.tsx b/client/src/i18n/TranslationContext.tsx index ee03102..a8a595a 100644 --- a/client/src/i18n/TranslationContext.tsx +++ b/client/src/i18n/TranslationContext.tsx @@ -8,6 +8,7 @@ import hu from './translations/hu' import it from './translations/it' import ru from './translations/ru' import zh from './translations/zh' +import zhTw from './translations/zhTw' import nl from './translations/nl' import ar from './translations/ar' import br from './translations/br' @@ -27,13 +28,14 @@ export const SUPPORTED_LANGUAGES = [ { value: 'cs', label: 'Česky' }, { value: 'pl', label: 'Polski' }, { value: 'ru', label: 'Русский' }, - { value: 'zh', label: '中文' }, + { value: 'zh', label: '简体中文' }, + { value: 'zh-TW', label: '繁體中文' }, { value: 'it', label: 'Italiano' }, { value: 'ar', label: 'العربية' }, ] as const -const translations: Record = { de, en, es, fr, hu, it, ru, zh, nl, ar, br, cs, pl } -const LOCALES: Record = { de: 'de-DE', en: 'en-US', es: 'es-ES', fr: 'fr-FR', hu: 'hu-HU', it: 'it-IT', ru: 'ru-RU', zh: 'zh-CN', nl: 'nl-NL', ar: 'ar-SA', br: 'pt-BR', cs: 'cs-CZ', pl: 'pl-PL' } +const translations: Record = { de, en, es, fr, hu, it, ru, zh, 'zh-TW': zhTw, nl, ar, br, cs, pl } +const LOCALES: Record = { de: 'de-DE', en: 'en-US', es: 'es-ES', fr: 'fr-FR', hu: 'hu-HU', it: 'it-IT', ru: 'ru-RU', zh: 'zh-CN', 'zh-TW': 'zh-TW', nl: 'nl-NL', ar: 'ar-SA', br: 'pt-BR', cs: 'cs-CZ', pl: 'pl-PL' } const RTL_LANGUAGES = new Set(['ar']) export function getLocaleForLanguage(language: string): string { @@ -42,7 +44,7 @@ export function getLocaleForLanguage(language: string): string { export function getIntlLanguage(language: string): string { if (language === 'br') return 'pt-BR' - return ['de', 'es', 'fr', 'hu', 'it', 'ru', 'zh', 'nl', 'ar', 'cs', 'pl'].includes(language) ? language : 'en' + return ['de', 'es', 'fr', 'hu', 'it', 'ru', 'zh', 'zh-TW', 'nl', 'ar', 'cs', 'pl'].includes(language) ? language : 'en' } export function isRtlLanguage(language: string): boolean { diff --git a/client/src/i18n/translations/zhTw.ts b/client/src/i18n/translations/zhTw.ts new file mode 100644 index 0000000..cbcf0fd --- /dev/null +++ b/client/src/i18n/translations/zhTw.ts @@ -0,0 +1,1545 @@ +const zhTw: Record = { + // Common + 'common.save': '儲存', + 'common.cancel': '取消', + 'common.delete': '刪除', + 'common.edit': '編輯', + 'common.add': '新增', + 'common.loading': '載入中...', + 'common.import': '匯入', + 'common.error': '錯誤', + 'common.back': '返回', + 'common.all': '全部', + 'common.close': '關閉', + 'common.open': '開啟', + 'common.upload': '上傳', + 'common.search': '搜尋', + 'common.confirm': '確認', + 'common.ok': '確定', + 'common.yes': '是', + 'common.no': '否', + 'common.or': '或', + 'common.none': '無', + 'common.date': '日期', + 'common.rename': '重新命名', + 'common.name': '名稱', + 'common.email': '郵箱', + 'common.password': '密碼', + 'common.saving': '儲存中...', + 'common.saved': '已儲存', + 'trips.reminder': '提醒', + 'trips.reminderNone': '無', + 'trips.reminderDay': '天', + 'trips.reminderDays': '天', + 'trips.reminderCustom': '自定義', + 'trips.reminderDaysBefore': '天前提醒', + 'trips.reminderDisabledHint': '旅行提醒已停用。請在管理 > 設定 > 通知中啟用。', + 'common.update': '更新', + 'common.change': '修改', + 'common.uploading': '上傳中…', + 'common.backToPlanning': '返回規劃', + 'common.reset': '重置', + + // Navbar + 'nav.trip': '旅行', + 'nav.share': '分享', + 'nav.settings': '設定', + 'nav.admin': '管理', + 'nav.logout': '退出登入', + 'nav.lightMode': '淺色模式', + 'nav.darkMode': '深色模式', + 'nav.autoMode': '自動模式', + 'nav.administrator': '管理員', + + // Dashboard + 'dashboard.title': '我的旅行', + 'dashboard.subtitle.loading': '載入旅行中...', + 'dashboard.subtitle.trips': '{count} 次旅行({archived} 已歸檔)', + 'dashboard.subtitle.empty': '開始你的第一次旅行', + 'dashboard.subtitle.activeOne': '{count} 個進行中的旅行', + 'dashboard.subtitle.activeMany': '{count} 個進行中的旅行', + 'dashboard.subtitle.archivedSuffix': ' · {count} 已歸檔', + 'dashboard.newTrip': '新建旅行', + 'dashboard.gridView': '網格檢視', + 'dashboard.listView': '列表檢視', + 'dashboard.currency': '貨幣', + 'dashboard.timezone': '時區', + 'dashboard.localTime': '本地', + 'dashboard.timezoneCustomTitle': '自定義時區', + 'dashboard.timezoneCustomLabelPlaceholder': '標籤(可選)', + 'dashboard.timezoneCustomTzPlaceholder': '如 America/New_York', + 'dashboard.timezoneCustomAdd': '新增', + 'dashboard.timezoneCustomErrorEmpty': '請輸入時區識別符號', + 'dashboard.timezoneCustomErrorInvalid': '無效的時區。請使用 Europe/Berlin 這樣的格式', + 'dashboard.timezoneCustomErrorDuplicate': '已新增', + 'dashboard.emptyTitle': '暫無旅行', + 'dashboard.emptyText': '建立你的第一次旅行,開始規劃吧!', + 'dashboard.emptyButton': '建立第一次旅行', + 'dashboard.nextTrip': '下次旅行', + 'dashboard.shared': '共享', + 'dashboard.sharedBy': '由 {name} 分享', + 'dashboard.days': '天', + 'dashboard.places': '地點', + 'dashboard.members': '旅伴', + 'dashboard.archive': '歸檔', + 'dashboard.copyTrip': '複製', + 'dashboard.copySuffix': '副本', + 'dashboard.restore': '恢復', + 'dashboard.archived': '已歸檔', + 'dashboard.status.ongoing': '進行中', + 'dashboard.status.today': '今天', + 'dashboard.status.tomorrow': '明天', + 'dashboard.status.past': '已結束', + 'dashboard.status.daysLeft': '還剩 {count} 天', + 'dashboard.toast.loadError': '載入旅行失敗', + 'dashboard.toast.created': '旅行建立成功!', + 'dashboard.toast.createError': '建立旅行失敗', + 'dashboard.toast.updated': '旅行已更新!', + 'dashboard.toast.updateError': '更新旅行失敗', + 'dashboard.toast.deleted': '旅行已刪除', + 'dashboard.toast.deleteError': '刪除旅行失敗', + 'dashboard.toast.archived': '旅行已歸檔', + 'dashboard.toast.archiveError': '歸檔旅行失敗', + 'dashboard.toast.restored': '旅行已恢復', + 'dashboard.toast.restoreError': '恢復旅行失敗', + 'dashboard.toast.copied': '旅行已複製!', + 'dashboard.toast.copyError': '複製旅行失敗', + 'dashboard.confirm.delete': '刪除旅行「{title}」?所有地點和計劃將被永久刪除。', + 'dashboard.editTrip': '編輯旅行', + 'dashboard.createTrip': '建立新旅行', + 'dashboard.tripTitle': '標題', + 'dashboard.tripTitlePlaceholder': '如:日本夏日之旅', + 'dashboard.tripDescription': '描述', + 'dashboard.tripDescriptionPlaceholder': '這次旅行是關於什麼的?', + 'dashboard.startDate': '開始日期', + 'dashboard.endDate': '結束日期', + 'dashboard.noDateHint': '未設定日期——將預設建立 7 天。你可以隨時修改。', + 'dashboard.coverImage': '封面圖片', + 'dashboard.addCoverImage': '新增封面圖片', + 'dashboard.addMembers': '旅伴', + 'dashboard.addMember': '新增成員', + 'dashboard.coverSaved': '封面圖片已儲存', + 'dashboard.coverUploadError': '上傳失敗', + 'dashboard.coverRemoveError': '移除失敗', + 'dashboard.titleRequired': '標題為必填項', + 'dashboard.endDateError': '結束日期必須晚於開始日期', + + // Settings + 'settings.title': '設定', + 'settings.subtitle': '配置你的個人設定', + 'settings.map': '地圖', + 'settings.mapTemplate': '地圖模板', + 'settings.mapTemplatePlaceholder.select': '選擇模板...', + 'settings.mapDefaultHint': '留空則使用 OpenStreetMap(預設)', + 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': '地圖瓦片 URL 模板', + 'settings.latitude': '緯度', + 'settings.longitude': '經度', + 'settings.saveMap': '儲存地圖', + 'settings.apiKeys': 'API 金鑰', + 'settings.mapsKey': 'Google Maps API 金鑰', + 'settings.mapsKeyHint': '用於地點搜尋。需要 Places API (New)。在 console.cloud.google.com 獲取', + 'settings.weatherKey': 'OpenWeatherMap API 金鑰', + 'settings.weatherKeyHint': '用於天氣資料。在 openweathermap.org/api 免費獲取', + 'settings.keyPlaceholder': '輸入金鑰...', + 'settings.configured': '已配置', + 'settings.saveKeys': '儲存金鑰', + 'settings.display': '顯示', + 'settings.colorMode': '顏色模式', + 'settings.light': '淺色', + 'settings.dark': '深色', + 'settings.auto': '自動', + 'settings.language': '語言', + 'settings.temperature': '溫度單位', + 'settings.timeFormat': '時間格式', + 'settings.routeCalculation': '路線計算', + 'settings.blurBookingCodes': '模糊預訂程式碼', + 'settings.notifications': '通知', + 'settings.notifyTripInvite': '旅行邀請', + 'settings.notifyBookingChange': '預訂變更', + 'settings.notifyTripReminder': '旅行提醒', + 'settings.notifyVacayInvite': 'Vacay 融合邀請', + 'settings.notifyPhotosShared': '共享照片 (Immich)', + 'settings.notifyCollabMessage': '聊天訊息 (Collab)', + 'settings.notifyPackingTagged': '行李清單:分配', + 'settings.notifyWebhook': 'Webhook 通知', + 'settings.notificationsDisabled': '通知尚未配置。請聯絡管理員啟用電子郵件或 Webhook 通知。', + 'settings.notificationsActive': '活躍頻道', + 'settings.notificationsManagedByAdmin': '通知事件由管理員配置。', + 'admin.notifications.title': '通知', + 'admin.notifications.hint': '選擇一個通知渠道。一次只能啟用一個。', + 'admin.notifications.none': '已停用', + 'admin.notifications.email': '電子郵件 (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.events': '通知事件', + 'admin.notifications.eventsHint': '選擇哪些事件為所有使用者觸發通知。', + 'admin.notifications.configureFirst': '請先在下方配置 SMTP 或 Webhook,然後啟用事件。', + 'admin.notifications.save': '儲存通知設定', + 'admin.notifications.saved': '通知設定已儲存', + 'admin.notifications.testWebhook': '傳送測試 Webhook', + 'admin.notifications.testWebhookSuccess': '測試 Webhook 傳送成功', + 'admin.notifications.testWebhookFailed': '測試 Webhook 傳送失敗', + 'admin.smtp.title': '郵件與通知', + 'admin.smtp.hint': '用於傳送電子郵件通知的 SMTP 配置。', + 'admin.smtp.testButton': '傳送測試郵件', + 'admin.webhook.hint': '向外部 Webhook 傳送通知(Discord、Slack 等)。', + 'admin.smtp.testSuccess': '測試郵件傳送成功', + 'admin.smtp.testFailed': '測試郵件傳送失敗', + 'dayplan.icsTooltip': '匯出日曆 (ICS)', + 'share.linkTitle': '公開連結', + 'share.linkHint': '建立一個連結,任何人無需登入即可檢視此旅行。僅可檢視,無法編輯。', + 'share.createLink': '建立連結', + 'share.deleteLink': '刪除連結', + 'share.createError': '無法建立連結', + 'common.copy': '複製', + 'common.copied': '已複製', + 'share.permMap': '地圖與計劃', + 'share.permBookings': '預訂', + 'share.permPacking': '行李', + 'shared.expired': '連結已過期或無效', + 'shared.expiredHint': '此共享旅行連結已失效。', + 'shared.readOnly': '只讀共享檢視', + 'shared.tabPlan': '計劃', + 'shared.tabBookings': '預訂', + 'shared.tabPacking': '行李', + 'shared.tabBudget': '預算', + 'shared.tabChat': '聊天', + 'shared.days': '天', + 'shared.places': '個地點', + 'shared.other': '其他', + 'shared.totalBudget': '總預算', + 'shared.messages': '條訊息', + 'shared.sharedVia': '透過以下分享', + 'shared.confirmed': '已確認', + 'shared.pending': '待確認', + 'share.permBudget': '預算', + 'share.permCollab': '聊天', + 'settings.on': '開', + 'settings.off': '關', + 'settings.mcp.title': 'MCP 配置', + 'settings.mcp.endpoint': 'MCP 端點', + 'settings.mcp.clientConfig': '客戶端配置', + 'settings.mcp.clientConfigHint': '將 替換為下方列表中的 API 令牌。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。', + 'settings.mcp.copy': '複製', + 'settings.mcp.copied': '已複製!', + 'settings.mcp.apiTokens': 'API 令牌', + 'settings.mcp.createToken': '建立新令牌', + 'settings.mcp.noTokens': '暫無令牌,請建立一個以連線 MCP 客戶端。', + 'settings.mcp.tokenCreatedAt': '創建於', + 'settings.mcp.tokenUsedAt': '使用於', + 'settings.mcp.deleteTokenTitle': '刪除令牌', + 'settings.mcp.deleteTokenMessage': '此令牌將立即失效,使用它的所有 MCP 客戶端將失去訪問許可權。', + 'settings.mcp.modal.createTitle': '建立 API 令牌', + 'settings.mcp.modal.tokenName': '令牌名稱', + 'settings.mcp.modal.tokenNamePlaceholder': '例如:Claude Desktop、工作電腦', + 'settings.mcp.modal.creating': '建立中…', + 'settings.mcp.modal.create': '建立令牌', + 'settings.mcp.modal.createdTitle': '令牌已建立', + 'settings.mcp.modal.createdWarning': '此令牌只會顯示一次,請立即複製並妥善儲存——無法找回。', + 'settings.mcp.modal.done': '完成', + 'settings.mcp.toast.created': '令牌已建立', + 'settings.mcp.toast.createError': '建立令牌失敗', + 'settings.mcp.toast.deleted': '令牌已刪除', + 'settings.mcp.toast.deleteError': '刪除令牌失敗', + 'settings.account': '賬戶', + 'settings.about': '關於', + 'settings.username': '使用者名稱', + 'settings.email': '郵箱', + 'settings.role': '角色', + 'settings.roleAdmin': '管理員', + 'settings.oidcLinked': '已關聯', + 'settings.changePassword': '修改密碼', + 'settings.mustChangePassword': '您必須更改密碼才能繼續。請在下方設定新密碼。', + 'settings.currentPassword': '當前密碼', + 'settings.currentPasswordRequired': '請輸入當前密碼', + 'settings.newPassword': '新密碼', + 'settings.confirmPassword': '確認新密碼', + 'settings.updatePassword': '更新密碼', + 'settings.passwordRequired': '請輸入當前密碼和新密碼', + 'settings.passwordTooShort': '密碼至少需要 8 個字元', + 'settings.passwordMismatch': '兩次輸入的密碼不一致', + 'settings.passwordWeak': '密碼必須包含大寫字母、小寫字母、數字和特殊字元', + 'settings.passwordChanged': '密碼修改成功', + 'settings.deleteAccount': '刪除賬戶', + 'settings.deleteAccountTitle': '確定刪除賬戶?', + 'settings.deleteAccountWarning': '你的賬戶以及所有旅行、地點和檔案將被永久刪除。此操作無法撤銷。', + 'settings.deleteAccountConfirm': '永久刪除', + 'settings.deleteBlockedTitle': '無法刪除', + 'settings.deleteBlockedMessage': '你是唯一的管理員。請先將其他使用者提升為管理員,然後再刪除賬戶。', + 'settings.roleUser': '使用者', + 'settings.saveProfile': '儲存資料', + 'settings.mfa.title': '雙因素認證 (2FA)', + 'settings.mfa.description': '登入時新增第二步驗證。使用身份驗證器應用(Google Authenticator、Authy 等)。', + 'settings.mfa.requiredByPolicy': '管理員要求雙因素身份驗證。請先完成下方的身份驗證器設定後再繼續。', + 'settings.mfa.backupTitle': '備用程式碼', + 'settings.mfa.backupDescription': '如果你無法使用身份驗證器應用,可使用這些一次性備用程式碼登入。', + 'settings.mfa.backupWarning': '請立即儲存這些程式碼。每個程式碼只能使用一次。', + 'settings.mfa.backupCopy': '複製程式碼', + 'settings.mfa.backupDownload': '下載 TXT', + 'settings.mfa.backupPrint': '列印 / PDF', + 'settings.mfa.backupCopied': '備用程式碼已複製', + 'settings.mfa.enabled': '您的賬戶已啟用 2FA。', + 'settings.mfa.disabled': '2FA 未啟用。', + 'settings.mfa.setup': '設定身份驗證器', + 'settings.mfa.scanQr': '使用應用掃描此二維碼,或手動輸入金鑰。', + 'settings.mfa.secretLabel': '金鑰(手動輸入)', + 'settings.mfa.codePlaceholder': '6 位驗證碼', + 'settings.mfa.enable': '啟用 2FA', + 'settings.mfa.cancelSetup': '取消', + 'settings.mfa.disableTitle': '停用 2FA', + 'settings.mfa.disableHint': '輸入您的賬戶密碼和身份驗證器中的當前驗證碼。', + 'settings.mfa.disable': '停用 2FA', + 'settings.mfa.toastEnabled': '雙因素認證已啟用', + 'settings.mfa.toastDisabled': '雙因素認證已停用', + 'settings.mfa.demoBlocked': '演示模式下不可用', + 'settings.toast.mapSaved': '地圖設定已儲存', + 'settings.toast.keysSaved': 'API 金鑰已儲存', + 'settings.toast.displaySaved': '顯示設定已儲存', + 'settings.toast.profileSaved': '資料已儲存', + 'settings.uploadAvatar': '上傳頭像', + 'settings.removeAvatar': '移除頭像', + 'settings.avatarUploaded': '頭像已更新', + 'settings.avatarRemoved': '頭像已移除', + 'settings.avatarError': '上傳失敗', + + // Login + 'login.error': '登入失敗,請檢查你的憑據。', + 'login.tagline': '你的旅行。\n你的計劃。', + 'login.description': '透過互動地圖、預算管理和即時同步,協同規劃旅行。', + 'login.features.maps': '互動地圖', + 'login.features.mapsDesc': 'Google Places、路線和聚類', + 'login.features.realtime': '即時同步', + 'login.features.realtimeDesc': '透過 WebSocket 協同規劃', + 'login.features.budget': '預算跟蹤', + 'login.features.budgetDesc': '分類、圖表和人均費用', + 'login.features.collab': '協作', + 'login.features.collabDesc': '多使用者共享旅行', + 'login.features.packing': '行李清單', + 'login.features.packingDesc': '分類、進度和建議', + 'login.features.bookings': '預訂', + 'login.features.bookingsDesc': '航班、酒店、餐廳等', + 'login.features.files': '文件', + 'login.features.filesDesc': '上傳和管理文件', + 'login.features.routes': '智慧路線', + 'login.features.routesDesc': '自動最佳化和匯出到 Google Maps', + 'login.selfHosted': '自託管 · 開源 · 資料由你掌控', + 'login.title': '登入', + 'login.subtitle': '歡迎回來', + 'login.signingIn': '登入中…', + 'login.signIn': '登入', + 'login.createAdmin': '建立管理員賬戶', + 'login.createAdminHint': '為 TREK 設定第一個管理員賬戶。', + 'login.setNewPassword': '設定新密碼', + 'login.setNewPasswordHint': '您必須更改密碼才能繼續。', + 'login.createAccount': '建立賬戶', + 'login.createAccountHint': '註冊新賬戶。', + 'login.creating': '建立中…', + 'login.noAccount': '還沒有賬戶?', + 'login.hasAccount': '已有賬戶?', + 'login.register': '註冊', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': '使用者名稱', + 'login.oidc.registrationDisabled': '註冊已關閉。請聯絡管理員。', + 'login.oidc.noEmail': '未從提供商獲取到郵箱。', + 'login.mfaTitle': '雙因素認證', + 'login.mfaSubtitle': '請輸入身份驗證器應用中的 6 位驗證碼。', + 'login.mfaCodeLabel': '驗證碼', + 'login.mfaCodeRequired': '請輸入身份驗證器應用中的驗證碼。', + 'login.mfaHint': '開啟 Google Authenticator、Authy 或其他 TOTP 應用。', + 'login.mfaBack': '← 返回登入', + 'login.mfaVerify': '驗證', + 'login.oidc.tokenFailed': '認證失敗。', + 'login.oidc.invalidState': '會話無效,請重試。', + 'login.demoFailed': '演示登入失敗', + 'login.oidcSignIn': '透過 {name} 登入', + 'login.oidcOnly': '密碼登入已關閉。請透過 SSO 提供商登入。', + 'login.demoHint': '試用演示——無需註冊', + + // Register + 'register.passwordMismatch': '兩次輸入的密碼不一致', + 'register.passwordTooShort': '密碼至少需要 8 個字元', + 'register.failed': '註冊失敗', + 'register.getStarted': '開始使用', + 'register.subtitle': '建立賬戶,開始規劃你的夢想旅行。', + 'register.feature1': '無限旅行計劃', + 'register.feature2': '互動地圖檢視', + 'register.feature3': '管理地點和分類', + 'register.feature4': '跟蹤預訂', + 'register.feature5': '建立行李清單', + 'register.feature6': '儲存照片和檔案', + 'register.createAccount': '建立賬戶', + 'register.startPlanning': '開始規劃你的旅行', + 'register.minChars': '至少 6 個字元', + 'register.confirmPassword': '確認密碼', + 'register.repeatPassword': '重複密碼', + 'register.registering': '註冊中...', + 'register.register': '註冊', + 'register.hasAccount': '已有賬戶?', + 'register.signIn': '登入', + + // Admin + 'admin.title': '管理後臺', + 'admin.subtitle': '使用者管理和系統設定', + 'admin.tabs.users': '使用者', + 'admin.tabs.categories': '分類', + 'admin.tabs.backup': '備份', + 'admin.tabs.audit': '審計日誌', + 'admin.stats.users': '使用者', + 'admin.stats.trips': '旅行', + 'admin.stats.places': '地點', + 'admin.stats.photos': '照片', + 'admin.stats.files': '檔案', + 'admin.table.user': '使用者', + 'admin.table.email': '郵箱', + 'admin.table.role': '角色', + 'admin.table.created': '建立時間', + 'admin.table.lastLogin': '最後登入', + 'admin.table.actions': '操作', + 'admin.you': '(你)', + 'admin.editUser': '編輯使用者', + 'admin.newPassword': '新密碼', + 'admin.newPasswordHint': '留空則保持當前密碼', + 'admin.deleteUser': '刪除使用者「{name}」?所有旅行將被永久刪除。', + 'admin.deleteUserTitle': '刪除使用者', + 'admin.newPasswordPlaceholder': '輸入新密碼…', + 'admin.toast.loadError': '載入管理資料失敗', + 'admin.toast.userUpdated': '使用者已更新', + 'admin.toast.updateError': '更新失敗', + 'admin.toast.userDeleted': '使用者已刪除', + 'admin.toast.deleteError': '刪除失敗', + 'admin.toast.cannotDeleteSelf': '不能刪除自己的賬戶', + 'admin.toast.userCreated': '使用者已建立', + 'admin.toast.createError': '建立使用者失敗', + 'admin.toast.fieldsRequired': '使用者名稱、郵箱和密碼為必填項', + 'admin.createUser': '建立使用者', + 'admin.invite.title': '邀請連結', + 'admin.invite.subtitle': '建立一次性註冊連結', + 'admin.invite.create': '建立連結', + 'admin.invite.createAndCopy': '建立並複製', + 'admin.invite.empty': '尚未建立邀請連結', + 'admin.invite.maxUses': '最大使用次數', + 'admin.invite.expiry': '有效期', + 'admin.invite.uses': '已使用', + 'admin.invite.expiresAt': '過期時間', + 'admin.invite.createdBy': '由', + 'admin.invite.active': '有效', + 'admin.invite.expired': '已過期', + 'admin.invite.usedUp': '已用完', + 'admin.invite.copied': '邀請連結已複製', + 'admin.invite.copyLink': '複製連結', + 'admin.invite.deleted': '邀請連結已刪除', + 'admin.invite.createError': '建立連結失敗', + 'admin.invite.deleteError': '刪除連結失敗', + 'admin.tabs.settings': '設定', + 'admin.allowRegistration': '允許註冊', + 'admin.allowRegistrationHint': '新使用者可以自行註冊', + 'admin.requireMfa': '要求雙因素身份驗證(2FA)', + 'admin.requireMfaHint': '未啟用 2FA 的使用者必須先完成設定中的配置才能使用應用。', + 'admin.apiKeys': 'API 金鑰', + 'admin.apiKeysHint': '可選。啟用地點的擴充套件資料,如照片和天氣。', + 'admin.mapsKey': 'Google Maps API 金鑰', + 'admin.mapsKeyHint': '用於地點搜尋。在 console.cloud.google.com 獲取', + 'admin.mapsKeyHintLong': '沒有 API 金鑰時,使用 OpenStreetMap 搜尋地點。有了 Google API 金鑰,還可以載入照片、評分和營業時間。在 console.cloud.google.com 獲取。', + 'admin.recommended': '推薦', + 'admin.weatherKey': 'OpenWeatherMap API 金鑰', + 'admin.weatherKeyHint': '用於天氣資料。在 openweathermap.org 免費獲取', + 'admin.validateKey': '測試', + 'admin.keyValid': '已連線', + 'admin.keyInvalid': '無效', + 'admin.keySaved': 'API 金鑰已儲存', + 'admin.oidcTitle': '單點登入 (OIDC)', + 'admin.oidcSubtitle': '允許透過 Google、Apple、Authentik 或 Keycloak 等外部提供商登入。', + 'admin.oidcDisplayName': '顯示名稱', + 'admin.oidcIssuer': '頒發者 URL', + 'admin.oidcIssuerHint': '提供商的 OpenID Connect 頒發者 URL。如 https://accounts.google.com', + 'admin.oidcSaved': 'OIDC 配置已儲存', + 'admin.oidcOnlyMode': '停用密碼登入', + 'admin.oidcOnlyModeHint': '啟用後,僅允許 SSO 登入。密碼登入和註冊將被停用。', + + // File Types + 'admin.fileTypes': '允許的檔案型別', + 'admin.fileTypesHint': '配置使用者可以上傳的檔案型別。', + 'admin.fileTypesFormat': '以逗號分隔的副檔名(如 jpg,png,pdf,doc)。使用 * 允許所有型別。', + 'admin.fileTypesSaved': '檔案型別設定已儲存', + + 'admin.bagTracking.title': '行李追蹤', + 'admin.bagTracking.subtitle': '為打包物品啟用重量和行李分配', + 'admin.tabs.config': '配置', + 'admin.tabs.templates': '打包模板', + 'admin.packingTemplates.title': '打包模板', + 'admin.packingTemplates.subtitle': '建立可複用的旅行打包清單', + 'admin.packingTemplates.create': '新建模板', + 'admin.packingTemplates.namePlaceholder': '模板名稱(如:海灘度假)', + 'admin.packingTemplates.empty': '尚未建立模板', + 'admin.packingTemplates.items': '物品', + 'admin.packingTemplates.categories': '分類', + 'admin.packingTemplates.itemName': '物品名稱', + 'admin.packingTemplates.itemCategory': '分類', + 'admin.packingTemplates.categoryName': '分類名稱(如:衣物)', + 'admin.packingTemplates.addCategory': '新增分類', + 'admin.packingTemplates.created': '模板已建立', + 'admin.packingTemplates.deleted': '模板已刪除', + 'admin.packingTemplates.loadError': '載入模板失敗', + 'admin.packingTemplates.createError': '建立模板失敗', + 'admin.packingTemplates.deleteError': '刪除模板失敗', + 'admin.packingTemplates.saveError': '儲存失敗', + + // Addons + 'admin.tabs.addons': '擴充套件', + 'admin.addons.title': '擴充套件', + 'admin.addons.subtitle': '啟用或停用功能以自定義你的 TREK 體驗。', + 'admin.addons.catalog.memories.name': '照片 (Immich)', + 'admin.addons.catalog.memories.description': '透過 Immich 例項分享旅行照片', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': '用於 AI 助手整合的模型上下文協議', + 'admin.addons.catalog.packing.name': '行李', + 'admin.addons.catalog.packing.description': '每次旅行的行李準備清單', + 'admin.addons.catalog.budget.name': '預算', + 'admin.addons.catalog.budget.description': '跟蹤支出並規劃旅行預算', + 'admin.addons.catalog.documents.name': '文件', + 'admin.addons.catalog.documents.description': '儲存和管理旅行文件', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': '帶日曆檢視的個人假期規劃器', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': '標記已訪問國家和旅行統計的世界地圖', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': '旅行規劃的即時筆記、投票和聊天', + 'admin.addons.subtitleBefore': '啟用或停用功能以自定義你的 ', + 'admin.addons.subtitleAfter': ' 體驗。', + 'admin.addons.enabled': '已啟用', + 'admin.addons.disabled': '已停用', + 'admin.addons.type.trip': '旅行', + 'admin.addons.type.global': '全域性', + 'admin.addons.type.integration': '整合', + 'admin.addons.tripHint': '在每次旅行中作為標籤頁顯示', + 'admin.addons.globalHint': '在主導航中作為獨立板塊顯示', + 'admin.addons.integrationHint': '後端服務和 API 整合,無專屬頁面', + 'admin.addons.toast.updated': '擴充套件已更新', + 'admin.addons.toast.error': '更新擴充套件失敗', + 'admin.addons.noAddons': '暫無可用擴充套件', + // Weather info + 'admin.weather.title': '天氣資料', + 'admin.weather.badge': '自 2026 年 3 月 24 日起', + 'admin.weather.description': 'TREK 使用 Open-Meteo 作為天氣資料來源。Open-Meteo 是免費的開源天氣服務——無需 API 金鑰。', + 'admin.weather.forecast': '16 天天氣預報', + 'admin.weather.forecastDesc': '之前為 5 天 (OpenWeatherMap)', + 'admin.weather.climate': '歷史氣候資料', + 'admin.weather.climateDesc': '16 天預報之外的日期使用過去 85 年的平均值', + 'admin.weather.requests': '每天 10,000 次請求', + 'admin.weather.requestsDesc': '免費,無需 API 金鑰', + 'admin.weather.locationHint': '天氣基於每天中第一個有座標的地點。如果當天沒有分配地點,則使用地點列表中的任意地點作為參考。', + + // MCP Tokens + 'admin.tabs.mcpTokens': 'MCP 令牌', + 'admin.mcpTokens.title': 'MCP 令牌', + 'admin.mcpTokens.subtitle': '管理所有使用者的 API 令牌', + 'admin.mcpTokens.owner': '所有者', + 'admin.mcpTokens.tokenName': '令牌名稱', + 'admin.mcpTokens.created': '建立時間', + 'admin.mcpTokens.lastUsed': '最後使用', + 'admin.mcpTokens.never': '從未', + 'admin.mcpTokens.empty': '尚未建立任何 MCP 令牌', + 'admin.mcpTokens.deleteTitle': '刪除令牌', + 'admin.mcpTokens.deleteMessage': '此令牌將立即被撤銷。使用者將失去透過此令牌的 MCP 訪問許可權。', + 'admin.mcpTokens.deleteSuccess': '令牌已刪除', + 'admin.mcpTokens.deleteError': '刪除令牌失敗', + 'admin.mcpTokens.loadError': '載入令牌失敗', + + // GitHub + 'admin.tabs.github': 'GitHub', + + 'admin.audit.subtitle': '安全與管理員操作記錄(備份、使用者、MFA、設定)。', + 'admin.audit.empty': '暫無審計記錄。', + 'admin.audit.refresh': '重新整理', + 'admin.audit.loadMore': '載入更多', + 'admin.audit.showing': '已載入 {count} 條 · 共 {total} 條', + 'admin.audit.col.time': '時間', + 'admin.audit.col.user': '使用者', + 'admin.audit.col.action': '操作', + 'admin.audit.col.resource': '資源', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': '詳情', + + 'admin.github.title': '版本歷史', + 'admin.github.subtitle': '{repo} 的最新更新', + 'admin.github.latest': '最新', + 'admin.github.prerelease': '預釋出', + 'admin.github.showDetails': '顯示詳情', + 'admin.github.hideDetails': '隱藏詳情', + 'admin.github.loadMore': '載入更多', + 'admin.github.loading': '載入中...', + 'admin.github.support': '幫助我繼續開發 TREK', + 'admin.github.error': '載入版本失敗', + 'admin.github.by': '作者', + + 'admin.update.available': '有可用更新', + 'admin.update.text': 'TREK {version} 已釋出。你當前使用的是 {current}。', + 'admin.update.button': '在 GitHub 檢視', + 'admin.update.install': '安裝更新', + 'admin.update.confirmTitle': '確定安裝更新?', + 'admin.update.confirmText': 'TREK 將從 {current} 更新到 {version}。伺服器將自動重啟。', + 'admin.update.dataInfo': '你的所有資料(旅行、使用者、API 金鑰、上傳檔案、Vacay、Atlas、預算)將被保留。', + 'admin.update.warning': '重啟期間應用將短暫不可用。', + 'admin.update.confirm': '立即更新', + 'admin.update.installing': '更新中…', + 'admin.update.success': '更新已安裝!伺服器正在重啟…', + 'admin.update.failed': '更新失敗', + 'admin.update.backupHint': '建議在更新前建立備份。', + 'admin.update.backupLink': '前往備份', + 'admin.update.howTo': '如何更新', + 'admin.update.dockerText': '你的 TREK 例項執行在 Docker 中。要更新到 {version},請在伺服器上執行以下命令:', + 'admin.update.reloadHint': '請在幾秒後重新整理頁面。', + + // Vacay addon + 'vacay.subtitle': '規劃和管理假期', + 'vacay.settings': '設定', + 'vacay.year': '年份', + 'vacay.addYear': '新增下一年', + 'vacay.addPrevYear': '新增上一年', + 'vacay.removeYear': '移除年份', + 'vacay.removeYearConfirm': '移除 {year}?', + 'vacay.removeYearHint': '該年度所有假期記錄和公司假日將被永久刪除。', + 'vacay.remove': '移除', + 'vacay.persons': '成員', + 'vacay.noPersons': '暫無成員', + 'vacay.addPerson': '新增成員', + 'vacay.editPerson': '編輯成員', + 'vacay.removePerson': '移除成員', + 'vacay.removePersonConfirm': '移除 {name}?', + 'vacay.removePersonHint': '該成員的所有假期記錄將被永久刪除。', + 'vacay.personName': '姓名', + 'vacay.personNamePlaceholder': '輸入姓名', + 'vacay.color': '顏色', + 'vacay.add': '新增', + 'vacay.legend': '圖例', + 'vacay.publicHoliday': '公共假日', + 'vacay.companyHoliday': '公司假日', + 'vacay.weekend': '週末', + 'vacay.modeVacation': '休假', + 'vacay.modeCompany': '公司假日', + 'vacay.entitlement': '年假額度', + 'vacay.entitlementDays': '天', + 'vacay.used': '已用', + 'vacay.remaining': '剩餘', + 'vacay.carriedOver': '從 {year} 結轉', + 'vacay.blockWeekends': '鎖定週末', + 'vacay.blockWeekendsHint': '禁止在週六和週日安排假期', + 'vacay.weekendDays': '週末', + 'vacay.mon': '週一', + 'vacay.tue': '週二', + 'vacay.wed': '週三', + 'vacay.thu': '週四', + 'vacay.fri': '週五', + 'vacay.sat': '週六', + 'vacay.sun': '週日', + 'vacay.publicHolidays': '公共假日', + 'vacay.publicHolidaysHint': '在日曆中標記公共假日', + 'vacay.selectCountry': '選擇國家', + 'vacay.selectRegion': '選擇地區(可選)', + 'vacay.companyHolidays': '公司假日', + 'vacay.companyHolidaysHint': '允許標記公司統一休假日', + 'vacay.companyHolidaysNoDeduct': '公司假日不計入年假天數。', + 'vacay.carryOver': '結轉', + 'vacay.carryOverHint': '自動將剩餘年假天數結轉到下一年', + 'vacay.sharing': '共享', + 'vacay.sharingHint': '與其他 TREK 使用者共享你的假期計劃', + 'vacay.owner': '所有者', + 'vacay.shareEmailPlaceholder': 'TREK 使用者郵箱', + 'vacay.shareSuccess': '計劃共享成功', + 'vacay.shareError': '無法共享計劃', + 'vacay.dissolve': '解除合併', + 'vacay.dissolveHint': '重新分離日曆。你的記錄將被保留。', + 'vacay.dissolveAction': '解除', + 'vacay.dissolved': '日曆已分離', + 'vacay.fusedWith': '已合併', + 'vacay.you': '你', + 'vacay.noData': '暫無資料', + 'vacay.changeColor': '更改顏色', + 'vacay.inviteUser': '邀請使用者', + 'vacay.inviteHint': '邀請其他 TREK 使用者共享合併的假期日曆。', + 'vacay.selectUser': '選擇使用者', + 'vacay.sendInvite': '傳送邀請', + 'vacay.inviteSent': '邀請已傳送', + 'vacay.inviteError': '無法傳送邀請', + 'vacay.pending': '待處理', + 'vacay.noUsersAvailable': '沒有可用使用者', + 'vacay.accept': '接受', + 'vacay.decline': '拒絕', + 'vacay.acceptFusion': '接受併合並', + 'vacay.inviteTitle': '合併請求', + 'vacay.inviteWantsToFuse': '想要與你共享假期日曆。', + 'vacay.fuseInfo1': '你們雙方將在一個共享日曆中看到所有假期記錄。', + 'vacay.fuseInfo2': '雙方都可以為對方建立和編輯記錄。', + 'vacay.fuseInfo3': '雙方都可以刪除記錄和修改年假額度。', + 'vacay.fuseInfo4': '公共假日和公司假日等設定將共享。', + 'vacay.fuseInfo5': '任何一方都可以隨時解除合併。你的記錄將被保留。', + 'vacay.addCalendar': '新增日曆', + 'vacay.calendarColor': '顏色', + 'vacay.calendarLabel': '標籤', + 'vacay.noCalendars': '無日曆', + 'nav.myTrips': '我的旅行', + + // Atlas addon + 'atlas.subtitle': '你的全球旅行足跡', + 'atlas.countries': '國家', + 'atlas.trips': '旅行', + 'atlas.places': '地點', + 'atlas.days': '天', + 'atlas.visitedCountries': '已訪問國家', + 'atlas.cities': '城市', + 'atlas.noData': '暫無旅行資料', + 'atlas.noDataHint': '建立旅行並新增地點以檢視世界地圖', + 'atlas.lastTrip': '上次旅行', + 'atlas.nextTrip': '下次旅行', + 'atlas.daysLeft': '天后出發', + 'atlas.streak': '連續', + 'atlas.year': '年', + 'atlas.years': '年', + 'atlas.yearInRow': '年連續', + 'atlas.yearsInRow': '年連續', + 'atlas.tripIn': '次旅行在', + 'atlas.tripsIn': '次旅行在', + 'atlas.since': '自', + 'atlas.europe': '歐洲', + 'atlas.asia': '亞洲', + 'atlas.northAmerica': '北美洲', + 'atlas.southAmerica': '南美洲', + 'atlas.africa': '非洲', + 'atlas.oceania': '大洋洲', + 'atlas.other': '其他', + 'atlas.firstVisit': '首次旅行', + 'atlas.lastVisitLabel': '最近旅行', + 'atlas.tripSingular': '次旅行', + 'atlas.tripPlural': '次旅行', + 'atlas.placeVisited': '個地點已訪問', + 'atlas.placesVisited': '個地點已訪問', + 'atlas.statsTab': '統計', + 'atlas.bucketTab': '心願單', + 'atlas.addBucket': '新增到心願單', + 'atlas.bucketNamePlaceholder': '地點或目的地...', + 'atlas.bucketNotesPlaceholder': '備註(可選)', + 'atlas.bucketEmpty': '你的心願單是空的', + 'atlas.bucketEmptyHint': '新增你夢想去的地方', + 'atlas.unmark': '移除', + 'atlas.confirmMark': '將此國家標記為已訪問?', + 'atlas.confirmUnmark': '從已訪問列表中移除此國家?', + 'atlas.markVisited': '標記為已訪問', + 'atlas.markVisitedHint': '將此國家新增到已訪問列表', + 'atlas.addToBucket': '新增到心願單', + 'atlas.addPoi': '新增地點', + 'atlas.searchCountry': '搜尋國家...', + 'atlas.month': '月份', + 'atlas.addToBucketHint': '儲存為想去的地方', + 'atlas.bucketWhen': '你計劃什麼時候去?', + + // Trip Planner + 'trip.tabs.plan': '計劃', + 'trip.tabs.reservations': '預訂', + 'trip.tabs.reservationsShort': '預訂', + 'trip.tabs.packing': '行李清單', + 'trip.tabs.packingShort': '行李', + 'trip.tabs.budget': '預算', + 'trip.tabs.files': '檔案', + 'trip.loading': '載入旅行中...', + 'trip.loadingPhotos': '正在載入地點照片...', + 'trip.mobilePlan': '計劃', + 'trip.mobilePlaces': '地點', + 'trip.toast.placeUpdated': '地點已更新', + 'trip.toast.placeAdded': '地點已新增', + 'trip.toast.placeDeleted': '地點已刪除', + 'trip.toast.selectDay': '請先選擇一天', + 'trip.toast.assignedToDay': '地點已分配到當天', + 'trip.toast.reorderError': '排序失敗', + 'trip.toast.reservationUpdated': '預訂已更新', + 'trip.toast.reservationAdded': '預訂已新增', + 'trip.toast.deleted': '已刪除', + 'trip.confirm.deletePlace': '確定要刪除這個地點嗎?', + + // Day Plan Sidebar + 'dayplan.emptyDay': '當天暫無計劃', + 'dayplan.addNote': '新增備註', + 'dayplan.editNote': '編輯備註', + 'dayplan.noteAdd': '新增備註', + 'dayplan.noteEdit': '編輯備註', + 'dayplan.noteTitle': '備註', + 'dayplan.noteSubtitle': '每日備註', + 'dayplan.totalCost': '總費用', + 'dayplan.days': '天', + 'dayplan.dayN': '第 {n} 天', + 'dayplan.calculating': '計算中...', + 'dayplan.route': '路線', + 'dayplan.optimize': '最佳化', + 'dayplan.optimized': '路線已最佳化', + 'dayplan.routeError': '路線計算失敗', + 'dayplan.toast.needTwoPlaces': '路線最佳化至少需要兩個地點', + 'dayplan.toast.routeOptimized': '路線已最佳化', + 'dayplan.toast.noGeoPlaces': '未找到有座標的地點用於路線計算', + 'dayplan.confirmed': '已確認', + 'dayplan.pendingRes': '待確認', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': '匯出當天計劃為 PDF', + 'dayplan.pdfError': 'PDF 匯出失敗', + 'dayplan.cannotReorderTransport': '有固定時間的預訂無法重新排序', + 'dayplan.confirmRemoveTimeTitle': '移除時間?', + 'dayplan.confirmRemoveTimeBody': '此地點有固定時間({time})。移動後將移除時間並允許自由排序。', + 'dayplan.confirmRemoveTimeAction': '移除時間並移動', + 'dayplan.cannotDropOnTimed': '無法將專案放置在有固定時間的條目之間', + 'dayplan.cannotBreakChronology': '這將打亂已計劃專案和預訂的時間順序', + + // Places Sidebar + 'places.addPlace': '新增地點/活動', + 'places.importGpx': 'GPX', + 'places.gpxImported': '已從 GPX 匯入 {count} 個地點', + 'places.gpxError': 'GPX 匯入失敗', + 'places.importGoogleList': 'Google 列表', + 'places.googleListHint': '貼上共享的 Google Maps 列表連結以匯入所有地點。', + 'places.googleListImported': '已從"{list}"匯入 {count} 個地點', + 'places.googleListError': 'Google Maps 列表匯入失敗', + 'places.viewDetails': '檢視詳情', + 'places.urlResolved': '已從 URL 匯入地點', + 'places.assignToDay': '新增到哪一天?', + 'places.all': '全部', + 'places.unplanned': '未規劃', + 'places.search': '搜尋地點...', + 'places.allCategories': '所有分類', + 'places.categoriesSelected': '個分類', + 'places.clearFilter': '清除篩選', + 'places.count': '{count} 個地點', + 'places.countSingular': '1 個地點', + 'places.allPlanned': '所有地點已規劃', + 'places.noneFound': '未找到地點', + 'places.editPlace': '編輯地點', + 'places.formName': '名稱', + 'places.formNamePlaceholder': '如:埃菲爾鐵塔', + 'places.formDescription': '描述', + 'places.formDescriptionPlaceholder': '簡短描述...', + 'places.formAddress': '地址', + 'places.formAddressPlaceholder': '街道、城市、國家', + 'places.formLat': '緯度(如 48.8566)', + 'places.formLng': '經度(如 2.3522)', + 'places.formCategory': '分類', + 'places.noCategory': '無分類', + 'places.categoryNamePlaceholder': '分類名稱', + 'places.formTime': '時間', + 'places.startTime': '開始', + 'places.endTime': '結束', + 'places.endTimeBeforeStart': '結束時間早於開始時間', + 'places.timeCollision': '時間衝突:', + 'places.formWebsite': '網站', + 'places.formNotesPlaceholder': '個人備註...', + 'places.formReservation': '預訂', + 'places.reservationNotesPlaceholder': '預訂備註、確認號...', + 'places.mapsSearchPlaceholder': '搜尋地點...', + 'places.mapsSearchError': '地點搜尋失敗。', + 'places.osmHint': '使用 OpenStreetMap 搜尋(無照片、營業時間或評分)。在設定中新增 Google API 金鑰以獲取完整資訊。', + 'places.osmActive': '透過 OpenStreetMap 搜尋(無照片、評分或營業時間)。在設定中新增 Google API 金鑰以獲取增強資料。', + 'places.categoryCreateError': '建立分類失敗', + 'places.nameRequired': '請輸入名稱', + 'places.saveError': '儲存失敗', + // Place Inspector + 'inspector.opened': '營業中', + 'inspector.closed': '已關閉', + 'inspector.openingHours': '營業時間', + 'inspector.showHours': '顯示營業時間', + 'inspector.files': '檔案', + 'inspector.filesCount': '{count} 個檔案', + 'inspector.removeFromDay': '從當天移除', + 'inspector.addToDay': '新增到當天', + 'inspector.confirmedRes': '已確認預訂', + 'inspector.pendingRes': '待確認預訂', + 'inspector.google': '在 Google Maps 中開啟', + 'inspector.website': '開啟網站', + 'inspector.addRes': '預訂', + 'inspector.editRes': '編輯預訂', + 'inspector.participants': '參與者', + 'inspector.trackStats': '軌跡資料', + + // Reservations + 'reservations.title': '預訂', + 'reservations.empty': '暫無預訂', + 'reservations.emptyHint': '新增航班、酒店等預訂資訊', + 'reservations.add': '新增預訂', + 'reservations.addManual': '手動新增', + 'reservations.placeHint': '提示:建議從地點直接建立預訂,以便與日程計劃關聯。', + 'reservations.confirmed': '已確認', + 'reservations.pending': '待確認', + 'reservations.summary': '{confirmed} 已確認,{pending} 待確認', + 'reservations.fromPlan': '來自計劃', + 'reservations.showFiles': '檢視檔案', + 'reservations.editTitle': '編輯預訂', + 'reservations.status': '狀態', + 'reservations.datetime': '日期和時間', + 'reservations.startTime': '開始時間', + 'reservations.endTime': '結束時間', + 'reservations.date': '日期', + 'reservations.time': '時間', + 'reservations.timeAlt': '時間(備選,如 19:30)', + 'reservations.notes': '備註', + 'reservations.notesPlaceholder': '其他備註...', + 'reservations.meta.airline': '航空公司', + 'reservations.meta.flightNumber': '航班號', + 'reservations.meta.from': '出發', + 'reservations.meta.to': '到達', + 'reservations.meta.trainNumber': '車次', + 'reservations.meta.platform': '站臺', + 'reservations.meta.seat': '座位', + 'reservations.meta.checkIn': '入住', + 'reservations.meta.checkOut': '退房', + 'reservations.meta.linkAccommodation': '住宿', + 'reservations.meta.pickAccommodation': '關聯住宿', + 'reservations.meta.noAccommodation': '無', + 'reservations.meta.hotelPlace': '住宿', + 'reservations.meta.pickHotel': '選擇住宿', + 'reservations.meta.fromDay': '從', + 'reservations.meta.toDay': '到', + 'reservations.meta.selectDay': '選擇日期', + 'reservations.type.flight': '航班', + 'reservations.type.hotel': '住宿', + 'reservations.type.restaurant': '餐廳', + 'reservations.type.train': '火車', + 'reservations.type.car': '租車', + 'reservations.type.cruise': '郵輪', + 'reservations.type.event': '活動', + 'reservations.type.tour': '旅遊團', + 'reservations.type.other': '其他', + 'reservations.confirm.delete': '確定要刪除預訂「{name}」嗎?', + 'reservations.confirm.deleteTitle': '刪除預訂?', + 'reservations.confirm.deleteBody': '"{name}" 將被永久刪除。', + 'reservations.toast.updated': '預訂已更新', + 'reservations.toast.removed': '預訂已刪除', + 'reservations.toast.fileUploaded': '檔案已上傳', + 'reservations.toast.uploadError': '上傳失敗', + 'reservations.newTitle': '新建預訂', + 'reservations.bookingType': '預訂型別', + 'reservations.titleLabel': '標題', + 'reservations.titlePlaceholder': '如:漢莎 LH123、阿德隆酒店...', + 'reservations.locationAddress': '地點 / 地址', + 'reservations.locationPlaceholder': '地址、機場、酒店...', + 'reservations.confirmationCode': '預訂碼', + 'reservations.confirmationPlaceholder': '如:ABC12345', + 'reservations.day': '日期', + 'reservations.noDay': '無日期', + 'reservations.place': '地點', + 'reservations.noPlace': '無地點', + 'reservations.pendingSave': '將被儲存…', + 'reservations.uploading': '上傳中...', + 'reservations.attachFile': '附加檔案', + 'reservations.linkExisting': '關聯已有檔案', + 'reservations.toast.saveError': '儲存失敗', + 'reservations.toast.updateError': '更新失敗', + 'reservations.toast.deleteError': '刪除失敗', + 'reservations.confirm.remove': '移除「{name}」的預訂?', + 'reservations.linkAssignment': '關聯日程分配', + 'reservations.pickAssignment': '從計劃中選擇一個分配...', + 'reservations.noAssignment': '無關聯(獨立)', + + // Budget + 'budget.title': '預算', + 'budget.exportCsv': '匯出 CSV', + 'budget.emptyTitle': '尚未建立預算', + 'budget.emptyText': '建立分類和條目來規劃旅行預算', + 'budget.emptyPlaceholder': '輸入分類名稱...', + 'budget.createCategory': '建立分類', + 'budget.category': '分類', + 'budget.categoryName': '分類名稱', + 'budget.table.name': '名稱', + 'budget.table.total': '合計', + 'budget.table.persons': '人數', + 'budget.table.days': '天數', + 'budget.table.perPerson': '人均', + 'budget.table.perDay': '日均', + 'budget.table.perPersonDay': '人日均', + 'budget.table.note': '備註', + 'budget.table.date': '日期', + 'budget.newEntry': '新建條目', + 'budget.defaultEntry': '新建條目', + 'budget.defaultCategory': '新分類', + 'budget.total': '合計', + 'budget.totalBudget': '總預算', + 'budget.byCategory': '按分類', + 'budget.editTooltip': '點選編輯', + 'budget.confirm.deleteCategory': '確定刪除分類「{name}」及其 {count} 個條目?', + 'budget.deleteCategory': '刪除分類', + 'budget.perPerson': '人均', + 'budget.paid': '已支付', + 'budget.open': '未支付', + 'budget.noMembers': '未分配成員', + 'budget.settlement': '結算', + 'budget.settlementInfo': '點選預算專案上的成員頭像將其標記為綠色——表示該成員已付款。結算會顯示誰欠誰多少。', + 'budget.netBalances': '淨餘額', + + // Files + 'files.title': '檔案', + 'files.count': '{count} 個檔案', + 'files.countSingular': '1 個檔案', + 'files.uploaded': '已上傳 {count} 個', + 'files.uploadError': '上傳失敗', + 'files.dropzone': '將檔案拖放到此處', + 'files.dropzoneHint': '或點選瀏覽', + 'files.allowedTypes': '圖片、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大 50 MB', + 'files.uploading': '上傳中...', + 'files.filterAll': '全部', + 'files.filterPdf': 'PDF', + 'files.filterImages': '圖片', + 'files.filterDocs': '文件', + 'files.filterCollab': '協作筆記', + 'files.sourceCollab': '來自協作筆記', + 'files.empty': '暫無檔案', + 'files.emptyHint': '上傳檔案以附加到旅行中', + 'files.openTab': '在新標籤頁中開啟', + 'files.confirm.delete': '確定要刪除此檔案嗎?', + 'files.toast.deleted': '檔案已刪除', + 'files.toast.deleteError': '刪除檔案失敗', + 'files.sourcePlan': '日程計劃', + 'files.sourceBooking': '預訂', + 'files.attach': '附加', + 'files.pasteHint': '也可以從剪貼簿貼上圖片 (Ctrl+V)', + 'files.trash': '回收站', + 'files.trashEmpty': '回收站為空', + 'files.emptyTrash': '清空回收站', + 'files.restore': '恢復', + 'files.star': '收藏', + 'files.unstar': '取消收藏', + 'files.assign': '分配', + 'files.assignTitle': '分配檔案', + 'files.assignPlace': '地點', + 'files.assignBooking': '預訂', + 'files.unassigned': '未分配', + 'files.unlink': '移除關聯', + 'files.toast.trashed': '已移至回收站', + 'files.toast.restored': '檔案已恢復', + 'files.toast.trashEmptied': '回收站已清空', + 'files.toast.assigned': '檔案已分配', + 'files.toast.assignError': '分配失敗', + 'files.toast.restoreError': '恢復失敗', + 'files.confirm.permanentDelete': '永久刪除此檔案?此操作無法撤銷。', + 'files.confirm.emptyTrash': '永久刪除回收站中的所有檔案?此操作無法撤銷。', + 'files.noteLabel': '備註', + 'files.notePlaceholder': '新增備註...', + + // Packing + 'packing.title': '行李清單', + 'packing.empty': '行李清單為空', + 'packing.import': '匯入', + 'packing.importTitle': '匯入裝箱清單', + 'packing.importHint': '每行一個物品。可選用逗號、分號或製表符分隔類別和數量:名稱, 類別, 數量', + 'packing.importPlaceholder': '牙刷\n防曬霜, 衛生\nT恤, 衣物, 5\n護照, 證件', + 'packing.importCsv': '載入 CSV/TXT', + 'packing.importAction': '匯入 {count}', + 'packing.importSuccess': '已匯入 {count} 項', + 'packing.importError': '匯入失敗', + 'packing.importEmpty': '沒有可匯入的專案', + 'packing.progress': '已打包 {packed}/{total}({percent}%)', + 'packing.clearChecked': '移除 {count} 個已勾選', + 'packing.clearCheckedShort': '移除 {count} 個', + 'packing.suggestions': '建議', + 'packing.suggestionsTitle': '新增建議', + 'packing.allSuggested': '所有建議已新增', + 'packing.allPacked': '全部打包完成!', + 'packing.addPlaceholder': '新增新物品...', + 'packing.categoryPlaceholder': '分類...', + 'packing.filterAll': '全部', + 'packing.filterOpen': '未完成', + 'packing.filterDone': '已完成', + 'packing.emptyTitle': '行李清單為空', + 'packing.emptyHint': '新增物品或使用建議', + 'packing.emptyFiltered': '沒有匹配的物品', + 'packing.menuRename': '重新命名', + 'packing.menuCheckAll': '全部勾選', + 'packing.menuUncheckAll': '取消全部勾選', + 'packing.menuDeleteCat': '刪除分類', + 'packing.addItem': '新增物品', + 'packing.addItemPlaceholder': '物品名稱...', + 'packing.addCategory': '新增分類', + 'packing.newCategoryPlaceholder': '分類名稱(如:衣物)', + 'packing.applyTemplate': '應用模板', + 'packing.template': '模板', + 'packing.templateApplied': '已從模板新增 {count} 個物品', + 'packing.templateError': '應用模板失敗', + 'packing.assignUser': '分配使用者', + 'packing.noMembers': '無成員', + 'packing.bags': '行李', + 'packing.noBag': '未分配', + 'packing.totalWeight': '總重量', + 'packing.bagName': '名稱...', + 'packing.addBag': '新增行李', + 'packing.changeCategory': '更改分類', + 'packing.confirm.clearChecked': '確定移除 {count} 個已勾選的物品?', + 'packing.confirm.deleteCat': '確定刪除分類「{name}」及其 {count} 個物品?', + 'packing.defaultCategory': '其他', + 'packing.toast.saveError': '儲存失敗', + 'packing.toast.deleteError': '刪除失敗', + 'packing.toast.renameError': '重新命名失敗', + 'packing.toast.addError': '新增失敗', + + // Packing suggestions + 'packing.suggestions.items': [ + { name: '護照', category: '證件' }, + { name: '身份證', category: '證件' }, + { name: '旅行保險', category: '證件' }, + { name: '機票', category: '證件' }, + { name: '信用卡', category: '財務' }, + { name: '現金', category: '財務' }, + { name: '簽證', category: '證件' }, + { name: 'T恤', category: '衣物' }, + { name: '褲子', category: '衣物' }, + { name: '內衣', category: '衣物' }, + { name: '襪子', category: '衣物' }, + { name: '外套', category: '衣物' }, + { name: '睡衣', category: '衣物' }, + { name: '泳衣', category: '衣物' }, + { name: '雨衣', category: '衣物' }, + { name: '舒適的鞋子', category: '衣物' }, + { name: '牙刷', category: '洗漱用品' }, + { name: '牙膏', category: '洗漱用品' }, + { name: '洗髮水', category: '洗漱用品' }, + { name: '除臭劑', category: '洗漱用品' }, + { name: '防曬霜', category: '洗漱用品' }, + { name: '剃鬚刀', category: '洗漱用品' }, + { name: '充電器', category: '電子產品' }, + { name: '充電寶', category: '電子產品' }, + { name: '耳機', category: '電子產品' }, + { name: '旅行轉換插頭', category: '電子產品' }, + { name: '相機', category: '電子產品' }, + { name: '止痛藥', category: '健康' }, + { name: '創可貼', category: '健康' }, + { name: '消毒液', category: '健康' }, + ], + + // Members / Sharing + 'members.shareTrip': '分享旅行', + 'members.inviteUser': '邀請使用者', + 'members.selectUser': '選擇使用者…', + 'members.invite': '邀請', + 'members.allHaveAccess': '所有使用者均已擁有訪問許可權。', + 'members.access': '訪問許可權', + 'members.person': '人', + 'members.persons': '人', + 'members.you': '你', + 'members.owner': '所有者', + 'members.leaveTrip': '退出旅行', + 'members.removeAccess': '移除訪問許可權', + 'members.confirmLeave': '退出旅行?你將失去訪問許可權。', + 'members.confirmRemove': '移除該使用者的訪問許可權?', + 'members.loadError': '載入成員失敗', + 'members.added': '已新增', + 'members.addError': '新增失敗', + 'members.removed': '成員已移除', + 'members.removeError': '移除失敗', + + // Categories (Admin) + 'categories.title': '分類', + 'categories.subtitle': '管理地點分類', + 'categories.new': '新建分類', + 'categories.empty': '暫無分類', + 'categories.namePlaceholder': '分類名稱', + 'categories.icon': '圖示', + 'categories.color': '顏色', + 'categories.customColor': '選擇自定義顏色', + 'categories.preview': '預覽', + 'categories.defaultName': '分類', + 'categories.update': '更新', + 'categories.create': '建立', + 'categories.confirm.delete': '刪除分類?該分類下的地點不會被刪除。', + 'categories.toast.loadError': '載入分類失敗', + 'categories.toast.nameRequired': '請輸入名稱', + 'categories.toast.updated': '分類已更新', + 'categories.toast.created': '分類已建立', + 'categories.toast.saveError': '儲存失敗', + 'categories.toast.deleted': '分類已刪除', + 'categories.toast.deleteError': '刪除失敗', + + // Backup (Admin) + 'backup.title': '資料備份', + 'backup.subtitle': '資料庫和所有上傳檔案', + 'backup.refresh': '重新整理', + 'backup.upload': '上傳備份', + 'backup.uploading': '上傳中…', + 'backup.create': '建立備份', + 'backup.creating': '建立中…', + 'backup.empty': '暫無備份', + 'backup.createFirst': '建立第一個備份', + 'backup.download': '下載', + 'backup.restore': '恢復', + 'backup.confirm.restore': '恢復備份「{name}」?\n\n所有當前資料將被備份資料替換。', + 'backup.confirm.uploadRestore': '上傳並恢復備份檔案「{name}」?\n\n所有當前資料將被覆蓋。', + 'backup.confirm.delete': '刪除備份「{name}」?', + 'backup.toast.loadError': '載入備份失敗', + 'backup.toast.created': '備份建立成功', + 'backup.toast.createError': '建立備份失敗', + 'backup.toast.restored': '備份已恢復。頁面即將重新整理…', + 'backup.toast.restoreError': '恢復失敗', + 'backup.toast.uploadError': '上傳失敗', + 'backup.toast.deleted': '備份已刪除', + 'backup.toast.deleteError': '刪除失敗', + 'backup.toast.downloadError': '下載失敗', + 'backup.toast.settingsSaved': '自動備份設定已儲存', + 'backup.toast.settingsError': '儲存設定失敗', + 'backup.auto.title': '自動備份', + 'backup.auto.subtitle': '按計劃自動備份', + 'backup.auto.enable': '啟用自動備份', + 'backup.auto.enableHint': '將按所選計劃自動建立備份', + 'backup.auto.interval': '間隔', + 'backup.auto.hour': '執行時間', + 'backup.auto.hourHint': '伺服器本地時間({format} 格式)', + 'backup.auto.dayOfWeek': '星期幾', + 'backup.auto.dayOfMonth': '每月幾號', + 'backup.auto.dayOfMonthHint': '限 1–28 以相容所有月份', + 'backup.auto.scheduleSummary': '計劃', + 'backup.auto.summaryDaily': '每天 {hour}:00', + 'backup.auto.summaryWeekly': '每{day} {hour}:00', + 'backup.auto.summaryMonthly': '每月 {day} 號 {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': '自動備份透過 Docker 環境變數配置。要更改設定,請更新 docker-compose.yml 並重啟容器。', + 'backup.auto.copyEnv': '複製 Docker 環境變數', + 'backup.auto.envCopied': 'Docker 環境變數已複製到剪貼簿', + 'backup.auto.keepLabel': '自動刪除舊備份', + 'backup.dow.sunday': '週日', + 'backup.dow.monday': '週一', + 'backup.dow.tuesday': '週二', + 'backup.dow.wednesday': '週三', + 'backup.dow.thursday': '週四', + 'backup.dow.friday': '週五', + 'backup.dow.saturday': '週六', + 'backup.interval.hourly': '每小時', + 'backup.interval.daily': '每天', + 'backup.interval.weekly': '每週', + 'backup.interval.monthly': '每月', + 'backup.keep.1day': '1 天', + 'backup.keep.3days': '3 天', + 'backup.keep.7days': '7 天', + 'backup.keep.14days': '14 天', + 'backup.keep.30days': '30 天', + 'backup.keep.forever': '永久保留', + + // Photos + 'photos.allDays': '所有天', + 'photos.noPhotos': '暫無照片', + 'photos.uploadHint': '上傳你的旅行照片', + 'photos.clickToSelect': '或點選選擇', + 'photos.linkPlace': '關聯地點', + 'photos.noPlace': '無地點', + 'photos.uploadN': '上傳 {n} 張照片', + + // Backup restore modal + 'backup.restoreConfirmTitle': '恢復備份?', + 'backup.restoreWarning': '所有當前資料(旅行、地點、使用者、上傳檔案)將被備份資料永久替換。此操作無法撤銷。', + 'backup.restoreTip': '提示:恢復前建議先備份當前狀態。', + 'backup.restoreConfirm': '確認恢復', + + // PDF + 'pdf.travelPlan': '旅行計劃', + 'pdf.planned': '已規劃', + 'pdf.costLabel': '費用 EUR', + 'pdf.preview': 'PDF 預覽', + 'pdf.saveAsPdf': '儲存為 PDF', + + // Planner + 'planner.places': '地點', + 'planner.bookings': '預訂', + 'planner.packingList': '行李清單', + 'planner.documents': '文件', + 'planner.dayPlan': '日程計劃', + 'planner.reservations': '預訂', + 'planner.minTwoPlaces': '至少需要 2 個有座標的地點', + 'planner.noGeoPlaces': '沒有有座標的地點', + 'planner.routeCalculated': '路線已計算', + 'planner.routeCalcFailed': '無法計算路線', + 'planner.routeError': '路線計算錯誤', + 'planner.routeOptimized': '路線已最佳化', + 'planner.reservationUpdated': '預訂已更新', + 'planner.reservationAdded': '預訂已新增', + 'planner.confirmDeleteReservation': '刪除預訂?', + 'planner.reservationDeleted': '預訂已刪除', + 'planner.days': '天', + 'planner.allPlaces': '所有地點', + 'planner.totalPlaces': '共 {n} 個地點', + 'planner.noDaysPlanned': '尚未規劃天數', + 'planner.editTrip': '編輯旅行 \u2192', + 'planner.placeOne': '1 個地點', + 'planner.placeN': '{n} 個地點', + 'planner.addNote': '新增備註', + 'planner.noEntries': '當天無條目', + 'planner.addPlace': '新增地點/活動', + 'planner.addPlaceShort': '+ 新增地點/活動', + 'planner.resPending': '預訂待確認 · ', + 'planner.resConfirmed': '預訂已確認 · ', + 'planner.notePlaceholder': '備註…', + 'planner.noteTimePlaceholder': '時間(可選)', + 'planner.noteExamplePlaceholder': '如:14:30 從中央車站乘 S3,7 號碼頭渡輪,午餐休息…', + 'planner.totalCost': '總費用', + 'planner.searchPlaces': '搜尋地點…', + 'planner.allCategories': '所有分類', + 'planner.noPlacesFound': '未找到地點', + 'planner.addFirstPlace': '新增第一個地點', + 'planner.noReservations': '暫無預訂', + 'planner.addFirstReservation': '新增第一個預訂', + 'planner.new': '新建', + 'planner.addToDay': '+ 天', + 'planner.calculating': '計算中…', + 'planner.route': '路線', + 'planner.optimize': '最佳化', + 'planner.openGoogleMaps': '在 Google Maps 中開啟', + 'planner.selectDayHint': '從左側列表選擇一天以檢視日程計劃', + 'planner.noPlacesForDay': '當天暫無地點', + 'planner.addPlacesLink': '新增地點 \u2192', + 'planner.minTotal': '分鐘 合計', + 'planner.noReservation': '無預訂', + 'planner.removeFromDay': '從當天移除', + 'planner.addToThisDay': '新增到當天', + 'planner.overview': '概覽', + 'planner.noDays': '暫無天數', + 'planner.editTripToAddDays': '編輯旅行以新增天數', + 'planner.dayCount': '{n} 天', + 'planner.clickToUnlock': '點選解鎖', + 'planner.keepPosition': '路線最佳化時保持位置', + 'planner.dayDetails': '日程詳情', + 'planner.dayN': '第 {n} 天', + + // Dashboard Stats + 'stats.countries': '國家', + 'stats.cities': '城市', + 'stats.trips': '旅行', + 'stats.places': '地點', + 'stats.worldProgress': '全球進度', + 'stats.visited': '已訪問', + 'stats.remaining': '未訪問', + 'stats.visitedCountries': '已訪問國家', + + // Day Detail Panel + 'day.precipProb': '降水機率', + 'day.precipitation': '降水量', + 'day.wind': '風速', + 'day.sunrise': '日出', + 'day.sunset': '日落', + 'day.hourlyForecast': '逐小時預報', + 'day.climateHint': '歷史平均值——實際預報在該日期前 16 天內可用。', + 'day.noWeather': '無天氣資料。請新增有座標的地點。', + 'day.overview': '每日概覽', + 'day.accommodation': '住宿', + 'day.addAccommodation': '新增住宿', + 'day.hotelDayRange': '應用到天數', + 'day.noPlacesForHotel': '請先在旅行中新增地點', + 'day.allDays': '全部', + 'day.checkIn': '入住', + 'day.checkOut': '退房', + 'day.confirmation': '確認號', + 'day.editAccommodation': '編輯住宿', + 'day.reservations': '預訂', + + // Memories / Immich + 'memories.title': '照片', + 'memories.notConnected': 'Immich 未連線', + 'memories.notConnectedHint': '在設定中連線您的 Immich 例項以在此檢視旅行照片。', + 'memories.noDates': '為旅行新增日期以載入照片。', + 'memories.noPhotos': '未找到照片', + 'memories.noPhotosHint': 'Immich 中未找到此旅行日期範圍內的照片。', + 'memories.photosFound': '張照片', + 'memories.fromOthers': '來自他人', + 'memories.sharePhotos': '分享照片', + 'memories.sharing': '分享中', + 'memories.reviewTitle': '審查您的照片', + 'memories.reviewHint': '點選照片以將其從分享中排除。', + 'memories.shareCount': '分享 {count} 張照片', + 'memories.immichUrl': 'Immich 伺服器地址', + 'memories.immichApiKey': 'API 金鑰', + 'memories.testConnection': '測試連線', + 'memories.testFirst': '請先測試連線', + 'memories.connected': '已連線', + 'memories.disconnected': '未連線', + 'memories.connectionSuccess': '已連線到 Immich', + 'memories.connectionError': '無法連線到 Immich', + 'memories.saved': 'Immich 設定已儲存', + 'memories.oldest': '最早優先', + 'memories.newest': '最新優先', + 'memories.allLocations': '所有地點', + 'memories.addPhotos': '新增照片', + 'memories.linkAlbum': '關聯相簿', + 'memories.selectAlbum': '選擇 Immich 相簿', + 'memories.noAlbums': '未找到相簿', + 'memories.syncAlbum': '同步相簿', + 'memories.unlinkAlbum': '取消關聯', + 'memories.photos': '張照片', + 'memories.selectPhotos': '從 Immich 選擇照片', + 'memories.selectHint': '點選照片以選擇。', + 'memories.selected': '已選擇', + 'memories.addSelected': '新增 {count} 張照片', + 'memories.alreadyAdded': '已新增', + 'memories.private': '私密', + 'memories.stopSharing': '停止分享', + 'memories.tripDates': '旅行日期', + 'memories.allPhotos': '所有照片', + 'memories.confirmShareTitle': '與旅行成員分享?', + 'memories.confirmShareHint': '{count} 張照片將對本次旅行的所有成員可見。你可以稍後將單張照片設為私密。', + 'memories.confirmShareButton': '分享照片', + + // Collab Addon + 'collab.tabs.chat': '聊天', + 'collab.tabs.notes': '筆記', + 'collab.tabs.polls': '投票', + 'collab.whatsNext.title': '接下來', + 'collab.whatsNext.today': '今天', + 'collab.whatsNext.tomorrow': '明天', + 'collab.whatsNext.empty': '暫無活動', + 'collab.whatsNext.until': '至', + 'collab.whatsNext.emptyHint': '有時間安排的活動將顯示在此', + 'collab.chat.send': '傳送', + 'collab.chat.placeholder': '輸入訊息...', + 'collab.chat.empty': '開始對話', + 'collab.chat.emptyHint': '訊息對所有旅行成員可見', + 'collab.chat.emptyDesc': '與旅伴分享想法、計劃和動態', + 'collab.chat.today': '今天', + 'collab.chat.yesterday': '昨天', + 'collab.chat.deletedMessage': '刪除了一條訊息', + 'collab.chat.reply': '回覆', + 'collab.chat.loadMore': '載入更早的訊息', + 'collab.chat.justNow': '剛剛', + 'collab.chat.minutesAgo': '{n} 分鐘前', + 'collab.chat.hoursAgo': '{n} 小時前', + 'collab.notes.title': '筆記', + 'collab.notes.new': '新建筆記', + 'collab.notes.empty': '暫無筆記', + 'collab.notes.emptyHint': '開始記錄想法和計劃', + 'collab.notes.all': '全部', + 'collab.notes.titlePlaceholder': '筆記標題', + 'collab.notes.contentPlaceholder': '寫點什麼...', + 'collab.notes.categoryPlaceholder': '分類', + 'collab.notes.newCategory': '新建分類...', + 'collab.notes.category': '分類', + 'collab.notes.noCategory': '無分類', + 'collab.notes.color': '顏色', + 'collab.notes.save': '儲存', + 'collab.notes.cancel': '取消', + 'collab.notes.edit': '編輯', + 'collab.notes.delete': '刪除', + 'collab.notes.pin': '置頂', + 'collab.notes.unpin': '取消置頂', + 'collab.notes.daysAgo': '{n} 天前', + 'collab.notes.categorySettings': '管理分類', + 'collab.notes.create': '建立', + 'collab.notes.website': '網站', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': '附加檔案', + 'collab.notes.noCategoriesYet': '暫無分類', + 'collab.notes.emptyDesc': '建立一個筆記開始吧', + 'collab.polls.title': '投票', + 'collab.polls.new': '新建投票', + 'collab.polls.empty': '暫無投票', + 'collab.polls.emptyHint': '向團隊提問並一起投票', + 'collab.polls.question': '問題', + 'collab.polls.questionPlaceholder': '我們應該做什麼?', + 'collab.polls.addOption': '+ 新增選項', + 'collab.polls.optionPlaceholder': '選項 {n}', + 'collab.polls.create': '建立投票', + 'collab.polls.close': '關閉', + 'collab.polls.closed': '已關閉', + 'collab.polls.votes': '{n} 票', + 'collab.polls.vote': '{n} 票', + 'collab.polls.multipleChoice': '多選', + 'collab.polls.multiChoice': '多選', + 'collab.polls.deadline': '截止時間', + 'collab.polls.option': '選項', + 'collab.polls.options': '選項', + 'collab.polls.delete': '刪除', + 'collab.polls.closedSection': '已關閉', + + // Permissions + 'admin.tabs.permissions': '許可權', + 'perm.title': '許可權設定', + 'perm.subtitle': '控制誰可以在應用中執行操作', + 'perm.saved': '許可權設定已儲存', + 'perm.resetDefaults': '恢復預設', + 'perm.customized': '已自定義', + 'perm.level.admin': '僅管理員', + 'perm.level.tripOwner': '旅行所有者', + 'perm.level.tripMember': '旅行成員', + 'perm.level.everybody': '所有人', + 'perm.cat.trip': '旅行管理', + 'perm.cat.members': '成員管理', + 'perm.cat.files': '檔案', + 'perm.cat.content': '內容與日程', + 'perm.cat.extras': '預算、行李與協作', + 'perm.action.trip_create': '建立旅行', + 'perm.action.trip_edit': '編輯旅行詳情', + 'perm.action.trip_delete': '刪除旅行', + 'perm.action.trip_archive': '歸檔 / 取消歸檔旅行', + 'perm.action.trip_cover_upload': '上傳封面圖片', + 'perm.action.member_manage': '新增 / 移除成員', + 'perm.action.file_upload': '上傳檔案', + 'perm.action.file_edit': '編輯檔案後設資料', + 'perm.action.file_delete': '刪除檔案', + 'perm.action.place_edit': '新增 / 編輯 / 刪除地點', + 'perm.action.day_edit': '編輯日程、備註與分配', + 'perm.action.reservation_edit': '管理預訂', + 'perm.action.budget_edit': '管理預算', + 'perm.action.packing_edit': '管理行李清單', + 'perm.action.collab_edit': '協作(筆記、投票、聊天)', + 'perm.action.share_manage': '管理分享連結', + 'perm.actionHint.trip_create': '誰可以建立新旅行', + 'perm.actionHint.trip_edit': '誰可以更改旅行名稱、日期、描述和貨幣', + 'perm.actionHint.trip_delete': '誰可以永久刪除旅行', + 'perm.actionHint.trip_archive': '誰可以歸檔或取消歸檔旅行', + 'perm.actionHint.trip_cover_upload': '誰可以上傳或更改封面圖片', + 'perm.actionHint.member_manage': '誰可以邀請或移除旅行成員', + 'perm.actionHint.file_upload': '誰可以向旅行上傳檔案', + 'perm.actionHint.file_edit': '誰可以編輯檔案描述和連結', + 'perm.actionHint.file_delete': '誰可以將檔案移至回收站或永久刪除', + 'perm.actionHint.place_edit': '誰可以新增、編輯或刪除地點', + 'perm.actionHint.day_edit': '誰可以編輯日程、日程備註和地點分配', + 'perm.actionHint.reservation_edit': '誰可以建立、編輯或刪除預訂', + 'perm.actionHint.budget_edit': '誰可以建立、編輯或刪除預算專案', + 'perm.actionHint.packing_edit': '誰可以管理行李物品和包袋', + 'perm.actionHint.collab_edit': '誰可以建立筆記、投票和傳送訊息', + 'perm.actionHint.share_manage': '誰可以建立或刪除公開分享連結', + // Undo + 'undo.button': '撤銷', + 'undo.tooltip': '撤銷:{action}', + 'undo.assignPlace': '地點已分配至某天', + 'undo.removeAssignment': '地點已從某天移除', + 'undo.reorder': '地點已重新排序', + 'undo.optimize': '路線已最佳化', + 'undo.deletePlace': '地點已刪除', + 'undo.moveDay': '地點已移至另一天', + 'undo.lock': '地點鎖定已切換', + 'undo.importGpx': 'GPX 匯入', + 'undo.importGoogleList': 'Google 地圖匯入', + + // Notifications + 'notifications.title': '通知', + 'notifications.markAllRead': '全部標為已讀', + 'notifications.deleteAll': '全部刪除', + 'notifications.showAll': '檢視所有通知', + 'notifications.empty': '暫無通知', + 'notifications.emptyDescription': '您已全部查閱!', + 'notifications.all': '全部', + 'notifications.unreadOnly': '未讀', + 'notifications.markRead': '標為已讀', + 'notifications.markUnread': '標為未讀', + 'notifications.delete': '刪除', + 'notifications.system': '系統', + 'memories.error.loadAlbums': '載入相簿失敗', + 'memories.error.linkAlbum': '關聯相簿失敗', + 'memories.error.unlinkAlbum': '取消關聯相簿失敗', + 'memories.error.syncAlbum': '同步相簿失敗', + 'memories.error.loadPhotos': '載入照片失敗', + 'memories.error.addPhotos': '新增照片失敗', + 'memories.error.removePhoto': '刪除照片失敗', + 'memories.error.toggleSharing': '更新共享設定失敗', + 'undo.addPlace': '地點已新增', + 'undo.done': '已撤銷:{action}', + 'notifications.test.title': '來自 {actor} 的測試通知', + 'notifications.test.text': '這是一條簡單的測試通知。', + 'notifications.test.booleanTitle': '{actor} 請求您的審批', + 'notifications.test.booleanText': '測試布林通知。', + 'notifications.test.accept': '批准', + 'notifications.test.decline': '拒絕', + 'notifications.test.navigateTitle': '檢視詳情', + 'notifications.test.navigateText': '測試跳轉通知。', + 'notifications.test.goThere': '前往', + 'notifications.test.adminTitle': '管理員廣播', + 'notifications.test.adminText': '{actor} 向所有管理員傳送了測試通知。', + 'notifications.test.tripTitle': '{actor} 在您的行程中發帖', + 'notifications.test.tripText': '行程"{trip}"的測試通知。', +} + +export default zhTw \ No newline at end of file diff --git a/server/src/services/notifications.ts b/server/src/services/notifications.ts index 588d726..87ed770 100644 --- a/server/src/services/notifications.ts +++ b/server/src/services/notifications.ts @@ -92,6 +92,7 @@ const I18N: Record = { nl: { footer: 'Je ontvangt dit omdat je meldingen hebt ingeschakeld in TREK.', manage: 'Voorkeuren beheren', madeWith: 'Made with', openTrek: 'TREK openen' }, ru: { footer: 'Вы получили это, потому что у вас включены уведомления в TREK.', manage: 'Управление настройками', madeWith: 'Made with', openTrek: 'Открыть TREK' }, zh: { footer: '您收到此邮件是因为您在 TREK 中启用了通知。', manage: '管理偏好设置', madeWith: 'Made with', openTrek: '打开 TREK' }, + 'zh-TW': { footer: '您收到這封郵件是因為您在 TREK 中啟用了通知。', manage: '管理偏好設定', madeWith: 'Made with', openTrek: '開啟 TREK' }, ar: { footer: 'تلقيت هذا لأنك قمت بتفعيل الإشعارات في TREK.', manage: 'إدارة التفضيلات', madeWith: 'Made with', openTrek: 'فتح TREK' }, }; @@ -163,6 +164,15 @@ const EVENT_TEXTS: Record> = { collab_message: p => ({ title: `"${p.trip}"中的新消息`, body: `${p.actor}:${p.preview}` }), packing_tagged: p => ({ title: `行李清单:${p.category}`, body: `${p.actor} 将你分配到"${p.trip}"中的"${p.category}"类别。` }), }, + 'zh-TW': { + trip_invite: p => ({ title: `邀請加入「${p.trip}」`, body: `${p.actor} 邀請了 ${p.invitee || '成員'} 加入行程「${p.trip}」。` }), + booking_change: p => ({ title: `新預訂:${p.booking}`, body: `${p.actor} 在「${p.trip}」中新增了預訂「${p.booking}」(${p.type})。` }), + trip_reminder: p => ({ title: `行程提醒:${p.trip}`, body: `您的行程「${p.trip}」即將開始!` }), + vacay_invite: p => ({ title: 'Vacay 融合邀請', body: `${p.actor} 邀請您合併假期計畫。開啟 TREK 以接受或拒絕。` }), + photos_shared: p => ({ title: `已分享 ${p.count} 張照片`, body: `${p.actor} 在「${p.trip}」中分享了 ${p.count} 張照片。` }), + collab_message: p => ({ title: `「${p.trip}」中的新訊息`, body: `${p.actor}:${p.preview}` }), + packing_tagged: p => ({ title: `打包清單:${p.category}`, body: `${p.actor} 已將您指派到「${p.trip}」中的「${p.category}」分類。` }), + }, ar: { trip_invite: p => ({ title: `دعوة إلى "${p.trip}"`, body: `${p.actor} دعا ${p.invitee || 'عضو'} إلى الرحلة "${p.trip}".` }), booking_change: p => ({ title: `حجز جديد: ${p.booking}`, body: `${p.actor} أضاف حجز "${p.booking}" (${p.type}) إلى "${p.trip}".` }),