import store from '@/store' import globalMessages from '@/locales/messages' export const ZH_LOCALE = 'zh-Hans' export const EN_LOCALE = 'en' export const DEFAULT_LOCALE = ZH_LOCALE function safeReadStorageLocale() { try { const lifeData = uni.getStorageSync('lifeData') || {} return lifeData.vuex_locale || '' } catch (error) { return '' } } export function normalizeLocale(locale) { const normalized = String(locale || '').trim().toLowerCase() if (!normalized) { return DEFAULT_LOCALE } if (normalized === 'en' || normalized === 'en-us' || normalized === 'en_us') { return EN_LOCALE } if (normalized === 'zh' || normalized === 'zh-cn' || normalized === 'zh_cn' || normalized === 'zh-hans') { return ZH_LOCALE } return normalized.startsWith('en') ? EN_LOCALE : ZH_LOCALE } export function getLocaleShort(locale) { return normalizeLocale(locale) === EN_LOCALE ? 'en' : 'zh' } export function getCurrentLocale() { const stateLocale = store && store.state ? store.state.vuex_locale : '' return normalizeLocale(stateLocale || safeReadStorageLocale()) } export function getCurrentLanguage() { return getLocaleShort(getCurrentLocale()) } export function getNextLocale(locale) { return normalizeLocale(locale) === EN_LOCALE ? ZH_LOCALE : EN_LOCALE } export function resolveLanguageHeader(locale) { return getLocaleShort(locale) === 'en' ? 'en-US' : 'zh-CN' } export function formatI18nText(template, params = {}) { return String(template || '').replace(/\{(\w+)\}/g, (match, key) => { return Object.prototype.hasOwnProperty.call(params, key) ? params[key] : match }) } function getMessageText(messages = {}, locale, key) { const shortLocale = getLocaleShort(locale) const fallbackMessages = messages.zh || messages[ZH_LOCALE] || {} const scopedMessages = messages[shortLocale] || messages[normalizeLocale(locale)] || fallbackMessages if (scopedMessages[key] !== undefined) { return scopedMessages[key] } return fallbackMessages[key] } export function translateMessages(messages = {}, locale, key, params = {}) { const text = getMessageText(messages, locale, key) return formatI18nText(text !== undefined ? text : key, params) } export function getMessageScope(messages = {}, namespace = '') { if (!namespace) { return messages } return String(namespace) .split('.') .filter(Boolean) .reduce((current, segment) => { if (!current || typeof current !== 'object') { return undefined } return current[segment] }, messages) } export function getGlobalMessages(namespace = '') { if (!namespace) { return globalMessages } const zhScope = getMessageScope(globalMessages.zh || globalMessages[ZH_LOCALE] || {}, namespace) const enScope = getMessageScope(globalMessages.en || globalMessages[EN_LOCALE] || {}, namespace) if (!zhScope && !enScope) { return undefined } return { zh: zhScope || {}, en: enScope || {} } } export function translateGlobalMessages(namespace, locale, key, params = {}) { const scopedMessages = getGlobalMessages(namespace) if (!scopedMessages || typeof scopedMessages !== 'object') { return key } return translateMessages(scopedMessages, locale, key, params) } export function getGlobalMessage(namespace, locale, key, params = {}) { const scopedMessages = getGlobalMessages(namespace) if (!scopedMessages || typeof scopedMessages !== 'object') { return '' } const text = getMessageText(scopedMessages, locale, key) return text === undefined ? '' : formatI18nText(text, params) } export function pickLocalizedText(options = {}) { const locale = getLocaleShort(options.locale) const zhText = options.zh !== undefined ? options.zh : '' const enText = options.en !== undefined ? options.en : zhText if (locale === 'en') { return enText || options.fallback || zhText || '' } return zhText || options.fallback || enText || '' } // === 派生扁平字典(中文 → 英文)=== // 从 globalMessages 的 zh/en 双套结构反向派生 {'中文':'英文'} map, // 供 translateText / $tr 使用。模块加载后懒加载、进程内缓存。 let __flatDictionary = null let __dictConflicts = null // 手动裁决的冲突项:键为中文,值为优先采用的英文。 // 当同一中文在 messages.js 不同位置有不同英文译法时,在此指定通用译法; // 强上下文相关、无法单一裁决的条目,组件内保留 $tm 作为逃生口。 const DICT_OVERRIDES = {} function buildFlatDictionary() { if (__flatDictionary) return __flatDictionary const dict = Object.create(null) const conflicts = [] const seen = Object.create(null) // 1. 优先注入手动裁决项 Object.keys(DICT_OVERRIDES).forEach((zh) => { dict[zh] = DICT_OVERRIDES[zh] seen[zh] = { en: DICT_OVERRIDES[zh], path: '' } }) const zhRoot = globalMessages.zh || globalMessages[ZH_LOCALE] || {} const enRoot = globalMessages.en || globalMessages[EN_LOCALE] || {} // 2. 平行 DFS:以 zh 叶子(中文)为 key,同路径 en 叶子为 value function walk(zhNode, enNode, path) { if (zhNode && typeof zhNode === 'object' && !Array.isArray(zhNode)) { const keys = Object.keys(zhNode) for (let i = 0; i < keys.length; i += 1) { const k = keys[i] walk(zhNode[k], enNode ? enNode[k] : undefined, path ? path + '.' + k : k) } return } const zhText = zhNode == null ? '' : String(zhNode) if (!zhText) return const enText = enNode == null ? zhText : String(enNode) if (seen[zhText]) { if (seen[zhText].en !== enText) { conflicts.push({ zh: zhText, enFirst: seen[zhText].en, enCurrent: enText, pathFirst: seen[zhText].path, pathCurrent: path }) } // 首次写入优先:已存在则不覆盖 return } dict[zhText] = enText seen[zhText] = { en: enText, path } } walk(zhRoot, enRoot, '') __flatDictionary = dict __dictConflicts = conflicts if (process.env.NODE_ENV !== 'production' && conflicts.length) { console.warn('[i18n] flat dictionary built with ' + conflicts.length + ' conflicts; call getDictionaryConflicts() for details') } return dict } // 中文即 key 的翻译函数:中文环境原样返回,英文环境查扁平字典,查不到回退中文。 // 支持与 $tm/$translate 一致的 {param} 占位符替换。 export function translateText(text, locale, params = {}) { if (text === null || text === undefined) return '' const zhText = String(text) if (!zhText) return '' if (normalizeLocale(locale) !== EN_LOCALE) { return formatI18nText(zhText, params) } const dict = buildFlatDictionary() const enText = dict[zhText] if (enText === undefined) { if (process.env.NODE_ENV !== 'production') { console.warn('[i18n] missing dictionary entry for: ' + zhText) } return formatI18nText(zhText, params) } return formatI18nText(enText, params) } export function getFlatDictionary() { return buildFlatDictionary() } export function getDictionaryConflicts() { if (!__dictConflicts) buildFlatDictionary() return __dictConflicts || [] }