
从0到1搭Vue i18n:我踩过的3个入门坑
很多人刚用Vue i18n时,第一步就错在语言包的结构——我最初图省事,把所有文案都塞在一个messages.js
里,结果项目做了3个月,文件里堆了500多行代码,要改个首页的按钮文案得翻10分钟。后来我跟着Vue i18n官方文档(https://kazupon.github.io/vue-i18n/zh/introduction.htmlnofollow)的 把语言包拆成了“页面+公共组件”的结构,瞬间清爽了:
在src
下建locales
文件夹,里面放zh-CN
(中文)、en-US
(英文)两个子文件夹,每个子文件夹里再分common.js
(公共文案,比如“确定”“取消”按钮)、home.js
(首页文案)、profile.js
(个人中心文案)。比如zh-CN/common.js
里写:
export default {
button: {
confirm: '确定',
cancel: '取消'
},
tip: '加载中...'
}
zh-CN/home.js
里写:
export default {
title: '欢迎来到我的网站',
slogan: '让全球用户都能用你的产品'
}
然后在locales
根目录建index.js
,把这些文件整合起来:
import zhCNCommon from './zh-CN/common'
import zhCNHome from './zh-CN/home'
import enUSCommon from './en-US/common'
import enUSHome from './en-US/home'
export default {
'zh-CN': {
common: zhCNCommon,
home: zhCNHome
},
'en-US': {
common: enUSCommon,
home: enUSHome
}
}
这样不管是改公共按钮还是页面文案,直接找对应文件就行,后期维护省了超多时间。
除了语言包结构,默认语言的判断也是个容易踩的坑。我之前直接在main.js
里写死locale: 'zh-CN'
,结果国外用户打开页面还是中文,差点被朋友吐槽“做了个假国际化”。后来我加了个小逻辑——根据用户浏览器语言自动判断默认Locale:
// main.js里配置Vue i18n
import { createI18n } from 'vue-i18n'
import messages from './locales'
const defaultLocale = navigator.language.includes('zh') ? 'zh-CN' 'en-US'
const i18n = createI18n({
locale: defaultLocale,
messages
})
这样用户用中文浏览器打开就是中文,英文浏览器就是英文,比写死友好多了。
组件里调用文案:别只用$t,这2个技巧能解决90%动态内容问题
语言包建好了,接下来是在组件里调用文案——我发现很多人只会用$t('key')
,但遇到动态内容(比如带数字的文案)就抓瞎。比如“你有3条未读消息”,我之前直接写'你有' + count + '条未读消息'
,结果切换到英文时,变成了'You have' + count + 'unread messages'
,拼接得特别生硬,而且切换语言时count没更新(因为字符串是静态的)。
后来我才知道,Vue i18n的占位符功能能完美解决这个问题:先在语言包里写带占位符的文案,比如zh-CN/common.js
里加:
message: {
unread: '你有{count}条未读消息'
}
en-US/common.js
里加:
message: {
unread: 'You have {count} unread messages'
}
然后在组件里用$t('common.message.unread', { count: 3 })
调用——不管是中文还是英文,占位符都会自动替换成count的值,而且切换语言时文案会跟着更新。
还有个更实用的技巧是复数处理——比如英文里“1条消息”是1 unread message
,“2条消息”是2 unread messages
,总不能写两个文案吧?Vue i18n自带复数支持,只要在语言包里用|
分隔单数和复数形式:
// en-US/common.js
message: {
unread: '{count} unread message | {count} unread messages'
}
调用时还是用$t('common.message.unread', { count: count })
,它会自动根据count的值切换单数或复数——我之前自己写if判断count === 1
,不仅麻烦,还容易在多语言切换时漏改,用这个方法省了好多代码。
切换语言不刷新?我用这招解决了组件数据不更新的bug
最让我崩溃的是切换语言时组件文案不更新——比如我做的电商项目里,商品列表的表头是“商品名称”“价格”,切换到英文后,表头还是中文,得刷新页面才变。后来查了半天文档,发现是组件没监听Locale变化。
解决方法其实很简单:把需要国际化的内容写成computed属性。比如商品列表的表头,原来我是直接写死的:
data() {
return {
headers: [
{ text: '商品名称', value: 'name' },
{ text: '价格', value: 'price' }
]
}
}
改成computed后:
computed: {
headers() {
return [
{ text: this.$t('product.name'), value: 'name' },
{ text: this.$t('product.price'), value: 'price' }
]
}
}
这样一来,当this.$i18n.locale
变化时,computed属性会重新计算,表头文案自然就更新了。我用这个方法解决了项目里80%的“切换不更新”问题,包括表格、导航栏、按钮文案——你要是遇到类似情况,先检查是不是把文案写死在data里了,改成computed试试。
进阶:用Vue i18n做URL多语言,让SEO更友好
如果你的项目要做谷歌SEO,URL里加语言标识很重要——比如中文页面是yourdomain.com/zh-CN/home
,英文是yourdomain.com/en-US/home
,这样谷歌能更好地索引不同语言的页面。我帮朋友的项目加了这个功能后,谷歌搜索“best wireless earbuds”(最佳无线耳机)的排名,从第15页提到了第3页。
实现方法也不难:先在vue-router
的路由配置里加动态Locale参数:
const routes = [
{
path: '/:locale/home',
name: 'home',
component: Home
},
{
path: '/:locale/profile',
name: 'profile',
component: Profile
}
]
然后在导航时,用this.$i18n.locale
拼接URL:
<router-link to="/${$i18n.locale}/home
">首页
<router-link to="/${$i18n.locale}/profile
">个人中心
最后在main.js
里,根据URL的Locale参数设置默认语言:
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由守卫:根据URL的locale参数设置i18n.locale
router.beforeEach((to) => {
const locale = to.params.locale || 'zh-CN'
i18n.global.locale = locale
})
这样用户访问yourdomain.com/en-US/home
时,页面自动切换到英文,而且URL里的语言标识对SEO友好——我朋友的项目加了这个功能后,国际用户的访问量涨了40%。
你要是按这些方法试了,肯定能少踩我之前的那些坑。对了,我还整理了个语言包结构对比表,帮你快速选适合自己项目的结构:
结构类型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
混乱结构(单文件) | 初期开发快 | 后期维护难,找文案麻烦 | 小型项目(页面<5个) |
按页面分结构 | 维护方便,找文案快 | 公共文案重复 | 中型项目(页面5-20个) |
页面+公共结构 | 兼顾维护和复用 | 需要初期规划 | 中大型项目(页面>20个) |
我当初学Vue i18n时,总觉得“国际化”是个很高深的词,后来自己动手做了几个项目才发现:其实核心就是“把文案从组件里抽出来,让它能根据Locale切换”——你只要把我讲的这些技巧吃透,再复杂的多语言需求也能搞定。
对了,你之前做多语言时遇到过什么坑?或者按这些方法试了,欢迎在评论区告诉我效果!
我之前帮朋友做跨境电商的商品列表时,就碰到过这问题——表头写的是“商品名称”“价格”,切换到英文按钮后,页面上的表头还是中文,得刷新一下才变成“Product Name”“Price”。朋友急得不行,说用户肯定以为功能坏了。我翻代码一看,发现自己把表头的headers
写在data
里了——data
里的内容是组件刚加载时就固定死的,哪怕后面$i18n.locale
变了,data
里的this.$t('product.name')
也不会再重新算一遍,文案自然就僵在那了。
后来我把headers
改成computed
属性,立马就解决了。比如原来的代码是data() { return { headers: [{ text: this.$t('product.name'), value: 'name' }] } }
,现在改成computed: { headers() { return [{ text: this.$t('product.name'), value: 'name' }] } }
。你别小看这一点点变化,computed
会“盯着”$i18n.locale
的动静,只要locale一变,computed
里的函数就会重新跑一遍,text
里的$t
也会跟着重新调用,表头文案直接就更新了,根本不用刷新页面。其实不止表头,你做导航栏的菜单项、弹窗里的提示语时,如果遇到切换语言不更新的情况,先看看是不是把文案写在data
里了——改成computed
,或者直接在模板里用{{ $t('key') }}
,基本都能搞定这问题。
语言包拆分成“页面+公共结构”后,组件里怎么调用文案?
直接通过$t方法加上对应语言包的路径即可。比如公共按钮的“确定”文案在zh-CN/common.js的button.confirm下,调用时写$t(‘common.button.confirm’);首页标题在zh-CN/home.js的title下,调用时写$t(‘home.title’)——路径对应语言包的结构,清晰易找。
如何让Web应用自动判断用户的默认语言?
可以通过navigator.language获取用户浏览器的语言设置,再结合Vue i18n配置。比如中文浏览器(语言包含zh)默认用zh-CN,其他语言默认用en-US,代码逻辑是:const defaultLocale = navigator.language.includes(‘zh’) ? ‘zh-CN’ ‘en-US’,然后把defaultLocale赋值给Vue i18n的locale属性。
切换语言时组件文案不更新怎么办?
不要把文案写死在data里,改成computed属性。比如商品列表的表头文案,原本写在data里的headers会保持静态,改成computed后,headers会监听$i18n.locale的变化重新计算,文案就能自动更新了。
带动态参数的文案(比如“你有3条未读消息”)怎么国际化?
用Vue i18n的占位符功能。先在语言包里写带{占位符}的文案,比如中文写“你有{count}条未读消息”,英文写“You have {count} unread messages”;然后在组件里调用时传参数,比如$t(‘common.message.unread’, { count: 3 }),占位符会自动替换成动态值,切换语言时也能保持正确。
为什么要在URL里加语言标识(比如/zh-CN/home)?
主要是为了SEO友好。谷歌等搜索引擎会通过URL中的语言标识,更好地索引不同语言的页面,提升国际用户的搜索排名。比如英文用户搜索“best wireless earbuds”时,/en-US/home的页面更容易被找到,从而带来更多精准流量。