真正能接近系统语言的只有冷启动时通过原生能力获取的原始值,App端必须走原生桥接,navigator.language在App中完全失效,需用uni-get-language插件封装原生API并做BCP-47标准化映射与持久化。

uni.getLocale() 不可靠,它返回的是上次 uni.setLocale() 设置的值或框架“猜”的 fallback 结果,不是真实系统语言快照。真正能接近系统语言的,只有冷启动时调用原生能力获取的原始值——但必须自己做映射、兜底和持久化。
App 端必须走原生桥接,navigator.language 完全失效
打包成 App 后,navigator.language 返回的是 WebView 内核初始化时的默认 locale(比如硬编码的 zh-CN),和手机设置无关。iOS WKWebView 可能返回空字符串;Android X5 或系统 WebView 表现也不一致。这不是 bug,是 WebView 沙箱限制。
- 不要在
main.js里直接用navigator.language初始化 i18n - 不要在页面
onLoad中读取它来判断用户语言——此时早已过时 - H5 环境可用,App 环境必须放弃
uni.getSystemInfoSync().language 返回值五花八门,不能直接当 locale 用
Android 返回 zh、en 这类简写;iOS 返回 zh-Hans、en-US;而你的语言包名(如 zh-CN.json)和 i18n 初始化写的 locale: 'zh-CN' 往往是 BCP-47 标准第三种格式。三者不匹配,$t('key') 就会 fallback 到空字符串或默认语言。
- 别写
if (res.language.includes('zh'))—— 会漏掉zh-Hant、zh-HK - 必须做标准化映射:
zh → zh-CN、zh-Hans → zh-CN、zh-Hant → zh-TW、en → en-US - 映射失败时必须 fallback 到默认语言(如
zh-CN),否则界面文字可能全空 - 某些 Android 机型冷启动时
uni.getSystemInfoSync()可能返回undefined,建议改用异步版 +Promise.race加超时兜底
推荐方案:用 uni-get-language 插件 + 映射逻辑 + storage 持久化
插件市场搜「系统语言」可找到已验证的 uni-get-language,它封装了 iOS 的 NSLocale.preferredLanguages 和 Android 的 Resources.getConfiguration().getLocales(),返回标准 BCP-47 标签(如 zh-Hant、en-GB),比 uni.getSystemInfo 更可信。
- 在
App.vue的onLaunch里调用,确保环境就绪 - 拿到结果后立刻存入
uni.setStorageSync('app_locale', locale) - i18n 实例必须在
main.js中根据uni.getStorageSync('app_locale') || 'zh-CN'动态创建,不能写死 - 不要依赖
uni.onLocaleChange监听系统切换——它只响应uni.setLocale调用,对系统级变更完全无感
最易被忽略的一点:App 端没有后台常驻机制,系统语言变更后,Android 会杀进程重启,iOS 行为不一致。所谓“监听”,本质只是每次冷启动时重新探测——你没法在运行中感知,只能靠启动时那一瞬的准确读取和后续的稳定 fallback。


















