i18nano: локализация React приложений

от автора

Разрабатывая продукт, в какой-то момент задумываешься о поддержке языков. И, казалось бы, что могло пойти не так?

Иллюзия выбора

Давайте посмотрим, какие инструменты и библиотеки существуют, и что у них под капотом:

react-localization

Не самое популярное решение и на то есть причины, использование выглядит примерно так:

import LocalizedStrings from 'react-localization';  // Инициализация: const strings = new LocalizedStrings({   en: {     how: "How do you want your egg today?",     choice: "How to choose the egg"   },   it: {     how: "Come vuoi il tuo uovo oggi?",     choice: "Come scegliere l'uovo"   } });  // В компоненте: <div>{strings.how}</div>  // Переключение: strings.setLanguage('it');

Плюсы:

  • Простой API

  • Маленький вес (6.7kB)

Минусы:

  • При переключении языков автоматически не обновляются компоненты, нужно писать свою обертку

  • Нет поддержки асинхронной подгрузки переводов, вручную добавлять можно, но это, опять же, дополнительная сложность

  • Нет полноценной поддержки фолбеков, если в текущей локали не найден перевод

Под капотом используется localized-strings.

react-i18next

Пожалуй, самое популярное и распространенное решения для локализации. Использование выглядит так:

import i18n from "i18next"; import { useTranslation, initReactI18next } from "react-i18next";  // Инициализация: i18n.use(initReactI18next).init({   resources: {     en: {       translation: {         how: "How do you want your egg today?",         choice: "How to choose the egg"       }     },     it: {       translation: {         how: "Come vuoi il tuo uovo oggi?",         choice: "Come scegliere l'uovo"       }     }   },   lng: "en",    fallbackLng: "en" });  // В компоненте: const { t } = useTranslation(); <div>{t('how')}</div> // Или используя готовый компонент: const { t } = useTranslation(); <Trans t={t}>how</Trans>  // Переключение: const { i18n } = useTranslation(); i18n.changeLanguage('it');

Плюсы:

  • Большое комьюнити

  • Множество плагинов, которые позволяют, например, использовать асинхронную подгрузку других локалей

  • Автоматическое обновление компонентов при смене языка

  • Поддержка concurrent features (React 18), для упрощения работы с асинхронными манипуляциями

Минусы:

  • Весит 20.1 kB, но не дайте себя обмануть! Для его работы требуется i18next, который добавляет 54.5 kB, и того мы имеем 74.6 kB

  • Каждый плагин добавляет дополнительный вес вашему бандлу

Из примера использования понятно, что использует i18next.

@lingui/react

import { i18n } from '@lingui/core'  // Инициализация: i18n.load('en', {   how: "How do you want your egg today?",   choice: "How to choose the egg" }); i18n.load('it', {   how: "Come vuoi il tuo uovo oggi?",   choice: "Come scegliere l'uovo" }); i18n.activate('en');  // В компоненте: const context = useLingui(); <div>{context.i18n._('how')}</div> // Или используя готовый компонент: <Trans id="how" />  // Переключение: const context = useLingui(); context.i18.activate('it');

Плюсы:

  • Простое API

  • Маленький вес (6.6 kB)

  • Поддерживает асинхронную загрузку, правда, не обойдется без дополнительного кода

  • Есть плагины, для более удобного использования, например, для автоматического определения языка

Минусы:

  • Все удобные инструменты находятся в @lingui/macro, которые не понятно сколько на самом деле весят, так как там используются макросы babel

react-intl

Достаточно распространенное решение, так как использует нативные методы форматирования и предоставляет полифилы для них.

import { IntlProvider } from 'react-intl';  const messages = {   en: {     how: "How do you want your egg today?",     choice: "How to choose the egg"   },   it: {    how: "Come vuoi il tuo uovo oggi?",     choice: "Come scegliere l'uovo"   } };  // В рутовом компоненте: const [lang, setLang] = useState('en'); <IntlProvider messages={messages[lang]} locale={lang}> ... </IntlProvider>  // В компоненте: const intl = useIntl(); <div>{intl.formatMessage({ id: 'how' })}</div> // Или используя готовый компонент: <FormattedMessage id="how" />  // Переключение: // Нужно прокинуть в нужный компонент `setLang` setLang('it');

Плюсы:

  • Поддерживает AST для переводов (подробнее в документации), с помощью которого можно уменьшить вес конечного бандла

Минусы:

  • Для переключения языков нужно реализовывать свою обертку

  • Большой вес (56.7 kB)

  • Имеет собственную достаточно специфичную реализацию форматирования

Под капотом использует formatjs.

Что имеем

Без рисков и излишнего бойлерплейта выбор падает на react-i18next, но не поймите меня не правильно, это не чистое решение в экосистеме React, да и вес оставляет желать лучшего.

i18nano

Давайте сформируем функциональные требования к библиотеке для локализации:

  • Поддержка асинхронной загрузки переводов, без бойлерплейта и специфичных решений, чтобы не грузить на клиент все переводы сразу и не раздувать тем самым бандл

  • Соответственно, поддержка лоадера или скелетона, пока загружается перевод

  • Удобный способ переключения между языками и автоматическое переключение на текущую локаль в компонентах

  • Поддержка фолбеков, если в текущем переводе не нашлось перевода

  • Поддержка шаблонизации, например, чтобы вставлять имя пользователя в строку с переводом

Из хотелок можно выделить поддержку вложенных переводов для человекопонятой структуризации, то бишь, например, main.header.title и предзагрузку переводов.

Как выглядит?

import { TranslationProvider } from 'i18nano';  // Инициализация const translations = {   // Используя динамический импорт   'en': () => import('translations/en.json'),   // или с помощью собственной реализации загрузки   'it': () => load('it') };  // В рутовом компоненте: <TranslationProvider translations={translations} language="en"> ... </TranslationProvider>  // В компоненте: const t = useTranslation(); <div>{t('how')}</div> // Или используя готовый компонент: <Translation path="how" />  // Переключение: const translation = useTranslationChange(); translation.change('it');

Для удобства

Поддерживается передача любого компонента в качестве лоадера или скелетона:

<Translation path="how"> <Loader /> </Translation>

Передача всех переданных поддерживаемых языков:

const translation = useTranslationChange();  <select value={translation.lang} onChange={(event) => {   translation.change(event.target.value); }}> {translation.all.map((lang) => (   <option key={lang} value={lang}>   {lang}   </option> ))} </select>

Предзагрузка перевода отличного от текущего:

const translation = useTranslationChange();  <button onHover={() => translation.preload('it')}> Sono italiano </button>

Использование concurrent features из React 18, которые позволяют показывать предыдущий перевод, вместо лоадера, при смене языка:

<TranslationProvider unstable_transition={true}> ... </TranslationProvider>

Поддержка шаблонизации:

// В файле перевода: hello: 'Hello {{user.name}}!'  // Использование: <Translation path="hello" values={{ user: { name: 'Ivan' } }}> <Skeleton /> </Translation> // -> Hello Ivan!

«Но есть один нюанс»

Кто-то может возразить, а как же форматирование дат? Как реализовать пруализацию?

Все проще, чем кажется: во-первых, можно реализовать через шаблонизацию, во-вторых, у всех встроенных типов, будь то Date или Number, есть методы toLocaleString и подобные, в которые можно передать свой язык, а не только установленный по-умолчанию в браузере.

Через шаблонизацию:

date: '{{day}}/{{month}}/{{year}}'  <Translation path="date" values={{ day: '1', month: '4', year: '2021' }} /> // -> 1/4/2021

Через встроенный метод:

date: '{{date}}'  const date = new Date('2022-04-01').toLocaleDateString('it'); <Translation path="date" values={{ date }} /> // -> 1/4/2021

По поводу поддержки браузерами можно посмотреть caniuse — там все очень хорошо.

Что касается поддержки множественных чисел, то тут тоже все просто:

count: ['zero', 'one', 'two']  <Translation path={`count[${2}]`} /> // -> two

Ссылки

  1. react-localization

  2. react-i18next

  3. @lingui/react

  4. react-intl

  5. i18nano

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какую библиотеку вы используете в своих проектах?
33.33% react-localization 1
33.33% react-i18next 1
0% @lingui/react 0
0% react-intl 0
33.33% Другую библиотеку 1
Проголосовали 3 пользователя. Воздержались 3 пользователя.

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


Комментарии

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

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