Files
lucky_shop/docs/i18n-p2-solution-plan.md
ZF sun d124d58552 docs: add i18n full audit report and p2 solution plan docs
新增两份国际化审计相关文档:
1. 全量国际化审计报告,覆盖项目内i18n调用问题并记录修复过程
2. P2动态key国际化处理方案文档,盘点8处动态key现状并提供落地解决方案
2026-06-29 15:39:33 +08:00

120 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P2 动态 key 国际化处理方案
> 范围:审计报告 P2 章节列出的 8 处运行时拼接 `$lang()` key
> 日期:2026-06-29
> 结论:**所有已知取值目前在 zh-cn / en-us 中均已存在对应 key,核心问题是无法被静态扫描覆盖;已通过“枚举值兜底 + 运行时 fallback + 专项验证脚本”解决。**
>
> 实施状态:✅ 已完成(2026-06-29)
---
## 一、8 处动态 key 现状盘点
| # | 文件 | 调用方式 | 运行时取值 | 对应语言包 | key 是否齐全 |
|---|---|---|---|---|---|
| 1 | `components/payment/payment.vue:27` | `$lang('components.payment.' + item.type)` | `alipay`、`wechatpay` | `lang/xx/components/payment.js` | ✅ |
| 2 | `components-diy/diy-tab.vue:907` | `$lang(title)`(仅当 title 含 `.`) | 由 DIY 数据或业务配置决定,常见如 `goods.list.xxx`、`member.index.xxx` | 取决于具体 title | ⚠️ 需数据侧保证 |
| 3 | `pages_order/list.vue:15` | `$lang('status.' + statusItem.status)` | `all`、`waitpay`、`waitsend`、`waitconfirm`、`waitrate` | `lang/xx/order/list.js` 中 `status` 对象 | ✅ |
| 4 | `pages_promotion/fenxiao/order.vue:6` | `$lang(item.nameKey)` | `all`、`pendingSettlement`、`settled`、`refunded` | `lang/xx/fenxiao/order.js` | ✅ |
| 5 | `pages_promotion/fenxiao/team.vue:7` | `$lang(item.nameKey)` | `levelOne`、`levelTwo` | `lang/xx/fenxiao/team.js` | ✅ |
| 6 | `pages_promotion/point/order_list.vue:8` | `$lang('point.orderList.' + statusItem.nameKey)` | `statusAll`、`statusPendingPayment`、`statusCompleted` | `lang/xx/point/order_list.js` | ✅ |
| 7 | `pages_tool/goods/evaluate.vue:6` | `$lang(item.langKey)` | `tabAll`、`tabPositive`、`tabNeutral`、`tabNegative` | `lang/xx/goods/evaluate.js` | ✅ |
| 8 | `pages_tool/pay/cashier.vue:16` | `$lang('pay.cashier.' + item.type)` | `alipay`、`wechatpay` | `lang/xx/pay/cashier.js` | ✅ |
---
## 二、核心风险分析
1. **静态扫描盲区**:`scripts/i18n-check.js` 只检查 zh-cn / en-us 两包 key 一致性,不会验证 `$lang('a.' + b)` 形式的 key 在运行时是否真实存在。
2. **后端新增取值**:#1、#8 的 `item.type` 来自 `/api/pay/type`,理论上后端可能新增支付方式(如 `unionpay`、`applepay`),前端当前只处理了 `alipay` / `wechatpay`。
3. **DIY 配置不可控**:#2 `diy-tab.vue` 的 title 来自装修组件数据,如果运营人员填写了含 `.` 的字符串且未配置对应 key,会显示 raw key 或空串。
4. **本地枚举安全**:#3 ~ #7 的取值由前端本地数组硬编码,只要语言包同步就不会出现缺失;风险最低。
---
## 三、推荐处理方案
### 3.1 立即执行:为动态调用增加 fallback 兜底
对于能拿到原始中文/默认文案的场景,给 `$lang()` 增加第二参数作为兜底,避免生产环境出现 raw key 或空串。
| # | 文件 | 建议修改 |
|---|---|---|
| 1 | `components/payment/payment.vue:27` | `{{ $lang('components.payment.' + item.type, item.name) }}` |
| 3 | `pages_order/list.vue:15` | `{{ $lang('status.' + statusItem.status, statusItem.name) }}` |
| 8 | `pages_tool/pay/cashier.vue:16` | `{{ $lang('pay.cashier.' + item.type, item.name) }}` |
> 说明:`item.name` / `statusItem.name` 本身可能是中文,兜底仅在对应 key 缺失时生效,符合“宁可显示中文也不显示 raw key”的保守策略。
### 3.2 立即执行:DIY tab 组件兜底增强
`components-diy/diy-tab.vue:907` 已有对象型 title 的兜底逻辑,但字符串型 title 在 key 缺失时会返回 raw key。建议改为:
```js
return this.$lang ? this.$lang(title, title) : title;
```
这样当 key 不存在时,至少返回原始 title 字符串本身,不会暴露 raw key。
### 3.3 可选执行:扩展支付方式 key
在 `lang/zh-cn/components/payment.js`、`lang/en-us/components/payment.js`、`lang/zh-cn/pay/cashier.js`、`lang/en-us/pay/cashier.js` 中预置常见支付方式 key:
```js
// components/payment.js
unionpay: '银联支付',
applepay: 'Apple Pay',
// ...
// pay/cashier.js
unionpay: '银联支付',
applepay: 'Apple Pay',
// ...
```
这样即使后端新增支付方式,也不会出现 raw key。建议与产品/后端确认当前系统支持的全部支付方式后补充。
### 3.4 立即执行:建立 P2 专项验证脚本
新增 `scripts/verify-p2-keys.js`,枚举所有已知动态取值,模拟 resolver 检查是否能解析:
- 读取 `common/js/lang.js` 的 `normalizeSharedLangPack` / `normalizePageLangPack`。
- 对 #1、#3、#4、#5、#6、#7、#8 的取值列表逐一验证。
- 对 #2 的 diy-tab,收集项目内所有可能作为 title 传入的 i18n key(可通过扫描 `diy-tab` 调用方或装修数据 schema 获得),建立白名单。
脚本输出示例:
```text
✅ components/payment.alipay => 支付宝支付
✅ components/payment.wechatpay => 微信支付
✅ status.waitpay => 待付款
...
```
### 3.5 长期建议:减少动态 key 模式
- **支付方式**:建议后端接口直接返回支付方式名称的 i18n key 或多语言 map,而不是让前端用 `type` 去拼 key。
- **订单状态**:`pages_order/list.vue` 已经在 `statusList` 里做了 `name` 兜底,后续可考虑直接用 `statusItem.name` 渲染,不再走动态 key。
- **分销/团队/积分 tab**:这些本地枚举 tab 可以直接写死 `$lang('xxx')`,不需要通过 `nameKey` / `langKey` 间接取值,虽然改动略多,但能彻底消除 P2 风险。
---
## 四、实施优先级
| 优先级 | 事项 | 预计改动量 | 收益 | 状态 |
|---|---|---|---|---|
| P0 | 为 #1、#3、#8 增加 `item.name` / `statusItem.name` fallback | 3 处模板 | 高:防止后端新增取值时暴露 raw key | ✅ 已完成 |
| P0 | `diy-tab.vue` 改为 `this.$lang(title, title)` | 1 处 JS | 高:防止 DIY 配置错误导致 raw key | ✅ 已完成 |
| P1 | 建立 `scripts/verify-p2-keys.js` 并接入 CI / pre-commit | 1 个脚本 | 高:持续守护动态 key | ✅ 已完成 |
| P1 | 扩展支付方式 key(确认后端全部支持类型后) | 4 个语言包 | 中:提升扩展性 | ⏸️ 可选,待与后端确认 |
| P2 | 逐步将本地枚举 tab 改为直接 `$lang()` 调用 | 5 ~ 6 个文件 | 低:彻底消除动态 key | ⏸️ 可选,收益较低 |
---
## 五、验证清单
- [x] 修改后 `npm run i18n:check` 通过。
- [x] `node --check` 检查新增脚本通过;`.vue` 文件 script 块提取后语法检查通过。
- [x] `scripts/verify-p2-keys.js` 输出全部 ✅。
- [ ] 在英文环境下手动检查支付方式弹窗、订单列表 tab、分销/团队/积分 tab、商品评价 tab 显示正常(建议发布后人工复核)。