CC Switch 文档漂移的三种形态:语言数、安全前提、参数表
想给一个陌生仓库提第一个 PR,最先读的往往是 CONTRIBUTING.md。CC Switch 的这份指南在国际化那一节写得很明确:支持三种语言,改动用户可见文本时要同时更新三个语言文件,路径分别是 src/locales/en/translation.json、src/locales/zh/translation.json、src/locales/ja/translation.json(CONTRIBUTING.md:119-128 英文节,:253-262 中文节,两节措辞一致)。照着做,你会先卡在一个不存在的目录上——src/locales 这个路径在仓库里根本没有,find . -name "translation.json" 也是零命中。
这不是孤例。同一个仓库里,我们还找到另外两处性质不同的偏差:一处是安全策略里的技术前提被后来的提交改掉了,一处是整张参数表和代码里的默认值全线对不上。三处放在一起看,正好凑齐文档漂移的三种典型形态——它们的成因不同、危害不同,识别它们的动作也不同。
先说清楚这篇的依据
本文对应的快照是 CC Switch v3.20.1,commit 3217f725,核对日 2026-08-31。所有结论都来自静态阅读该快照下的 docs/、根目录的几份治理文档,以及 src/、src-tauri/src/ 的源码;行号一律指这份快照。我们没有安装、没有编译、没有运行过这个桌面应用,因此不涉及任何界面、操作或性能层面的描述。文中提到的默认值都是 v3.20.1 的代码默认配置,可配置、也会随版本变动。
形态一:清单型断言被时间拉低
CONTRIBUTING.md 那一节错在三个层次上,而且三处是连着的。
| 项 | 指南写的 | v3.20.1 实读 |
|---|---|---|
| 语言数 | 三种(CONTRIBUTING.md:121 / :255) | 四个语言文件 |
| 目录 | src/locales/ | src/i18n/locales/ |
| 文件名 | <lang>/translation.json | <lang>.json |
ls src/i18n/locales/ 返回 en.json、ja.json、zh.json、zh-TW.json,繁体中文是第四份。src/i18n/index.ts:9 的类型定义也写着 type Language = "zh" | "zh-TW" | "en" | "ja";——这是前端 i18n 初始化认的那份语言枚举。顺带说一句,同一套语言判定在这个项目里不止一份:src/hooks/useSettingsForm.ts:12-37 有一个平行实现 normalizeLanguage,src-tauri/src/tray.rs:70-88 的 map_locale_to_tray_language() 又在 Rust 侧写了一份,托盘那几条文案由 TrayTexts::from_language()(tray.rs:100-140 一带)写死在代码里,不在 src/i18n/locales/*.json 里。所以「改语言文件」这个动作本身也覆盖不到全部界面文字。
有意思的是,同一个仓库里另一份文档给出了正确答案。docs/pi-frontend-uiux-guidelines-zh.md:289 的验收清单里有一条「中、英、日、繁中术语覆盖」,四种,与代码一致。两份文档对同一件事给出不同的数,我们只陈述这个差异,并以实读的目录为准。
这种形态的特征是:断言本身在写下的那一刻是对的,是被后来的变更拉低的。 语言从三种变成四种,只需要合入一次本地化贡献,而 CONTRIBUTING.md 里的「三」不会自己跟着涨。危害落在第一次照着这份指南做的人身上:目录找不到,而且只改三份、漏掉繁中。
值得补一句的是,这类偏差在这个仓库里缺少一道全局性的自动化拦截。tests/config/localeCoverage.test.ts 的检查范围被显式收窄到 pi. 开头的键与一份硬编码白名单(localeCoverage.test.ts:41-45);同目录下另有几份按特性划分的语言测试——managementListLocales.test.ts、toolManagementLocales.test.ts、xaiOauthLocales.test.ts——各守自己那一小段键。也就是说这里的翻译校验是按功能特性一块一块补起来的,不是一张覆盖全量键的总表,没被任何一份覆盖到的键就处在裸奔状态。四份语言文件的键集合目前几乎完全一致,靠的是「同一次提交里四个文件一起改」这条人工纪律,不是某一条全量断言。
识别动作很直接:文档里凡是「有几个」「有哪几份」这类可枚举的断言,拿去和 ls 的输出比一次。 这是三种形态里最容易查的一种。
形态二:结论的支撑前提被推翻
SECURITY.md 里有一段写法很讲究。它先给出结论——打包的 WebView 渲染进程被视为可信组件——然后立刻声明这是一项「范围划定决策,由下列事实支撑,而非从中必然推出」,并写明「已针对 v3.18.0 核实」(SECURITY.md:26-28)。接着列了四条支撑事实(:30-37),再列出五条失效条件(:53-62),明说任一条不再成立,该决策必须重新评估。
四条支撑事实里的第二条是:CSP 把脚本执行限制在打包资源内,原文写的是 script-src 'self'(SECURITY.md:32)。
而 src-tauri/tauri.conf.json:29 里的实际值,script-src 一项是 'self' 之外再加一条 'sha256-…' 形式的内联脚本白名单。这个改动来自提交 d4fefefc(2026-08-16),提交信息是 fix(windows): eliminate startup white-black flash (FOUC) (#6252)——为解决 Windows 上启动时的白黑闪烁而放行了一段内联脚本。SECURITY.md 的最后一次改动是 2026-07-29,比这次配置变更早 18 天,此后未再更新。
这里的分寸要拿准,两头都不能滑。往一头滑是轻描淡写:SECURITY.md:53-62 的失效条件清单里,第二条恰恰就是「script-src 被放宽到 'self' 之外」,按文档自设的判据,这一条已经不逐字成立。往另一头滑是危言耸听:sha256 白名单是对某一段特定内联脚本内容的精确匹配,与 'unsafe-inline' 那种普遍放宽不是一回事。我们只说两处口径不一致,不评价哪种做法更好。
同节其余三条事实在 v3.20.1 仍然成立:grep -rniE "<iframe|<webview" src/ 零命中;src/ 下无 eval() 与 new Function();全库唯一的 dangerouslySetInnerHTML 在 src/components/ProviderIcon.tsx:79,用户提供的 icon 字段只作为查表的键。所以这是四条里失效一条,不是整节作废。
这一形态的价值恰恰在于它能被指认出来,是因为文档自己写了核实版本和失效条件。作为对照:docs/release-notes/v3.6.1-en.md:275-277 那句「UI Components i18n - 100% coverage across all user-facing components」没有任何时限限定,它一直躺在 docs/ 里,读的人无从判断这句话是在描述哪个版本的状态。引用这类句子时必须写成「v3.6.1 的发布说明当时称……」。
识别动作是:看到带具体技术前提的结论,先查这份文档的最后改动日期,再查被引用配置项的最后改动日期。 后者晚于前者,就该逐条重核。
形态三:整张参数表离线太久
第三份是 docs/guides/proxy-guide-zh.md,本地代理功能的总览指南。它有三个元层面的信号,任何一个单独出现都值得留意,何况三个同时出现:
- 它是
docs/guides/下唯一没有版本声明的一篇(其余每篇第 3 行都写了适用版本); - 它是唯一没有其他语种译本的一篇;
- 全仓库
grep -rn "proxy-guide" --include=*.md .排除自身后零命中,没有任何文档链接指向它,其余指南都在对应版本的发布说明里被点过名。
它最后改动于 2026-05-30,而同目录里最新的一篇指南改于 2026-08-16。逐条核下来,它的参数表和技术细节几乎整片对不上:
| 指南写的 | 位置 | v3.20.1 源码 |
|---|---|---|
| 连续失败 5 次后触发熔断 | :51 | 建表列默认 circuit_failure_threshold 为 4 |
| 30 秒后尝试恢复 | :53 | circuit_timeout_seconds 默认 60 |
| 请求超时 120 秒(单一参数) | :97 | 已拆成 streaming_first_byte_timeout / streaming_idle_timeout / non_streaming_timeout 三档 |
| 参数表按全局单例组织 | :92-98 | proxy_config 主键是 app_type,按应用分行 |
circuit_breaker_config 是独立表 | :164 | 已合并进 proxy_config |
右列的依据集中在 src-tauri/src/database/schema.rs:126-135 那段建表语句里,五个 circuit_* 列与三个超时列的 DEFAULT 值都写在这里。表合并那条有代码注释直说:schema.rs:261 写着「注意:circuit_breaker_config 已合并到 proxy_config 表中」,迁移里还有一句 DROP TABLE IF EXISTS circuit_breaker_config。
「按全局单例组织」这条尤其容易误导人。proxy_config 的主键是 app_type,也就是每个受管应用各有一行配置,而且各行的 seed 值并不相同——schema.rs:152 那条给 claude 行插的熔断失败阈值是 8、恢复等待是 90 秒,都不等于列默认的 4 和 60。指南把它当成一张全局表来讲,读者按它调参会找错位置。
还有一个细节让这条更结实:熔断器的实现文件 src-tauri/src/proxy/circuit_breaker.rs 在 v3.19.2 与 v3.20.1 两个快照之间 diff 为空,逐字节相同,那五个阈值的 Default 值也一字未改。也就是说,指南里的 5 次 / 30 秒不是被新版本刚刚改掉的,它至少在这两个版本里都对不上。上面这些默认值都是 v3.20.1 的代码默认配置,可配、会随版本变;它们描述的是配置层长什么样,不能拿来推算实际运行时会不会掉线、会不会重试。
识别动作是:参数表不要跟发布说明对,要跟建表 DDL 或配置结构体的默认值对,那才是同一层的东西。
三种形态,三种查法
回头看,三处偏差的性质完全不同:第一处是可枚举的清单被后续变更拉低,第二处是一条结论的支撑前提被一次不相干的修复推翻,第三处是一份没人引用的文档整体离线。对应的查法也不一样——第一种拿 ls 对,第二种拿两个最后改动日期对,第三种拿建表默认值对。
这个仓库同时给出了正面样本。SECURITY.md 把「信任渲染进程」写成一项范围划定决策,附四条可核验事实和五条失效条件;正因为它写了「已针对 v3.18.0 核实」和那五条失效条件,第 2 条失效才有可能被具体指认出来,而不是变成一句无从证伪的判断。以上核对动作都可以在本地快照里原样重跑一遍,不需要运行这个应用。
本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册、
路由指南与发布说明,以及 src/、src-tauri/、tests/ 的源码整理,
核对日 2026-08-31,对应仓库快照 3217f725(仓库内版本号 3.20.1)。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。
留言讨论
评论发布后会被人工复核,违规内容将被删除。
如果发表没有反应,可以前往联系我们告诉我们。