NPM библиотека для интернационализации и локализации i18n
очень популярна, однако за последние годы она сильно располнела. В ней много возможностей для локализации дат, чисел, установки нужных склонений, поддержки RTL языков, загрузки локалей с сервера и кучи еще чего. На сайте i18next она называется уже даже "интернационализационным фреймворком".
в тему
Новый авиалайнер. Входит стюардесса в пассажирский салон:
— Вы находитесь на нашем новом авиалайнере. В носовой части самолета у нас находится кинозал, в хвостовой — зал игровых автоматов, на нижней палубе — бассейн, на верхней — сауна. А теперь, уважаемые господа, пристегните ремни, и мы со всей этой хренотенью попробуем взлететь.
В то же время часто для локализации сайта в большинстве случаев требуются очень простые вещи, занимающие всего пару процентов от всего функционала тяжеловеса i18n.
В частности лично мне обычно нужны:
Нахождение перевода по составному ключу -
t("finance.transactions.deposit")
Перевод с параметром -
t("hello-message", "Вася")
Массивы для списков или параграфов текста
На примере Vue 3 я покажу как можно избавиться от i18next
без потери функционала в данном случае, не только облегчая js бандл, но и сокращая код, при этом с сохранением реактивности (смена языка сайта налету).
Простоту и элегантность нижеописанного рефакторинга обеспечит Vue 3 Composition API, но в целом данная методика должна подойти для любого реактивного фреймворка.
Свой i18n
Вот чистая реализация вышеуказанного функционала в 30 строчек супротив полутора мегабайт без каких-либо зависимостей - https://stackblitz.com/edit/i18n-detox?file=src%2FApp.vue
Проект Vue 3 с i18next
Стандартно в проекте с Composition API подключение и использование i18next происходят примерно следующим образом:
До
// main.js
import { i18n, useI18n } from "@/app/composables/i18n";
const { initI18n } = useI18n();
initI18n();
app.use(i18n);
// useI18n.js
import { ref } from "vue";
import { api } from "@/services";
import { createI18n } from "vue-i18n";
const locales = [
{
code: "en",
name: "English",
flag: "england",
},
{
code: "ru",
name: "Pусский",
flag: "russian",
},
];
const locale = ref();
export const i18n = createI18n({
I18nScope: "global",
globalInjection: true,
legacy: false,
allowComposition: true,
fallbackLocale: import.meta.env.VITE_I18N_FALLBACK_LOCALE || "en",
formatFallbackMessages: true,
});
export function useI18n() {
function initI18n() {
const lang =
localStorage.getItem(import.meta.env.VITE_APP_NAME + "_lang") ??
(import.meta.env.VITE_DEFAULT_LOCALE || "en");
loadLanguage(lang);
}
async function loadLanguage(lang) {
if (i18n.global.locale !== lang) {
locale.value = locales.find((l) => l.code === lang);
const data = await api.utils.downloadLanguage(lang);
i18n.global.setLocaleMessage(lang, data[lang]);
i18n.global.locale.value = lang;
localStorage.setItem(import.meta.env.VITE_APP_NAME + "_lang", lang);
}
}
return {
i18n,
locale,
locales,
initI18n,
loadLanguage,
};
};
// Использование в компонентах Composition API
import { useI18n } from "vue-i18n";
const { t } = useI18n();
t("finance.transactions.deposit"),
// Использование в js файлах
import { i18n } from "@/app/composables/i18n";
i18n.global.t("finance.transactions.deposit")
Всё, что нужно для избавления от i18next
, это задать явно объект messages
для хранения всех локалей, и добавить в useI18n()
реактивную функцию t()
, которая как раз и будет обрабатывать составной ключ, параметр и массив.
После этого можно закомментировать всё использование библиотеки vue-i18n
После
// main.js
import { useI18n } from "@/app/composables/i18n";
const { initI18n } = useI18n();
initI18n();
// app.use(i18n);
// useI18n.js
import { ref } from "vue";
import { api } from "@/services";
// import { createI18n } from "vue-i18n";
const locales = [
{
code: "en",
name: "English",
flag: "england",
},
{
code: "ru",
name: "Pусский",
flag: "russian",
},
];
// export const i18n = createI18n({
// I18nScope: "global",
// globalInjection: true,
// legacy: false,
// allowComposition: true,
// fallbackLocale: import.meta.env.VITE_I18N_FALLBACK_LOCALE || "en",
// formatFallbackMessages: true,
// // messages: { en: messages }
// });
const locale = ref();
let messages;
// Делаем доступ для использования в js модулях
export const t = useI18n().t;
export function useI18n() {
function initI18n() {
messages = [];
const lang =
localStorage.getItem(import.meta.env.VITE_APP_NAME + "_lang") ??
(import.meta.env.VITE_APP_DEFAULT_LOCALE || "en");
loadLanguage(lang);
}
async function loadLanguage(lang) {
if (locale.value !== lang) {
const localeMessages = await api.utils.downloadLanguage(lang);
messages[lang] = localeMessages[lang];
locale.value = locales.find((l) => l.code === lang);
// i18n.global.setLocaleMessage(lang, localeMessages[lang]);
// i18n.global.locale.value = lang;
localStorage.setItem(import.meta.env.VITE_APP_NAME + "_lang", lang);
}
}
function t(msg, param = null) {
let val = msg.split(".").reduce((val, part) => val[part], messages[locale.value.code]);
if (param) {
val = val.replace("{0}", param);
}
return val;
}
return {
t,
// i18n,
locale,
locales,
initI18n,
loadLanguage,
};
}
// Использование в компонентах Composition API
import { useI18n } from "@/app/composables/i18n";
const { t } = useI18n();
t("finance.transactions.deposit"),
// ИЛИ
import { t } from "@/app/composables/i18n";
t("finance.transactions.deposit")
// Использование в js файлах
import { t } from "@/app/composables/i18n";
t("finance.transactions.deposit")
В данном примере перевод для конкретной локали грузится с сервера по запросу, но объект messages
можно иметь на клиенте сразу.
Alias export const t = useI18n().t;
позволяет использовать один синтаксис и в компонентах, и в js модулях.
I18next расширения
У I18next
есть расширение для `Vue DevTools` (довольно бесполезное), и есть расширение I18next Ally для MS VS Code (весьма полезное). Так вот I18next Ally
работает с новой реализацией если в package.json будет прописан пакет vue-i18n
в dependencies (в коде подключать его не надо). Рекомендую. Оба расширения, впрочем, неплохо едят ресурсы, так что пользоваться ими лучше по надобности.
Итого
Мы закомментировали больше строк, чем добавили, и JavaScript бандл после билда уменьшился на 50 Кб. Функционал остался. Реактивный.
До (vue 3, vue-router, toaster, vue-i18n)
После (vue 3, vue-router, toaster)
Спасибо, I18next, и до свидания.
Другая моя статья по теме - "Работа с i18n — автоматизация Google Translate и другие полезные советы".
mayorovp
Что-то вы вообще всё порезали. Вы же знаете русский язык, и знаете про склонение и про числительные...
gmtd Автор
Спасибо, подправил
mayorovp
Подправили что? Я говорил про функцию
__n
из пакета i18n, она у вас выкинута.gmtd Автор
Я же в начале статьи написал, какой функционал будет дублироваться
Нахождение перевода по составному ключу -
t("finance.transactions.deposit")
Перевод с параметром -
t("hello-message", "Вася")
Массивы для списков или параграфов текста
Задачи отзеркалить i18n не стояло
mayorovp
Ну, собственно, вот именно к этим трём пунктам у меня и претензия.
Их должно быть четыре, потому что
__n
нужна.gmtd Автор
Если вам нужен функционал i18n, то вам нужна i18n, и решение данной статьи вам не подходит
По-моему, это очевидно
nin-jin
Не заставляйте пользователя вчитываться в тексты. Такой текст:
воспринимается быстрее, чем "очеловеченный":
Курсивом отмечены вариативные тексты.
SergeyMax
Вообще этот текст гораздо хуже воспринимается. Например нужно выискивать глазами сумму оплаты.