在全球化应用的浪潮下,为 uni-app 项目添加多语言支持已经成为出海与本地化运营的必备能力。然而,uni-app 的跨端特性给国际化带来了额外挑战:不同平台对语言切换的支持程度不一、小程序的包体积限制要求语言文件不能过大、动态语言包加载需要兼顾离线场景。本文将带你从零搭建一套健壮的多语言体系,覆盖静态内置语言包、远程动态加载、运行时切换及本地缓存,并提供覆盖 H5、App 和小程序的完整实战代码。
一、技术选型:为什么选择 Vue I18n
在 uni-app 生态中,实现国际化的主流方案是使用 Vue I18n。它是 Vue 官方推荐的国际化插件,与 Vue 3 完美配合,支持模板中的 $t()、组合式 API 中的 useI18n() 以及日期、数字的本地化格式化。同时,Vue I18n 的 messages 数据结构天然适合按模块拆分,便于管理大型项目的语言包。
除了 Vue I18n,部分开发者也会考虑直接使用 uni.getLocale() 和 uni.setLocale() 来读取系统语言,但这两个 API 在小程序中返回的语言标识可能不完整,且不支持复杂的插值和复数规则。因此,本方案选择 Vue I18n 作为核心引擎,并结合 uni-app 的平台能力进行适配。
二、项目初始化与 Vue I18n 集成
创建一个基于 Vue 3 的 uni-app 项目(使用 HBuilderX 或 CLI),安装 Vue I18n 和本地存储库(用于语言偏好持久化):
npm install vue-i18n@9
在 src 目录下新建 locale 文件夹,用于存放语言包和 i18n 实例配置:
src/
├── locale/
│ ├── index.js # i18n 实例创建与配置
│ ├── messages/
│ │ ├── zh-CN.js # 简体中文语言包
│ │ └── en.js # 英文语言包
│ └── cache.js # 语言偏好缓存工具
├── store/
│ └── locale.js # Pinia 语言状态管理(可选)
├── pages/
│ └── index/
│ └── index.vue
├── App.vue
└── main.js
2.1 编写语言包文件
语言包采用 JavaScript 模块导出,方便按页面或模块拆分后动态合并。以基础字段为例:
// locale/messages/zh-CN.js
export default {
common: {
confirm: '确定',
cancel: '取消',
loading: '加载中...'
},
home: {
title: '首页',
welcome: '欢迎使用 uni-app'
},
settings: {
language: '语言',
switchLang: '切换语言'
}
};
// locale/messages/en.js
export default {
common: {
confirm: 'Confirm',
cancel: 'Cancel',
loading: 'Loading...'
},
home: {
title: 'Home',
welcome: 'Welcome to uni-app'
},
settings: {
language: 'Language',
switchLang: 'Switch Language'
}
};
三、创建 i18n 实例与全局注册
在 locale/index.js 中创建 i18n 实例,并设置默认语言。为了支持异步加载远程语言包,我们保留 i18n 实例的动态修改能力。
// locale/index.js
import { createI18n } from 'vue-i18n'
import zhCN from './messages/zh-CN'
import en from './messages/en'
import { getLocaleCache, setLocaleCache } from './cache'
// 从本地缓存读取上次设置的语言,若无则尝试获取系统语言
function getDefaultLocale() {
const cached = getLocaleCache()
if (cached) return cached
// 使用 uni.getSystemInfoSync 获取系统语言
const systemInfo = uni.getSystemInfoSync()
const sysLang = systemInfo.language || systemInfo.appLanguage || 'zh-CN'
// 简单映射,只支持中文和英文
return sysLang.startsWith('zh') ? 'zh-CN' : 'en'
}
// 创建 i18n 实例
const i18n = createI18n({
legacy: false, // 使用组合式 API 模式
globalInjection: true, // 全局注入 $t
locale: getDefaultLocale(),
fallbackLocale: 'zh-CN',
messages: {
'zh-CN': zhCN,
'en': en
}
})
// 提供动态加载远程语言包的方法
export async function loadLocaleMessages(locale) {
// 如果已有该语言包,则直接设置
if (i18n.global.availableLocales.includes(locale)) {
i18n.global.locale.value = locale
setLocaleCache(locale)
return
}
try {
// 从远程服务器加载语言包(示例)
const response = await uni.request({
url: `https://api.example.com/locales/${locale}.json`,
method: 'GET'
})
const messages = response.data
i18n.global.setLocaleMessage(locale, messages)
i18n.global.locale.value = locale
setLocaleCache(locale)
} catch (error) {
console.error('加载语言包失败,回退到默认语言', error)
// 保持当前语言不变或回退到后备语言
}
}
export default i18n
3.1 语言偏好缓存工具
locale/cache.js 使用 uni.setStorageSync 实现简单的本地持久化:
// locale/cache.js
const STORAGE_KEY = 'APP_LOCALE'
export function getLocaleCache() {
return uni.getStorageSync(STORAGE_KEY) || null
}
export function setLocaleCache(locale) {
uni.setStorageSync(STORAGE_KEY, locale)
}
四、在 main.js 中注册 i18n 及 Pinia(可选)
在 main.js 中挂载 i18n 实例,并初始化 Pinia(若需要全局状态管理):
import App from './App'
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import i18n from './locale'
export function createApp() {
const app = createSSRApp(App)
app.use(createPinia())
app.use(i18n)
return {
app
}
}
五、组件中使用多语言
在页面或组件中,可以通过模板中的 $t() 或组合式 API 的 useI18n() 来使用翻译。以下是一个首页示例 pages/index/index.vue:
<template>
<view class="home">
<text class="title">{{ $t('home.title') }}</text>
<text class="welcome">{{ $t('home.welcome') }}</text>
<button @click="switchLang">{{ $t('settings.switchLang') }}</button>
<text class="current">当前语言: {{ locale }}</text>
</view>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { loadLocaleMessages } from '@/locale/index'
const { t, locale } = useI18n()
const switchLang = async () => {
const newLang = locale.value === 'zh-CN' ? 'en' : 'zh-CN'
await loadLocaleMessages(newLang)
}
</script>
如果需要复杂的插值或复数,可以利用 Vue I18n 的语法:
<!-- 插值 -->
<text>{{ $t('message.hello', { name: 'Alice' }) }}</text>
<!-- 复数 -->
<text>{{ $tc('car', 2) }}</text>
六、处理平台差异与特殊场景
由于 uni-app 跨端特性,在某些平台会遇到以下问题,需要针对性处理。
6.1 小程序包体积限制
如果语言包过大,可能导致小程序超过单包体积限制。解决方案是仅内置主要语言包(如中英文),其他语言通过远程加载。同时,在 pages.json 中合理配置分包,或将语言模块拆分到独立的分包中。
6.2 App 端原生导航栏标题国际化
uni-app 的页面导航栏标题通常在 pages.json 中静态配置,但动态切换语言时需要更新。可以使用 uni.setNavigationBarTitle() 在页面 onShow 中动态设置:
import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
onShow(() => {
uni.setNavigationBarTitle({
title: t('home.title')
})
})
6.3 小程序中无法使用 navigator.language
我们在 getDefaultLocale 中已使用 uni.getSystemInfoSync() 获取语言,兼容所有平台。
七、动态语言包加载与远程管理
对于需要运营灵活调整文案的场景,可以将语言包托管在远程服务器。在用户切换语言时,调用 loadLocaleMessages 从接口获取最新翻译,并合并到 i18n 实例中。为防止网络异常导致界面无文案,可以保留一份本地内置的 fallback 语言包。
// 增强 loadLocaleMessages,添加版本检查和缓存策略
export async function loadLocaleMessages(locale) {
// 检查本地是否已有缓存且未过期
const cacheKey = `LOCALE_CACHE_${locale}`
const cached = uni.getStorageSync(cacheKey)
const now = Date.now()
if (cached && cached.expireAt > now) {
i18n.global.setLocaleMessage(locale, cached.data)
i18n.global.locale.value = locale
return
}
try {
const response = await uni.request({
url: `https://api.example.com/locales/${locale}.json?t=${now}`,
method: 'GET'
})
const messages = response.data
i18n.global.setLocaleMessage(locale, messages)
i18n.global.locale.value = locale
setLocaleCache(locale)
// 缓存到本地,有效期 7 天
uni.setStorageSync(cacheKey, {
data: messages,
expireAt: now + 7 * 24 * 60 * 60 * 1000
})
} catch (error) {
console.error('远程语言包加载失败', error)
// 如果已内置该语言包,则直接使用内置版本
if (i18n.global.availableLocales.includes(locale)) {
i18n.global.locale.value = locale
}
}
}
这种策略兼顾了实时性和离线可用性,非常适合需要长期维护的多语言应用。
八、构建一个完整的语言切换设置页面
下面给出一个完整的设置页面示例 pages/settings/settings.vue,展示语言列表、当前选中状态以及切换操作。
<template>
<view class="settings">
<view class="lang-list">
<view
v-for="lang in languages"
:key="lang.code"
class="lang-item"
@click="changeLang(lang.code)"
>
<text>{{ lang.label }}</text>
<text v-if="locale === lang.code" class="check">✓</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { loadLocaleMessages } from '@/locale/index'
const { locale } = useI18n()
const languages = ref([
{ code: 'zh-CN', label: '简体中文' },
{ code: 'en', label: 'English' },
// 可继续扩展
])
const changeLang = async (code) => {
if (code === locale.value) return
uni.showLoading({ title: '切换中...' })
await loadLocaleMessages(code)
uni.hideLoading()
uni.showToast({ title: '语言已切换', icon: 'success' })
}
</script>
该页面结构清晰,用户可以直观选择目标语言,切换后通过 loadLocaleMessages 加载语言包并更新整个应用。
九、总结与优化建议
本文提供了一套从零开始的 uni-app 多语言国际化方案,具备以下特点:
- 静态内置 + 动态远程:核心语言包内置于项目,扩展语言包可从服务器加载,兼顾性能与灵活性。
- 全平台兼容:通过
uni.getSystemInfoSync获取系统语言,使用 Storage 持久化偏好,覆盖 H5、App 及小程序。 - 易于维护:语言包按模块拆分,可结合翻译平台或多人协作,降低维护成本。
- 用户体验优秀:切换过程带有加载提示,回退机制完善,避免出现空白文案。
在实际项目中,你还可以加入以下优化:
- 语言包瘦身:对语言文件进行 JSON 压缩,或仅加载当前模块需要的词条。
- RTL 支持:若需支持阿拉伯语等从右向左语言,可以通过 CSS 变量配合方向切换实现。
- 自动化翻译工作流:结合第三方翻译 API 自动生成其他语言包,减少人工投入。
国际化不仅是一种技术实现,更是产品走向全球的关键一步。掌握了这套方案,你的 uni-app 应用将能够轻松拥抱多语言市场。

