Nuxt.js国际化和本地化
章节目标
通过本章节的学习,你将能够:
- 理解国际化和本地化的基本概念
- 掌握Nuxt.js中的多语言支持实现
- 实现日期和时间的本地化格式化
- 实现数字和货币的本地化格式化
- 了解国际化最佳实践
1. 国际化和本地化的基本概念
1.1 什么是国际化和本地化?
- 国际化(Internationalization,i18n):指设计和开发应用时,使其能够适应不同语言和地区的需求,而不需要对代码进行重大修改。
- 本地化(Localization,l10n):指将国际化的应用适配到特定语言和地区的过程,包括翻译文本、调整日期格式、货币符号等。
1.2 国际化和本地化的重要性
- 全球市场:帮助应用进入全球市场,覆盖更多用户
- 用户体验:为用户提供母语界面,提升用户体验
- 品牌形象:显示对不同文化的尊重,提升品牌形象
- 法律合规:某些国家/地区要求提供本地化服务
2. Nuxt.js中的多语言支持
2.1 使用@nuxtjs/i18n模块
@nuxtjs/i18n是Nuxt.js官方推荐的国际化模块,提供了全面的国际化功能。
2.1.1 安装和配置
安装模块:
npm install @nuxtjs/i18n配置nuxt.config.js:
export default {
modules: [
'@nuxtjs/i18n'
],
i18n: {
locales: [
{
code: 'zh',
iso: 'zh-CN',
name: '中文',
file: 'zh.js'
},
{
code: 'en',
iso: 'en-US',
name: 'English',
file: 'en.js'
}
],
lazy: true,
langDir: 'lang/',
defaultLocale: 'zh',
vueI18n: {
fallbackLocale: 'zh'
},
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
alwaysRedirect: false,
fallbackLocale: 'zh'
}
}
}2.1.2 创建语言文件
在项目根目录创建lang文件夹,并添加语言文件:
lang/zh.js:
export default {
welcome: '欢迎使用Nuxt.js',
about: '关于我们',
contact: '联系我们',
home: '首页',
hello: '你好,{name}',
count: '共 {count} 条记录',
date: {
format: 'YYYY年MM月DD日',
today: '今天',
yesterday: '昨天'
}
}lang/en.js:
export default {
welcome: 'Welcome to Nuxt.js',
about: 'About Us',
contact: 'Contact Us',
home: 'Home',
hello: 'Hello, {name}',
count: '{count} records in total',
date: {
format: 'MM/DD/YYYY',
today: 'Today',
yesterday: 'Yesterday'
}
}2.2 在组件中使用
2.2.1 基本用法
<template>
<div>
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('hello', { name: 'World' }) }}</p>
<p>{{ $t('count', { count: 100 }) }}</p>
<div class="language-switcher">
<button
v-for="locale in $i18n.locales"
:key="locale.code"
@click="switchLanguage(locale.code)"
:class="{ active: $i18n.locale === locale.code }"
>
{{ locale.name }}
</button>
</div>
</div>
</template>
<script>
export default {
methods: {
switchLanguage(lang) {
this.$i18n.locale = lang;
}
}
}
</script>2.2.2 路由国际化
@nuxtjs/i18n模块支持路由国际化,可通过以下配置实现:
export default {
i18n: {
// 其他配置...
parsePages: true,
pages: {
about: {
zh: '/about',
en: '/about-us'
},
contact: {
zh: '/contact',
en: '/contact-us'
}
}
}
}3. 日期和时间的本地化格式化
3.1 使用date-fns或dayjs
3.1.1 安装依赖
# 使用date-fns
npm install date-fns date-fns-tz date-fns/locale
# 或使用dayjs
npm install dayjs dayjs-timezone3.1.2 创建日期格式化工具
// plugins/date-formatter.js
import Vue from 'vue';
import { format, formatDistanceToNow } from 'date-fns';
import { zhCN, enUS } from 'date-fns/locale';
export default (context, inject) => {
const locales = {
zh: zhCN,
en: enUS
};
const formatDate = (date, formatStr, lang = context.app.i18n.locale) => {
const locale = locales[lang] || zhCN;
return format(date, formatStr, { locale });
};
const formatRelativeTime = (date, lang = context.app.i18n.locale) => {
const locale = locales[lang] || zhCN;
return formatDistanceToNow(date, { locale, addSuffix: true });
};
inject('formatDate', formatDate);
inject('formatRelativeTime', formatRelativeTime);
Vue.prototype.$formatDate = formatDate;
Vue.prototype.$formatRelativeTime = formatRelativeTime;
};3.1.3 在组件中使用
<template>
<div>
<p>{{ $formatDate(new Date(), 'yyyy年MM月dd日 EEEE') }}</p>
<p>{{ $formatRelativeTime(new Date(Date.now() - 3600000)) }}</p>
</div>
</template>4. 数字和货币的本地化格式化
4.1 使用Intl.NumberFormat
Intl.NumberFormat是JavaScript内置的国际化API,可用于格式化数字和货币。
4.1.1 创建数字格式化工具
// plugins/number-formatter.js
import Vue from 'vue';
export default (context, inject) => {
const formatNumber = (number, options = {}, lang = context.app.i18n.locale) => {
return new Intl.NumberFormat(lang, options).format(number);
};
const formatCurrency = (amount, currency = 'CNY', lang = context.app.i18n.locale) => {
return new Intl.NumberFormat(lang, {
style: 'currency',
currency,
minimumFractionDigits: 2
}).format(amount);
};
inject('formatNumber', formatNumber);
inject('formatCurrency', formatCurrency);
Vue.prototype.$formatNumber = formatNumber;
Vue.prototype.$formatCurrency = formatCurrency;
};4.1.2 在组件中使用
<template>
<div>
<p>{{ $formatNumber(123456.78, { maximumFractionDigits: 2 }) }}</p>
<p>{{ $formatCurrency(123456.78, 'CNY') }}</p>
<p>{{ $formatCurrency(123456.78, 'USD', 'en') }}</p>
</div>
</template>5. 国际化最佳实践
5.1 文本提取和管理
5.1.1 集中管理翻译文本
- 将所有翻译文本集中到语言文件中,避免硬编码
- 按功能模块组织语言文件,提高可维护性
- 使用有意义的键名,便于理解和管理
5.1.2 自动化文本提取
使用i18n-extract工具自动提取代码中的翻译键:
npm install --save-dev i18n-extract创建提取脚本:
// scripts/extract-i18n.js
const { extractFromFiles } = require('i18n-extract');
const fs = require('fs');
const path = require('path');
const files = [
'./pages/**/*.vue',
'./components/**/*.vue',
'./layouts/**/*.vue'
];
const extractedKeys = extractFromFiles(files, {
marker: '$t',
keySeparator: '.',
});
// 生成语言文件模板
const template = {};
extractedKeys.forEach(key => {
let current = template;
const parts = key.split('.');
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (!current[part]) {
current[part] = i === parts.length - 1 ? '' : {};
}
current = current[part];
}
});
// 写入文件
fs.writeFileSync(
path.join(__dirname, '../lang/template.json'),
JSON.stringify(template, null, 2)
);
console.log('Translation keys extracted successfully!');5.2 处理复数形式
不同语言的复数规则可能不同,@nuxtjs/i18n支持复数形式:
// lang/zh.js
export default {
apple: '苹果',
apple_plural: '苹果'
}
// lang/en.js
export default {
apple: 'apple',
apple_plural: 'apples'
}使用:
<template>
<p>{{ $tc('apple', count) }}</p>
</template>5.3 处理性别形式
某些语言需要根据性别调整文本:
// lang/en.js
export default {
welcome: {
male: 'Welcome, Mr. {name}',
female: 'Welcome, Ms. {name}',
other: 'Welcome, {name}'
}
}使用:
<template>
<p>{{ $t(`welcome.${gender}`, { name: userName }) }}</p>
</template>5.4 图片和资源的国际化
对于需要国际化的图片和资源,可以按语言组织:
static/
images/
zh/
banner.png
en/
banner.png使用:
<template>
<img :src="`/images/${$i18n.locale}/banner.png`" alt="Banner">
</template>6. 实用案例分析
6.1 案例:多语言网站
6.1.1 项目结构
nuxt-i18n-example/
├── lang/
│ ├── zh.js
│ └── en.js
├── pages/
│ ├── index.vue
│ ├── about.vue
│ └── contact.vue
├── components/
│ └── LanguageSwitcher.vue
├── nuxt.config.js
└── package.json6.1.2 语言切换组件
<!-- components/LanguageSwitcher.vue -->
<template>
<div class="language-switcher">
<button
v-for="locale in locales"
:key="locale.code"
@click="switchLanguage(locale.code)"
:class="{ active: currentLocale === locale.code }"
:aria-label="`Switch to ${locale.name}`"
>
{{ locale.name }}
</button>
</div>
</template>
<script>
export default {
computed: {
locales() {
return this.$i18n.locales;
},
currentLocale() {
return this.$i18n.locale;
}
},
methods: {
switchLanguage(lang) {
this.$i18n.locale = lang;
}
}
}
</script>6.1.3 页面组件
<!-- pages/index.vue -->
<template>
<div class="home">
<LanguageSwitcher />
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('home.description') }}</p>
<div class="stats">
<div class="stat-item">
<h3>{{ $t('stats.users') }}</h3>
<p>{{ $formatNumber(123456) }}</p>
</div>
<div class="stat-item">
<h3>{{ $t('stats.revenue') }}</h3>
<p>{{ $formatCurrency(9876543.21) }}</p>
</div>
</div>
<div class="latest-news">
<h2>{{ $t('news.latest') }}</h2>
<div v-for="news in newsList" :key="news.id" class="news-item">
<h3>{{ news.title }}</h3>
<p>{{ $formatDate(news.date, 'yyyy-MM-dd') }}</p>
<p>{{ news.excerpt }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
newsList: [
{
id: 1,
title: this.$t('news.item1.title'),
excerpt: this.$t('news.item1.excerpt'),
date: new Date()
},
{
id: 2,
title: this.$t('news.item2.title'),
excerpt: this.$t('news.item2.excerpt'),
date: new Date(Date.now() - 86400000)
}
]
};
}
};
</script>6.2 案例:本地化表单验证
<template>
<form @submit.prevent="submitForm" class="contact-form">
<h2>{{ $t('contact.form.title') }}</h2>
<div class="form-group">
<label :for="'name-' + $i18n.locale">{{ $t('contact.form.name') }}</label>
<input
:id="'name-' + $i18n.locale"
v-model="form.name"
:placeholder="$t('contact.form.namePlaceholder')"
@blur="validateField('name')"
/>
<span v-if="errors.name" class="error">{{ errors.name }}</span>
</div>
<div class="form-group">
<label :for="'email-' + $i18n.locale">{{ $t('contact.form.email') }}</label>
<input
:id="'email-' + $i18n.locale"
type="email"
v-model="form.email"
:placeholder="$t('contact.form.emailPlaceholder')"
@blur="validateField('email')"
/>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<div class="form-group">
<label :for="'message-' + $i18n.locale">{{ $t('contact.form.message') }}</label>
<textarea
:id="'message-' + $i18n.locale"
v-model="form.message"
:placeholder="$t('contact.form.messagePlaceholder')"
rows="4"
@blur="validateField('message')"
></textarea>
<span v-if="errors.message" class="error">{{ errors.message }}</span>
</div>
<button type="submit" :disabled="isSubmitting">{{ $t('contact.form.submit') }}</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {
name: '',
email: '',
message: ''
},
errors: {},
isSubmitting: false
};
},
methods: {
validateField(field) {
this.errors[field] = '';
if (!this.form[field]) {
this.errors[field] = this.$t(`contact.form.errors.${field}.required`);
return;
}
if (field === 'email') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.form[email])) {
this.errors[field] = this.$t('contact.form.errors.email.invalid');
}
}
if (field === 'message' && this.form.message.length < 10) {
this.errors[field] = this.$t('contact.form.errors.message.minLength');
}
},
submitForm() {
// 验证所有字段
Object.keys(this.form).forEach(field => {
this.validateField(field);
});
// 检查是否有错误
if (Object.values(this.errors).some(error => error)) {
return;
}
this.isSubmitting = true;
// 模拟表单提交
setTimeout(() => {
alert(this.$t('contact.form.success'));
this.form = {
name: '',
email: '',
message: ''
};
this.isSubmitting = false;
}, 1000);
}
}
};
</script>7. 总结回顾
通过本章节的学习,你已经了解了:
- 国际化和本地化的基本概念及重要性
- 如何使用@nuxtjs/i18n模块实现多语言支持
- 如何实现日期和时间的本地化格式化
- 如何实现数字和货币的本地化格式化
- 国际化最佳实践,包括文本提取、复数形式处理、性别形式处理等
国际化和本地化是构建全球应用的重要组成部分。通过本章节介绍的技术和方法,你可以创建能够适应不同语言和地区需求的Nuxt.js应用,为全球用户提供更好的用户体验。
8. 扩展阅读
9. 课后练习
- 创建一个支持中英文切换的Nuxt.js项目
- 实现日期和时间的本地化格式化
- 实现数字和货币的本地化格式化
- 处理复数形式和性别形式
- 为项目添加语言切换功能并测试