diff --git a/packages/app/package.json b/packages/app/package.json index 799e38b9c..2be9015ae 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -5,6 +5,7 @@ "type": "module", "exports": { ".": "./src/index.ts", + "./desktop-api": "./src/desktop-api.ts", "./vite": "./vite.js", "./index.css": "./src/index.css" }, diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index c09d4dd42..b3947a9e5 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -45,7 +45,7 @@ import { TerminalProvider } from "@/context/terminal" import DirectoryLayout from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" -import { buildDesktopContext, type DesktopContext } from "./utils/desktop-context" +import { buildDesktopContext, desktopWindowTitle, type DesktopContext } from "./utils/desktop-context" import { useCheckServerHealth } from "./utils/server-health" const HomeRoute = lazy(() => import("@/pages/home")) @@ -178,6 +178,13 @@ function DesktopContextRouteBridge() { }) } + createEffect(() => { + if (typeof document !== "object") return + const pathname = location.pathname + if (isSessionRoute(pathname)) return + document.title = desktopWindowTitle(language.locale()) + }) + createEffect(() => { if (!window.api?.setDesktopContext) return if (isSessionRoute(location.pathname)) { diff --git a/packages/app/src/context/highlights.test.ts b/packages/app/src/context/highlights.test.ts index 616a46a79..596e26abd 100644 --- a/packages/app/src/context/highlights.test.ts +++ b/packages/app/src/context/highlights.test.ts @@ -10,7 +10,7 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { body: "## Downloads\n\n- [macOS](https://example.com/app.dmg)\n\n## App Update Notice\n\nFixed first-message crash\n", }, ] - const highlights = loadReleaseHighlights(payload, "0.2.3", "0.2.2") + const highlights = loadReleaseHighlights(payload, "0.2.3", "0.2.2", "en") expect(highlights).toHaveLength(1) expect(highlights[0]).toMatchObject({ title: "PawWork v0.2.3", @@ -18,6 +18,62 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { }) }) + test("prefers the Chinese update notice for zh locale", () => { + const payload = [ + { + tag_name: "v0.2.10", + body: [ + "## App Update Notice", + "", + "- Fixed first-message crash", + "", + "## 中文版本", + "", + "### 主要更新", + "", + "- 修复首条消息崩溃", + "- 调整更新提示", + ].join("\n"), + }, + ] + const highlights = loadReleaseHighlights(payload, "0.2.10", "0.2.9", "zh") + expect(highlights).toHaveLength(1) + expect(highlights[0]).toMatchObject({ + title: "爪印 v0.2.10", + description: "修复首条消息崩溃", + }) + }) + + test("falls back to bullets directly under 中文版本 when 主要更新 is absent", () => { + const payload = [ + { + tag_name: "v0.2.10", + body: ["## App Update Notice", "", "- Fixed first-message crash", "", "## 中文版本", "", "- 修复首条消息崩溃", "- 调整更新提示"].join("\n"), + }, + ] + const highlights = loadReleaseHighlights(payload, "0.2.10", "0.2.9", "zh") + expect(highlights).toHaveLength(1) + expect(highlights[0]).toMatchObject({ + title: "爪印 v0.2.10", + description: "修复首条消息崩溃", + }) + }) + + test("falls back to the English update notice when Chinese summary is missing", () => { + const payload = [ + { + tag_name: "v0.2.10", + body: "## App Update Notice\n\n- Fixed first-message crash\n", + }, + ] + const highlights = loadReleaseHighlights(payload, "0.2.10", "0.2.9", "zh") + expect(highlights).toHaveLength(1) + expect(highlights[0]).toMatchObject({ + title: "爪印 v0.2.10", + description: "Fixed first-message crash", + }) + }) + test("skips markdown headings and strips bullet markers inside the app update notice section", () => { const payload = [ { @@ -25,14 +81,14 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { body: "## Downloads\n\n- [macOS](https://example.com/app.dmg)\n\n## App Update Notice\n\n### Desktop\n\n- Added dark theme\n- Fixed dock icon\n\n## Verification\n\n- CI passed\n", }, ] - const highlights = loadReleaseHighlights(payload, "0.3.0", "0.2.3") + const highlights = loadReleaseHighlights(payload, "0.3.0", "0.2.3", "en") expect(highlights[0].description).toBe("Added dark theme") }) test("truncates long summaries with an ellipsis", () => { const long = "a".repeat(300) const payload = [{ tag_name: "v1.0.0", body: `## App Update Notice\n\n${long}` }] - const highlights = loadReleaseHighlights(payload, "1.0.0", "0.9.0") + const highlights = loadReleaseHighlights(payload, "1.0.0", "0.9.0", "en") expect(highlights[0].description.endsWith("…")).toBe(true) expect(highlights[0].description.length).toBe(201) }) @@ -44,7 +100,7 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { body: "## Downloads\n\n- [macOS Apple Silicon](https://github.com/Astro-Han/pawwork/releases/download/v0.2.6/pawwork-mac-arm64.dmg)\n\n## Highlights\n\n- Maintenance fixes\n", }, ] - expect(loadReleaseHighlights(payload, "0.2.6", "0.2.5")).toHaveLength(0) + expect(loadReleaseHighlights(payload, "0.2.6", "0.2.5", "en")).toHaveLength(0) }) test("stops app update notice parsing at empty same-level headings", () => { @@ -54,14 +110,14 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { body: "## App Update Notice\n\n- Fixed update notices\n\n##\n\n- [macOS](https://example.com/app.dmg)\n", }, ] - const highlights = loadReleaseHighlights(payload, "0.2.6", "0.2.5") + const highlights = loadReleaseHighlights(payload, "0.2.6", "0.2.5", "en") expect(highlights).toHaveLength(1) expect(highlights[0].description).toBe("Fixed update notices") }) test("returns no highlights when the body is empty or only headings", () => { const payload = [{ tag_name: "v0.2.4", body: "# Title only\n\n## Heading only\n" }] - expect(loadReleaseHighlights(payload, "0.2.4", "0.2.3")).toHaveLength(0) + expect(loadReleaseHighlights(payload, "0.2.4", "0.2.3", "en")).toHaveLength(0) }) test("keeps backward compatibility with the structured highlights schema", () => { @@ -76,8 +132,24 @@ describe("loadReleaseHighlights (GitHub Releases API)", () => { ], }, ] - const highlights = loadReleaseHighlights(payload, "0.2.5", "0.2.4") + const highlights = loadReleaseHighlights(payload, "0.2.5", "0.2.4", "zh") expect(highlights).toHaveLength(1) expect(highlights[0]).toMatchObject({ title: "Card Title", description: "Card Description" }) }) + + test("does not rewrite structured highlight titles for zh locale", () => { + const payload = [ + { + tag: "v0.2.5", + highlights: [ + { + source: "desktop", + items: [{ title: "PawWork card", description: "Card Description" }], + }, + ], + }, + ] + const highlights = loadReleaseHighlights(payload, "0.2.5", "0.2.4", "zh") + expect(highlights[0]?.title).toBe("PawWork card") + }) }) diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx index c3184a885..86536edd5 100644 --- a/packages/app/src/context/highlights.tsx +++ b/packages/app/src/context/highlights.tsx @@ -2,6 +2,7 @@ import { createEffect, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { useDialog } from "@opencode-ai/ui/context/dialog" +import { type Locale, useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" import { persisted } from "@/utils/persist" @@ -18,6 +19,8 @@ type ParsedRelease = { highlights: Highlight[] } +type ReleaseLocale = Locale + function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value) } @@ -61,9 +64,9 @@ function parseHighlight(value: unknown): Highlight | undefined { return { title, description, media } } -function findAppUpdateNotice(body: string): string | undefined { +function findHeadingSection(body: string, matcher: RegExp): string | undefined { const lines = body.split(/\r?\n/) - const start = lines.findIndex((line) => /^#{2,6}\s+App Update Notice\s*$/i.test(line.trim())) + const start = lines.findIndex((line) => matcher.test(line.trim())) if (start === -1) return const headingLevel = lines[start].trim().match(/^#+/)?.[0].length ?? 2 @@ -75,8 +78,17 @@ function findAppUpdateNotice(body: string): string | undefined { return (end === -1 ? section : section.slice(0, end)).join("\n") } -function summarizeAppUpdateNotice(body: string): string | undefined { - const notice = findAppUpdateNotice(body) +function findAppUpdateNotice(body: string) { + return findHeadingSection(body, /^#{2,6}\s+App Update Notice\s*$/i) +} + +function findChineseUpdateNotice(body: string) { + const chinese = findHeadingSection(body, /^#{2,6}\s+中文版本\s*$/) + if (!chinese) return + return findHeadingSection(chinese, /^#{3,6}\s+主要更新\s*$/) ?? chinese +} + +function summarizeNotice(notice: string | undefined): string | undefined { if (!notice) return const lines = notice @@ -88,7 +100,19 @@ function summarizeAppUpdateNotice(body: string): string | undefined { return first.length > 200 ? first.slice(0, 200).trimEnd() + "…" : first } -function parseRelease(value: unknown): ParsedRelease | undefined { +function summarizeReleaseBody(body: string, locale: ReleaseLocale) { + if (locale === "zh") { + const chinese = summarizeNotice(findChineseUpdateNotice(body)) + if (chinese) return chinese + } + return summarizeNotice(findAppUpdateNotice(body)) +} + +function releaseTitle(tag: string, locale: ReleaseLocale) { + return `${locale === "zh" ? "爪印" : "PawWork"} ${tag}` +} + +function parseRelease(value: unknown, locale: ReleaseLocale): ParsedRelease | undefined { if (!isRecord(value)) return const tag = getText(value.tag) ?? getText(value.tag_name) ?? getText(value.name) @@ -114,11 +138,11 @@ function parseRelease(value: unknown): ParsedRelease | undefined { const body = getText(value.body) if (tag && body) { - const summary = summarizeAppUpdateNotice(body) + const summary = summarizeReleaseBody(body, locale) if (summary) { return { tag, - highlights: [{ title: `PawWork ${tag}`, description: summary }], + highlights: [{ title: releaseTitle(tag, locale), description: summary }], } } } @@ -126,15 +150,19 @@ function parseRelease(value: unknown): ParsedRelease | undefined { return { tag, highlights: [] } } -function parseChangelog(value: unknown): ParsedRelease[] | undefined { +function parseChangelog(value: unknown, locale: ReleaseLocale): ParsedRelease[] | undefined { if (Array.isArray(value)) { - return value.map(parseRelease).filter((release): release is ParsedRelease => release !== undefined) + return value + .map((release) => parseRelease(release, locale)) + .filter((release): release is ParsedRelease => release !== undefined) } if (!isRecord(value)) return if (!Array.isArray(value.releases)) return - return value.releases.map(parseRelease).filter((release): release is ParsedRelease => release !== undefined) + return value.releases + .map((release) => parseRelease(release, locale)) + .filter((release): release is ParsedRelease => release !== undefined) } function sliceHighlights(input: { releases: ParsedRelease[]; current?: string; previous?: string }) { @@ -169,8 +197,8 @@ function dedupeKey(highlight: Highlight) { return [highlight.title, highlight.description, highlight.media?.type ?? "", highlight.media?.src ?? ""].join("\n") } -export function loadReleaseHighlights(value: unknown, current?: string, previous?: string) { - const releases = parseChangelog(value) +export function loadReleaseHighlights(value: unknown, current?: string, previous?: string, locale: ReleaseLocale = "en") { + const releases = parseChangelog(value, locale) if (!releases?.length) return [] return sliceHighlights({ releases, current, previous }) } @@ -179,6 +207,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple name: "Highlights", gate: false, init: () => { + const language = useLanguage() const platform = usePlatform() const dialog = useDialog() const settings = useSettings() @@ -228,7 +257,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple }) .then((json) => { if (!json) return - const highlights = loadReleaseHighlights(json, platform.version, previous) + const highlights = loadReleaseHighlights(json, platform.version, previous, language.locale()) if (controller.signal.aborted) return if (highlights.length === 0) { diff --git a/packages/app/src/desktop-api.ts b/packages/app/src/desktop-api.ts new file mode 100644 index 000000000..b02c73b1f --- /dev/null +++ b/packages/app/src/desktop-api.ts @@ -0,0 +1,2 @@ +export { buildDesktopContext, desktopWindowTitle, type DesktopContext } from "./utils/desktop-context" +export type { ReportProblemInput, ReportProblemResult, UpdateInfo } from "./context/platform" diff --git a/packages/app/src/i18n/zh-branding.test.ts b/packages/app/src/i18n/zh-branding.test.ts new file mode 100644 index 000000000..6596e3fad --- /dev/null +++ b/packages/app/src/i18n/zh-branding.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "bun:test" +import { dict as zh } from "./zh" + +describe("zh branding copy", () => { + test("uses Chinese product naming on key user-facing surfaces", () => { + expect(zh["dialog.model.unpaid.freeModels.title"]).toBe("爪印内置免费模型") + expect(zh["session.new.subtitle"]).toBe("爪印可以帮你处理文件、分析信息、撰写内容并完成各类任务。") + expect(zh["sidebar.gettingStarted.line1"]).toBe("爪印内置免费模型,你可以立即开始使用。") + expect(zh["app.name.desktop"]).toBe("爪印") + expect(zh["toast.update.description"]).toBe("爪印有新版本 ({{version}}) 可安装。") + expect(zh["error.page.report.prefix"]).toBe("请将此错误报告给开发团队") + }) + + test("removes standalone PawWork from curated Chinese UI strings", () => { + const curatedKeys = [ + "dialog.model.unpaid.freeModels.title", + "session.new.subtitle", + "sidebar.gettingStarted.line1", + "app.name.desktop", + "toast.update.description", + "error.page.report.prefix", + ] as const + + for (const key of curatedKeys) { + expect(zh[key]).not.toContain("PawWork") + } + }) +}) diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 8c087d033..27958229c 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -149,7 +149,7 @@ export const dict = { "dialog.model.manage": "管理模型", "dialog.model.manage.description": "自定义模型选择器中显示的模型。", "dialog.model.manage.provider.toggle": "切换所有 {{provider}} 模型", - "dialog.model.unpaid.freeModels.title": "PawWork 提供的免费模型", + "dialog.model.unpaid.freeModels.title": "爪印内置免费模型", "dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型", "dialog.provider.viewAll": "查看更多提供商", @@ -161,8 +161,7 @@ export const dict = { "provider.connect.status.inProgress": "正在授权...", "provider.connect.status.waiting": "等待授权...", "provider.connect.status.failed": "授权失败:{{error}}", - "provider.connect.apiKey.description": - "输入你的 {{provider}} API 密钥以连接帐户,并在 PawWork 中使用 {{provider}} 模型。", + "provider.connect.apiKey.description": "输入你的 {{provider}} API 密钥以连接帐户,并在爪印中使用 {{provider}} 模型。", "provider.connect.apiKey.label": "{{provider}} API 密钥", "provider.connect.apiKey.placeholder": "API 密钥", "provider.connect.apiKey.required": "API 密钥为必填项", @@ -173,14 +172,14 @@ export const dict = { "provider.connect.opencodeZen.visit.suffix": " 获取你的 API 密钥。", "provider.connect.oauth.code.visit.prefix": "访问 ", "provider.connect.oauth.code.visit.link": "此链接", - "provider.connect.oauth.code.visit.suffix": " 获取授权码,以连接你的帐户并在 PawWork 中使用 {{provider}} 模型。", + "provider.connect.oauth.code.visit.suffix": " 获取授权码,以连接你的帐户并在爪印中使用 {{provider}} 模型。", "provider.connect.oauth.code.label": "{{method}} 授权码", "provider.connect.oauth.code.placeholder": "授权码", "provider.connect.oauth.code.required": "授权码为必填项", "provider.connect.oauth.code.invalid": "授权码无效", "provider.connect.oauth.auto.visit.prefix": "访问 ", "provider.connect.oauth.auto.visit.link": "此链接", - "provider.connect.oauth.auto.visit.suffix": " 并输入以下代码,以连接你的帐户并在 PawWork 中使用 {{provider}} 模型。", + "provider.connect.oauth.auto.visit.suffix": " 并输入以下代码,以连接你的帐户并在爪印中使用 {{provider}} 模型。", "provider.connect.oauth.auto.confirmationCode": "确认码", "provider.connect.toast.connected.title": "{{provider}} 已连接", "provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。", @@ -322,7 +321,7 @@ export const dict = { "prompt.toast.imageUnsupported.description": "这张图片没有添加。请换成支持图片的模型,再重新添加。", "prompt.toast.imageUnsupported.chooseModel": "选择模型", "prompt.toast.pathRequired.title": "请用附件按钮选择文件", - "prompt.toast.pathRequired.description": "PawWork 需要文件路径,才能让助手读取这个文件。请用附件按钮重新选择。", + "prompt.toast.pathRequired.description": "爪印需要文件路径,才能让助手读取这个文件。请用附件按钮重新选择。", "prompt.toast.modelAgentRequired.title": "请选择智能体和模型", "prompt.toast.modelAgentRequired.description": "发送提示前请先选择智能体和模型。", "prompt.toast.worktreeCreateFailed.title": "创建工作树失败", @@ -351,7 +350,7 @@ export const dict = { "dialog.directory.empty": "未找到文件夹", "dialog.server.title": "服务器", - "dialog.server.description": "切换此应用连接的 PawWork 服务器。", + "dialog.server.description": "切换此应用连接的爪印服务器。", "dialog.server.search.placeholder": "搜索服务器", "dialog.server.empty": "暂无服务器", "dialog.server.add.title": "添加服务器", @@ -467,7 +466,7 @@ export const dict = { "toast.session.unshare.failed.description": "取消分享会话时发生错误", "toast.session.listFailed.title": "无法加载 {{project}} 的会话", "toast.update.title": "有可用更新", - "toast.update.description": "PawWork 有新版本 ({{version}}) 可安装。", + "toast.update.description": "爪印有新版本 ({{version}}) 可安装。", "toast.update.action.installRestart": "安装并重启", "toast.update.action.notYet": "稍后", @@ -478,20 +477,19 @@ export const dict = { "error.page.action.checking": "检查中...", "error.page.action.checkUpdates": "检查更新", "error.page.action.updateTo": "更新到 {{version}}", - "error.page.action.upToDate": "PawWork 已是最新版本。", - "error.page.action.busy": "PawWork 正在检查更新。", + "error.page.action.upToDate": "爪印已是最新版本。", + "error.page.action.busy": "爪印正在检查更新。", "error.page.action.checkFailed": "检查更新失败。", "error.page.action.disabled": "此构建不支持更新。", - "error.page.report.prefix": "请将此错误报告给 PawWork 团队", + "error.page.report.prefix": "请将此错误报告给开发团队", "error.page.report.github": "在 GitHub 上", "error.page.known.localState.title": "本地状态问题", - "error.page.known.localState.description": - "PawWork 在读取这个工作区的本地状态时遇到了问题。这通常不影响你的原始项目文件。", + "error.page.known.localState.description": "爪印在读取这个工作区的本地状态时遇到了问题。这通常不影响你的原始项目文件。", "error.page.report.action": "报告问题", "error.page.report.preparing": "正在准备报告...", "error.page.report.githubFallback": "也可以在 GitHub 反馈。", "error.page.report.formFallbackAction": "手动打开反馈表单。", - "error.page.report.confirm.description": "PawWork 会准备一份问题报告并打开反馈表单。", + "error.page.report.confirm.description": "爪印会准备一份问题报告并打开反馈表单。", "error.page.report.confirm.privacy": "报告用于排查问题,默认不会自动上传你的原始项目文件。", "error.page.report.confirm.details": "查看会包含哪些信息", "error.page.report.confirm.item.error": "错误摘要和完整错误详情", @@ -502,8 +500,8 @@ export const dict = { "error.page.report.success": "反馈表单已打开。摘要已复制,完整报告已保存到本地,可手动上传。", "error.page.report.summaryOnly": "已复制当前错误摘要。请在表单里粘贴这段内容。", "error.page.report.formFallback": "反馈表单没有自动打开。请手动打开链接,然后粘贴已复制的摘要。", - "error.page.report.failed": "PawWork 无法准备问题报告。反馈时请使用下面的技术详情。", - "error.page.report.copiedFallback": "PawWork 无法准备问题报告。已改为复制当前错误详情。", + "error.page.report.failed": "爪印无法准备问题报告。反馈时请使用下面的技术详情。", + "error.page.report.copiedFallback": "爪印无法准备问题报告。已改为复制当前错误详情。", "error.page.report.unavailable": "当前构建不支持问题报告。请使用 GitHub 链接,或复制下面的技术详情。", "error.page.version": "版本:{{version}}", "error.dev.rootNotFound": "未找到根元素。你是不是忘了把它添加到 index.html?或者 id 属性拼写错了?", @@ -520,7 +518,7 @@ export const dict = { "error.chain.didYouMean": "你是不是想输入:{{suggestions}}", "error.chain.modelNotFound": "未找到模型:{{provider}}/{{model}}", "error.chain.checkConfig": "请检查你的配置 (pawwork.json) 中的 provider/model 名称", - "error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: PawWork 暂不支持 MCP 认证。', + "error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意:爪印暂不支持 MCP 认证。', "error.chain.providerAuthFailed": "提供商认证失败({{provider}}):{{message}}", "error.chain.providerInitFailed": '无法初始化提供商 "{{provider}}"。请检查凭据和配置。', "error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)", @@ -581,7 +579,7 @@ export const dict = { "session.revertDock.expand": "展开已回滚消息", "session.revertDock.restore": "恢复消息", "session.new.title": "今天想做什么?", - "session.new.subtitle": "PawWork 可以帮你处理文件、分析信息、撰写内容并完成各类任务。", + "session.new.subtitle": "爪印可以帮你处理文件、分析信息、撰写内容并完成各类任务。", "session.new.reassurance": "文件和对话仅在本机处理", "session.new.card.document.title": "处理文档", "session.new.card.document.description": "编辑、转换并提取 Word、Excel、PowerPoint 和 PDF 文件内容。", @@ -668,7 +666,7 @@ export const dict = { "sidebar.workspaces.enable": "启用工作区", "sidebar.workspaces.disable": "禁用工作区", "sidebar.gettingStarted.title": "入门", - "sidebar.gettingStarted.line1": "PawWork 提供免费模型,你可以立即开始使用。", + "sidebar.gettingStarted.line1": "爪印内置免费模型,你可以立即开始使用。", "sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。", "sidebar.project.recentSessions": "最近会话", "sidebar.project.viewAllSessions": "查看全部会话", @@ -685,7 +683,7 @@ export const dict = { "sidebar.pawwork.sort.byProject": "按项目分组", "sidebar.pawwork.sort.byTime": "按时间排序", - "app.name.desktop": "PawWork Desktop", + "app.name.desktop": "爪印", "settings.section.desktop": "桌面", "settings.section.server": "服务器", @@ -695,7 +693,7 @@ export const dict = { "settings.desktop.section.wsl": "WSL", "settings.desktop.wsl.title": "WSL 集成", - "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 PawWork 服务器。", + "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行爪印服务器。", "settings.general.section.appearance": "外观", "settings.general.section.notifications": "系统通知", @@ -704,13 +702,13 @@ export const dict = { "settings.general.section.feed": "动态", "settings.general.section.display": "显示", "settings.general.row.language.title": "语言", - "settings.general.row.language.description": "更改 PawWork 的显示语言", + "settings.general.row.language.description": "更改界面显示语言", "settings.general.row.appearance.title": "外观", - "settings.general.row.appearance.description": "自定义 PawWork 在你的设备上的外观", + "settings.general.row.appearance.description": "自定义应用在你的设备上的外观", "settings.general.row.colorScheme.title": "配色方案", - "settings.general.row.colorScheme.description": "选择 PawWork 跟随系统、浅色或深色主题", + "settings.general.row.colorScheme.description": "选择跟随系统、浅色或深色主题", "settings.general.row.theme.title": "主题", - "settings.general.row.theme.description": "自定义 PawWork 的主题。", + "settings.general.row.theme.description": "自定义应用主题。", "settings.general.row.font.title": "代码字体", "settings.general.row.font.description": "自定义代码块和终端使用的字体", "settings.general.row.uiFont.title": "界面字体", @@ -732,18 +730,18 @@ export const dict = { "settings.general.row.releaseNotes.description": "更新后显示“新功能”弹窗", "settings.updates.row.startup.title": "启动时检查更新", - "settings.updates.row.startup.description": "在 PawWork 启动时自动检查更新", + "settings.updates.row.startup.description": "在应用启动时自动检查更新", "settings.updates.row.check.title": "检查更新", "settings.updates.row.check.description": "手动检查更新并在有更新时安装", "settings.updates.action.checkNow": "立即检查", "settings.updates.action.checking": "正在检查...", "settings.updates.toast.busy.title": "正在检查更新", - "settings.updates.toast.busy.description": "PawWork 已经在检查更新。", + "settings.updates.toast.busy.description": "正在检查更新。", "settings.updates.toast.disabled.title": "更新不可用", "settings.updates.toast.disabled.description": "此构建不支持更新。", "settings.updates.toast.failed.description": "检查更新失败。", "settings.updates.toast.latest.title": "已是最新版本", - "settings.updates.toast.latest.description": "你正在使用最新版本的 PawWork。", + "settings.updates.toast.latest.description": "你正在使用最新版本。", "sound.option.none": "无", "sound.option.alert01": "警报 01", diff --git a/packages/app/src/utils/desktop-context.test.ts b/packages/app/src/utils/desktop-context.test.ts index 3ac9d9a8a..0f7c32b94 100644 --- a/packages/app/src/utils/desktop-context.test.ts +++ b/packages/app/src/utils/desktop-context.test.ts @@ -13,6 +13,7 @@ describe("desktop context", () => { sessionID: null, route: "/abc", locale: "zh", + title: "爪印", }) }) @@ -29,6 +30,7 @@ describe("desktop context", () => { sessionID: "ses_123", route: "/abc/session/ses_123", locale: "en", + title: "PawWork", }) }) }) diff --git a/packages/app/src/utils/desktop-context.ts b/packages/app/src/utils/desktop-context.ts index 826c03903..8b2c4c7c0 100644 --- a/packages/app/src/utils/desktop-context.ts +++ b/packages/app/src/utils/desktop-context.ts @@ -5,6 +5,11 @@ export type DesktopContext = { sessionID: string | null route: string locale: Locale + title: string +} + +export function desktopWindowTitle(locale: Locale) { + return locale === "zh" ? "爪印" : "PawWork" } export function buildDesktopContext(input: { @@ -18,5 +23,6 @@ export function buildDesktopContext(input: { sessionID: input.sessionID ?? null, route: input.route, locale: input.locale, + title: desktopWindowTitle(input.locale), } } diff --git a/packages/desktop-electron/electron-builder-app-update.test.ts b/packages/desktop-electron/electron-builder-app-update.test.ts index 4a0ad37b0..94eb43e71 100644 --- a/packages/desktop-electron/electron-builder-app-update.test.ts +++ b/packages/desktop-electron/electron-builder-app-update.test.ts @@ -41,6 +41,17 @@ describe("electron builder app-update config", () => { expect(typeof createConfig("prod").afterPack).toBe("function") }) + test("mac packaging enables a localized display name", () => { + const config = createConfig("prod") + expect(config.productName).toBe("PawWork") + expect(config.appId).toBe("ai.pawwork.desktop") + expect(config.artifactName).toBe("pawwork-${os}-${arch}.${ext}") + expect(config.publish).toMatchObject({ owner: "Astro-Han", repo: "pawwork" }) + expect(createConfig("prod").mac?.extendInfo).toMatchObject({ + LSHasLocalizedDisplayName: true, + }) + }) + test("afterPack writes app-update.yml to the packager-reported macOS resources path", async () => { const root = mkdtempSync(join(tmpdir(), "pawwork-builder-config-")) roots.push(root) @@ -53,6 +64,24 @@ describe("electron builder app-update config", () => { expect(readFileSync(configPath, "utf8")).toContain("repo: pawwork\n") }) + test("afterPack writes localized macOS display names to the final resources path", async () => { + const root = mkdtempSync(join(tmpdir(), "pawwork-builder-config-")) + roots.push(root) + const config = createConfig("prod") + + await config.afterPack!(macAfterPackContext(root, "PawWork")) + + const zhHans = join(root, "PawWork.app", "Contents", "Resources", "zh-Hans.lproj", "InfoPlist.strings") + const zhCn = join(root, "PawWork.app", "Contents", "Resources", "zh_CN.lproj", "InfoPlist.strings") + + expect(existsSync(zhHans)).toBe(true) + expect(existsSync(zhCn)).toBe(true) + expect(readFileSync(zhHans, "utf8")).toContain('CFBundleDisplayName = "爪印";') + expect(readFileSync(zhHans, "utf8")).toContain('CFBundleName = "爪印";') + expect(readFileSync(zhCn, "utf8")).toContain('CFBundleDisplayName = "爪印";') + expect(readFileSync(zhCn, "utf8")).toContain('CFBundleName = "爪印";') + }) + test("afterPack writes beta app-update.yml to the beta app resources path", async () => { const root = mkdtempSync(join(tmpdir(), "pawwork-builder-config-")) roots.push(root) diff --git a/packages/desktop-electron/electron-builder.config.ts b/packages/desktop-electron/electron-builder.config.ts index a3b805fe5..a0f43d591 100644 --- a/packages/desktop-electron/electron-builder.config.ts +++ b/packages/desktop-electron/electron-builder.config.ts @@ -1,4 +1,5 @@ import { execFile } from "node:child_process" +import { mkdir, writeFile } from "node:fs/promises" import path from "node:path" import { fileURLToPath } from "node:url" import { promisify } from "node:util" @@ -10,6 +11,11 @@ const execFileAsync = promisify(execFile) const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..") const signScript = path.join(rootDir, "script", "sign-windows.ps1") type Channel = "dev" | "beta" | "prod" +const localizedMacDisplayNameByChannel: Record = { + dev: "爪印 Dev", + beta: "爪印 Beta", + prod: "爪印", +} async function signWindows(configuration: { path: string }) { if (process.platform !== "win32") return @@ -34,6 +40,17 @@ export function getPublishConfig(channel: Channel): GitHubPublishConfig | undefi return undefined } +async function writeLocalizedMacDisplayName(resourcesDir: string, channel: Channel) { + const name = localizedMacDisplayNameByChannel[channel] + const content = [`CFBundleDisplayName = "${name}";`, `CFBundleName = "${name}";`, ""].join("\n") + + for (const locale of ["zh-Hans.lproj", "zh_CN.lproj"]) { + const dir = path.join(resourcesDir, locale) + await mkdir(dir, { recursive: true }) + await writeFile(path.join(dir, "InfoPlist.strings"), content, "utf8") + } +} + const getBase = (): Configuration => ({ artifactName: "pawwork-${os}-${arch}.${ext}", directories: { @@ -63,6 +80,9 @@ const getBase = (): Configuration => ({ icon: `resources/icons/icon.icns`, hardenedRuntime: true, gatekeeperAssess: false, + extendInfo: { + LSHasLocalizedDisplayName: true, + }, entitlements: "resources/entitlements.plist", entitlementsInherit: "resources/entitlements.plist", notarize: true, @@ -106,8 +126,11 @@ export function createConfig(channel: Channel = currentChannel(), baseOverrides: if (typeof configuration.afterPack === "function") { await configuration.afterPack(context) } - if (context.electronPlatformName !== "darwin" || publish === undefined) return - await writeAppUpdateConfig(context.packager.getMacOsResourcesDir(context.appOutDir), publish) + if (context.electronPlatformName !== "darwin") return + const resourcesDir = context.packager.getMacOsResourcesDir(context.appOutDir) + await writeLocalizedMacDisplayName(resourcesDir, channel) + if (publish === undefined) return + await writeAppUpdateConfig(resourcesDir, publish) }, }) diff --git a/packages/desktop-electron/src/main/app-display-name.test.ts b/packages/desktop-electron/src/main/app-display-name.test.ts new file mode 100644 index 000000000..b4a60dbff --- /dev/null +++ b/packages/desktop-electron/src/main/app-display-name.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "bun:test" +import { localizedAppDisplayName } from "./app-display-name" + +describe("localized app display name", () => { + test("keeps stable English names outside zh locale", () => { + expect(localizedAppDisplayName("PawWork", "en")).toBe("PawWork") + expect(localizedAppDisplayName("PawWork Beta", "en")).toBe("PawWork Beta") + }) + + test("localizes stable product names for zh locale without changing identifiers", () => { + expect(localizedAppDisplayName("PawWork", "zh")).toBe("爪印") + expect(localizedAppDisplayName("PawWork Beta", "zh")).toBe("爪印 Beta") + expect(localizedAppDisplayName("PawWork Dev", "zh")).toBe("爪印 Dev") + expect(localizedAppDisplayName("PawWork Nightly", "zh")).toBe("爪印 Nightly") + }) +}) diff --git a/packages/desktop-electron/src/main/app-display-name.ts b/packages/desktop-electron/src/main/app-display-name.ts new file mode 100644 index 000000000..35e15f448 --- /dev/null +++ b/packages/desktop-electron/src/main/app-display-name.ts @@ -0,0 +1,12 @@ +import type { MenuLocale } from "./menu-labels" + +const zhNameMap = new Map([ + ["PawWork", "爪印"], + ["PawWork Beta", "爪印 Beta"], + ["PawWork Dev", "爪印 Dev"], +]) + +export function localizedAppDisplayName(appName: string, locale: MenuLocale) { + if (locale !== "zh") return appName + return zhNameMap.get(appName) ?? appName.replace(/^PawWork\b/, "爪印") +} diff --git a/packages/desktop-electron/src/main/desktop-context-window.test.ts b/packages/desktop-electron/src/main/desktop-context-window.test.ts new file mode 100644 index 000000000..8c7e88a4f --- /dev/null +++ b/packages/desktop-electron/src/main/desktop-context-window.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, mock, test } from "bun:test" +import { normalizeDesktopContextPayload, syncWindowTitleForDesktopContext } from "./desktop-context-window" + +describe("desktop context window helpers", () => { + test("normalizes zh payloads and assigns the Chinese runtime title", () => { + expect( + normalizeDesktopContextPayload( + { + directory: "/tmp/project", + sessionID: "ses_123", + route: "/workspace", + locale: "zh", + }, + "en", + ), + ).toEqual({ + directory: "/tmp/project", + sessionID: "ses_123", + route: "/workspace", + locale: "zh", + title: "爪印", + }) + }) + + test("falls back safely for malformed IPC payloads", () => { + expect(normalizeDesktopContextPayload({ route: "", locale: "fr", title: "Wrong" }, "en")).toEqual({ + directory: null, + sessionID: null, + route: "/", + locale: "en", + title: "PawWork", + }) + }) + + test("syncs the BrowserWindow title from normalized desktop context", () => { + const win = { setTitle: mock(() => undefined) } + syncWindowTitleForDesktopContext(win, { + directory: null, + sessionID: null, + route: "/", + locale: "zh", + title: "爪印", + }) + + expect(win.setTitle).toHaveBeenCalledWith("爪印") + }) +}) diff --git a/packages/desktop-electron/src/main/desktop-context-window.ts b/packages/desktop-electron/src/main/desktop-context-window.ts new file mode 100644 index 000000000..e9872d5ec --- /dev/null +++ b/packages/desktop-electron/src/main/desktop-context-window.ts @@ -0,0 +1,24 @@ +import type { BrowserWindow } from "electron" +import { desktopWindowTitle } from "@opencode-ai/app/desktop-api" +import type { DesktopContext } from "../preload/types" +import type { MenuLocale } from "./menu-labels" + +export function normalizeDesktopContextPayload(context: unknown, fallbackLocale: MenuLocale): DesktopContext { + const value = context && typeof context === "object" ? (context as Record) : {} + const locale = value.locale === "zh" ? "zh" : value.locale === "en" ? "en" : fallbackLocale + + return { + directory: typeof value.directory === "string" ? value.directory : null, + sessionID: typeof value.sessionID === "string" ? value.sessionID : null, + route: typeof value.route === "string" && value.route.length > 0 ? value.route : "/", + locale, + title: desktopWindowTitle(locale), + } +} + +export function syncWindowTitleForDesktopContext( + win: Pick, + context: Pick, +) { + win.setTitle(context.title) +} diff --git a/packages/desktop-electron/src/main/feedback.test.ts b/packages/desktop-electron/src/main/feedback.test.ts index 3ddec192e..dd423e3e0 100644 --- a/packages/desktop-electron/src/main/feedback.test.ts +++ b/packages/desktop-electron/src/main/feedback.test.ts @@ -81,6 +81,8 @@ describe("feedback handler", () => { expect(feedbackDialogLabels("zh").message).toContain("简短摘要") expect(feedbackDialogLabels("zh").message).toContain("完整问题报告文件") expect(feedbackDialogLabels("zh").message).toContain("提交后可以删除") + expect(feedbackDialogLabels("zh").message).not.toContain("PawWork") + expect(feedbackDialogLabels("zh").formOpenFailedMessage).not.toContain("PawWork") }) test("has English confirmation labels", () => { diff --git a/packages/desktop-electron/src/main/feedback.ts b/packages/desktop-electron/src/main/feedback.ts index 6d65c235e..b97e7a160 100644 --- a/packages/desktop-electron/src/main/feedback.ts +++ b/packages/desktop-electron/src/main/feedback.ts @@ -91,13 +91,13 @@ export function feedbackDialogLabels(locale: MenuLocale) { zh: { title: "准备问题报告?", message: - "PawWork 会复制一份简短摘要到剪贴板,保存完整问题报告文件到本地,并打开反馈表单。\n\n如果表单需要更多细节,可以上传完整问题报告文件。提交后可以删除本地完整报告文件。", + "应用会复制一份简短摘要到剪贴板,保存完整问题报告文件到本地,并打开反馈表单。\n\n如果表单需要更多细节,可以上传完整问题报告文件。提交后可以删除本地完整报告文件。", confirm: "复制摘要并打开表单", cancel: "取消", failedTitle: "问题报告失败", failedMessage: "无法准备问题报告。你可以重新点击“报告问题”再试一次。", formOpenFailedTitle: "反馈表单未打开", - formOpenFailedMessage: "PawWork 已准备好问题报告,但无法打开反馈表单。请手动打开这个链接继续提交反馈。", + formOpenFailedMessage: "问题报告已准备好,但无法打开反馈表单。请手动打开这个链接继续提交反馈。", }, } satisfies Record< MenuLocale, diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index f0aaa3b1c..c07fc4cc3 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -8,6 +8,7 @@ import { dirname, join } from "node:path" import type { Event } from "electron" import { app, BrowserWindow, clipboard, dialog, shell } from "electron" import pkg from "electron-updater" +import { buildDesktopContext } from "@opencode-ai/app/desktop-api" import contextMenu from "electron-context-menu" contextMenu({ showSaveImageAs: true, showLookUpSelection: false, showSearchWithGoogle: false }) @@ -51,6 +52,7 @@ const { autoUpdater } = pkg import type { DesktopContext, InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types" import { checkAppExists, resolveAppPath, wslPath } from "./apps" import { CHANNEL, FEEDBACK_FORM_URL, UPDATER_ENABLED } from "./constants" +import { normalizeDesktopContextPayload, syncWindowTitleForDesktopContext } from "./desktop-context-window" import { createDesktopContextStore } from "./desktop-context-store" import { createFeedbackHandler, feedbackDialogLabels } from "./feedback" import { registerIpcHandlers, sendDeepLinks, sendMenuCommand, sendSqliteMigrationProgress } from "./ipc" @@ -115,10 +117,7 @@ const loadingComplete = defer() const deepLinkReadyWindows = new WeakSet() let menuLocale: MenuLocale = readStoredMenuLocale(app.getLocale()) const defaultDesktopContext = (): DesktopContext => ({ - directory: null, - sessionID: null, - route: "/", - locale: menuLocale, + ...buildDesktopContext({ route: "/", locale: menuLocale }), }) const desktopContexts = createDesktopContextStore(defaultDesktopContext) const contextWindowCleanup = new Set() @@ -199,18 +198,8 @@ function currentDesktopContext() { return desktopContexts.current(BrowserWindow.getFocusedWindow()?.id) } -function normalizeDesktopContext(context: unknown): DesktopContext { - const value = context && typeof context === "object" ? (context as Record) : {} - return { - directory: typeof value.directory === "string" ? value.directory : null, - sessionID: typeof value.sessionID === "string" ? value.sessionID : null, - route: typeof value.route === "string" && value.route.length > 0 ? value.route : "/", - locale: value.locale === "zh" ? "zh" : "en", - } -} - function feedbackContext(context: unknown): DesktopContext { - return context === undefined ? currentDesktopContext() : normalizeDesktopContext(context) + return context === undefined ? currentDesktopContext() : normalizeDesktopContextPayload(context, menuLocale) } const reportProblem = createFeedbackHandler({ @@ -529,8 +518,9 @@ registerIpcHandlers({ reportDeepLinkReady: (win) => reportDeepLinkReady(win), reportCiSmokeReady: () => reportCiSmokeReady(), setDesktopContext: (context, win) => { - const next = normalizeDesktopContext(context) + const next = normalizeDesktopContextPayload(context, menuLocale) desktopContexts.set(win.id, next) + syncWindowTitleForDesktopContext(win, next) if (!contextWindowCleanup.has(win.id)) { contextWindowCleanup.add(win.id) win.once("closed", () => { diff --git a/packages/desktop-electron/src/main/menu-labels.test.ts b/packages/desktop-electron/src/main/menu-labels.test.ts index df0ff27c8..32f30d697 100644 --- a/packages/desktop-electron/src/main/menu-labels.test.ts +++ b/packages/desktop-electron/src/main/menu-labels.test.ts @@ -42,6 +42,7 @@ describe("menu labels", () => { expect(menuLabel("zh", "file")).toBe("文件") expect(menuLabel("zh", "reloadWindow")).toBe("重新加载窗口") expect(menuLabel("zh", "reportProblem")).toBe("报告问题") + expect(menuLabel("zh", "pawworkOnGithub")).toBe("在 GitHub 上查看爪印") expect(menuLabel("fr" as never, "file")).toBe("File") }) }) diff --git a/packages/desktop-electron/src/main/menu-labels.ts b/packages/desktop-electron/src/main/menu-labels.ts index 182cf8b16..9a48743eb 100644 --- a/packages/desktop-electron/src/main/menu-labels.ts +++ b/packages/desktop-electron/src/main/menu-labels.ts @@ -98,7 +98,7 @@ const labels: Record> = { nextSession: "下一个会话", previousProject: "上一个项目", nextProject: "下一个项目", - pawworkOnGithub: "PawWork 在 GitHub", + pawworkOnGithub: "在 GitHub 上查看爪印", reportProblem: "报告问题", openGithubIssue: "打开 GitHub Issue", }, diff --git a/packages/desktop-electron/src/main/menu.test.ts b/packages/desktop-electron/src/main/menu.test.ts index a0bf56b3b..5e7878a8c 100644 --- a/packages/desktop-electron/src/main/menu.test.ts +++ b/packages/desktop-electron/src/main/menu.test.ts @@ -125,7 +125,7 @@ describe("desktop menu template", () => { test("localizes PawWork-controlled labels", () => { const template = buildMenuTemplate({ deps: deps(), - appName: "PawWork", + appName: "爪印", locale: "zh", feedbackEnabled: true, }) @@ -134,10 +134,11 @@ describe("desktop menu template", () => { expect(labels(template)).toContain("视图") expect(labels(template)).toContain("前往") expect(labels(template)).toContain("帮助") + expect(submenu(template, "帮助")).toContainEqual(expect.objectContaining({ label: "在 GitHub 上查看爪印" })) }) test("localizes Chinese labels for role-backed menu items while preserving roles", () => { - const appName = "PawWork" + const appName = "爪印" const template = buildMenuTemplate({ deps: deps(), appName, diff --git a/packages/desktop-electron/src/main/menu.ts b/packages/desktop-electron/src/main/menu.ts index 0dbf80ebe..85319e6aa 100644 --- a/packages/desktop-electron/src/main/menu.ts +++ b/packages/desktop-electron/src/main/menu.ts @@ -2,6 +2,7 @@ import { app, Menu, shell } from "electron" // electron-log exposes this ESM entrypoint with the `.js` suffix. import log from "electron-log/main.js" +import { localizedAppDisplayName } from "./app-display-name" import { FEEDBACK_FORM_URL } from "./constants" import { readStoredMenuLocale } from "./menu-i18n" import { buildMenuTemplate, type MenuTemplateDeps } from "./menu-template" @@ -20,7 +21,7 @@ export function createMenu(deps: Deps, locale = readStoredMenuLocale(app.getLoca }) }, }, - appName: app.getName(), + appName: localizedAppDisplayName(app.getName(), locale), locale, feedbackEnabled: Boolean(FEEDBACK_FORM_URL), }) as Electron.MenuItemConstructorOptions[] diff --git a/packages/desktop-electron/src/main/updater-dialog-labels.test.ts b/packages/desktop-electron/src/main/updater-dialog-labels.test.ts index 4037e59f5..cacea042d 100644 --- a/packages/desktop-electron/src/main/updater-dialog-labels.test.ts +++ b/packages/desktop-electron/src/main/updater-dialog-labels.test.ts @@ -6,9 +6,10 @@ describe("updater dialog labels", () => { const labels = updaterDialogLabels("zh") expect(labels.busy.title).toBe("正在检查更新") + expect(labels.busy.message).toBe("正在检查更新。") expect(labels.disabled.message).toBe("此构建不支持更新。") expect(labels.failed.title).toBe("更新失败") - expect(labels.none.message).toBe("PawWork 已是最新版本。") + expect(labels.none.message).toBe("已是最新版本。") expect(labels.ready.message("0.2.5")).toBe("更新 0.2.5 已下载。现在重启?") expect(labels.ready.buttons).toEqual(["重启", "稍后"]) }) diff --git a/packages/desktop-electron/src/main/updater-dialog-labels.ts b/packages/desktop-electron/src/main/updater-dialog-labels.ts index 0a88130e2..1b59da6c1 100644 --- a/packages/desktop-electron/src/main/updater-dialog-labels.ts +++ b/packages/desktop-electron/src/main/updater-dialog-labels.ts @@ -53,7 +53,7 @@ const labels: Record = { zh: { busy: { title: "正在检查更新", - message: "PawWork 正在检查更新。", + message: "正在检查更新。", }, disabled: { title: "更新不可用", @@ -74,7 +74,7 @@ const labels: Record = { }, none: { title: "没有可用更新", - message: "PawWork 已是最新版本。", + message: "已是最新版本。", }, ready: { title: "更新已准备好", diff --git a/packages/desktop-electron/src/preload/index.ts b/packages/desktop-electron/src/preload/index.ts index 79e38297d..e97b229bb 100644 --- a/packages/desktop-electron/src/preload/index.ts +++ b/packages/desktop-electron/src/preload/index.ts @@ -1,4 +1,5 @@ import { contextBridge, ipcRenderer } from "electron" +import { buildDesktopContext } from "@opencode-ai/app/desktop-api" import type { DesktopContext, ElectronAPI, InitStep, SqliteMigrationProgress } from "./types" import { getRuntimeFlags } from "./runtime-flags" @@ -73,12 +74,7 @@ const api: ElectronAPI = { setTitlebar: (theme) => ipcRenderer.invoke("set-titlebar", theme), setDesktopContext: (context) => invokeSetDesktopContext(context), initializeDesktopContext: (locale) => - invokeSetDesktopContext({ - directory: null, - sessionID: null, - route: "/", - locale, - }), + invokeSetDesktopContext(buildDesktopContext({ route: "/", locale })), loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"), runUpdater: (alertOnFail) => ipcRenderer.invoke("run-updater", alertOnFail), checkUpdate: () => ipcRenderer.invoke("check-update"), diff --git a/packages/desktop-electron/src/preload/types.ts b/packages/desktop-electron/src/preload/types.ts index 69caa472e..528e98d56 100644 --- a/packages/desktop-electron/src/preload/types.ts +++ b/packages/desktop-electron/src/preload/types.ts @@ -1,5 +1,4 @@ -import type { ReportProblemInput, ReportProblemResult, UpdateInfo } from "../../../app/src/context/platform" -import type { DesktopContext } from "../../../app/src/utils/desktop-context" +import type { DesktopContext, ReportProblemInput, ReportProblemResult, UpdateInfo } from "@opencode-ai/app/desktop-api" export type { DesktopContext } export type { ReportProblemInput, ReportProblemResult, UpdateInfo } diff --git a/packages/desktop-electron/src/renderer/i18n/zh.test.ts b/packages/desktop-electron/src/renderer/i18n/zh.test.ts new file mode 100644 index 000000000..c314f355a --- /dev/null +++ b/packages/desktop-electron/src/renderer/i18n/zh.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, test } from "bun:test" +import { dict as zh } from "./zh" + +describe("desktop renderer zh copy", () => { + test("does not expose the English product name in updater copy", () => { + expect(zh["desktop.updater.none.message"]).toBe("你已经在使用最新版本。") + expect(zh["desktop.updater.downloaded.prompt"]).toBe("已下载 {{version}} 版本,是否安装并重启?") + }) +}) diff --git a/packages/desktop-electron/src/renderer/i18n/zh.ts b/packages/desktop-electron/src/renderer/i18n/zh.ts index 563151ff7..540fbd1ed 100644 --- a/packages/desktop-electron/src/renderer/i18n/zh.ts +++ b/packages/desktop-electron/src/renderer/i18n/zh.ts @@ -11,11 +11,11 @@ export const dict = { "desktop.updater.checkFailed.title": "检查更新失败", "desktop.updater.checkFailed.message": "无法检查更新", "desktop.updater.none.title": "没有可用更新", - "desktop.updater.none.message": "你已经在使用最新版本的 PawWork", + "desktop.updater.none.message": "你已经在使用最新版本。", "desktop.updater.downloadFailed.title": "更新失败", "desktop.updater.downloadFailed.message": "无法下载更新", "desktop.updater.downloaded.title": "更新已下载", - "desktop.updater.downloaded.prompt": "已下载 PawWork {{version}} 版本,是否安装并重启?", + "desktop.updater.downloaded.prompt": "已下载 {{version}} 版本,是否安装并重启?", "desktop.updater.installFailed.title": "更新失败", "desktop.updater.installFailed.message": "无法安装更新",