一、目录结构设计
建议将多语言资源集中管理,典型目录结构:
plaintext
src/
├── lang/ # 多语言核心目录
│ ├── index.js # i18n 配置入口
│ ├── zh.js # 中文
│ ├── en.js # 英文
│ └── ar.js # 阿拉伯
├── main.js # Vue 入口
├── components/ # 业务组件
└── views/ # 页面组件
二、编写语言包
每个语言包导出键值对格式的文本资源,键名保持统一,值为对应语言的文本。
src/lang/zh.js(中文):
js
export default {
common: {
title: 'Vue2 项目',
welcome: '欢迎 {name} 登录,今日剩余次数:{count}',
cancel: '取消',
confirm: '确认',
placeholder: '请输入内容',
item: '项目', // 单数 | 复数
count: '您有 {count} 个项目 | 您有 {count} 个项目',
},
login: {
title: '用户登录',
user: {
name: '用户名',
},
},
}
src/lang/en.js(英文):
js
export default {
common: {
title: 'Vue2 Lighthouse Demo',
welcome: 'welcome {name} login, today remaining times: {count}',
cancel: 'Cancel',
confirm: 'Confirm',
placeholder: 'Please enter content',
item: 'item | items', // 单数 | 复数
count: 'You have {count} item | You have {count} items',
},
login: {
title: 'User Login',
user: {
name: 'Username',
},
}
}
src/lang/ar.js(阿拉伯):
js
// 阿拉伯语语言包 - 注意:阿拉伯语是RTL(从右到左)语言
export default {
common: {
title: 'عرض Vue2 لـ Lighthouse',
welcome: 'مرحبًا بك {name} تسجيل الدخول، المرات المتبقية اليوم: {count}',
cancel: 'إلغاء',
confirm: 'تأكيد',
submit: 'تأكيدتأ تأكيدتأ تأكيدتأ كيدتأكيد',
placeholder: 'الرجاء إدخال المحتوى',
item: 'عنصر | عناصر', // مفرد | جمع
count: 'لديك {count} عنصر | لديك {count} عناصر',
price: 'السعر: {amount}', // 带变量的文本(变量位置不影响RTL)
mixedText: "الرقم الطلبي: <span dir='ltr'>#12345</span>", // 混合文本(数字强制LTR)
},
login: {
title: 'تسجيل دخول المستخدم',
user: {
name: 'اسم المستخدم',
},
},
}
三、配置 vue-i18n
在 src/lang/index.js 中初始化 i18n 实例:
javascript
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 按需引入配置
// const messages = {
// en: () => import('./en'),
// zh: () => import('./zh'),
// }
import en from './en'
import zh from './zh'
import ar from './ar' // 导入阿拉伯语
const messages = {
en,
zh,
ar, // 注册阿拉伯语
}
// 获取默认语言(优先级:本地存储 > 浏览器语言 > 简体中文)
const getDefaultLang = () => {
const lang =
localStorage.getItem('lang') || navigator.language || navigator.userLanguage
console.log('lang', lang)
if (lang && messages[lang]) {
// 设置html根节点的dir和lang属性(核心!)
document.documentElement.dir = lang === 'ar' ? 'rtl' : 'ltr'
document.documentElement.lang = lang
return lang
} else {
// 兜底:如果浏览器语言不支持,默认使用简体中文
return 'zh'
}
}
const i18n = new VueI18n({
locale: getDefaultLang(), // 当前语言标识
fallbackLocale: 'zh', // 语言不存在时的兜底语言
// 按需加载
// messages: {},
// 同步加载
messages,
// 防止页面闪烁
silentTranslationWarn: true,
dateTimeFormats: {
zh: {
short: {
year: 'numeric',
month: '2-digit',
day: '2-digit',
},
},
en: {
short: {
year: 'numeric',
month: '2-digit',
day: '2-digit',
},
},
ar: {
short: {
year: 'numeric',
month: '2-digit',
day: '2-digit',
},
},
},
numberFormats: {
zh: {
currency: {
style: 'currency',
currency: 'CNY',
},
},
en: {
currency: {
style: 'currency',
currency: 'USD',
},
},
ar: {
currency: {
style: 'currency',
currency: 'EGP', // 埃及镑,阿拉伯语国家常用货币
},
},
},
})
// 动态加载默认语言包
// const loadLang = async (lang) => {
// const langModule = await messages[lang]()
// console.log('langModule', langModule)
// i18n.setLocaleMessage(lang, langModule.default)
// i18n.locale = lang
// }
// loadLang(getDefaultLang())
export default i18n
在 Vue 入口注册 i18n
修改 src/main.js:
js
import Vue from 'vue'
import App from './App.vue'
import i18n from './lang' // 导入 i18n 实例
Vue.config.productionTip = false
new Vue({
i18n, // 注入到 Vue 实例
render: h => h(App)
}).$mount('#app')
四、在项目中使用多语言
1. 模板中使用(核心用法)
- 基础用法:
$t('键名') - 嵌套键名:
$t('common.submit')
html
<template>
<div>
<el-select v-model="value" placeholder="请选择语言" @change="handleChange">
<el-option label="中文" value="zh"> </el-option>
<el-option label="英文" value="en"> </el-option>
<el-option label="阿拉伯" value="ar"> </el-option>
</el-select>
<div class="content">
<p>传参:{{ $t('common.welcome', { name: '生华', count: '10' }) }}</p>
<p>{{ $t('common.cancel') }}</p>
<p>{{ $t('common.confirm') }}</p>
<p>{{ $t('login.title') }}</p>
<p>三层翻译:{{ $t('login.user.name') }}</p>
<!-- 基本用法 -->
<p>单数:{{ $tc('common.item', 1) }}</p>
<!-- 输出: item -->
<p>复数:{{ $tc('common.item', 3) }}</p>
<!-- 输出: items -->
<!-- 带变量的用法 -->
<p>{{ $tc('common.count', 1, { count: 1 }) }}</p>
<!-- 输出: You have 1 item -->
<p>{{ $tc('common.count', 5, { count: 5 }) }}</p>
<!-- 输出: You have 5 items -->
<div>
水印<el-input
style="width: 300px"
v-model="lang"
:placeholder="$t('common.placeholder')"
/>
</div>
<p>
<span>日期:</span>
<b>{{ $d(new Date()) }}</b>
</p>
<p>
数字:
{{ $n(123.22, 'currency') }}
</p>
<p>
箭头图标:
<i class="arrow-icon el-icon-right"></i>
</p>
<!-- 带变量的文本(数字格式化) -->
<p>{{ $t('common.price', { amount: formatArabicNumber(99.99) }) }}</p>
<!-- 混合文本(阿拉伯语+数字,需保留 HTML) -->
<p v-html="$t('common.mixedText')"></p>
</div>
<div>组件内自定义文案:<language-private /></div>
<!-- 适配多语言文本长度的按钮 -->
<button class="flex-btn">{{ $t('common.submit') }}</button>
</div>
</template>
2. 脚本中使用
- 实例内:
this.$t('键名') - 全局 / 非组件环境:导入 i18n 实例,使用
i18n.t('键名')
javascript
// 组件内
export default {
methods: {
submit() {
if (!this.username) {
alert(this.$t('error.empty'))
}
}
}
}
// 非组件环境(如 router.js)
// 异步加载语言包路由也要使用异步
// setTimeout(() => {
document.title = i18n.t(String(to.meta.title))
// }, 500)
3. 动态切换语言
实现语言切换按钮,修改 i18n.locale 即可:
js
<template>
<div>
<el-select v-model="value" placeholder="请选择语言" @change="handleChange">
<el-option label="中文" value="zh"> </el-option>
<el-option label="英文" value="en"> </el-option>
<el-option label="阿拉伯" value="ar"> </el-option>
</el-select>
<div>
<template>
<script>
methods: {
handleChange (val) {
this.$i18n.locale = val
localStorage.setItem('lang', val)
// 可选:刷新页面(部分场景需要,如路由、组件内硬编码文本)
window.location.reload()
},
// 格式化数字为阿拉伯-Indic数字(٩٩.٩٩ 而非 99.99)
formatArabicNumber (num) {
if (this.$i18n.locale !== 'ar') return num;
// 阿拉伯数字映射表
const digitMap = {
'0': '٠', '1': '١', '2': '٢', '3': '٣', '4': '٤',
'5': '٥', '6': '٦', '7': '٧', '8': '٨', '9': '٩', '.': '.'
};
return num.toString().split('').map(d => digitMap[d] || d).join('');
}
},
}
</script>
五、高级用法
1. 带参数的翻译(插值)
如果文本需要动态替换内容,语言包中使用 {变量名},调用时传入参数:
- 语言包(zh-CN.js):
js
export default {
common: {
welcome: '欢迎 {name} 登录,今日剩余次数:{count}'
}
}
- 使用:
js
<!-- 模板中 -->
<div>{{ $t('common.welcome', { name: '张三', count: 5 }) }}</div>
<!-- 脚本中 -->
this.$t('common.welcome', { name: '张三', count: 5 })
2. 复数处理
不同语言的复数规则不同(如英文有单复数,中文无),vue-i18n 支持复数格式化:
- 语言包(en-US.js):
js
export default {
common: {
item: 'item | items', // 单数 | 复数
count: 'You have {count} {count, plural, one {item} other {items}}'
}
}
- 使用:
js
<div>{{ $t('common.count', { count: 1 }) }}</div> <!-- You have 1 item -->
<div>{{ $t('common.count', { count: 5 }) }}</div> <!-- You have 5 items -->
3. 日期 / 数字格式化
vue-i18n 内置日期、数字格式化能力,需结合 $d(日期)、$n(数字)使用:
- 模板中:
html
<!-- 日期格式化 -->
<div>{{ $d(new Date(), 'short') }}</div> <!-- 2025/12/17(中文)| 12/17/2025(英文) -->
<!-- 数字格式化 -->
<div>{{ $n(12345.67, 'currency') }}</div> <!-- ¥12,345.67(中文)| $12,345.67(英文) -->
- 配置格式化规则(在 lang/index.js 中):
js
const i18n = new VueI18n({
// ... 其他配置
dateTimeFormats: {
'zh-CN': {
short: {
year: 'numeric', month: '2-digit', day: '2-digit'
}
},
'en-US': {
short: {
year: 'numeric', month: '2-digit', day: '2-digit'
}
}
},
numberFormats: {
'zh-CN': {
currency: {
style: 'currency', currency: 'CNY'
}
},
'en-US': {
currency: {
style: 'currency', currency: 'USD'
}
}
}
})
4. 路由国际化
如果某个组件的文本仅在内部使用,可在组件内定义局部语言包:
js
// 异步加载语言包路由也要使用异步
// setTimeout(() => {
document.title = i18n.t(String(to.meta.title))
// }, 500)
5. 动态加载
如果某个组件的文本仅在内部使用,可在组件内定义局部语言包:
js
// lang/index.js
// 按需引入配置
const messages = {
en: () => import('./en'),
zh: () => import('./zh'),
}
// 动态加载默认语言包
const loadLang = async (lang) => {
const langModule = await messages[lang]()
console.log('langModule', langModule)
i18n.setLocaleMessage(lang, langModule.default)
i18n.locale = lang
}
loadLang(getDefaultLang())
5. 阿拉伯语言特殊处理
- 切换阿拉伯语后,页面文本从右向左排列,字符无乱码;
- 混合文本(如 "السعر: ٩٩.٩٩")中数字保持 LTR 方向,整体排版正常;
- 图标、表单、按钮等组件方向适配,交互逻辑自洽。
js
// index.html
/* 全局样式:阿拉伯语字体 + 行高适配 */
html[lang='ar'] {
font-family: 'Noto Naskh Arabic', serif;
line-height: 2.4; /* 阿拉伯字符高度高,避免文字重叠 */
}
html[lang='ar'] .arrow-icon {
transform: scaleX(-1); /* 水平翻转 */
}
/* 全局 RTL 布局基础 */
html[dir='rtl'] {
direction: rtl;
unicode-bidi: embed; /* 处理混合文本(阿拉伯+数字/英文) */
text-align: right; /* 文本默认右对齐 */
}
<p>
箭头图标:
<i class="arrow-icon el-icon-right"></i>
</p>
<!-- 带变量的文本(数字格式化) -->
<p>{{ $t('common.price', { amount: formatArabicNumber(99.99) }) }}</p>
formatArabicNumber (num) {
if (this.$i18n.locale !== 'ar') return num;
// 阿拉伯数字映射表
const digitMap = {
'0': '٠', '1': '١', '2': '٢', '3': '٣', '4': '٤',
'5': '٥', '6': '٦', '7': '٧', '8': '٨', '9': '٩', '.': '.'
};
return num.toString().split('').map(d => digitMap[d] || d).join('');
}
6. 长度适配
- 所有容器「弹性宽度 + 最小 / 最大宽度」替代固定尺寸;
- 文本「单行省略 / 多行截断」作为兜底;
- 针对不同语言动态调整字体大小 / 内边距;
- 关键组件通过 JS 计算文本宽度,精准适配;
- 测试覆盖最长 / 最短语言 + 小屏场景。
五、注意事项
-
语言标识规范 :建议使用
zh-CN、en-US等 BCP 47 规范标识,避免自定义(如zh、en)导致浏览器语言匹配问题。 -
避免硬编码 :所有展示文本必须抽离到语言包,禁止在模板 / 脚本中直接写死(如
alert('提交成功')需改为alert(this.$t('common.success')))。 -
第三方组件适配 :如果使用 Element UI 等组件库,需单独配置其多语言(Element UI 有内置语言包,可结合
vue-i18n整合):
js
// main.js
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import enLocale from 'element-ui/lib/locale/lang/en'
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
import i18n from './lang'
// 整合 Element UI 语言包
ElementUI.i18n((key, value) => i18n.t(key, value))
Vue.use(ElementUI, {
locale: i18n.locale === 'en-US' ? enLocale : zhLocale
})