diff --git a/common/js/lang.js b/common/js/lang.js index 7f40af2..d58c42e 100644 --- a/common/js/lang.js +++ b/common/js/lang.js @@ -20,6 +20,95 @@ const _fieldResultCache = {}; let _cachedLangPath = ''; let _cachedLangPathPage = null; +// 组件级共享语言包。 +// 这些语言包不隶属于某个页面,但组件会在任意页面中直接调用 +// $lang('components.xxx') / $lang('components-diy.xxx')。 +// 因此必须在每个页面的合并语言包中统一注入,否则运行时会出现 raw key。 +const SHARED_LANG_PACKS = [ + 'components/img-cropping', + 'components/mescroll', + 'components/ns-birthday-gift', + 'components/ns-form', + 'components/ns-login', + 'components/ns-new-gift', + 'components/ns-newform', + 'components/ns-payment', + 'components/ns-select-time', + 'components/nsChatOrder', + 'components/nsContact', + 'components/nsGoodsPromotion', + 'components/payment', + 'components/privacy-popup', + 'components/register-reward', + 'components/wxwork-contact', + 'components/yuyue-date', + 'components-diy/diy-article', + 'components-diy/diy-bargain', + 'components-diy/diy-category', + 'components-diy/diy-category-item', + 'components-diy/diy-coupon', + 'components-diy/diy-fenxiao-goods-list', + 'components-diy/diy-goods-list', + 'components-diy/diy-index-page', + 'components-diy/diy-member-info', + 'components-diy/diy-notice', + 'components-diy/diy-payment-qrcode', + 'components-diy/diy-pinfan', + 'components-diy/diy-pintuan', + 'components-diy/diy-seckill', + 'components-diy/diy-store', + 'components-diy/diy-video', +]; + +function toCamelCaseName(name) { + return String(name || '').replace(/-([a-z])/g, function(_, letter) { + return letter.toUpperCase(); + }); +} + +function normalizeSharedLangPack(path, langData) { + if (!langData || typeof langData !== 'object') return {}; + + const segments = String(path || '').split('/'); + const group = segments[0]; + const fileName = segments[segments.length - 1]; + if (group !== 'components' && group !== 'components-diy') return langData; + + if (langData[group]) return langData; + + const componentName = toCamelCaseName(fileName); + const keys = Object.keys(langData); + + if (keys.length === 1 && langData[keys[0]] && typeof langData[keys[0]] === 'object') { + return { + [group]: langData + }; + } + + return { + [group]: { + [componentName]: langData + } + }; +} + +function normalizePageLangPack(path, langData) { + if (!path || !langData || typeof langData !== 'object') return langData || {}; + + const segments = String(path).split('/').filter(Boolean); + if (!segments.length) return langData; + + 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 = cursor[segment]; + } + + return deepMergeLang(deepCloneLang(langData), namespaced); +} + // 清除所有语言缓存(切语言/切租户/远端覆写更新时调用) function invalidateLangCaches() { for (const k in _mergedLangCache) { if (Object.prototype.hasOwnProperty.call(_mergedLangCache, k)) delete _mergedLangCache[k]; } @@ -715,8 +804,15 @@ export default { if (!mergedLang) { try { const lang = loadLangPackSync(locale, 'common'); - const currentPageLang = langPath ? loadLangPackSync(locale, langPath) : {}; - mergedLang = deepMergeLang(deepCloneLang(lang), deepCloneLang(currentPageLang)); + const currentPageLang = langPath ? normalizePageLangPack(langPath, loadLangPackSync(locale, langPath)) : {}; + mergedLang = deepCloneLang(lang); + for (const sharedPath of SHARED_LANG_PACKS) { + const sharedLang = normalizeSharedLangPack(sharedPath, loadLangPackSync(locale, sharedPath)); + if (sharedLang && typeof sharedLang === 'object') { + mergedLang = deepMergeLang(mergedLang, deepCloneLang(sharedLang)); + } + } + mergedLang = deepMergeLang(mergedLang, deepCloneLang(currentPageLang)); const ovr = overrideCache[getOverrideMemKey(locale)]; if (ovr && ovr.map) applyOverrides(mergedLang, ovr.map); } catch (e) {