Перейти к содержимому

20. Интернационализация

@nuxtjs/i18n — официальный модуль Nuxt для интернационализации приложений. Он обеспечивает маршрутизацию на нескольких языках, ленивую загрузку переводов и полную TypeScript-поддержку.


Окно терминала
npx nuxi module add i18n
nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
// Доступные языки
locales: [
{ code: 'ru', language: 'ru-RU', name: 'Русский', file: 'ru.json' },
{ code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
{ code: 'de', language: 'de-DE', name: 'Deutsch', file: 'de.json' },
],
// Язык по умолчанию
defaultLocale: 'ru',
// Стратегия URL
strategy: 'prefix_except_default',
// Директория с переводами
langDir: 'locales/',
// Ленивая загрузка
lazy: true,
// SEO мета-теги
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
redirectOn: 'root',
alwaysRedirect: false
}
}
})

locales/
├── ru.json # Русский
├── en.json # Английский
└── de.json # Немецкий
locales/ru.json
{
"nav": {
"home": "Главная",
"about": "О нас",
"blog": "Блог",
"contact": "Контакты"
},
"hero": {
"title": "Добро пожаловать в {appName}",
"subtitle": "Современное веб-приложение на Nuxt 3",
"cta": "Начать сейчас"
},
"auth": {
"login": "Войти",
"logout": "Выйти",
"register": "Зарегистрироваться",
"email": "Email",
"password": "Пароль"
},
"messages": {
"items": "Нет элементов | {count} элемент | {count} элемента | {count} элементов"
},
"errors": {
"notFound": "Страница не найдена",
"serverError": "Ошибка сервера"
}
}
locales/en.json
{
"nav": {
"home": "Home",
"about": "About",
"blog": "Blog",
"contact": "Contact"
},
"hero": {
"title": "Welcome to {appName}",
"subtitle": "Modern web application with Nuxt 3",
"cta": "Get Started"
},
"auth": {
"login": "Sign In",
"logout": "Sign Out",
"register": "Sign Up",
"email": "Email",
"password": "Password"
},
"messages": {
"items": "No items | {count} item | {count} items"
},
"errors": {
"notFound": "Page not found",
"serverError": "Server error"
}
}

Основной composable для работы с переводами:

<script setup lang="ts">
const { t, locale, locales, setLocale, availableLocales } = useI18n()
// t() — получение перевода
const title = t('hero.title', { appName: 'MyApp' })
const loginText = t('auth.login')
// locale — текущая локаль (ref)
console.log(locale.value) // 'ru'
// Смена языка
const changeLocale = (code: string) => setLocale(code)
// Плюрализация
const itemCount = t('messages.items', 3)
</script>
<template>
<h1>{{ t('hero.title', { appName: 'MyApp' }) }}</h1>
<p>{{ t('hero.subtitle') }}</p>
<button>{{ t('auth.login') }}</button>
</template>

Генерация локализованных путей:

<script setup lang="ts">
const localePath = useLocalePath()
// Локализованный путь
const homePath = localePath('/') // ru: '/', en: '/en/'
const aboutPath = localePath('/about') // ru: '/about', en: '/en/about'
const blogPath = localePath({ name: 'blog' })
</script>
<template>
<!-- Автоматически добавляет языковой префикс -->
<NuxtLink :to="localePath('/')">
{{ t('nav.home') }}
</NuxtLink>
<NuxtLink :to="localePath('/about')">
{{ t('nav.about') }}
</NuxtLink>
<!-- Именованный маршрут -->
<NuxtLink :to="localePath({ name: 'blog-slug', params: { slug: 'my-post' } })">
Статья
</NuxtLink>
</template>

Переключение языка для текущей страницы:

<script setup lang="ts">
const switchLocalePath = useSwitchLocalePath()
const { locale, locales } = useI18n()
</script>
<template>
<nav>
<NuxtLink
v-for="loc in locales"
:key="loc.code"
:to="switchLocalePath(loc.code)"
:class="{ active: locale === loc.code }"
>
{{ loc.name }}
</NuxtLink>
</nav>
</template>

Nuxt i18n поддерживает 4 стратегии:

/ → все языки
/ → русский (по умолчанию)
/en/ → английский
/de/ → немецкий
/ru/ → русский
/en/ → английский
/de/ → немецкий
/ → перенаправление на /ru/
/ru/ → русский
/en/ → английский
nuxt.config.ts
export default defineNuxtConfig({
i18n: {
strategy: 'prefix_except_default',
defaultLocale: 'ru'
}
})

nuxt.config.ts
export default defineNuxtConfig({
i18n: {
lazy: true,
langDir: 'locales/',
locales: [
{ code: 'ru', file: 'ru.json' },
{ code: 'en', file: 'en.json' }
]
}
})

Для разделения переводов на части:

// locales/ru/index.ts — динамическая загрузка
export default async () => {
const messages = await import('./messages.json')
const common = await import('./common.json')
return { ...messages.default, ...common.default }
}

locales/ru.json
{
"apples": "Нет яблок | {count} яблоко | {count} яблока | {count} яблок"
}
<script setup>
const { t } = useI18n()
</script>
<template>
<p>{{ t('apples', 0) }}</p> <!-- Нет яблок -->
<p>{{ t('apples', 1) }}</p> <!-- 1 яблоко -->
<p>{{ t('apples', 3) }}</p> <!-- 3 яблока -->
<p>{{ t('apples', 11) }}</p> <!-- 11 яблок -->
</template>

<script setup>
const { d, n } = useI18n()
// Форматирование даты
const date = d(new Date(), 'long') // "15 января 2024 г."
// Форматирование числа
const price = n(1234567.89, 'currency') // "1 234 567,89 ₽"
</script>
// nuxt.config.ts — настройка форматов
i18n: {
datetimeFormats: {
ru: {
short: { year: 'numeric', month: 'short', day: 'numeric' },
long: { year: 'numeric', month: 'long', day: 'numeric' }
}
},
numberFormats: {
ru: {
currency: { style: 'currency', currency: 'RUB' },
decimal: { style: 'decimal', minimumFractionDigits: 2 }
}
}
}

layouts/default.vue
<script setup>
useHead({
htmlAttrs: {
lang: useI18n().locale.value
}
})
</script>
nuxt.config.ts
i18n: {
// Автоматически добавляет hreflang теги
seo: true,
baseUrl: 'https://myapp.com'
}