feat: 新增多语言配置并优化语言加载逻辑
1. 新增多个页面的中英文多语言词条,包括会员卡协议、积分商品、收银台等 2. 新增退款商品选择、微信支付、电子名片等页面的语言包 3. 优化lang.js语言加载逻辑,支持蛇形命名转换和多路径匹配,完善$lang调用兼容处理
This commit is contained in:
@@ -61,11 +61,35 @@ const SHARED_LANG_PACKS = [
|
||||
];
|
||||
|
||||
function toCamelCaseName(name) {
|
||||
return String(name || '').replace(/-([a-z])/g, function(_, letter) {
|
||||
return String(name || '').replace(/[-_]([a-z])/g, function(_, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function toSnakeCaseName(name) {
|
||||
return String(name || '')
|
||||
.replace(/([A-Z])/g, '_$1')
|
||||
.replace(/-/g, '_')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function buildLangPathCandidates(parts) {
|
||||
const results = [];
|
||||
function walk(index, current) {
|
||||
if (index >= parts.length) {
|
||||
const path = current.join('/');
|
||||
if (!results.includes(path)) results.push(path);
|
||||
return;
|
||||
}
|
||||
const raw = parts[index];
|
||||
const snake = toSnakeCaseName(raw);
|
||||
walk(index + 1, current.concat(raw));
|
||||
if (snake !== raw) walk(index + 1, current.concat(snake));
|
||||
}
|
||||
walk(0, []);
|
||||
return results;
|
||||
}
|
||||
|
||||
function normalizeSharedLangPack(path, langData) {
|
||||
if (!langData || typeof langData !== 'object') return {};
|
||||
|
||||
@@ -98,15 +122,96 @@ function normalizePageLangPack(path, langData) {
|
||||
const segments = String(path).split('/').filter(Boolean);
|
||||
if (!segments.length) return langData;
|
||||
|
||||
let leaf = langData;
|
||||
let hasLeaf = true;
|
||||
for (const segment of segments) {
|
||||
const key = toCamelCaseName(segment);
|
||||
if (leaf && typeof leaf === 'object' && !Array.isArray(leaf) && leaf[key] !== undefined) {
|
||||
leaf = leaf[key];
|
||||
} else {
|
||||
hasLeaf = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const lastSegment = toCamelCaseName(segments[segments.length - 1]);
|
||||
const currentPageLeaf =
|
||||
hasLeaf && leaf && typeof leaf === 'object' && !Array.isArray(leaf)
|
||||
? leaf
|
||||
: (langData[lastSegment] && typeof langData[lastSegment] === 'object' && !Array.isArray(langData[lastSegment]) ? langData[lastSegment] : langData);
|
||||
let result = deepCloneLang(langData);
|
||||
if (currentPageLeaf !== langData) {
|
||||
// 部分语言包本身就是完整路径结构,如 { pay: { result: { title } } }。
|
||||
// 页面内通常仍会调用 $lang('title') / $lang('xxx'),所以需要把当前页面 leaf
|
||||
// 也展开到顶层,避免运行时出现缺失 key 警告。
|
||||
result = deepMergeLang(deepCloneLang(currentPageLeaf), result);
|
||||
}
|
||||
if (!result[lastSegment]) {
|
||||
// 兼容页面内按文件名命名空间调用的写法,如 point/goods_list 页面调用
|
||||
// $lang('goods_list.searchPlaceholder')。
|
||||
result[lastSegment] = deepCloneLang(currentPageLeaf);
|
||||
}
|
||||
|
||||
let namespaced = {};
|
||||
let cursor = namespaced;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const segment = toCamelCaseName(segments[i]);
|
||||
cursor[segment] = i === segments.length - 1 ? deepCloneLang(langData) : {};
|
||||
cursor[segment] = i === segments.length - 1 ? deepCloneLang(currentPageLeaf) : {};
|
||||
cursor = cursor[segment];
|
||||
}
|
||||
|
||||
return deepMergeLang(deepCloneLang(langData), namespaced);
|
||||
return deepMergeLang(result, namespaced);
|
||||
}
|
||||
|
||||
function tryResolveQualifiedPageField(locale, field) {
|
||||
const arr = String(field || '').split('.');
|
||||
if (arr.length < 3) return undefined;
|
||||
|
||||
for (let i = arr.length - 1; i >= 2; i--) {
|
||||
const langPathCandidates = buildLangPathCandidates(arr.slice(0, i));
|
||||
for (const langPath of langPathCandidates) {
|
||||
const pack = normalizePageLangPack(langPath, loadLangPackSync(locale, langPath));
|
||||
const value = resolveLangField(pack, arr);
|
||||
if (value !== undefined) return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveLangField(langData, arr) {
|
||||
let temp = langData;
|
||||
for (const key of arr) {
|
||||
if (temp && typeof temp === 'object' && temp[key] !== undefined) {
|
||||
temp = temp[key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return typeof temp === 'string' ? temp : undefined;
|
||||
}
|
||||
|
||||
function tryResolveLoadedPageField(locale, field) {
|
||||
const arr = String(field || '').split('.');
|
||||
if (!arr.length) return undefined;
|
||||
|
||||
let matchedValue;
|
||||
let matchedCount = 0;
|
||||
const prefix = `${locale}_`;
|
||||
for (const cacheKey in loadedLangPacks) {
|
||||
if (!Object.prototype.hasOwnProperty.call(loadedLangPacks, cacheKey)) continue;
|
||||
if (cacheKey.indexOf(prefix) !== 0) continue;
|
||||
const langPath = cacheKey.slice(prefix.length);
|
||||
if (langPath === 'common' || SHARED_LANG_PACKS.includes(langPath)) continue;
|
||||
|
||||
const pack = normalizePageLangPack(langPath, loadedLangPacks[cacheKey]);
|
||||
const value = resolveLangField(pack, arr);
|
||||
if (value !== undefined) {
|
||||
matchedValue = value;
|
||||
matchedCount++;
|
||||
if (matchedCount > 1) return undefined;
|
||||
}
|
||||
}
|
||||
return matchedCount === 1 ? matchedValue : undefined;
|
||||
}
|
||||
|
||||
// 清除所有语言缓存(切语言/切租户/远端覆写更新时调用)
|
||||
@@ -848,6 +953,16 @@ export default {
|
||||
value = (raw !== undefined && typeof raw === 'string') ? raw : field;
|
||||
}
|
||||
|
||||
if (value === field && arr.length >= 3) {
|
||||
const qualifiedValue = tryResolveQualifiedPageField(locale, field);
|
||||
if (qualifiedValue !== undefined) value = qualifiedValue;
|
||||
}
|
||||
|
||||
if (value === field && field !== 'title') {
|
||||
const loadedPageValue = tryResolveLoadedPageField(locale, field);
|
||||
if (loadedPageValue !== undefined) value = loadedPageValue;
|
||||
}
|
||||
|
||||
// 将结果存入字段缓存(不含参数替换部分:参数每次可能不同)
|
||||
_fieldResultCache[fieldCacheKey] = value;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user