Детокс для i18n

от автора

NPM библиотека для интернационализации и локализации i18n очень популярна, однако за последние годы она сильно разрослась. В ней много возможностей для локализации дат, чисел, плурализации, двунаправленных алфавитов, загрузки локалей с сервера и кучи еще чего. На сайте i18next она называется уже даже «интернационализационным фреймворком«.

в тему

Новый авиалайнер. Входит стюардесса в пассажирский салон:

— Вы находитесь на нашем новом авиалайнере. В носовой части самолета у нас находится кинозал, в хвостовой — зал игровых автоматов, на нижней палубе — бассейн, на верхней — сауна. А теперь, уважаемые господа, пристегните ремни, и мы со всей этой хренотенью попробуем взлететь.

В то же время часто для локализации сайта в большинстве случаев требуются очень простые вещи, занимающие всего пару процентов от всего функционала тяжеловеса i18n.

В частности лично мне обычно нужны:

  1. Нахождение перевода по составному ключу —t("finance.transactions.deposit")

  2. Перевод с параметром — t("hello-message", "Вася")

  3. Массивы для списков или параграфов текста

На примере 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 и другие полезные советы«


ссылка на оригинал статьи https://habr.com/ru/articles/736530/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *