diff --git a/Dayflow/Dayflow.xcodeproj/project.pbxproj b/Dayflow/Dayflow.xcodeproj/project.pbxproj index 0f97c3544..d32d63eb0 100644 --- a/Dayflow/Dayflow.xcodeproj/project.pbxproj +++ b/Dayflow/Dayflow.xcodeproj/project.pbxproj @@ -202,6 +202,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + "zh-Hans", Base, ); mainGroup = C4C1D2982DB56805007D5A55; diff --git a/Dayflow/Dayflow/Views/Components/ProviderCardComponents.swift b/Dayflow/Dayflow/Views/Components/ProviderCardComponents.swift index 1d6cbedad..3852f6f84 100644 --- a/Dayflow/Dayflow/Views/Components/ProviderCardComponents.swift +++ b/Dayflow/Dayflow/Views/Components/ProviderCardComponents.swift @@ -127,7 +127,7 @@ struct FlexibleProviderCard: View { private var titleSection: some View { HStack { Spacer() - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 18)) .fontWeight(.semibold) .foregroundColor(.black.opacity(0.9)) @@ -155,7 +155,7 @@ struct FlexibleProviderCard: View { Image(systemName: "checkmark.circle.fill") .font(.system(size: 12)) .foregroundColor(.green) - Text(customStatusText ?? "Currently selected") + Text(LocalizedStringKey(customStatusText ?? "Currently selected")) .font(.custom("Figtree", size: 12)) .fontWeight(.medium) .foregroundColor(.black.opacity(0.6)) @@ -184,7 +184,7 @@ struct FlexibleProviderCard: View { DayflowSurfaceButton( action: buttonAction, content: { - Text(buttonTitle) + Text(LocalizedStringKey(buttonTitle)) .font(.custom("Figtree", size: 14)) .fontWeight(.semibold) .foregroundColor(buttonForegroundColor) @@ -290,7 +290,7 @@ struct BadgeView: View { var body: some View { HStack(spacing: 4 * scale) { - Text(text) + Text(LocalizedStringKey(text)) .font(Font.custom("Figtree", size: 10 * scale * fontScale).weight(textWeight)) .kerning(kerningValue * scale * fontScale) .foregroundColor(textColor) @@ -534,7 +534,7 @@ struct FeatureRowView: View { ) .frame(width: 16 * scale) - Text(feature.text) + Text(LocalizedStringKey(feature.text)) .font(.custom("Figtree", size: 14 * scale * fontScale)) .foregroundColor(.black.opacity(0.75)) .fixedSize(horizontal: false, vertical: true) diff --git a/Dayflow/Dayflow/Views/Onboarding/APIKeyInputView.swift b/Dayflow/Dayflow/Views/Onboarding/APIKeyInputView.swift index d2d99c690..30e193363 100644 --- a/Dayflow/Dayflow/Views/Onboarding/APIKeyInputView.swift +++ b/Dayflow/Dayflow/Views/Onboarding/APIKeyInputView.swift @@ -26,12 +26,12 @@ struct APIKeyInputView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 16)) .fontWeight(.semibold) .foregroundColor(.black.opacity(0.9)) - Text(subtitle) + Text(LocalizedStringKey(subtitle)) .font(.custom("Figtree", size: 14)) .foregroundColor(.black.opacity(0.6)) diff --git a/Dayflow/Dayflow/Views/Onboarding/LLMProviderSetupView.swift b/Dayflow/Dayflow/Views/Onboarding/LLMProviderSetupView.swift index 584b2a96b..5f9d44763 100644 --- a/Dayflow/Dayflow/Views/Onboarding/LLMProviderSetupView.swift +++ b/Dayflow/Dayflow/Views/Onboarding/LLMProviderSetupView.swift @@ -58,7 +58,7 @@ struct LLMProviderSetupView: View { // Title in the content area HStack { - Text(headerTitle) + Text(LocalizedStringKey(headerTitle)) .font(.custom("Figtree", size: 32)) .fontWeight(.semibold) .foregroundColor(.black.opacity(0.9)) @@ -142,7 +142,7 @@ struct LLMProviderSetupView: View { action: handleContinue, content: { HStack(spacing: 6) { - Text(nextButtonText).font(.custom("Figtree", size: 14)).fontWeight(.semibold) + Text(LocalizedStringKey(nextButtonText)).font(.custom("Figtree", size: 14)).fontWeight(.semibold) if nextButtonText == "Next" { Image(systemName: "chevron.right").font(.system(size: 12, weight: .medium)) } @@ -417,12 +417,12 @@ struct LLMProviderSetupView: View { case .information(let title, let description): VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 16) { - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 24)) .fontWeight(.semibold) .foregroundColor(.black.opacity(0.9)) if !description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - Text(description) + Text(LocalizedStringKey(description)) .font(.custom("Figtree", size: 14)) .foregroundColor(.black.opacity(0.6)) .fixedSize(horizontal: false, vertical: true) diff --git a/Dayflow/Dayflow/Views/Onboarding/SetupSidebarView.swift b/Dayflow/Dayflow/Views/Onboarding/SetupSidebarView.swift index ab41e83e7..ffb8e4f2a 100644 --- a/Dayflow/Dayflow/Views/Onboarding/SetupSidebarView.swift +++ b/Dayflow/Dayflow/Views/Onboarding/SetupSidebarView.swift @@ -108,7 +108,7 @@ struct SetupSidebarItem: View { } .frame(width: 20, height: 20) // Fixed frame for consistent centering - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 15)) .fontWeight(isSelected ? .semibold : .medium) .foregroundColor(textColor) diff --git a/Dayflow/Dayflow/Views/Onboarding/TerminalCommandView.swift b/Dayflow/Dayflow/Views/Onboarding/TerminalCommandView.swift index 6cba72b99..ed5181fed 100644 --- a/Dayflow/Dayflow/Views/Onboarding/TerminalCommandView.swift +++ b/Dayflow/Dayflow/Views/Onboarding/TerminalCommandView.swift @@ -16,12 +16,12 @@ struct TerminalCommandView: View { @State private var isCopied = false var body: some View { VStack(alignment: .leading, spacing: 12) { - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 16)) .fontWeight(.semibold) .foregroundColor(.black.opacity(0.9)) - Text(subtitle) + Text(LocalizedStringKey(subtitle)) .font(.custom("Figtree", size: 14)) .foregroundColor(.black.opacity(0.6)) diff --git a/Dayflow/Dayflow/Views/UI/Settings/SettingsComponents.swift b/Dayflow/Dayflow/Views/UI/Settings/SettingsComponents.swift index c57a1692e..3fb7fb854 100644 --- a/Dayflow/Dayflow/Views/UI/Settings/SettingsComponents.swift +++ b/Dayflow/Dayflow/Views/UI/Settings/SettingsComponents.swift @@ -73,12 +73,12 @@ struct SettingsSection: View { VStack(alignment: .leading, spacing: 0) { HStack(alignment: .firstTextBaseline, spacing: 16) { VStack(alignment: .leading, spacing: 4) { - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 17)) .fontWeight(.semibold) .foregroundColor(SettingsStyle.text) if let subtitle { - Text(subtitle) + Text(LocalizedStringKey(subtitle)) .font(.custom("Figtree", size: 12)) .foregroundColor(SettingsStyle.secondary) .fixedSize(horizontal: false, vertical: true) @@ -129,13 +129,13 @@ struct SettingsRow: View { VStack(alignment: .leading, spacing: 0) { HStack(alignment: .center, spacing: 16) { VStack(alignment: .leading, spacing: 3) { - Text(label) + Text(LocalizedStringKey(label)) .font(.custom("Figtree", size: 14)) .fontWeight(.semibold) .foregroundColor(SettingsStyle.text) .fixedSize(horizontal: false, vertical: true) if let subtitle { - Text(subtitle) + Text(LocalizedStringKey(subtitle)) .font(.custom("Figtree", size: 12)) .foregroundColor(SettingsStyle.secondary) .fixedSize(horizontal: false, vertical: true) @@ -193,7 +193,7 @@ struct SettingsPrimaryButton: View { Image(systemName: systemImage) .font(.system(size: 12, weight: .semibold)) } - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 13)) .fontWeight(.semibold) } @@ -224,7 +224,7 @@ struct SettingsSecondaryButton: View { Image(systemName: systemImage) .font(.system(size: 11, weight: .semibold)) } - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 13)) .fontWeight(.semibold) } @@ -250,7 +250,7 @@ struct SettingsLinkButton: View { var body: some View { Button(action: action) { HStack(spacing: 5) { - Text(title) + Text(LocalizedStringKey(title)) .font(.custom("Figtree", size: 13)) .fontWeight(.semibold) if let systemImage { @@ -306,7 +306,7 @@ struct SettingsStatusDot: View { Circle() .fill(color) .frame(width: 8, height: 8) - Text(label) + Text(LocalizedStringKey(label)) .font(.custom("Figtree", size: 13)) .fontWeight(.semibold) .foregroundColor(color) @@ -348,7 +348,7 @@ struct SettingsBadge: View { var isAccent: Bool = false var body: some View { - Text(text) + Text(LocalizedStringKey(text)) .font(.custom("Figtree", size: 10)) .fontWeight(.bold) .kerning(0.6) @@ -371,7 +371,7 @@ struct SettingsBadge: View { struct SettingsMetadata: View { let text: String var body: some View { - Text(text) + Text(LocalizedStringKey(text)) .font(.custom("Figtree", size: 13)) .foregroundColor(SettingsStyle.secondary) } diff --git a/Dayflow/Dayflow/Views/UI/Settings/SettingsStorageTabView.swift b/Dayflow/Dayflow/Views/UI/Settings/SettingsStorageTabView.swift index de638508b..deada39b1 100644 --- a/Dayflow/Dayflow/Views/UI/Settings/SettingsStorageTabView.swift +++ b/Dayflow/Dayflow/Views/UI/Settings/SettingsStorageTabView.swift @@ -18,9 +18,22 @@ struct SettingsStorageTabView: View { let option = StorageSettingsViewModel.storageOptions[pending.index] let categoryName = pending.category.displayName return Alert( - title: Text("Lower \(categoryName) limit?"), + title: Text( + String( + format: NSLocalizedString("Lower %@ limit?", comment: "Confirm lowering storage cap"), + categoryName + ) + ), message: Text( - "Reducing the \(categoryName) limit to \(option.label) will immediately delete the oldest \(categoryName) data to stay under the new cap." + String( + format: NSLocalizedString( + "Reducing the %@ limit to %@ will immediately delete the oldest %@ data to stay under the new cap.", + comment: "Confirm lowering storage cap and deleting oldest files" + ), + categoryName, + option.label, + categoryName + ) ), primaryButton: .destructive(Text("Confirm")) { viewModel.applyLimit(for: pending.category, index: pending.index) diff --git a/Dayflow/Dayflow/Views/UI/Settings/StorageSettingsViewModel.swift b/Dayflow/Dayflow/Views/UI/Settings/StorageSettingsViewModel.swift index 4551b7bbf..03b8085ad 100644 --- a/Dayflow/Dayflow/Views/UI/Settings/StorageSettingsViewModel.swift +++ b/Dayflow/Dayflow/Views/UI/Settings/StorageSettingsViewModel.swift @@ -94,12 +94,20 @@ final class StorageSettingsViewModel: ObservableObject { func storageFooterText() -> String { let recordingsText = recordingsLimitBytes == Int64.max - ? "Unlimited" : usageFormatter.string(fromByteCount: recordingsLimitBytes) + ? NSLocalizedString("Unlimited", comment: "No storage cap") + : usageFormatter.string(fromByteCount: recordingsLimitBytes) let timelapsesText = timelapsesLimitBytes == Int64.max - ? "Unlimited" : usageFormatter.string(fromByteCount: timelapsesLimitBytes) - return - "Recording cap: \(recordingsText) • Timelapse cap: \(timelapsesText). Lowering a cap immediately deletes the oldest files for that type. Timeline card text stays preserved. Please avoid deleting files manually so you do not remove Dayflow's database." + ? NSLocalizedString("Unlimited", comment: "No storage cap") + : usageFormatter.string(fromByteCount: timelapsesLimitBytes) + return String( + format: NSLocalizedString( + "Recording cap: %@ • Timelapse cap: %@. Lowering a cap immediately deletes the oldest files for that type. Timeline card text stays preserved. Please avoid deleting files manually so you do not remove Dayflow's database.", + comment: "Storage footer explaining caps and deletion behavior" + ), + recordingsText, + timelapsesText + ) } func handleLimitSelection(for category: StorageCategory, index: Int) { @@ -248,8 +256,8 @@ enum StorageCategory { var displayName: String { switch self { - case .recordings: return "Recordings" - case .timelapses: return "Timelapses" + case .recordings: return NSLocalizedString("Recordings", comment: "Storage category") + case .timelapses: return NSLocalizedString("Timelapses", comment: "Storage category") } } } diff --git a/Dayflow/Dayflow/zh-Hans.lproj/InfoPlist.strings b/Dayflow/Dayflow/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 000000000..423457604 --- /dev/null +++ b/Dayflow/Dayflow/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +"CFBundleName" = "Dayflow"; +"CFBundleDisplayName" = "Dayflow"; diff --git a/Dayflow/Dayflow/zh-Hans.lproj/Localizable.strings b/Dayflow/Dayflow/zh-Hans.lproj/Localizable.strings new file mode 100644 index 000000000..0260b679f --- /dev/null +++ b/Dayflow/Dayflow/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,323 @@ +"Account" = "账户"; +"Storage" = "存储"; +"Privacy" = "隐私"; +"Providers" = "供应商"; +"Export" = "导出"; +"Other" = "其他"; +"Settings" = "设置"; +"Release notes" = "更新说明"; + +"Dayflow account" = "Dayflow 账户"; +"Sign in" = "登录"; +"Sign out" = "退出登录"; +"Signed in" = "已登录"; +"Signed out" = "未登录"; +"Sign in once to keep Dayflow Pro and cloud features attached to this Mac." = "登录一次,即可把 Dayflow Pro 和云端功能绑定到这台 Mac。"; +"Manage your Dayflow account and subscription." = "管理你的 Dayflow 账户和订阅。"; +"Upgrade to Dayflow Pro" = "升级到 Dayflow Pro"; +"Pick a plan, then finish securely in Stripe Checkout." = "选择一个方案,然后在 Stripe Checkout 安全完成付款。"; +"Monthly" = "月付"; +"Yearly" = "年付"; +"Flexible monthly billing." = "灵活按月付费。"; +"Billed yearly." = "按年计费。"; +"2 months free" = "赠送 2 个月"; +"Start 14-day trial" = "开始 14 天试用"; +"Sign in to upgrade" = "登录后升级"; +"Cancel any time. No-questions-asked refunds." = "可随时取消,无条件退款。"; +"Privacy policy" = "隐私政策"; + +"Current configuration" = "当前配置"; +"Active provider and runtime details." = "当前启用的供应商和运行时详情。"; +"Edit configuration" = "编辑配置"; +"Manage local model" = "管理本地模型"; +"Upgrade local model" = "升级本地模型"; +"Primary provider" = "主供应商"; +"Secondary provider" = "备用供应商"; +"PRIMARY" = "主"; +"SECONDARY" = "备用"; +"CONFIGURED" = "已配置"; +"NOT SET" = "未设置"; +"Not configured" = "未配置"; +"Engine" = "引擎"; +"Model" = "模型"; +"Endpoint" = "端点"; +"API key" = "API 密钥"; +"Stored in UserDefaults" = "已存入 UserDefaults"; +"Stored safely in Keychain" = "已安全存入钥匙串"; +"Not set" = "未设置"; +"Currently selected" = "当前已选"; +"Coming Soon" = "即将推出"; +"Proceed" = "继续"; +"Switch" = "切换"; +"Edit Configuration" = "编辑配置"; +"Model preference" = "模型偏好"; +"CLI preference" = "CLI 偏好"; +"Status" = "状态"; +"Requires Dayflow Pro" = "需要 Dayflow Pro"; +"Coming soon" = "即将推出"; + +"Connection health" = "连接状态"; +"Run a quick test for the primary provider." = "对主供应商运行一次快速测试。"; +"Hosted cards and transcription run through your Dayflow account." = "云端卡片和转写会通过你的 Dayflow 账户运行。"; +"Dayflow Pro diagnostics coming soon" = "Dayflow Pro 诊断功能即将推出"; +"Dayflow Pro is required for hosted cards and transcription." = "云端卡片和转写需要 Dayflow Pro。"; +"Manage Dayflow Pro from Account." = "请在“账户”中管理 Dayflow Pro。"; +"Failover routing" = "故障转移路由"; +"Choose primary and secondary providers." = "选择主供应商和备用供应商。"; +"Upgrade account" = "升级账户"; +"Set primary" = "设为主供应商"; +"Set secondary" = "设为备用供应商"; +"Unset secondary" = "取消备用供应商"; +"Setup" = "设置"; +"Gemini model preference" = "Gemini 模型偏好"; +"Choose which Gemini model Dayflow should prioritize." = "选择 Dayflow 优先使用的 Gemini 模型。"; +"Gemini model" = "Gemini 模型"; +"Dayflow automatically downgrades if your chosen model is rate limited or unavailable." = "如果所选模型被限流或暂时不可用,Dayflow 会自动降级。"; +"Gemini prompt customization" = "Gemini 提示词自定义"; +"Override Dayflow's defaults to tailor card generation." = "覆盖 Dayflow 默认提示词,以调整卡片生成方式。"; +"Overrides apply only when their toggle is on. Unchecked sections fall back to Dayflow's defaults." = "只有开关打开时才会使用覆盖内容。未勾选的部分会回退到 Dayflow 默认设置。"; +"Card titles" = "卡片标题"; +"Shape how card titles read and tweak the example list." = "调整卡片标题的写法和示例列表。"; +"Card summaries" = "卡片摘要"; +"Control tone and style for the summary field." = "控制摘要字段的语气和风格。"; +"Detailed summaries" = "详细摘要"; +"Define the minute-by-minute breakdown format and examples." = "定义逐分钟拆解的格式和示例。"; +"Local prompt customization" = "本地提示词自定义"; +"Adjust the prompts used for local timeline summaries." = "调整本地时间线摘要使用的提示词。"; +"Customize the local model prompts for summary and title generation." = "自定义本地模型用于摘要和标题生成的提示词。"; +"Timeline summaries" = "时间线摘要"; +"Reset" = "重置"; +"Save" = "保存"; +"Saved" = "已保存"; + +"Recording status" = "录屏状态"; +"Ensure Dayflow can capture your screen." = "确认 Dayflow 可以捕获你的屏幕。"; +"Screen recording permission" = "屏幕录制权限"; +"Granted" = "已授权"; +"Missing" = "缺失"; +"Recorder" = "录制器"; +"Active" = "活动中"; +"Idle" = "空闲"; +"Blocked" = "已阻止"; +"Checking…" = "正在检查…"; +"Run status check" = "运行状态检查"; +"Disk usage" = "磁盘使用"; +"Open folders or adjust per-type storage caps." = "打开文件夹或调整各类型的存储上限。"; +"Recordings" = "录屏"; +"Timelapses" = "延时视频"; +"Open" = "打开"; +"Unlimited" = "不限"; +"Adjust storage limit" = "调整存储上限"; +"OK" = "确定"; +"Confirm" = "确认"; +"Recording cap: %@ • Timelapse cap: %@. Lowering a cap immediately deletes the oldest files for that type. Timeline card text stays preserved. Please avoid deleting files manually so you do not remove Dayflow's database." = "录屏上限:%@ • 延时视频上限:%@。降低上限会立即删除该类型中最旧的文件。时间线卡片文本会保留。请避免手动删除文件,以免移除 Dayflow 的数据库。"; +"Lower %@ limit?" = "降低 %@ 上限?"; +"Reducing the %@ limit to %@ will immediately delete the oldest %@ data to stay under the new cap." = "把 %@ 上限降低到 %@ 会立即删除最旧的 %@ 数据,以保持在新上限以内。"; + +"Recording privacy" = "录制隐私"; +"Choose apps Dayflow should hide from screenshots." = "选择 Dayflow 应在截图中隐藏的 App。"; +"Search installed apps" = "搜索已安装的 App"; +"Installed apps" = "已安装 App"; +"Loading apps..." = "正在加载 App..."; +"No apps match your search." = "没有匹配搜索的 App。"; +"Blocked apps" = "已阻止的 App"; +"Clear" = "清除"; +"Drag apps here to hide them from recording" = "把 App 拖到这里,即可在录制中隐藏它们"; + +"Export your data" = "导出你的数据"; +"Move your timeline into tools you already use." = "把时间线导入你已经在使用的工具。"; +"From" = "从"; +"To" = "到"; +"Export start date" = "导出开始日期"; +"Export end date" = "导出结束日期"; +"Use Markdown exports to archive in Notion, share with teammates, or paste into ChatGPT / Claude / Gemini for deeper analysis." = "使用 Markdown 导出,可以归档到 Notion、分享给团队成员,或粘贴到 ChatGPT / Claude / Gemini 做进一步分析。"; +"Exporting…" = "正在导出…"; +"Export as Markdown" = "导出为 Markdown"; +"Start must be on or before end." = "开始日期必须早于或等于结束日期。"; +"Reprocess day" = "重新处理某一天"; +"Re-run analysis for every batch on one timeline day." = "重新分析某个时间线日期中的所有批次。"; +"Day" = "日期"; +"Clears existing cards and observations for that day, then runs analysis again from the original recordings." = "清除当天已有卡片和观察结果,然后从原始录屏重新运行分析。"; +"Heads up: this can consume a large number of API calls." = "注意:这可能会消耗大量 API 调用。"; +"Reprocessing…" = "正在重新处理…"; +"Reprocess day?" = "重新处理这一天?"; +"Cancel" = "取消"; +"Reprocess" = "重新处理"; +"This will delete existing timeline cards for %@ and re-run analysis. It can consume many API calls." = "这会删除 %@ 的现有时间线卡片并重新运行分析,可能会消耗大量 API 调用。"; + +"App preferences" = "App 偏好"; +"General toggles and telemetry settings." = "常规开关和遥测设置。"; +"Launch Dayflow at login" = "登录时启动 Dayflow"; +"Keeps the menu bar controller running right after you sign in so capture can resume instantly." = "登录后立即保持菜单栏控制器运行,以便屏幕捕获能马上恢复。"; +"Share crash reports and anonymous usage data" = "共享崩溃报告和匿名使用数据"; +"Show Dock icon" = "显示 Dock 图标"; +"When off, Dayflow runs as a menu bar-only app." = "关闭后,Dayflow 将仅作为菜单栏 App 运行。"; +"Show app/website icons in timeline" = "在时间线中显示 App/网站图标"; +"When off, timeline cards won't show app or website icons." = "关闭后,时间线卡片不会显示 App 或网站图标。"; +"Show daily goal popups" = "显示每日目标弹窗"; +"When off, Dayflow won't automatically open goal setup or yesterday's review after 4am." = "关闭后,Dayflow 不会在凌晨 4 点后自动打开目标设置或昨日回顾。"; +"Save all timelapses to disk" = "把所有延时视频保存到磁盘"; +"New and reprocessed timeline cards will pre-generate timelapse videos and store them on disk instead of building them on demand. Uses more storage and background processing." = "新的和重新处理的时间线卡片会预先生成延时视频并保存到磁盘,而不是按需生成。这会占用更多存储和后台处理资源。"; +"Output language override" = "输出语言覆盖"; +"The default language is English. You can specify any language here (examples: English, 简体中文, Español, 日本語, 한국어, Français)." = "默认语言是英文。你可以在这里指定任意语言(例如 English、简体中文、Español、日本語、한국어、Français)。"; + +"Use local AI" = "使用本地 AI"; +"Connect ChatGPT or Claude" = "连接 ChatGPT 或 Claude"; +"Gemini" = "Gemini"; +"Back" = "返回"; +"Next" = "下一步"; +"Continue" = "继续"; +"Complete Setup" = "完成设置"; +"Test Required" = "需要先测试"; +"Before you begin" = "开始前"; +"Choose engine" = "选择引擎"; +"Install model" = "安装模型"; +"Test connection" = "测试连接"; +"Test Connection" = "测试连接"; +"Complete" = "完成"; +"All set!" = "全部完成!"; +"Get API key" = "获取 API 密钥"; +"Enter API key" = "输入 API 密钥"; +"For experienced users" = "适合有经验的用户"; +"This path is recommended only if you're comfortable running LLMs locally and debugging technical issues. If terms like vLLM or API endpoint don't ring a bell, we recommend going back and picking ChatGPT, Claude, or Gemini. It's non-technical and takes about 30 seconds.\n\nFor local mode, Dayflow recommends Qwen3-VL 4B as the core vision-language model (Qwen2.5-VL 3B remains available if you need a smaller download)." = "仅当你熟悉本地运行 LLM 并能排查技术问题时,才推荐选择这条路径。如果 vLLM 或 API endpoint 这些词听起来不熟,建议返回选择 ChatGPT、Claude 或 Gemini。这条路径不需要技术配置,大约 30 秒即可完成。\n\n本地模式下,Dayflow 推荐 Qwen3-VL 4B 作为核心视觉语言模型;如果需要更小的下载体积,也仍可使用 Qwen2.5-VL 3B。"; +"Click the button below to verify your local server responds to a simple chat completion." = "点击下面的按钮,验证你的本地服务器能响应一次简单的聊天补全。"; +"Local AI is configured and ready to use with Dayflow." = "本地 AI 已配置完成,可以与 Dayflow 一起使用。"; +"Install Codex CLI (ChatGPT) or Claude Code" = "安装 Codex CLI(ChatGPT)或 Claude Code"; +"If you have a paid ChatGPT/Claude account, you can have Dayflow tap into your existing usage limits. Everything flows through your current account - no extra charges - and you can opt out of training for privacy. You only need one CLI installed and signed in on this Mac; we'll verify it automatically next." = "如果你有付费 ChatGPT/Claude 账户,可以让 Dayflow 使用你现有账户的额度。所有请求都会走你当前的账户,不会产生额外费用;你也可以为隐私选择退出训练。你只需要在这台 Mac 上安装并登录其中一个 CLI,下一步会自动验证。"; +"Check installations" = "检查安装"; +"Run a quick test to verify your CLI is working and signed in." = "运行一次快速测试,确认 CLI 可用且已登录。"; +"ChatGPT and Claude tooling is ready. You can fine-tune which assistant to use anytime from Settings → AI Provider." = "ChatGPT 和 Claude 工具已准备好。你可以随时在“设置 → AI 供应商”中微调使用哪个助手。"; +"Click the button below to verify your API key works with Gemini" = "点击下面的按钮,验证你的 API 密钥可用于 Gemini"; +"Gemini is now configured and ready to use with Dayflow." = "Gemini 已配置完成,可以与 Dayflow 一起使用。"; +"Couldn't save your API key to Keychain. Please unlock Keychain and try again." = "无法把 API 密钥存入钥匙串。请解锁钥匙串后重试。"; +"Enter a command to run." = "输入要运行的命令。"; +"Running..." = "正在运行..."; +"Choose your local AI engine" = "选择你的本地 AI 引擎"; +"For local use, LM Studio is the most reliable; Ollama has a known thinking bug in onboarding (can't turn thinking off) and performance is unreliable." = "本地使用时,LM Studio 最可靠;Ollama 在引导流程中有一个已知的 thinking 问题(无法关闭 thinking),性能也不够稳定。"; +"Download LM Studio" = "下载 LM Studio"; +"Already have a local server? Make sure it’s OpenAI-compatible. You can set a custom base URL in the next step." = "已经有本地服务器?请确认它兼容 OpenAI API。你可以在下一步设置自定义 base URL。"; +"Install Qwen3-VL 4B" = "安装 Qwen3-VL 4B"; +"After installing Ollama, run this in your terminal to download the model (≈5GB):" = "安装 Ollama 后,在终端运行下面命令下载模型(约 5GB):"; +"Run this command:" = "运行此命令:"; +"Downloads Qwen3 Vision 4B for Ollama" = "为 Ollama 下载 Qwen3 Vision 4B"; +"After installing LM Studio, download the recommended model:" = "安装 LM Studio 后,下载推荐模型:"; +"Download Qwen3-VL 4B in LM Studio" = "在 LM Studio 中下载 Qwen3-VL 4B"; +"This will open LM Studio and prompt you to download the model (≈3GB)." = "这会打开 LM Studio,并提示你下载模型(约 3GB)。"; +"Once downloaded, turn on 'Local Server' in LM Studio (default http://localhost:1234)" = "下载完成后,在 LM Studio 中开启“Local Server”(默认 http://localhost:1234)。"; +"Manual setup:" = "手动设置:"; +"1. Open LM Studio → Models tab" = "1. 打开 LM Studio → Models 标签页"; +"2. Search for 'Qwen3-VL-4B' and install the Instruct variant" = "2. 搜索“Qwen3-VL-4B”并安装 Instruct 版本"; +"Use any OpenAI-compatible VLM" = "使用任意兼容 OpenAI 的 VLM"; +"Make sure your server exposes the OpenAI Chat Completions API and has Qwen3-VL 4B (or Qwen2.5-VL 3B if you need the legacy model) installed." = "请确认你的服务器暴露 OpenAI Chat Completions API,并已安装 Qwen3-VL 4B;如果需要旧模型,也可以安装 Qwen2.5-VL 3B。"; +"Terminal command:" = "终端命令:"; +"Copy the code below and try running it in your terminal" = "复制下面代码,并尝试在终端运行"; +"Enter your API key:" = "输入你的 API 密钥:"; +"Paste your Gemini API key below" = "在下方粘贴你的 Gemini API 密钥"; +"API key should be more than 10 characters" = "API 密钥应超过 10 个字符"; +"Your API key is encrypted and stored in your macOS Keychain - never uploaded anywhere" = "你的 API 密钥会加密存储在 macOS 钥匙串中,不会上传到任何地方"; +"Choose your Gemini model. We recommend 3.5 Flash, with 3.1 Flash-Lite available as a fallback." = "选择你的 Gemini 模型。推荐使用 3.5 Flash,3.1 Flash-Lite 可作为备用。"; +"Download the AI model" = "下载 AI 模型"; +"This model enables Dayflow to understand what's on your screen" = "该模型让 Dayflow 能理解屏幕上的内容"; +"Advanced users can pick any " = "高级用户可以选择任意"; +"vision-capable" = "支持视觉"; +" LLM, but we strongly recommend using Qwen3-VL 4B based on our internal benchmarks." = "的 LLM,但基于内部基准测试,我们强烈建议使用 Qwen3-VL 4B。"; +"Which tool are you using?" = "你正在使用哪个工具?"; +"Custom model" = "自定义模型"; +"Get your Gemini API key" = "获取你的 Gemini API 密钥"; +"allows you to run Dayflow for free. All you need is a Google account - no credit card required." = "可让你免费运行 Dayflow。你只需要一个 Google 账户,无需信用卡。"; +"Visit Google AI Studio " = "访问 Google AI Studio "; +"Click \"Create API key\" in the top right" = "点击右上角“Create API key”"; +"Create a new API key and copy it" = "创建一个新的 API 密钥并复制"; +"Open Google AI Studio" = "打开 Google AI Studio"; + +"Testing…" = "正在测试…"; +"No API key found. Enter your API key first." = "没有找到 API 密钥。请先输入 API 密钥。"; +"Connection successful." = "连接成功。"; +"API key works, but Gemini is rate limited right now." = "API 密钥可用,但 Gemini 当前受到限流。"; +"We'll ask your CLI a simple question to verify it's working and signed in." = "我们会让 CLI 回答一个简单问题,以确认它可用且已登录。"; +"Test CLI" = "测试 CLI"; +"Select ChatGPT or Claude above before running the test." = "运行测试前,请先在上方选择 ChatGPT 或 Claude。"; +"Test successful." = "测试成功。"; +"Copy logs" = "复制日志"; +"Pick ChatGPT or Claude first." = "请先选择 ChatGPT 或 Claude。"; +"CLI is working!" = "CLI 可用!"; +"Claude CLI returned an error. You may need to sign in — run 'claude login' in Terminal." = "Claude CLI 返回错误。你可能需要登录:请在终端运行“claude login”。"; +"Codex CLI returned an error. You may need to sign in — run 'codex auth' in Terminal." = "Codex CLI 返回错误。你可能需要登录:请在终端运行“codex auth”。"; +"Base URL" = "Base URL"; +"Model ID" = "模型 ID"; +"API key (optional)" = "API 密钥(可选)"; +"Stored locally in UserDefaults and sent as a Bearer token for custom endpoints (LiteLLM, OpenRouter, etc.)" = "本地存储在 UserDefaults 中,并作为 Bearer token 发送到自定义端点(LiteLLM、OpenRouter 等)。"; +"Test Local API" = "测试本地 API"; +"Invalid base URL" = "Base URL 无效"; +"If you get stuck here, you can go back and choose the ‘Bring your own key’ option — it only takes a minute to set up." = "如果卡在这里,可以返回并选择“使用自己的密钥”选项,设置只需要一分钟。"; +"It took longer than 30 seconds, so your machine doesn't appear powerful enough to run this model locally." = "耗时超过 30 秒,看起来这台机器不足以流畅运行该本地模型。"; +"Copied" = "已复制"; +"Copy" = "复制"; +"Dayflow can talk to ChatGPT (via the Codex CLI) or Claude Code. You only need one installed and signed in on this Mac. After installing, run `codex auth` or `claude login` in Terminal to connect it to your account." = "Dayflow 可以通过 Codex CLI 调用 ChatGPT,也可以调用 Claude Code。你只需要在这台 Mac 上安装并登录其中一个。安装后,在终端运行 `codex auth` 或 `claude login` 连接你的账户。"; +"Tip: Once both are installed, you can choose which provider Dayflow uses from Settings → AI Provider." = "提示:两个都安装后,可以在“设置 → AI 供应商”中选择 Dayflow 使用哪个供应商。"; +"Choose which provider Dayflow should use" = "选择 Dayflow 应使用的供应商"; +"Re-check" = "重新检查"; +"Ready to use" = "可使用"; +"Install to enable" = "安装后启用"; +"Setup guide" = "设置指南"; +"Install" = "安装"; + +"How Dayflow Works" = "Dayflow 如何工作"; +"Install and Forget" = "安装后自动运行"; +"Dayflow takes periodic screen captures to understand what you're working on, all stored privately on your device. You can toggle this whenever you like." = "Dayflow 会定期截屏来理解你正在做什么,所有数据都会私密存储在你的设备上。你可以随时开启或关闭。"; +"Privacy by Default" = "默认保护隐私"; +"Dayflow can run entirely on local AI models, which means your data never leaves your computer. You can also find the source code below - please consider giving it a star on Github!" = "Dayflow 可以完全运行在本地 AI 模型上,这意味着你的数据不会离开电脑。你也可以在下方找到源代码,欢迎在 GitHub 上给它点星。"; +"Understand your Day" = "理解你的一天"; +"Knows the difference between YouTube tutorials and YouTube rabbit holes. Dayflow actually gets what you're working on." = "它能区分 YouTube 教程和无目的刷视频。Dayflow 会真正理解你在做什么。"; +"Star Dayflow on GitHub" = "在 GitHub 上给 Dayflow 点星"; + +"Choose a way to run Dayflow" = "选择 Dayflow 的运行方式"; +"You have Codex/Claude CLI installed! " = "你已经安装了 Codex/Claude CLI!"; +"We recommend using it for the best experience." = "为了获得最佳体验,推荐使用它。"; +"Not sure which to choose? " = "不确定选哪个?"; +"Bring your own keys is the easiest setup (30s)." = "使用自己的密钥最容易设置(约 30 秒)。"; +" You can switch at any time in the settings." = " 你可以随时在设置中切换。"; +"MOST PRIVATE" = "最私密"; +"RECOMMENDED" = "推荐"; +"NEW" = "新"; +"100% private - everything's processed on your computer" = "100% 私密:所有内容都在你的电脑上处理"; +"Works completely offline" = "完全离线运行"; +"Significantly less intelligence" = "智能程度明显较低"; +"Requires the most setup" = "设置步骤最多"; +"16GB+ of RAM recommended" = "建议 16GB 以上内存"; +"Can be battery-intensive" = "可能更耗电"; +"Utilizes more intelligent AI via Google's Gemini models" = "通过 Google Gemini 模型使用更强的 AI"; +"Uses Gemini's generous free tier (no credit card needed)" = "使用 Gemini 慷慨的免费额度(无需信用卡)"; +"Faster, more accurate than local models" = "比本地模型更快、更准确"; +"Requires getting an API key (takes 2 clicks)" = "需要获取 API 密钥(约两次点击)"; +"ChatGPT or Claude" = "ChatGPT 或 Claude"; +"Perfect for existing ChatGPT Plus or Claude Pro subscribers" = "非常适合已有 ChatGPT Plus 或 Claude Pro 订阅的用户"; +"Superior intelligence and reliability" = "更强的智能和可靠性"; +"Minimal impact - uses <1% of your daily limit" = "影响很小:使用不到每日额度的 1%"; +"Requires installing Codex or Claude CLI" = "需要安装 Codex 或 Claude CLI"; +"Requires a paid ChatGPT or Claude subscription" = "需要付费 ChatGPT 或 Claude 订阅"; + +"Open System Settings" = "打开系统设置"; +"Checking..." = "正在检查..."; +"You are ready to go!" = "可以开始了!"; +"To get useful insights, let Dayflow run in the background for an hour or two to gather enough context, then check back in." = "要获得有用洞察,请让 Dayflow 在后台运行一两个小时以收集足够上下文,然后再回来查看。"; +"Launch Dayflow" = "启动 Dayflow"; + +"What's New in %@ 🎉" = "%@ 新功能 🎉"; +"It feels valuable" = "很有价值"; +"Useful, but needs work" = "有用,但还需要改进"; +"Not useful yet" = "暂时还不有用"; +"Dayflow Pro + New referral system - earn $20 when you refer your friends!" = "Dayflow Pro + 新邀请系统:邀请朋友即可获得 20 美元奖励!"; +"Thank you to everyone who already signed up for Dayflow Pro!" = "感谢已经注册 Dayflow Pro 的所有用户!"; +"We want Dayflow Pro to be an accessible option for everyone, but understand that not everyone can afford it. We set up a referral system so you can earn $20 of Dayflow credit for every friend you refer to Dayflow. You can find more details in Settings > Account." = "我们希望 Dayflow Pro 对每个人都尽可能可用,也理解不是每个人都能负担订阅。因此我们设置了邀请系统:每邀请一位朋友使用 Dayflow,你就可以获得 20 美元的 Dayflow 额度。更多详情可在“设置 > 账户”中查看。"; +"Dayflow will remain open source. We believe in creating open, accessible software for everyone. Dayflow Pro is there for people who want the simplest setup and maximum intelligence." = "Dayflow 会继续保持开源。我们相信应该为每个人创造开放、可访问的软件。Dayflow Pro 面向希望获得最简单设置和最高智能体验的用户。"; +"Close" = "关闭"; +"How do you feel about Weekly so far?" = "目前你觉得 Weekly 怎么样?"; +"A quick answer helps shape where Weekly goes next." = "一个简短回答就能帮助我们决定 Weekly 接下来怎么改进。"; +"How can we improve Weekly?" = "我们可以怎样改进 Weekly?"; +"New visualizations, data, comparisons, breakdowns, anything missing..." = "新的可视化、数据、对比、拆解,或任何缺失内容..."; +"Saving..." = "正在保存..."; +"Send feedback" = "发送反馈"; +"Saved." = "已保存。"; +"Could not submit. Please try again." = "提交失败,请重试。"; +"Configure WhatsNewConfiguration.configuredRelease to preview." = "配置 WhatsNewConfiguration.configuredRelease 以预览。";