**Интернационализация (i18n)** — это подготовка приложения к работе на разных языках. Вместо жёсткого текста в шаблонах вы используете ключи, которые переводятся в нужную строку в зависимости от выбранной локали. **vue-i18n** — официальная библиотека i18n для Vue.
npm install vue-i18n// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
const messages = {
ru: {
hello: 'Привет, {name}!',
nav: {
home: 'Главная',
about: 'О нас',
},
items: 'нет товаров | {n} товар | {n} товара | {n} товаров',
},
en: {
hello: 'Hello, {name}!',
nav: {
home: 'Home',
about: 'About',
},
items: 'no items | {n} item | {n} items',
},
}
export const i18n = createI18n({
legacy: false, // используем Composition API
locale: 'ru', // язык по умолчанию
fallbackLocale: 'en', // запасной язык
messages,
})// main.ts
import { createApp } from 'vue'
import { i18n } from './i18n'
import App from './App.vue'
createApp(App).use(i18n).mount('#app')import { useI18n } from 'vue-i18n'
const { t, locale, availableLocales } = useI18n()
// Простой перевод
t('nav.home') // 'Главная'
// С интерполяцией
t('hello', { name: 'Alice' }) // 'Привет, Alice!'
// Смена языка
locale.value = 'en' // реактивно — все t() немедленно обновятся<template>
<nav>
<a href="/">{{ $t('nav.home') }}</a>
<a href="/about">{{ $t('nav.about') }}</a>
</nav>
<p>{{ $t('hello', { name: user.name }) }}</p>
<select v-model="$i18n.locale">
<option value="ru">Русский</option>
<option value="en">English</option>
</select>
</template>Русский язык имеет три формы числа. vue-i18n поддерживает это через |:
const messages = {
ru: {
// 0 | 1 | 2-4 | 5+
apples: 'нет яблок | {n} яблоко | {n} яблока | {n} яблок',
},
en: {
apples: 'no apples | {n} apple | {n} apples',
},
}
// Использование:
t('apples', 0) // нет яблок
t('apples', 1) // 1 яблоко
t('apples', 3) // 3 яблока
t('apples', 5) // 5 яблок
t('apples', 21) // 21 яблоко (автоматически!)const { d, n } = useI18n()
// Дата
d(new Date(), 'short') // '04.03.2026' (для ru)
d(new Date(), 'long') // '4 марта 2026 г.'
// Число
n(1234567.89, 'currency') // '1 234 567,89 ₽' (для ru)
n(0.75, 'percent') // '75%'// Конфигурация форматов в createI18n:
datetimeFormats: {
ru: {
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
long: { year: 'numeric', month: 'long', day: 'numeric' },
},
},
numberFormats: {
ru: {
currency: { style: 'currency', currency: 'RUB' },
percent: { style: 'percent' },
},
},// Загружаем перевод только когда он нужен
async function setLocale(locale) {
if (!i18n.global.availableLocales.includes(locale)) {
const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessage(locale, messages.default)
}
i18n.global.locale.value = locale
}Реализация мини-библиотеки i18n с поддержкой вложенных ключей, интерполяции и плурализации — как работает vue-i18n под капотом
// ============================================
// Мини-реализация i18n (как работает vue-i18n)
// ============================================
class I18n {
constructor(options) {
this.messages = options.messages || {}
this.locale = options.locale || 'en'
this.fallbackLocale = options.fallbackLocale || 'en'
}
// Получить сообщение по вложенному ключу: 'nav.home' -> messages.nav.home
_getMessage(key, locale) {
const parts = key.split('.')
let current = this.messages[locale]
if (!current) return null
for (const part of parts) {
if (current === null || current === undefined) return null
current = current[part]
}
return current || null
}
// Интерполяция: 'Привет, {name}!' + {name: 'Alice'} -> 'Привет, Alice!'
_interpolate(str, params) {
if (!params) return str
return str.replace(/\{(\w+)\}/g, (_, key) => {
return key in params ? params[key] : `{${key}}`
})
}
// Плурализация для русского языка
_pluralizeRu(forms, n) {
const parts = forms.split('|').map(s => s.trim())
if (n === 0 && parts.length >= 1) return parts[0]
const abs = Math.abs(n)
const mod10 = abs % 10
const mod100 = abs % 100
let idx
if (mod100 >= 11 && mod100 <= 19) {
idx = parts.length >= 4 ? 3 : parts.length - 1 // много
} else if (mod10 === 1) {
idx = 1 // один
} else if (mod10 >= 2 && mod10 <= 4) {
idx = parts.length >= 3 ? 2 : parts.length - 1 // несколько
} else {
idx = parts.length >= 4 ? 3 : parts.length - 1 // много
}
return parts[Math.min(idx, parts.length - 1)]
}
// Плурализация для английского
_pluralizeEn(forms, n) {
const parts = forms.split('|').map(s => s.trim())
if (n === 0 && parts.length >= 1) return parts[0]
return n === 1 ? (parts[1] || parts[0]) : (parts[2] || parts[1] || parts[0])
}
// Основной метод перевода
t(key, paramsOrCount, params) {
// Определяем плурализацию
const isPluralCall = typeof paramsOrCount === 'number'
const count = isPluralCall ? paramsOrCount : undefined
const interpolateParams = isPluralCall ? (params || {}) : paramsOrCount
// Ищем сообщение: сначала в текущей локали, потом в fallback
const msg =
this._getMessage(key, this.locale) ||
this._getMessage(key, this.fallbackLocale)
if (msg === null || msg === undefined) {
console.warn(`[i18n] Ключ не найден: "${key}"`)
return key
}
// Применяем плурализацию если передано число
let result = msg
if (isPluralCall && typeof msg === 'string' && msg.includes('|')) {
const pluralFn = this.locale === 'ru' ? this._pluralizeRu : this._pluralizeEn
result = pluralFn.call(this, msg, count)
// Подставляем {n} в параметры
const finalParams = { n: count, ...(interpolateParams || {}) }
return this._interpolate(result, finalParams)
}
return this._interpolate(result, interpolateParams)
}
// Смена языка
setLocale(locale) {
if (!this.messages[locale]) {
console.warn(`[i18n] Нет сообщений для локали: "${locale}"`)
return
}
this.locale = locale
console.log(` Язык изменён на: ${locale}`)
}
}
// ============================================
// Демонстрация
// ============================================
const i18n = new I18n({
locale: 'ru',
fallbackLocale: 'en',
messages: {
ru: {
hello: 'Привет, {name}!',
nav: {
home: 'Главная',
about: 'О нас',
settings: 'Настройки',
},
apples: 'нет яблок | {n} яблоко | {n} яблока | {n} яблок',
items: 'нет элементов | {n} элемент | {n} элемента | {n} элементов',
},
en: {
hello: 'Hello, {name}!',
nav: {
home: 'Home',
about: 'About',
settings: 'Settings',
},
apples: 'no apples | {n} apple | {n} apples',
onlyInEn: 'This key only exists in English',
},
},
})
console.log('=== Русская локаль ===')
console.log(i18n.t('hello', { name: 'Alice' }))
console.log(i18n.t('nav.home'))
console.log(i18n.t('nav.about'))
console.log('\n=== Плурализация (русский) ===')
for (const n of [0, 1, 2, 3, 5, 11, 21, 101]) {
console.log(` apples(${n}): ${i18n.t('apples', n)}`)
}
console.log('\n=== Смена на английский ===')
i18n.setLocale('en')
console.log(i18n.t('hello', { name: 'Bob' }))
console.log(i18n.t('nav.home'))
console.log('\n=== Плурализация (английский) ===')
for (const n of [0, 1, 2, 5]) {
console.log(` apples(${n}): ${i18n.t('apples', n)}`)
}
console.log('\n=== Fallback (ключ только в en) ===')
i18n.setLocale('ru')
console.log(i18n.t('onlyInEn')) // fallback к 'en'
console.log('\n=== Несуществующий ключ ===')
console.log(i18n.t('nav.unknown')) // вернёт ключ**Интернационализация (i18n)** — это подготовка приложения к работе на разных языках. Вместо жёсткого текста в шаблонах вы используете ключи, которые переводятся в нужную строку в зависимости от выбранной локали. **vue-i18n** — официальная библиотека i18n для Vue.
npm install vue-i18n// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
const messages = {
ru: {
hello: 'Привет, {name}!',
nav: {
home: 'Главная',
about: 'О нас',
},
items: 'нет товаров | {n} товар | {n} товара | {n} товаров',
},
en: {
hello: 'Hello, {name}!',
nav: {
home: 'Home',
about: 'About',
},
items: 'no items | {n} item | {n} items',
},
}
export const i18n = createI18n({
legacy: false, // используем Composition API
locale: 'ru', // язык по умолчанию
fallbackLocale: 'en', // запасной язык
messages,
})// main.ts
import { createApp } from 'vue'
import { i18n } from './i18n'
import App from './App.vue'
createApp(App).use(i18n).mount('#app')import { useI18n } from 'vue-i18n'
const { t, locale, availableLocales } = useI18n()
// Простой перевод
t('nav.home') // 'Главная'
// С интерполяцией
t('hello', { name: 'Alice' }) // 'Привет, Alice!'
// Смена языка
locale.value = 'en' // реактивно — все t() немедленно обновятся<template>
<nav>
<a href="/">{{ $t('nav.home') }}</a>
<a href="/about">{{ $t('nav.about') }}</a>
</nav>
<p>{{ $t('hello', { name: user.name }) }}</p>
<select v-model="$i18n.locale">
<option value="ru">Русский</option>
<option value="en">English</option>
</select>
</template>Русский язык имеет три формы числа. vue-i18n поддерживает это через |:
const messages = {
ru: {
// 0 | 1 | 2-4 | 5+
apples: 'нет яблок | {n} яблоко | {n} яблока | {n} яблок',
},
en: {
apples: 'no apples | {n} apple | {n} apples',
},
}
// Использование:
t('apples', 0) // нет яблок
t('apples', 1) // 1 яблоко
t('apples', 3) // 3 яблока
t('apples', 5) // 5 яблок
t('apples', 21) // 21 яблоко (автоматически!)const { d, n } = useI18n()
// Дата
d(new Date(), 'short') // '04.03.2026' (для ru)
d(new Date(), 'long') // '4 марта 2026 г.'
// Число
n(1234567.89, 'currency') // '1 234 567,89 ₽' (для ru)
n(0.75, 'percent') // '75%'// Конфигурация форматов в createI18n:
datetimeFormats: {
ru: {
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
long: { year: 'numeric', month: 'long', day: 'numeric' },
},
},
numberFormats: {
ru: {
currency: { style: 'currency', currency: 'RUB' },
percent: { style: 'percent' },
},
},// Загружаем перевод только когда он нужен
async function setLocale(locale) {
if (!i18n.global.availableLocales.includes(locale)) {
const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessage(locale, messages.default)
}
i18n.global.locale.value = locale
}Реализация мини-библиотеки i18n с поддержкой вложенных ключей, интерполяции и плурализации — как работает vue-i18n под капотом
// ============================================
// Мини-реализация i18n (как работает vue-i18n)
// ============================================
class I18n {
constructor(options) {
this.messages = options.messages || {}
this.locale = options.locale || 'en'
this.fallbackLocale = options.fallbackLocale || 'en'
}
// Получить сообщение по вложенному ключу: 'nav.home' -> messages.nav.home
_getMessage(key, locale) {
const parts = key.split('.')
let current = this.messages[locale]
if (!current) return null
for (const part of parts) {
if (current === null || current === undefined) return null
current = current[part]
}
return current || null
}
// Интерполяция: 'Привет, {name}!' + {name: 'Alice'} -> 'Привет, Alice!'
_interpolate(str, params) {
if (!params) return str
return str.replace(/\{(\w+)\}/g, (_, key) => {
return key in params ? params[key] : `{${key}}`
})
}
// Плурализация для русского языка
_pluralizeRu(forms, n) {
const parts = forms.split('|').map(s => s.trim())
if (n === 0 && parts.length >= 1) return parts[0]
const abs = Math.abs(n)
const mod10 = abs % 10
const mod100 = abs % 100
let idx
if (mod100 >= 11 && mod100 <= 19) {
idx = parts.length >= 4 ? 3 : parts.length - 1 // много
} else if (mod10 === 1) {
idx = 1 // один
} else if (mod10 >= 2 && mod10 <= 4) {
idx = parts.length >= 3 ? 2 : parts.length - 1 // несколько
} else {
idx = parts.length >= 4 ? 3 : parts.length - 1 // много
}
return parts[Math.min(idx, parts.length - 1)]
}
// Плурализация для английского
_pluralizeEn(forms, n) {
const parts = forms.split('|').map(s => s.trim())
if (n === 0 && parts.length >= 1) return parts[0]
return n === 1 ? (parts[1] || parts[0]) : (parts[2] || parts[1] || parts[0])
}
// Основной метод перевода
t(key, paramsOrCount, params) {
// Определяем плурализацию
const isPluralCall = typeof paramsOrCount === 'number'
const count = isPluralCall ? paramsOrCount : undefined
const interpolateParams = isPluralCall ? (params || {}) : paramsOrCount
// Ищем сообщение: сначала в текущей локали, потом в fallback
const msg =
this._getMessage(key, this.locale) ||
this._getMessage(key, this.fallbackLocale)
if (msg === null || msg === undefined) {
console.warn(`[i18n] Ключ не найден: "${key}"`)
return key
}
// Применяем плурализацию если передано число
let result = msg
if (isPluralCall && typeof msg === 'string' && msg.includes('|')) {
const pluralFn = this.locale === 'ru' ? this._pluralizeRu : this._pluralizeEn
result = pluralFn.call(this, msg, count)
// Подставляем {n} в параметры
const finalParams = { n: count, ...(interpolateParams || {}) }
return this._interpolate(result, finalParams)
}
return this._interpolate(result, interpolateParams)
}
// Смена языка
setLocale(locale) {
if (!this.messages[locale]) {
console.warn(`[i18n] Нет сообщений для локали: "${locale}"`)
return
}
this.locale = locale
console.log(` Язык изменён на: ${locale}`)
}
}
// ============================================
// Демонстрация
// ============================================
const i18n = new I18n({
locale: 'ru',
fallbackLocale: 'en',
messages: {
ru: {
hello: 'Привет, {name}!',
nav: {
home: 'Главная',
about: 'О нас',
settings: 'Настройки',
},
apples: 'нет яблок | {n} яблоко | {n} яблока | {n} яблок',
items: 'нет элементов | {n} элемент | {n} элемента | {n} элементов',
},
en: {
hello: 'Hello, {name}!',
nav: {
home: 'Home',
about: 'About',
settings: 'Settings',
},
apples: 'no apples | {n} apple | {n} apples',
onlyInEn: 'This key only exists in English',
},
},
})
console.log('=== Русская локаль ===')
console.log(i18n.t('hello', { name: 'Alice' }))
console.log(i18n.t('nav.home'))
console.log(i18n.t('nav.about'))
console.log('\n=== Плурализация (русский) ===')
for (const n of [0, 1, 2, 3, 5, 11, 21, 101]) {
console.log(` apples(${n}): ${i18n.t('apples', n)}`)
}
console.log('\n=== Смена на английский ===')
i18n.setLocale('en')
console.log(i18n.t('hello', { name: 'Bob' }))
console.log(i18n.t('nav.home'))
console.log('\n=== Плурализация (английский) ===')
for (const n of [0, 1, 2, 5]) {
console.log(` apples(${n}): ${i18n.t('apples', n)}`)
}
console.log('\n=== Fallback (ключ только в en) ===')
i18n.setLocale('ru')
console.log(i18n.t('onlyInEn')) // fallback к 'en'
console.log('\n=== Несуществующий ключ ===')
console.log(i18n.t('nav.unknown')) // вернёт ключРеализуй функцию `createTranslator(messages, locale)`, которая возвращает функцию `t(key, params)`. Функция `t` должна: 1) находить строку по вложенному ключу через точку (например "nav.home"); 2) если ключ не найден — возвращать сам ключ; 3) подставлять параметры в шаблон вида `{key}` из объекта params.
Для вложенного ключа: const parts = key.split("."); let node = messages[locale]; for (const p of parts) { if (!node) return key; node = node[p]; }. После цикла если node это строка — применяй интерполяцию. Для замены шаблонов: node.replace(/{(\w+)}/g, (_, k) => params?.[k] ?? "{" + k + "}").
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке