Files
QSY-lvjuxcx/utils/i18n.js
liangjian 6e721ccc7e refactor: 国际化文本迁移至全局 locales/messages.js
将组件级 i18n 对象重构为统一由全局 locales/messages.js 管理,确保中英文语言切换时正确显示对应文本。

- 新增 utils/i18n.js 国际化工具函数
- 更新 store/$t.mixin.js 翻译混入逻辑
- 迁移 pkgService、pkgBooking、pkgAccount、pkgExplore、pages 等共 58 个组件的中英文文本至全局消息表
2026-07-24 13:43:41 +08:00

231 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: '<override>' }
})
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 || []
}