diff --git a/components-diy/diy-form.vue b/components-diy/diy-form.vue index f8fd923..ce66edd 100644 --- a/components-diy/diy-form.vue +++ b/components-diy/diy-form.vue @@ -308,7 +308,7 @@ export default { success: function(res) { if (res.code === 0) { uni.showToast({ - title: self.$lang('common.submitSuccess'), + title: self.$lang('submitSuccess'), icon: 'success' }); // 重置表单 diff --git a/components/ns-goods-recommend/ns-goods-recommend.vue b/components/ns-goods-recommend/ns-goods-recommend.vue index a1af059..4741b2a 100644 --- a/components/ns-goods-recommend/ns-goods-recommend.vue +++ b/components/ns-goods-recommend/ns-goods-recommend.vue @@ -69,7 +69,7 @@ mounted() { // 初始化 i18n 默认文案,实际展示以服务端配置 config.title / config.text 为准 if (!this.config.title) this.config.title = this.$lang('common.goodsRecommendTitle'); - if (!this.config.text) this.config.text = this.$lang('common.buyNow'); + if (!this.config.text) this.config.text = this.$lang('buyNow'); if (this.auto) { this.getLikeList(); } diff --git a/pages_goods/public/js/list.js b/pages_goods/public/js/list.js index 822f3d8..da99d7c 100644 --- a/pages_goods/public/js/list.js +++ b/pages_goods/public/js/list.js @@ -67,7 +67,7 @@ export default { }, onLoad(options) { - this.config.text = this.$lang('goods.list.buyText') || this.$lang('common.buyNow'); + this.config.text = this.$lang('goods.list.buyText') || this.$lang('buyNow'); this.$api.sendRequest({ url: '/api/lucky/status', diff --git a/tests/lang.test.js b/tests/lang.test.js index 7daae7d..563100d 100644 --- a/tests/lang.test.js +++ b/tests/lang.test.js @@ -387,6 +387,66 @@ test('嵌套命名空间: common 顶层键 vs common.common 子对象双层结 assert.strictEqual(fakeLang(en, 'common.goodsRecommendTitle'), 'Guess you like'); }); +test('嵌套命名空间: 双重 common. 误用回归保护(扫所有顶层 key)', () => { + // 防御:common.js 的 lang 对象整体即 common namespace, + // 顶层 key 不应再带 'common.' 前缀,否则会找不到 + // 这个测试是 2026-07-23 真实 console 报错后追加的回归保护 + // + // 策略:自动枚举 zh/en 顶层所有 key, + // 对每个 key,验证 fakeLang(zh, 'common.' + key) === '' + // —— 这样不管以后谁写出 common.,测试都会自动 fail + + // 收集两包顶层 key 的并集(只要 zh 或 en 一方有就检查) + const topKeys = new Set(); + for (const k of Object.keys(zh)) { + if (typeof zh[k] === 'string') topKeys.add(k); + } + for (const k of Object.keys(en)) { + if (typeof en[k] === 'string') topKeys.add(k); + } + + const mistakes = []; + for (const k of topKeys) { + // 注意:跳过常见会被合法命中的 key(common 里的合法子对象) + // 这些 key 在 zh.common / en.common 子对象里也存在(双 common 合法) + if (zh.common && typeof zh.common[k] === 'string') continue; + if (en.common && typeof en.common[k] === 'string') continue; + + const zhHit = fakeLang(zh, 'common.' + k); + const enHit = fakeLang(en, 'common.' + k); + if (zhHit !== '' || enHit !== '') { + mistakes.push({ k, zhHit, enHit }); + } + } + + if (mistakes.length > 0) { + console.error('以下 key 在顶层,但用 common. 调用居然命中了:'); + for (const m of mistakes) { + console.error(` common.${m.k} → zh="${m.zhHit}" en="${m.enHit}"`); + } + } + + assert.strictEqual(mistakes.length, 0, + '不应有任何 common. 调用能命中顶层 key(应该是空串)'); +}); + +test('嵌套命名空间: 双重 common.buyNow 是不存在的(防止误用回归)', () => { + // 历史 bug:common.buyNow 被当作 fallback,实际 key 是顶层 buyNow + assert.strictEqual( + fakeLang(zh, 'common.buyNow'), + '', + 'common.buyNow 双前缀不存在,真实调用必须用 $lang("buyNow")' + ); + assert.strictEqual( + fakeLang(en, 'common.buyNow'), + '', + 'en-us 同理,common.buyNow 双前缀不存在' + ); + // 同时验证顶层 buyNow 存在 + assert.strictEqual(fakeLang(zh, 'buyNow'), '立即购买'); + assert.strictEqual(fakeLang(en, 'buyNow'), 'Buy Now'); +}); + test('嵌套命名空间: 4 层路径 diy.networkErrorTitle', () => { assert.strictEqual(fakeLang(zh, 'diy.networkErrorTitle'), '提示'); assert.strictEqual(fakeLang(en, 'diy.networkErrorTitle'), 'Notice');