新增两份国际化审计相关文档: 1. 全量国际化审计报告,覆盖项目内i18n调用问题并记录修复过程 2. P2动态key国际化处理方案文档,盘点8处动态key现状并提供落地解决方案
120 lines
6.6 KiB
Markdown
120 lines
6.6 KiB
Markdown
# 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 显示正常(建议发布后人工复核)。
|