feat: 新增多语言配置并优化语言加载逻辑

1. 新增多个页面的中英文多语言词条,包括会员卡协议、积分商品、收银台等
2. 新增退款商品选择、微信支付、电子名片等页面的语言包
3. 优化lang.js语言加载逻辑,支持蛇形命名转换和多路径匹配,完善$lang调用兼容处理
This commit is contained in:
2026-06-27 09:07:51 +08:00
parent 84f5cd4568
commit bf4fe97aa3
29 changed files with 245 additions and 8 deletions

View File

@@ -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;