fix(remote lang tree): 修复命名空间被基本类型值覆盖的bug
修复remote-only模式下语言包归一化的历史bug:当页面leaf顶层存在与命名空间同名的字符串键时,会覆盖整个namespace导致子key失效。通过对齐normalizePageLangPack的两层合并逻辑来修复: 1. 先将页面语言包按路径段转为命名空间结构再合并 2. 额外平铺leaf的基本类型键到根节点以兼容短key调用
This commit is contained in:
@@ -170,11 +170,35 @@ function normalizePageLangPack(path, langData) {
|
||||
}
|
||||
|
||||
let namespaced = {};
|
||||
let cursor = namespaced;
|
||||
{
|
||||
let cursor = namespaced;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const camelSegment = toCamelCaseName(segments[i]);
|
||||
cursor[camelSegment] = i === segments.length - 1 ? deepCloneLang(currentPageLeaf) : {};
|
||||
cursor = cursor[camelSegment];
|
||||
}
|
||||
}
|
||||
|
||||
// 同步建立 snake_case 镜像:让 camelCase 与 snake_case 路径都能解析到同一份 leaf
|
||||
// (与 bundle 服务器侧 emit 形式对齐 —— bundle 用 snake_case 段名,如 point.order_list.statusAll)
|
||||
// 实现:在每个 namespace 节点同时挂一个 snake 别名键,与 camel 节点共享 leaf 引用(0 拷贝)
|
||||
const nested = [];
|
||||
{
|
||||
let cursor = namespaced;
|
||||
nested.push(cursor);
|
||||
for (let i = 0; i < segments.length - 1; i++) {
|
||||
cursor = cursor[toCamelCaseName(segments[i])];
|
||||
nested.push(cursor);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const segment = toCamelCaseName(segments[i]);
|
||||
cursor[segment] = i === segments.length - 1 ? deepCloneLang(currentPageLeaf) : {};
|
||||
cursor = cursor[segment];
|
||||
const raw = segments[i];
|
||||
const camel = toCamelCaseName(raw);
|
||||
const snake = toSnakeCaseName(raw);
|
||||
if (snake !== camel) {
|
||||
const isLeaf = i === segments.length - 1;
|
||||
nested[i][snake] = isLeaf ? deepCloneLang(currentPageLeaf) : {};
|
||||
}
|
||||
}
|
||||
|
||||
return deepMergeLang(result, namespaced);
|
||||
@@ -811,6 +835,19 @@ function emitUpdated(lang) {
|
||||
/**
|
||||
* remote-only 模式下,为当前页面做 tree 归一化
|
||||
* 把页面路径对应的 subtree 合并到顶层,兼容 $lang('title') 等短 key 调用
|
||||
*
|
||||
* 历史 bug: 之前直接 `deepMergeLang(tree, currentPageLeaf)`,
|
||||
* 如果 currentPageLeaf 顶层有一个与 `tree.login` 命名空间同名(key)且值为字符串的键
|
||||
* (例如 `login.login.js` 里的 `login: '登录/注册'` 会出现在 leaf 顶层),
|
||||
* 会触发 `merge` 的"基本类型覆盖对象"分支,把 `tree.login` 整个 namespace 替换为字符串,
|
||||
* 导致 `tree.login.login.welcomeText` 等所有子页 key 全部失效。
|
||||
*
|
||||
* 修复方案(对齐 `normalizePageLangPack` 的两层合并):
|
||||
* 1) namespaced merge: 把 currentPageLeaf 按 langPath 段逐层包成 `{seg1: {seg2: leaf}}`,
|
||||
* 再 merge 进 tree —— 同样的 key 仍能递归合并,而 namespace 不会被字符串覆盖。
|
||||
* 2) 在 result 上额外"轻量 overlay":把 leaf 里的 SCALAR key 平铺到 root,
|
||||
* 但跳过 target 已经是 namespace 对象的 key,避免再次触发 clobber。
|
||||
* 这一步让 `$lang('title')` / `$lang('welcomeText')` 这类短 key 仍能在 root 命中页面副本。
|
||||
*/
|
||||
function normalizeRemoteTreeForPage(tree, langPath) {
|
||||
if (!tree || typeof tree !== 'object') return tree || {};
|
||||
@@ -839,8 +876,37 @@ function normalizeRemoteTreeForPage(tree, langPath) {
|
||||
|
||||
if (!currentPageLeaf) return tree;
|
||||
|
||||
// 页面 leaf 合并到顶层,同时也保留完整路径
|
||||
return deepMergeLang(deepCloneLang(tree), deepCloneLang(currentPageLeaf));
|
||||
// (1) namespaced merge
|
||||
const namespaced = {};
|
||||
let cursor = namespaced;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const segment = toCamelCaseName(segments[i]);
|
||||
cursor[segment] = i === segments.length - 1 ? deepCloneLang(currentPageLeaf) : {};
|
||||
cursor = cursor[segment];
|
||||
}
|
||||
const merged = deepMergeLang(deepCloneLang(tree), namespaced);
|
||||
|
||||
// (2) 短 key 兼容: 把 leaf 里的 SCALAR 平铺到 root,但跳过 target 已有 namespace 的 key
|
||||
try {
|
||||
for (const k of Object.prototype.hasOwnProperty.call(currentPageLeaf, '__proto__') ? [] : Object.keys(currentPageLeaf)) {
|
||||
if (FORBIDDEN_KEYS.has(k)) continue;
|
||||
const srcVal = currentPageLeaf[k];
|
||||
const tgtVal = merged[k];
|
||||
// 源非对象(基本类型)才平铺;源是对象则让上一步 namespaced merge 处理
|
||||
if (srcVal === null || typeof srcVal !== 'object' || Array.isArray(srcVal)) {
|
||||
// 若 target 已是 namespace 对象,跳过,避免 string-vs-object clobber
|
||||
if (tgtVal && typeof tgtVal === 'object' && !Array.isArray(tgtVal)) continue;
|
||||
merged[k] = srcVal;
|
||||
} else {
|
||||
// 源是对象 —— 递归合并到 merged[k](若存在)或跳过
|
||||
if (tgtVal && typeof tgtVal === 'object' && !Array.isArray(tgtVal)) {
|
||||
merged[k] = deepMergeLang(deepCloneLang(tgtVal), deepCloneLang(srcVal));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) { /* 静默 */ }
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
// 导出主体
|
||||
|
||||
Reference in New Issue
Block a user