refactor: 完成多语言国际化全量替换与补充

1. 新增大量中英文语言包文件,覆盖自定义组件、页面、工具类的文案
2. 替换硬编码文本为多语言调用,统一全局文案管理
3. 优化语言包结构,拆分公共模块与业务模块
4. 修复部分语言加载与国际化适配问题
This commit is contained in:
2026-06-26 17:32:35 +08:00
parent f7fb9f34f9
commit 4ae21c5d7a
91 changed files with 2650 additions and 281 deletions

View File

@@ -5,7 +5,7 @@
export const langConfig = {
// 语言列表 — 与后端 Lang.php::overrides() 白名单对齐
// 增删需要后端同步改 LangOverrideModel::normalizeAppType 旁的 $allow_lang
langList: ['zh-cn', 'en-us', 'zh-tw', 'ja-jp', 'ko-kr', 'th-th', 'vi-vn', 'id-id', 'es-es', 'fr-fr', 'de-de', 'ru-ru', 'ar-sa', 'pt-pt', 'it-it', 'tr-tr'],
langList: ['zh-cn', 'en-us'],
// 默认语言
defaultLang: 'zh-cn',
// 语言包加载方式:'lazy'(按需加载)或 'preload'(预加载)

View File

@@ -11,7 +11,13 @@ try {
uniacid: site.uniacid,
}
} else {
console.warn('小程序发行环境下,必须配置site.js文件, 不然将使用默认配置,影响小程序定制分发!');
// 惰性加载 Lang 避免循环依赖(lang.js 引用了 config.js)
try {
const Lang = require('@/common/js/lang.js').default;
console.warn(Lang.lang('common.config.siteJsNotConfigured'));
} catch (e) {
console.warn('小程序发行环境下,必须配置site.js文件,不然将使用默认配置,影响小程序定制分发!');
}
}
} catch (e) {}
// #endif

View File

