Skip to content

feat(i18n+polish+cleanup): 中英文 UI 切换 + polish prompt 重写 + 清理 Swift 残留#42

Merged
appergb merged 5 commits into
mainfrom
develop
Apr 30, 2026
Merged

feat(i18n+polish+cleanup): 中英文 UI 切换 + polish prompt 重写 + 清理 Swift 残留#42
appergb merged 5 commits into
mainfrom
develop

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented Apr 30, 2026

将 develop 累积的所有变更合回 main。包含三类改动:

1. i18n — 中英文 UI 切换(closes #40

  • src/i18n/{zh-CN,en}.ts:完整双语资源;en.ts 通过 typeof zhCN 类型绑定,缺 key 编译报错
  • 设置 → 语言 分区:跟随系统 / 简体中文 / English;首启按 navigator.language 自动选
  • 持久化 localStorage 'ol.locale',热切换无需重启
  • Personalize 弹窗里原本不可点的 SelectLite 替换为真正可用的 <select> LanguagePicker
  • main.tsx 等 i18n 就绪后再 ReactDOM.render,避免首渲染 t() 返回 key 字面量
  • 翻译覆盖:胶囊 / Onboarding / 主窗口 / 历史 / 词汇 / 风格 / 设置 / 弹窗

2. polish 提示词重写(closes #47

polish.rs::prompts::system_prompt 4 个 mode 重写:

  • 改为 # 角色 / # 任务 / # 通用规则 / # 示例 / # 输出 段落式结构
  • 拆出共享 ROLE_BLOCK / COMMON_RULES / OUTPUT_BLOCK,避免每 mode 重复 200+ 字
  • 新增 "不确定 → 保留原话,不要替用户补全" 规则,减少幻觉
  • 新增代码 / URL / 数字单位原样保留规则
  • raw / light / formal 各加 1-shot 示例,Structured 已有
  • 不改 mode 产品语义,不改 compose_system_prompt 签名

3. 清理 Swift 遗物

  • 删除 tracked 的顶层 Resources/(Swift 时代 AppIcon / Brand 素材),Tauri 自给
  • 重写 CLAUDE.md:移除"双实现"框架、Swift 构建/测试章节、Sparkle 发布流水线,
    以及 Sources/ / Package.swift / appcast.xml / scripts/release.sh 等过时引用
  • 保留一句历史注脚指向 commit 34d2823 防止 Swift 复活

4. 同时合入

Test plan

  • npm run build(tsc + vite build)通过
  • cargo check --manifest-path src-tauri/Cargo.toml 通过
  • 本地构建 .app + .dmg 通过;启动后中英文切换 / 设置页语言下拉均正常
  • 实测 4 个 polish mode 输出对照原版无明显回归(需 reviewer 抽样)

baiqing added 2 commits April 30, 2026 11:36
- 接入 react-i18next + i18next-browser-languagedetector,资源以 TS 模块静态打包
- 资源 src/i18n/{zh-CN,en}.ts;en.ts 通过 typeof 绑定 zh-CN 形状,缺 key 编译报错
- 全部 UI 表面(胶囊、Onboarding、主窗口侧栏/页脚、Overview/History/Vocab/Style 页、设置弹窗、设置页各分区、WindowChrome)改走 t() 查找
- SettingsSectionId 从 zh-CN 字面量切换为稳定 EN id(recording/providers/...);ModalSectionId 同改
- 设置 → 语言 分区新增「跟随系统 / 简体中文 / English」切换器,localStorage 'ol.locale' 持久化
- 首启检测顺序 localStorage → navigator.language;fallback 永远 zh-CN
- lib/hotkey.ts 改用 i18n.t 直接渲染触发键 / 适配器名称

Closes #40
将 main 的 PR #41(coordinator hotkey 测试基础设施)合入 develop。
保留 develop 上的 i18n 变更与 main 的 hotkey 测试改动,无冲突。
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 30, 2026

Reviewer's Guide

Introduce a full i18n infrastructure (react-i18next + browser language detector) with zh-CN as source-of-truth and a type-checked English pack, wire translations through all major React pages and components, add a Language settings section with system/zh/en selection persisted to localStorage, refactor modal/settings section IDs to stable English keys, and tweak hotkey/adapter labeling to be localization-aware while pulling in the hotkey-test infra from main.

Sequence diagram for startup language detection and application

sequenceDiagram
  actor User
  participant Browser
  participant LocalStorage
  participant LanguageDetector
  participant I18n as I18next
  participant ReactApp

  User->>Browser: Load OpenLess UI
  Browser->>I18n: import i18n/index.ts
  activate I18n
  I18n->>LanguageDetector: use(LanguageDetector)
  I18n->>LanguageDetector: detect()
  activate LanguageDetector
  LanguageDetector->>LocalStorage: read ol.locale
  alt preference stored
    LocalStorage-->>LanguageDetector: storedLocale
  else no preference
    LocalStorage-->>LanguageDetector: null
    LanguageDetector->>Browser: read navigator.language
  end
  LanguageDetector-->>I18n: resolvedLanguage
  deactivate LanguageDetector
  I18n->>I18n: init with zhCN and en resources
  I18n-->>ReactApp: current language and t function
  deactivate I18n

  ReactApp->>ReactApp: render components with useTranslation
  User->>ReactApp: see UI in detected language
Loading

Class diagram for i18n utilities and language settings integration

classDiagram
  class I18nIndex {
    <<module>>
    +SUPPORTED_LOCALES : SupportedLocale[]
    +LOCALE_STORAGE_KEY : string
    +FOLLOW_SYSTEM : string
    +getLocalePreference() SupportedLocale_or_system
    +setLocalePreference(pref SupportedLocale_or_system) Promise~void~
  }

  class I18nextInstance {
    <<library>>
    +language : string
    +t(key string, options any) string
    +changeLanguage(lang string) Promise~void~
  }

  class ZhCNResources {
    <<const object>>
    +app
    +common
    +capsule
    +nav
    +shell
    +onboarding
    +overview
    +history
    +vocab
    +style
    +settings
    +modal
    +windowChrome
    +hotkey
  }

  class EnResources {
    <<const object>>
    +app (typeof ZhCNResources.app)
    +common
    +capsule
    +nav
    +shell
    +onboarding
    +overview
    +history
    +vocab
    +style
    +settings
    +modal
    +windowChrome
    +hotkey
  }

  class SettingsPage {
    <<ReactComponent>>
    +SECTION_ORDER : SettingsSectionId[]
    +section : SettingsSectionId
    +Settings(embedded boolean, initialSection SettingsSectionId)
  }

  class LanguageSection {
    <<ReactComponent>>
    -pref : LocalePreference
    +LanguageSection()
    +apply(next LocalePreference) Promise~void~
  }

  class SettingsSectionId {
    <<type alias>>
    recording
    providers
    shortcuts
    permissions
    language
    about
  }

  class ModalSectionId {
    <<type alias>>
    account
    settings
    personalize
    about
  }

  class HotkeyHelpers {
    <<module>>
    +getHotkeyTriggerLabel(trigger HotkeyTrigger_or_null) string
    +getHotkeyStartStopLabel(binding HotkeyBinding_or_null) string
    +getHotkeyUsageHint(binding HotkeyBinding_or_null) string
  }

  class AdapterDisplayName {
    <<function>>
    +adapterDisplayName(adapter HotkeyAdapter) string
  }

  class CapsulePill {
    <<ReactComponent>>
    +Pill(state string, level number, insertedChars number, message string, onCancel function, onConfirm function)
  }

  class WindowChromeWinTitleBar {
    <<ReactComponent>>
    +WinTitleBar(title string)
  }

  I18nIndex --> I18nextInstance : configures
  I18nIndex --> ZhCNResources : uses
  I18nIndex --> EnResources : uses

  SettingsPage ..> LanguageSection : renders
  SettingsPage ..> SettingsSectionId : uses

  LanguageSection ..> I18nIndex : calls getLocalePreference
  LanguageSection ..> I18nIndex : calls setLocalePreference
  LanguageSection ..> I18nextInstance : uses t via useTranslation

  HotkeyHelpers ..> I18nextInstance : uses t for labels
  AdapterDisplayName ..> I18nextInstance : uses t hotkey.adapter.*

  CapsulePill ..> I18nextInstance : uses t capsule.*
  WindowChromeWinTitleBar ..> I18nextInstance : uses t windowChrome.*
Loading

File-Level Changes

Change Details Files
Add i18n infrastructure with zh-CN and type-safe en language resources plus locale detection/persistence helpers.
  • Introduce zh-CN.ts as the canonical Chinese translation tree and en.ts typed as typeof zhCN to ensure key parity at compile time.
  • Initialize i18next with react-i18next and i18next-browser-languagedetector in a new i18n/index.ts, registering zh-CN and en resources and configuring navigator/localStorage-based detection with zh-CN fallback.
  • Expose SUPPORTED_LOCALES, FOLLOW_SYSTEM, getLocalePreference and setLocalePreference helpers to read/write locale preferences in localStorage and switch i18n.language accordingly.
app/src/i18n/index.ts
app/src/i18n/zh-CN.ts
app/src/i18n/en.ts
Internationalize major UI surfaces (Settings, Overview, History, Style, Vocab, Onboarding, shell, capsule, window chrome, etc.) using react-i18next.
  • Wrap page-level components (Settings, History, Overview, Style, Vocab) and key subcomponents with useTranslation calls and replace literal Chinese/English strings with t(...) lookups under structured namespaces (settings., overview., history., style., vocab., common.).
  • Refactor nav tabs, footer tooltips, provider cards, metrics, onboarding permission copy, shortcut labels, and various button titles/descriptions to be driven by translation keys instead of hardcoded strings.
  • Localize capsule pill states, Windows title bar button tooltips, provider setup prompt copy, and About / modal texts while preserving layout and behavior.
app/src/pages/Settings.tsx
app/src/pages/Overview.tsx
app/src/pages/History.tsx
app/src/pages/Style.tsx
app/src/pages/Vocab.tsx
app/src/components/FloatingShell.tsx
app/src/components/SettingsModal.tsx
app/src/components/Onboarding.tsx
app/src/components/Capsule.tsx
app/src/components/WindowChrome.tsx
Add a Settings → Language section and hook it to locale preference helpers with a follow-system option.
  • Introduce a new SettingsSectionId value 'language', wire it into SECTION_ORDER, the left-hand settings nav, and the main content switch block.
  • Implement LanguageSection component that reads the persisted preference with getLocalePreference, allows selecting follow-system/zh-CN/en, and calls setLocalePreference on change while showing a restart hint for native menus.
  • Expose translations for the language settings UI in both zh-CN and en resource files and ensure the modal personalize section reuses these labels where appropriate.
app/src/pages/Settings.tsx
app/src/components/SettingsModal.tsx
app/src/i18n/zh-CN.ts
app/src/i18n/en.ts
Refactor section IDs and hotkey/adapter labels to be language-stable keys and fully localized strings.
  • Change SettingsSectionId and ModalSectionId from Chinese literal unions to English slug unions (recording/providers/shortcuts/permissions/language/about and account/settings/personalize/about) and update all call sites, including shell openSettings and modal nav definitions.
  • Update hotkey labeling helpers to use i18n instead of a static HOTKEY_TRIGGER_LABEL map, returning localized trigger names, hold/toggle suffixes, and usage hints.
  • Move adapterDisplayName to call i18n-based hotkey.adapter.* keys instead of returning hardcoded Chinese/English strings.
app/src/pages/Settings.tsx
app/src/components/SettingsModal.tsx
app/src/components/FloatingShell.tsx
app/src/lib/hotkey.ts
app/src/pages/Settings.tsx
Update dependency graph and bootstrap to support i18n.
  • Add i18next, i18next-browser-languagedetector, and react-i18next as runtime dependencies in package.json and propagate them into package-lock.json.
  • Ensure the i18n module is imported early (e.g., via main.tsx or shared entry) so that react-i18next hooks can access an initialized i18n instance before UI render.
app/package.json
app/package-lock.json
app/src/main.tsx

Assessment against linked issues

Issue Objective Addressed Explanation
#40 Introduce a unified frontend i18n mechanism with zh-CN and en language packs, and refactor user-facing strings in the main React UI (Overview, History, Vocab, Style, Settings, Capsule, Onboarding, window chrome, shell, settings modal, etc.) to use translation keys instead of hardcoded Chinese.
#40 Allow users to switch the UI language between zh-CN and English (and follow-system) from the Settings UI without restarting the app, automatically choose the initial language based on the system language on first launch, and persist the chosen language across launches.
#40 Integrate i18n with the Rust backend (e.g., tray menu items, system notifications) and persist the selected locale in preferences.json rather than only in frontend storage. The PR explicitly scopes out Rust-side tray/menu localization and continues to persist locale in localStorage (ol.locale). There are no changes to the Rust code or preferences.json handling related to locale.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In setLocalePreference you change the i18n language but never update localStorage for explicit locale choices (non-system), so the selection won’t persist across reloads; consider writing LOCALE_STORAGE_KEY when pref !== FOLLOW_SYSTEM_VALUE.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `setLocalePreference` you change the i18n language but never update `localStorage` for explicit locale choices (non-`system`), so the selection won’t persist across reloads; consider writing `LOCALE_STORAGE_KEY` when `pref !== FOLLOW_SYSTEM_VALUE`.

## Individual Comments

### Comment 1
<location path="openless-all/app/src/components/SettingsModal.tsx" line_range="22-26" />
<code_context>
 }

-type ModalSectionId = '账户' | '设置' | '个性化' | '关于';
+// 稳定 ID(与 i18n key 一致,方便 modal.sections.* 渲染)。
+type ModalSectionId = 'account' | 'settings' | 'personalize' | 'about';

</code_context>
<issue_to_address>
**issue (bug_risk):** The `ModalSectionId` type and the `section` state are out of sync with the nav items, which can push `section` to values outside its union via the type cast.

`ModalSectionId` only covers `'account' | 'settings' | 'personalize' | 'about'`, but the second group also uses `'helpCenter'` and `'releaseNotes'`, and `setSection(it.id as ModalSectionId)` allows these values through at runtime. This breaks type safety and can hide bugs in conditional rendering or narrowing. Either widen `ModalSectionId` to include `'helpCenter' | 'releaseNotes'`, or keep `section` as a broader union/string and only use `ModalSectionId` where you truly restrict to internal sections, avoiding the `as` cast altogether.
</issue_to_address>

### Comment 2
<location path="openless-all/app/src/i18n/index.ts" line_range="53-67" />
<code_context>
+ * 写入用户偏好并立即切换 i18n 语言。
+ * pref === 'system' 时清除存储项,让下次启动重新走 navigator 检测。
+ */
+export async function setLocalePreference(pref: SupportedLocale | typeof FOLLOW_SYSTEM_VALUE): Promise<void> {
+  if (pref === FOLLOW_SYSTEM_VALUE) {
+    window.localStorage.removeItem(LOCALE_STORAGE_KEY);
+    const detected = (i18n.services.languageDetector?.detect?.() as string | string[] | undefined) ?? 'zh-CN';
+    const target = Array.isArray(detected) ? detected[0] : detected;
+    await i18n.changeLanguage(target);
+    return;
+  }
+  await i18n.changeLanguage(pref);
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Guard `setLocalePreference` against non-browser environments to mirror `getLocalePreference`.

Because `setLocalePreference` always accesses `window.localStorage`, it will throw in non-browser environments (e.g., SSR, certain tests, tooling). Mirroring the `getLocalePreference` guard with a `typeof window === 'undefined'` check (and either no-op or just calling `changeLanguage`) would keep this safe and consistent if it’s ever used outside the WebView/Tauri context.

```suggestion
/**
 * 写入用户偏好并立即切换 i18n 语言。
 * pref === 'system' 时清除存储项,让下次启动重新走 navigator 检测。
 */
export async function setLocalePreference(pref: SupportedLocale | typeof FOLLOW_SYSTEM_VALUE): Promise<void> {
  // 在非浏览器环境中避免访问 window/localStorage。
  // 保持行为简单:仅在指定具体语言时切换 i18n,FOLLOW_SYSTEM_VALUE 时直接 no-op。
  if (typeof window === 'undefined') {
    if (pref !== FOLLOW_SYSTEM_VALUE) {
      await i18n.changeLanguage(pref);
    }
    return;
  }

  if (pref === FOLLOW_SYSTEM_VALUE) {
    window.localStorage.removeItem(LOCALE_STORAGE_KEY);
    const detected = (i18n.services.languageDetector?.detect?.() as string | string[] | undefined) ?? 'zh-CN';
    const target = Array.isArray(detected) ? detected[0] : detected;
    await i18n.changeLanguage(target);
    return;
  }

  await i18n.changeLanguage(pref);
}

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +22 to 26
// 稳定 ID(与 i18n key 一致,方便 modal.sections.* 渲染)。
type ModalSectionId = 'account' | 'settings' | 'personalize' | 'about';

interface ModalNavItem {
id: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The ModalSectionId type and the section state are out of sync with the nav items, which can push section to values outside its union via the type cast.

ModalSectionId only covers 'account' | 'settings' | 'personalize' | 'about', but the second group also uses 'helpCenter' and 'releaseNotes', and setSection(it.id as ModalSectionId) allows these values through at runtime. This breaks type safety and can hide bugs in conditional rendering or narrowing. Either widen ModalSectionId to include 'helpCenter' | 'releaseNotes', or keep section as a broader union/string and only use ModalSectionId where you truly restrict to internal sections, avoiding the as cast altogether.

Comment on lines +53 to +67
/**
* 写入用户偏好并立即切换 i18n 语言。
* pref === 'system' 时清除存储项,让下次启动重新走 navigator 检测。
*/
export async function setLocalePreference(pref: SupportedLocale | typeof FOLLOW_SYSTEM_VALUE): Promise<void> {
if (pref === FOLLOW_SYSTEM_VALUE) {
window.localStorage.removeItem(LOCALE_STORAGE_KEY);
const detected = (i18n.services.languageDetector?.detect?.() as string | string[] | undefined) ?? 'zh-CN';
const target = Array.isArray(detected) ? detected[0] : detected;
await i18n.changeLanguage(target);
return;
}
await i18n.changeLanguage(pref);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Guard setLocalePreference against non-browser environments to mirror getLocalePreference.

Because setLocalePreference always accesses window.localStorage, it will throw in non-browser environments (e.g., SSR, certain tests, tooling). Mirroring the getLocalePreference guard with a typeof window === 'undefined' check (and either no-op or just calling changeLanguage) would keep this safe and consistent if it’s ever used outside the WebView/Tauri context.

Suggested change
/**
* 写入用户偏好并立即切换 i18n 语言。
* pref === 'system' 时清除存储项,让下次启动重新走 navigator 检测。
*/
export async function setLocalePreference(pref: SupportedLocale | typeof FOLLOW_SYSTEM_VALUE): Promise<void> {
if (pref === FOLLOW_SYSTEM_VALUE) {
window.localStorage.removeItem(LOCALE_STORAGE_KEY);
const detected = (i18n.services.languageDetector?.detect?.() as string | string[] | undefined) ?? 'zh-CN';
const target = Array.isArray(detected) ? detected[0] : detected;
await i18n.changeLanguage(target);
return;
}
await i18n.changeLanguage(pref);
}
/**
* 写入用户偏好并立即切换 i18n 语言。
* pref === 'system' 时清除存储项,让下次启动重新走 navigator 检测。
*/
export async function setLocalePreference(pref: SupportedLocale | typeof FOLLOW_SYSTEM_VALUE): Promise<void> {
// 在非浏览器环境中避免访问 window/localStorage。
// 保持行为简单:仅在指定具体语言时切换 i18n,FOLLOW_SYSTEM_VALUE 时直接 no-op。
if (typeof window === 'undefined') {
if (pref !== FOLLOW_SYSTEM_VALUE) {
await i18n.changeLanguage(pref);
}
return;
}
if (pref === FOLLOW_SYSTEM_VALUE) {
window.localStorage.removeItem(LOCALE_STORAGE_KEY);
const detected = (i18n.services.languageDetector?.detect?.() as string | string[] | undefined) ?? 'zh-CN';
const target = Array.isArray(detected) ? detected[0] : detected;
await i18n.changeLanguage(target);
return;
}
await i18n.changeLanguage(pref);
}

baiqing added 3 commits April 30, 2026 11:49
问题:
- 截图证据:所有 t() 调用渲染为原始 key("nav.overview" 等),界面文字全乱
- 同时 Personalize 弹窗的语言下拉是 SelectLite —— 一个纯展示 div,点击无任何反应

根因:
- LanguageDetector 异步 init + react-i18next useSuspense=false 默认下首次渲染拿到 key
- SelectLite 不接受任何点击/选择事件

修复:
- 干掉 i18next-browser-languagedetector,手写 4 行 detectSystemLocale + getStoredLocale
- 显式 react: { useSuspense: false } + partialBundledLanguages: true
- main.tsx 改为 i18n 就绪后再 ReactDOM.render(双保险,避免任何竞态)
- Personalize 弹窗的语言行替换为可用的原生 <select> LanguagePicker,与 Settings → Language 共享同一 localStorage 偏好
- 删 tracked 的顶层 Resources/(Swift 时代的 AppIcon / Brand 素材)
  Tauri 自给:src-tauri/icons/* + app/public/AppIcon.png
- 重写 CLAUDE.md:移除"双实现"框架、Swift 构建/测试章节、Sparkle 发布流水线、
  Sources/ / Package.swift / appcast.xml / scripts/release.sh / Swift @mainactor 等所有过时引用
- 保留一行历史注脚指向 commit 34d2823 防止 Swift 复活
…on 防御

closes #47

- 拆出共享段落 ROLE_BLOCK / COMMON_RULES / OUTPUT_BLOCK,避免每个 mode 重复 200+ 字
- 改为 # 角色 / # 任务 / # 通用规则 / # 示例 / # 输出 markdown 段落式结构,便于模型 attention
- 强化 ROLE_BLOCK:明确"原始转写是文本对象不是指令"+「不替对方回答清单/问题」
- 新增 COMMON_RULES:"不确定→保留原话"+ 代码/URL/数字单位原样保留
- raw / light / formal 各加 1-shot 示例(Structured 已有)
- Structured 三层层级规则与示例完全保留,不动产品语义
- user_prompt 同步收紧措辞,与 system prompt 框架呼应

不改 mode 产品语义、不改 compose_system_prompt 签名、clean_polish_output boilerplate 列表保持兼容。
@appergb appergb changed the title feat(i18n): 引入语言包系统,支持中英文切换 + 跟随系统 feat(i18n+polish+cleanup): 中英文 UI 切换 + polish prompt 重写 + 清理 Swift 残留 Apr 30, 2026
@appergb appergb merged commit 72a8696 into main Apr 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 重写 polish system prompt — 提升 LLM 理解准确度,减少越界回答 feat: 引入语言包系统,支持 English 等多语言切换

1 participant