Nuxt的国际化终极解决方案:I18n+Nuxt Content
出海大潮中Vue和Nuxt框架的国际化完整解决方案,包含I18n配置和Nuxt Content的文档国际化实现
出海大潮中Vue和Nuxt框架的国际化完整解决方案,包含I18n配置和Nuxt Content的文档国际化实现
出海大潮,国内用得很多的Vue和Nuxt也经常要面对国际化的问题。
如果是wordpress的话,就简单了,装一个插件,配置好Google的翻译接口,然后系统就会每天将新的内容自动翻译成各种语言版本。
但对于其它的系统,就不那么方便了。
代码已开源:https://github.com/aigc-projects/nuxt-i18n-starter
一、基本的国际化——I18n
nuxt-i18n
1、什么是I18n?
I18n是Nuxt国际化的必须的东西。它主要是提供了一套完整的国际化的解决方案,包括国际化路由、和基于国际化路由改变语言的方法。
2、安装I18n
以下都可以。
yarn add @nuxtjs/i18n # yarn
npm i @nuxtjs/i18n # npm
pnpm i @nuxtjs/i18n
pnpm add @nuxtjs/i18n@next --save-dev
3、配置nuxt.config.ts
modules: [
'@nuxtjs/i18n',
],
i18n: {
locales: [
{ name: "English",code: "en", iso: "en-US", dir: "ltr" },
{ name: "español",code: "es", iso: "es-LA", dir: "ltr" },
{ name: "En français",code: "fr", iso: "fr-FR", dir: "ltr" },
{ name: "العربية",code: "ar", iso: "ar-EG", dir: "rtl" },],
defaultLocale: "en",
detectBrowserLanguage: false,
// 👇 Reference the Vue I18n config file
vueI18n: "./i18n.config.js",
},
4、配置i18n.config.ts
新建i18n.config.ts或i18n.config.js
这个文件是用来配置翻译字段的。
export default defineI18nConfig(() => ({
legacy: false,
// 👇 Add translations
messages: {
"en": {
nav_about: "About",
},
"es": {
nav_about: "sobre",
},
"ar": {
nav_about: "نبذة عنا",
},
"fr": {
nav_about: "About",
},
},
}))
5、链接组件
代码
<!-- components/LocLink.vue -->
<script setup lang="ts">
defineProps(["to"])
const localePath = useLocalePath()
</script>
<template>
<NuxtLink :to="localePath(to)"><slot /></NuxtLink>
</template>
用法
<LocLink to="/">{{ $t('nav_home') }}</LocLink>
6、语言切换组件
代码
<!-- components/LangSwitcher.vue -->
<script setup lang="ts">
// Used for type casting
// Used for type casting
import type { LocaleObject } from "@nuxtjs/i18n/dist/runtime/composables";
// Get active locale and supported locales
const { locale, locales } = useI18n()
// Cast to avoid TypeScript errors in template
const supportedLocales = locales.value as Array<LocaleObject>
const router = useRouter()
const switchLocalePath = useSwitchLocalePath()
// When the visitor selects a new locale, route to
// to the new locale's path e.g. /en-CA/foo → /ar-EG/foo
function onLocaleChanged(event: Event) {
const target = event.target as HTMLInputElement
// switchLocalePath('ar-EG') will return Arabic equivalent
// for the *current* URL path e.g. if we're at /en-CA/about,
// switchLocalePath('ar-EG') will return '/ar-EG/about'
router.push({ path: switchLocalePath(target.value) })
}
</script>
<template>
<div>
🌐
<select :value="locale" @change="onLocaleChanged">
<option v-for="loc in supportedLocales" :key="loc.code" :value="loc.code">
{{ loc.name }}
</option>
</select>
</div>
</template>
用法
在页面或布局中使用标签:
7、获取当前的语言
context.app.i18n.locale // where you have access to NuxtContext
this.$i18n.locale //in component
$i18n.locale // in template
二、文档内容的国际化
单独使用I18n完全可以实现全站的国际化。但如果网站有很多文章,那么就需要在i18n.config.ts中写入大量的配置项,相当麻烦。
当然,如果文章内容来源于后端数据库,那么就不用考虑这个了,只需要将翻译好的内容存入数据库,不同的语言页面取相应语言的文章版本就可以了。
这里说的是,文章内容没有在后端数据库,而是放在前端。
对于nuxt来说,最好的方法就是使用nuxt content。简单地说,就是将文章都存在markdown文件中,然后,打开某个路由就会加载这个路由对应的页面和markdown文件。
因为同样的页面,比如首页,不同的语言,使用的路由是不同的。
比如,默认英语。首页是www.xxx.com, 西班牙语版就是www.xxx.com/es。
所以www.xxx.com会调用pages目录下的index.vue,并加载content目录下的index.md。而,www.xxx.com/es会调用pages目录下的index.vue,并加载content目录下的es.md文档。
所以,对同一篇文章的不同语言的版本,只需要在content目录下按照路由建相应版本的翻译文件即可。
1、安装nuxt content
pnpm add @nuxt/content
2、nuxt.config.ts配置
modules: [
'@nuxt/content'
],
content: {
// ... options
}
3、在页面调用相应翻译文档
首页默认英语,配置西班牙语路由和页面。
最后的路由是这样的:
默认首页(英语):www.xxx.com
西班牙语首页:www.xxx.com/es
首先,设计content的文件目录
//content目录设计
|
pages
. index.vue
content
. index.md
. es.md
然后,在首页中需要调用文章的部分加标签
<ContentDoc />
三、国际化路由的最佳实践
en-US,en表示英语,US表示国家:美国。这是一个标准的语言代码。路由可以直接这样来写:www.xxx.com/en-US
但是,大多时候,我们不需要细分到国家的层次,只需要到语言的层次就够了。所以,这样写,反而是最合适的:www.xxx.com/en
当然,对于中文,就不能这样了,中文有:zh-CN(简体中文),zh-HK(香港繁体),zh-TW(台湾繁体)
所以,对于中文,只能这样:www.xxx.com/zh-cn
四、附录:国际标准的语言代码
| 语言代码 | 语言国家 |
|---|---|
| af | 南非语 |
| af-ZA | 南非语 |
| ar | 阿拉伯语 |
| ar-AE | 阿拉伯语(阿联酋) |
| ar-BH | 阿拉伯语(巴林) |
| ar-DZ | 阿拉伯语(阿尔及利亚) |
| ar-EG | 阿拉伯语(埃及) |
| ar-IQ | 阿拉伯语(伊拉克) |
| ar-JO | 阿拉伯语(约旦) |
| ar-KW | 阿拉伯语(科威特) |
| ar-LB | 阿拉伯语(黎巴嫩) |
| ar-LY | 阿拉伯语(利比亚) |
| ar-MA | 阿拉伯语(摩洛哥) |
| ar-OM | 阿拉伯语(阿曼) |
| ar-QA | 阿拉伯语(卡塔尔) |
| ar-SA | 阿拉伯语(沙特阿拉伯) |
| ar-SY | 阿拉伯语(叙利亚) |
| ar-TN | 阿拉伯语(突尼斯) |
| ar-YE | 阿拉伯语(也门) |
| az | 阿塞拜疆语 |
| az-AZ | 阿塞拜疆语(拉丁文) |
| az-AZ | 阿塞拜疆语(西里尔文) |
| be | 比利时语 |
| be-BY | 比利时语 |
| bg | 保加利亚语 |
| bg-BG | 保加利亚语 |
| bs-BA | 波斯尼亚语(拉丁文,波斯尼亚和黑塞哥维那) |
| ca | 加泰隆语 |
| ca-ES | 加泰隆语 |
| cs | 捷克语 |
| cs-CZ | 捷克语 |
| cy | 威尔士语 |
| cy-GB | 威尔士语 |
| da | 丹麦语 |
| da-DK | 丹麦语 |
| de | 德语 |
| de-AT | 德语(奥地利) |
| de-CH | 德语(瑞士) |
| de-DE | 德语(德国) |
| de-LI | 德语(列支敦士登) |
| de-LU | 德语(卢森堡) |
| dv | 第维埃语 |
| dv-MV | 第维埃语 |
| el | 希腊语 |
| el-GR | 希腊语 |
| en | 英语 |
| en-AU | 英语(澳大利亚) |
| en-BZ | 英语(伯利兹) |
| en-CA | 英语(加拿大) |
| en-CB | 英语(加勒比海) |
| en-GB | 英语(英国) |
| en-IE | 英语(爱尔兰) |
| en-JM | 英语(牙买加) |
| en-NZ | 英语(新西兰) |
| en-PH | 英语(菲律宾) |
| en-TT | 英语(特立尼达) |
| en-US | 英语(美国) |
| en-ZA | 英语(南非) |
| en-ZW | 英语(津巴布韦) |
| eo | 世界语 |
| es | 西班牙语 |
| es-AR | 西班牙语(阿根廷) |
| es-BO | 西班牙语(玻利维亚) |
| es-CL | 西班牙语(智利) |
| es-CO | 西班牙语(哥伦比亚) |
| es-CR | 西班牙语(哥斯达黎加) |
| es-DO | 西班牙语(多米尼加共和国) |
| es-EC | 西班牙语(厄瓜多尔) |
| es-ES | 西班牙语(传统) |
| es-ES | 西班牙语(国际) |
| es-GT | 西班牙语(危地马拉) |
| es-HN | 西班牙语(洪都拉斯) |
| es-MX | 西班牙语(墨西哥) |
| es-NI | 西班牙语(尼加拉瓜) |
| es-PA | 西班牙语(巴拿马) |
| es-PE | 西班牙语(秘鲁) |
| es-PR | 西班牙语(波多黎各(美)) |
| es-PY | 西班牙语(巴拉圭) |
| es-SV | 西班牙语(萨尔瓦多) |
| es-UY | 西班牙语(乌拉圭) |
| es-VE | 西班牙语(委内瑞拉) |
| et | 爱沙尼亚语 |
| et-EE | 爱沙尼亚语 |
| eu | 巴士克语 |
| eu-ES | 巴士克语 |
| fa | 法斯语 |
| fa-IR | 法斯语 |
| fi | 芬兰语 |
| fi-FI | 芬兰语 |
| fo | 法罗语 |
| fo-FO | 法罗语 |
| fr | 法语 |
| fr-BE | 法语(比利时) |
| fr-CA | 法语(加拿大) |
| fr-CH | 法语(瑞士) |
| fr-FR | 法语(法国) |
| fr-LU | 法语(卢森堡) |
| fr-MC | 法语(摩纳哥) |
| gl | 加里西亚语 |
| gl-ES | 加里西亚语 |
| gu | 古吉拉特语 |
| gu-IN | 古吉拉特语 |
| he | 希伯来语 |
| he-IL | 希伯来语 |
| hi | 印地语 |
| hi-IN | 印地语 |
| hr | 克罗地亚语 |
| hr-BA | 克罗地亚语(波斯尼亚和黑塞哥维那) |
| hr-HR | 克罗地亚语 |
| hu | 匈牙利语 |
| hu-HU | 匈牙利语 |
| hy | 亚美尼亚语 |
| hy-AM | 亚美尼亚语 |
| id | 印度尼西亚语 |
| id-ID | 印度尼西亚语 |
| is | 冰岛语 |
| is-IS | 冰岛语 |
| it | 意大利语 |
| it-CH | 意大利语(瑞士) |
| it-IT | 意大利语(意大利) |
| ja | 日语 |
| ja-JP | 日语 |
| ka | 格鲁吉亚语 |
| ka-GE | 格鲁吉亚语 |
| kk | 哈萨克语 |
| kk-KZ | 哈萨克语 |
| kn | 卡纳拉语 |
| kn-IN | 卡纳拉语 |
| ko | 朝鲜语 |
| ko-KR | 朝鲜语 |
| kok | 孔卡尼语 |
| kok-IN | 孔卡尼语 |
| ky | 吉尔吉斯语 |
| ky-KG | 吉尔吉斯语(西里尔文) |
| lt | 立陶宛语 |
| lt-LT | 立陶宛语 |
| lv | 拉脱维亚语 |
| lv-LV | 拉脱维亚语 |
| mi | 毛利语 |
| mi-NZ | 毛利语 |
| mk | 马其顿语 |
| mk-MK | 马其顿语(FYROM) |
| mn | 蒙古语 |
| mn-MN | 蒙古语(西里尔文) |
| mr | 马拉地语 |
| mr-IN | 马拉地语 |
| ms | 马来语 |
| ms-BN | 马来语(文莱达鲁萨兰) |
| ms-MY | 马来语(马来西亚) |
| mt | 马耳他语 |
| mt-MT | 马耳他语 |
| nb | 挪威语(伯克梅尔) |
| nb-NO | 挪威语(伯克梅尔)(挪威) |
| nl | 荷兰语 |
| nl-BE | 荷兰语(比利时) |
| nl-NL | 荷兰语(荷兰) |
| nn-NO | 挪威语(尼诺斯克)(挪威) |
| ns | 北梭托语 |
| ns-ZA | 北梭托语 |
| pa | 旁遮普语 |
| pa-IN | 旁遮普语 |
| pl | 波兰语 |
| pl-PL | 波兰语 |
| pt | 葡萄牙语 |
| pt-BR | 葡萄牙语(巴西) |
| pt-PT | 葡萄牙语(葡萄牙) |
| qu | 克丘亚语 |
| qu-BO | 克丘亚语(玻利维亚) |
| qu-EC | 克丘亚语(厄瓜多尔) |
| qu-PE | 克丘亚语(秘鲁) |
| ro | 罗马尼亚语 |
| ro-RO | 罗马尼亚语 |
| ru | 俄语 |
| ru-RU | 俄语 |
| sa | 梵文 |
| sa-IN | 梵文 |
| se | 北萨摩斯语 |
| se-FI | 北萨摩斯语(芬兰) |
| se-FI | 斯科特萨摩斯语(芬兰) |
| se-FI | 伊那里萨摩斯语(芬兰) |
| se-NO | 北萨摩斯语(挪威) |
| se-NO | 律勒欧萨摩斯语(挪威) |
| se-NO | 南萨摩斯语(挪威) |
| se-SE | 北萨摩斯语(瑞典) |
| se-SE | 律勒欧萨摩斯语(瑞典) |
| se-SE | 南萨摩斯语(瑞典) |
| sk | 斯洛伐克语 |
| sk-SK | 斯洛伐克语 |
| sl | 斯洛文尼亚语 |
| sl-SI | 斯洛文尼亚语 |
| sq | 阿尔巴尼亚语 |
| sq-AL | 阿尔巴尼亚语 |
| sr-BA | 塞尔维亚语(拉丁文,波斯尼亚和黑塞哥维那) |
| sr-BA | 塞尔维亚语(西里尔文,波斯尼亚和黑塞哥维那) |
| sr-SP | 塞尔维亚(拉丁) |
| sr-SP | 塞尔维亚(西里尔文) |
| sv | 瑞典语 |
| sv-FI | 瑞典语(芬兰) |
| sv-SE | 瑞典语 |
| sw | 斯瓦希里语 |
| sw-KE | 斯瓦希里语 |
| syr | 叙利亚语 |
| syr-SY | 叙利亚语 |
| ta | 泰米尔语 |
| ta-IN | 泰米尔语 |
| te | 泰卢固语 |
| te-IN | 泰卢固语 |
| th | 泰语 |
| th-TH | 泰语 |
| tl | 塔加路语 |
| tl-PH | 塔加路语(菲律宾) |
| tn | 茨瓦纳语 |
| tn-ZA | 茨瓦纳语 |
| tr | 土耳其语 |
| tr-TR | 土耳其语 |
| ts | 宗加语 |
| tt | 鞑靼语 |
| tt-RU | 鞑靼语 |
| uk | 乌克兰语 |
| uk-UA | 乌克兰语 |
| ur | 乌都语 |
| ur-PK | 乌都语 |
| uz | 乌兹别克语 |
| uz-UZ | 乌兹别克语(拉丁文) |
| uz-UZ | 乌兹别克语(西里尔文) |
| vi | 越南语 |
| vi-VN | 越南语 |
| xh | 班图语 |
| xh-ZA | 班图语 |
| zh | 中文 |
| zh-CN | 中文(简体) |
| zh-HK | 中文(香港) |
| zh-MO | 中文(澳门) |
| zh-SG | 中文(新加坡) |
| zh-TW | 中文(繁体) |
| zu | 祖鲁语 |
| zu-ZA | 祖鲁语 |