@@ -100,7 +100,7 @@ export class CustomerService {
if (!config || !config.type) {
result.isValid = false;
result.errors.push('客服类型未配置');
result.errors.push(this.vm.$lang('customerService.serviceTypeNotConfigured'));
return result;
}

View File

@@ -58,6 +58,10 @@ export default {
isEnEnv() {
return this.$langConfig.getCurrentLocale() === 'en-us';
},
// 是否是非中文环境(用于多语言商品名/字段切换)
isNotZhEnv() {
return this.$langConfig.getCurrentLocale() !== 'zh-cn';
},
// 动态判断:当前页面是否是 TabBar 根页面(tab 根页不应显示返回按钮)
isTabRootPage() {
const tabBarList = this.$store.state.tabBarList;

View File

@@ -261,7 +261,7 @@ export class NavigationHelper {
}
// 显示用户友好的错误信息
this.showError(Lang.lang('navigation.serviceUnavailable'))
this.showError(Lang.lang('common.navigation.serviceUnavailable'))
}
// 显示错误提示

View File

@@ -149,28 +149,28 @@ export const Platform = {
// #ifdef H5
if (platform === 'h5') {
cachedAppTypeName = this.isWeiXin()
? _t('platform.wechatOfficialAccount', '微信公众号')
: _t('platform.h5', 'H5');
? _t('common.platform.wechatOfficialAccount', '微信公众号')
: _t('common.platform.h5', 'H5');
return cachedAppTypeName;
}
// #endif
const platformNameMap = {
weapp: _t('platform.wechatMiniProgram', '微信小程序'),
alipay: _t('platform.alipayMiniProgram', '支付宝小程序'),
baidu: _t('platform.baiduMiniProgram', '百度小程序'),
toutiao: _t('platform.toutiaoMiniProgram', '头条小程序'),
qq: _t('platform.qqMiniProgram', 'QQ小程序'),
kuaishou: _t('platform.kuaishouMiniProgram', '快手小程序'),
jd: _t('platform.jdMiniProgram', '京东小程序'),
'360': _t('platform.miniProgram360', '360小程序'),
xhs: _t('platform.xhsMiniProgram', '小红书小程序'),
harmony: _t('platform.harmonyService', '鸿蒙元服务'),
app: _t('platform.app', 'APP'),
pc: _t('platform.pc', 'PC端'),
weapp: _t('common.platform.wechatMiniProgram', '微信小程序'),
alipay: _t('common.platform.alipayMiniProgram', '支付宝小程序'),
baidu: _t('common.platform.baiduMiniProgram', '百度小程序'),
toutiao: _t('common.platform.toutiaoMiniProgram', '头条小程序'),
qq: _t('common.platform.qqMiniProgram', 'QQ小程序'),
kuaishou: _t('common.platform.kuaishouMiniProgram', '快手小程序'),
jd: _t('common.platform.jdMiniProgram', '京东小程序'),
'360': _t('common.platform.miniProgram360', '360小程序'),
xhs: _t('common.platform.xhsMiniProgram', '小红书小程序'),
harmony: _t('common.platform.harmonyService', '鸿蒙元服务'),
app: _t('common.platform.app', 'APP'),
pc: _t('common.platform.pc', 'PC端'),
};
cachedAppTypeName = platformNameMap[platform] || _t('platform.unknown', '未知平台');
cachedAppTypeName = platformNameMap[platform] || _t('common.platform.unknown', '未知平台');
return cachedAppTypeName;
},

View File

@@ -433,21 +433,46 @@ export default {
* @param {string} mode 模式 navigateTo、redirectTo、reLaunch、tabbar, 默认navigateTo
*/
redirectTo(to, param = {}, mode = 'navigateTo') {
let url = to;
// 1. 拆分 path 与已有 query,避免后面 indexOf('?') 误判
const queryIdx = to.indexOf('?');
let path = queryIdx === -1 ? to : to.substring(0, queryIdx);
const existingParams = {};
if (queryIdx !== -1) {
to.substring(queryIdx + 1).split('&').forEach(kv => {
if (!kv) return;
// 用 indexOf('=') 取首个等号,兼容 value 里带 '=' 的场景
const eqIdx = kv.indexOf('=');
const k = eqIdx === -1 ? kv : kv.substring(0, eqIdx);
const v = eqIdx === -1 ? '' : kv.substring(eqIdx + 1);
if (k) existingParams[k] = v;
});
}
// 2. 合并 param:同 key 时新值覆盖旧值,杜绝 ?name=A&name=B 这种重复
const merged = Object.assign({}, existingParams, param || {});
// 3. 统一编码。decode -> encode 双向 roundtrip 兼具两种语义:
// - 调用方传原始值(/pages_tool/index/diy?name=XXX) -> encode 后 %2F%3F...
// - 调用方已传编码值(%2F%3F...) -> decode 还原 -> encode 还原回去,不会被二次编码
// - 任何不能 decode 的非法序列(如裸 %) 走 catch 路径,直接 encode
const safeEncode = (v) => {
const s = v == null ? '' : String(v);
try {
return encodeURIComponent(decodeURIComponent(s));
} catch (e) {
return encodeURIComponent(s);
}
};
const queryStr = Object.keys(merged)
.map(k => safeEncode(k) + '=' + safeEncode(merged[k]))
.join('&');
let url = path + (queryStr ? '?' + queryStr : '');
// 替换url中的前缀
url = adaptSubpackageUrl(url);
if (param != undefined) {
Object.keys(param).forEach(function (key) {
if (url.indexOf('?') != -1) {
url += "&" + key + "=" + param[key];
} else {
url += "?" + key + "=" + param[key];
}
});
}
// 针对tabBar.list中的路径,直接切换tabBar
for (let i = 0; i < systemTabBarList.length; i++) {
const tabBarUrl = systemTabBarList[i];
@@ -897,7 +922,7 @@ export default {
}
if (!isObject(obj)) {
throw new Error('obj 不是一个对象!')
throw new Error(Lang.lang('util.deepCloneNotObject'))
}
//判断传进来的是对象还是数组
let isArray = Array.isArray(obj)
@@ -968,7 +993,7 @@ export default {
// #ifdef MP
this.redirectTo('/pages_tool/webview/webview', {
...link,
src: encodeURIComponent(url)
src: url
});
// #endif