refactor: 优化多语言结构并新增远端语言包加载能力
1. 扁平化多语言文件层级,移除嵌套的命名空间 2. 更新组件内多语言调用路径,适配新的语言包结构 3. 重构HTTP请求错误处理逻辑 4. 新增remote-only模式的远端语言包加载器 5. 新增多语言数据源remote-only配置项
This commit is contained in:
@@ -2,6 +2,7 @@ import { langConfig } from './config-external.js';
|
||||
import Config from './config.js';
|
||||
import http from './http.js';
|
||||
import { Platform } from './platform.js';
|
||||
import { loadBundleTree, getCachedBundle, clearBundleCache } from './lang-loader.js';
|
||||
|
||||
// 缓存已加载的语言包
|
||||
const loadedLangPacks = {};
|
||||
@@ -425,6 +426,10 @@ const overrideCache = {};
|
||||
// 远端覆写:并发去重
|
||||
const overrideInflight = {};
|
||||
|
||||
// remote-only 模式:缓存远端 bundle tree
|
||||
// key: getOverrideMemKey(locale), value: { version, tree, ts, source }
|
||||
const remoteTreeCache = {};
|
||||
|
||||
// 远端可用性三态机:'unknown'(启动后未拉过) | 'ok'(最近一次成功) | 'fail'(最近一次失败)
|
||||
// 用 epoch ms 记录"最近一次状态变更时间",便于 UI 显示"离线 X 秒"
|
||||
let remoteStatus = 'unknown';
|
||||
@@ -432,12 +437,13 @@ let remoteStatusAt = 0;
|
||||
const remoteStatusListeners = [];
|
||||
|
||||
/**
|
||||
* 校验 source 配置:只接受 local / remote / hybrid,其它一律视为 remote(安全默认)
|
||||
* 校验 source 配置:只接受 local / remote / hybrid / remote-only,其它一律视为 remote(安全默认)
|
||||
*/
|
||||
function normalizeSource(s) {
|
||||
return ['local', 'remote', 'hybrid'].includes(s) ? s : 'remote';
|
||||
return ['local', 'remote', 'hybrid', 'remote-only'].includes(s) ? s : 'remote';
|
||||
}
|
||||
const SOURCE = normalizeSource(langConfig.source);
|
||||
const IS_REMOTE_ONLY = SOURCE === 'remote-only';
|
||||
|
||||
/**
|
||||
* 远端失败原因归一(对外稳定枚举,内部细节不外泄)
|
||||
@@ -733,6 +739,41 @@ function emitUpdated(lang) {
|
||||
try { uni.$emit && uni.$emit('lang:updated', { lang }); } catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* remote-only 模式下,为当前页面做 tree 归一化
|
||||
* 把页面路径对应的 subtree 合并到顶层,兼容 $lang('title') 等短 key 调用
|
||||
*/
|
||||
function normalizeRemoteTreeForPage(tree, langPath) {
|
||||
if (!tree || typeof tree !== 'object') return tree || {};
|
||||
if (!langPath) return tree;
|
||||
|
||||
const segments = String(langPath).split('/').filter(Boolean);
|
||||
if (!segments.length) return tree;
|
||||
|
||||
let leaf = tree;
|
||||
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
|
||||
: (tree[lastSegment] && typeof tree[lastSegment] === 'object' && !Array.isArray(tree[lastSegment]) ? tree[lastSegment] : null);
|
||||
|
||||
if (!currentPageLeaf) return tree;
|
||||
|
||||
// 页面 leaf 合并到顶层,同时也保留完整路径
|
||||
return deepMergeLang(deepCloneLang(tree), deepCloneLang(currentPageLeaf));
|
||||
}
|
||||
|
||||
// 导出主体
|
||||
export default {
|
||||
langList: langConfig.langList,
|
||||
@@ -780,6 +821,36 @@ export default {
|
||||
return { ok: true, source: 'local', status: remoteStatus, lang };
|
||||
}
|
||||
|
||||
// remote-only 模式:加载完整 bundle tree
|
||||
if (IS_REMOTE_ONLY) {
|
||||
const currentUid = getCurrentUniacid();
|
||||
try {
|
||||
const lastUid = uni.getStorageSync('lang_last_uniacid');
|
||||
if (lastUid && String(lastUid) !== String(currentUid)) {
|
||||
clearBundleCache({ lang });
|
||||
}
|
||||
uni.setStorageSync('lang_last_uniacid', currentUid);
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
const { tree, version, source } = await loadBundleTree({ lang });
|
||||
remoteTreeCache[getOverrideMemKey(lang)] = { version, tree, ts: Date.now(), source };
|
||||
setRemoteStatus('ok', { lang });
|
||||
// bundle 就绪后清空字段缓存并触发全局刷新,避免首屏组件在 boot 前渲染的 raw key 被缓存
|
||||
emitUpdated(lang);
|
||||
return { ok: true, source, status: 'ok', version, lang };
|
||||
} catch (e) {
|
||||
const cached = getCachedBundle({ lang });
|
||||
if (cached) {
|
||||
remoteTreeCache[getOverrideMemKey(lang)] = { version: cached.version, tree: cached.tree, ts: cached.ts, source: 'cache' };
|
||||
setRemoteStatus('fail', { reason: 'exception', lang });
|
||||
return { ok: false, source: 'cache', status: 'fail', reason: e.message || 'bundle failed', lang };
|
||||
}
|
||||
setRemoteStatus('fail', { reason: 'exception', lang });
|
||||
return { ok: false, source: 'local', status: 'fail', reason: e.message || 'bundle failed', lang };
|
||||
}
|
||||
}
|
||||
|
||||
const remote = langConfig.remote;
|
||||
const t = new Promise((r) => setTimeout(() => r({ __timeout: true, code: -1, message: 'timeout' }), remote.bootTimeout));
|
||||
|
||||
@@ -822,6 +893,14 @@ export default {
|
||||
// 切租户后清除 storage 缓存,确保下次读时拿到新 uniacid
|
||||
invalidateStorageCache('uniacid');
|
||||
const lang = getCurrentLocale();
|
||||
|
||||
if (IS_REMOTE_ONLY) {
|
||||
clearBundleCache({ lang });
|
||||
delete remoteTreeCache[getOverrideMemKey(lang)];
|
||||
loadBundleTree({ lang });
|
||||
return;
|
||||
}
|
||||
|
||||
const sk = getOverrideStorageKey(lang);
|
||||
try { uni.removeStorageSync(sk); } catch (e) {}
|
||||
delete overrideCache[getOverrideMemKey(lang)];
|
||||
@@ -920,10 +999,16 @@ export default {
|
||||
}
|
||||
|
||||
// ------- 优化3: 合并语言包缓存(避免每次 deepClone + merge + 覆写应用) -------
|
||||
const mergeCacheKey = `${locale}__${langPath}__${_overrideVersion}`;
|
||||
let mergedLang = _mergedLangCache[mergeCacheKey];
|
||||
if (!mergedLang) {
|
||||
try {
|
||||
const mergeCacheKey = `${locale}__${langPath}__${_overrideVersion}`;
|
||||
let mergedLang = _mergedLangCache[mergeCacheKey];
|
||||
if (!mergedLang) {
|
||||
try {
|
||||
if (IS_REMOTE_ONLY) {
|
||||
// remote-only 模式:直接复用远端 bundle tree,不再 require 本地文件
|
||||
const cache = remoteTreeCache[getOverrideMemKey(locale)];
|
||||
const baseTree = (cache && cache.tree) ? cache.tree : {};
|
||||
mergedLang = normalizeRemoteTreeForPage(baseTree, langPath);
|
||||
} else {
|
||||
const lang = loadLangPackSync(locale, 'common');
|
||||
const currentPageLang = langPath ? normalizePageLangPack(langPath, loadLangPackSync(locale, langPath)) : {};
|
||||
mergedLang = deepCloneLang(lang);
|
||||
@@ -936,12 +1021,13 @@ export default {
|
||||
mergedLang = deepMergeLang(mergedLang, deepCloneLang(currentPageLang));
|
||||
const ovr = overrideCache[getOverrideMemKey(locale)];
|
||||
if (ovr && ovr.map) applyOverrides(mergedLang, ovr.map);
|
||||
} catch (e) {
|
||||
console.error('解析语言包失败:', e, { langPath, field, locale });
|
||||
mergedLang = {};
|
||||
}
|
||||
_mergedLangCache[mergeCacheKey] = mergedLang;
|
||||
} catch (e) {
|
||||
console.error('解析语言包失败:', e, { langPath, field, locale });
|
||||
mergedLang = {};
|
||||
}
|
||||
_mergedLangCache[mergeCacheKey] = mergedLang;
|
||||
}
|
||||
|
||||
// ------- 字段解析 -------
|
||||
let value = field;
|
||||
@@ -1042,11 +1128,20 @@ export default {
|
||||
Object.keys(overrideCache).forEach(mk => {
|
||||
if (!mk.endsWith('_' + locale)) delete overrideCache[mk];
|
||||
});
|
||||
// remote-only 模式:清除旧语种 bundle 缓存
|
||||
Object.keys(remoteTreeCache).forEach(mk => {
|
||||
if (!mk.endsWith('_' + locale)) delete remoteTreeCache[mk];
|
||||
});
|
||||
|
||||
// 切换语种后,先 await 远端覆写(有 storage 缓存的同语种秒开,无缓存的会触发一次网络)
|
||||
// 远端失败/超时也返回 — await 拿到的就是 { ok:false, source:'local' },不会阻塞后续 refresh/reLaunch
|
||||
// 注意:loadOverrides 内部已 emitUpdated(若成功),refresh() 内部不再重复 emit
|
||||
if (SOURCE !== 'local') {
|
||||
if (IS_REMOTE_ONLY) {
|
||||
try {
|
||||
const { tree, version, source } = await loadBundleTree({ lang: value });
|
||||
remoteTreeCache[getOverrideMemKey(value)] = { version, tree, ts: Date.now(), source };
|
||||
} catch (e) { /* 静默 */ }
|
||||
} else if (SOURCE !== 'local') {
|
||||
try { await loadOverrides(value); } catch (e) { /* 静默 */ }
|
||||
}
|
||||
|
||||
@@ -1119,6 +1214,16 @@ export default {
|
||||
const getCommonName = (langType) => {
|
||||
try {
|
||||
if (tmp[langType]) return tmp[langType];
|
||||
|
||||
// remote-only 模式下优先从 bundle tree 读取
|
||||
if (IS_REMOTE_ONLY) {
|
||||
const cache = remoteTreeCache[getOverrideMemKey(langType)];
|
||||
if (cache && cache.tree && cache.tree.common && cache.tree.common.name) {
|
||||
tmp[langType] = cache.tree.common.name;
|
||||
return tmp[langType];
|
||||
}
|
||||
}
|
||||
|
||||
const data = require(`@/lang/${langType}/common.js`).lang;
|
||||
tmp[langType] = (data && data.common) ? data.common.name : langType;
|
||||
return tmp[langType];
|
||||
|
||||
Reference in New Issue
Block a user