Привіт! Мене звуть Віктор, я Lead Software Engineer та Consultant у GlobalLogic. Останні вісім років я працюю з JavaScript.
Сьогодні хочу поділитися з вами однією цікавою знахідкою.
Про що йдеться?
Нещодавно мені знадобилося додати локалізацію до мого React Mobx застосунку. І найбільша річ, якої хотілося уникнути — написання цього механізму з нуля.
Дуже не хотілося знову:
- займатися парсингом довгих
.json
файлів; - перевіряти
navigator.language
; - перетворювати
en-US
вen
; - та займатися різною схожою рутиною.
Хотілося знайти рішення, яке б дозволило зосередитись на моєму коді. Та як виявилося, я знайшов дещо більше. Те, що перевершило мої очікування. Дозвольте представити вам i18next!
i18next
Це не просто бібліотека, яка виконує переклади тексту та плюралізацію. i18next — фреймворк, що дозволяє розгорнути повноцінне рішення для локалізації застосунків.
Я одразу почав читати документацію та планувати, як саме треба інтегрувати i18next у мій застосунок. Сказати чесно — був просто вражений можливостями i18next. З іншого боку, документація здалася мені дещо заплутаною та доволі розгалуженою. Щоб зрозуміти призначення деяких налаштувань, довелося експериментувати на реальному застосунку. Тому я вирішив написати цю статтю, щоб створити невелику базу знань та зручну інструкцію для знайомства з фреймворком.
Перш ніж почати, я хотів би відзначити, що i18next — суто JavaScript-рішення. Тому ви можете використовувати його із будь-яким фреймворком, який вам подобається: React, Angular, Vue тощо.
React та i18next
i18next має гарну систему плагінів і конекторів для платформ, з якими ви працюєте. Оскільки ми будемо вести розмову про React застосунок, нам доведеться працювати з двома основними бібліотеками:
- i18next — платформа для локалізації;
- react-i18next — набір біндингів (bindings) для вашого React застосунку.
Це практично так само, як mobx та mobx-react, чи redux та react-redux.
Почнемо
Перш за все, треба додати обидві залежності до програми.
npm install i18next react-18next
Я б рекомендував створити окремий файл під назвою i18n.js, де ми будемо зберігати конфігурацію для вашого процесу локалізації. Він буде невеликим спочатку, але розростатиметься із додаванням нових параметрів, налаштувань та плагінів. Отже, на початку у нас буде щось подібне:
/
- i18n.js
- index.jsx
Вміст файлів буде виглядати так:
// i18n.js
import i18n from 'i18next';
i18n.init({
debug: true,
lng: 'en-US',
resources:
'en-US':{
'translation': {
intro: 'Hello my name is'
}
}
},
react: {
wait: false
bindI18n: 'languageChanged loaded',
bindStore: 'added removed',
nsMode: 'default'
}
});
export default i18n;
// index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider, translate } from 'react-i18next';
import i18n from './i18n.js';
@translate()
class App extends React.Component {
render() {
const { t } = this.props;
return (
<span>{t('intro')} Viktor</span>
);
}
}
ReactDOM.render((
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>
), document.getElementById('mount')
);
Після застосування ми побачимо веб-сторінку, на якій відображатиметься "Hello my name is Viktor"
Поясню, що саме відбулося.
Для того, щоб використовувати i18next у вашому проекті, його потрібно ініціалізувати. Для цього використовується метод i18n.init()
. Метод приймає досить велику кількість різних налаштувань. Але ми зосередимо увагу на більш стандартних, з яких нам зручно починати.
-
debug: true
— корисне налаштування для розробки. Завдяки йому всі відомості про стан i18next будуть виведені в консоль браузера: наприклад, коли програму було ініціалізовано, яка мова була встановлена, а також відсутність певного перекладу. Передбачте перемикання його наfalse
у production-коді. Якщо ви використовуєте Webpack, встановіть зміннуNODE_ENV
та використовуєте її якdebug: process.env.NODE_ENV === 'development'
. -
lng: 'en-US'
— використовується як початкова мова. Пізніше ми побачимо, як можна динамічно визначати мову, уникаючи жорсткого кодування на init. -
resources
— JavaScript-об'єкт із перекладом називається ресурсом. Можливо визначити їх всерединіinit
-методу, завантажити їх динамічно за допомогою методуi18n.addResourceBundle
або (мені особливо подобається ця частина) вони можуть бути завантажені асинхронно на основі обраної мови. Об'єкт має бути наступної структури{localeName: {namespaceName: {key: 'value'}}
Значенням для простору імен (namespaceName) за замовчуванням є translation, тому я використав його у прикладі.
Namespace — дійсно класна функція i18next. Вона дозволяє розміщувати переклади в окремих файлах. В результаті, замість того, щоб завантажувати весь величезний файл .json, що неминуче блокуватиме відображення контенту вашого веб-застосунку, ви отримуєте змогу завантажувати переклади по сторінках. Оскільки мета цієї статті — дати лише короткий огляд локалізації, ми будемо використовувати тільки простір імен за замовчуванням ( translation ).
-
react
— об'єкт, що описує конфігурацію для застосунку react-18next. Вважаю, що ніхто не зможе розказати про нього краще за офіційну документацію. У наведеному вище прикладі використовуються значення за замовчуванням. Якщо ви не плануєте змінювати їх, просто пропустіть налаштування react для i18next init.
У index.jsx ви також можете побачити кілька відмінностей:
-
I18nextProvider
— компонент, наданий бібліотекою react-i18next. Він приймає один атрибут i18n із вашим екземпляром i18n. Переконайтеся, що він імпортований на початку файлу. I18nextProvider — постачальник контексту (context provider). Він дозволяє споживачам контексту (context consumers) отримувати деякі додаткові параметри за допомогою React Context. -
@translate()
— моя улюблена частина. Декоратор translate також імпортується з бібліотеки react-i18next. Це компонент вищого порядку React. Він передає два додаткові параметри нашому компоненту: функціюt
та екземплярi18n
. -
t
— викликаючи функціюt('intro')
, виконується перевірка наявності перекладу для ключа intro для обраної мови (у нашому випадкуen-US
) та простір імен за замовчуванням (translation). Вона відобразить переклад, якщо ресурс був знайдений, або ключ. Примітка: якщо ви перебуваєте в режимі debug, то на консолі браузера побачите повідомлення про те, що переклад не був знайдений.
Динамічне визначення мови користувача
Звичайно, ми не хочемо жорстко кодувати локаль користувача в методі init програми i18n. А ще більше ми не хочемо писати код для її визначення. В цьому нам допоможе бібліотека i18next-browser-languagedetector
.
Отже…
npm install i18next-browser-languagedetector
Внесемо деякі зміни у файл i18n.js:
// i18n.js
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n.use(LanguageDetector)
.init({
debug: true,
resources:
'en-US':{
'translation': {
intro: 'Hello my name is'
}
}
}
});
export default i18n;
Ми видалили lng: 'en-US'
і додали виклик спеціального методу i18n — use
. Ця функція використовується для завантаження додаткових плагінів для i18n. Отже, у цьому випадку ми скористаємося плагіном LanguageDetector. Він виконує ряд перевірок і визначає для вас локаль користувача.
Ми також видалили властивість react
, оскільки використовуємо значення за замовчуванням.
Завантаження файлів перекладів із зовнішніх джерел
Ніхто не зберігає великі .json
файли з перекладами у коді проекту. Часто вони є окремими файлами (і можуть бути розміщені на CDN, а не разом з кодом). Розглянемо спосіб, що дозволить нам завантажити відповідний файл.
Знову ж таки, ми скористаємося модульною архітектурою i18next.
npm install i18next-xhr-backend;
Трохи змінимо код:
// i18n.js
import i18n from 'i18next';
import XHR from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n.use(XHR)
.use(LanguageDetector)
.init({
debug: true
});
export default i18n;
Після застосування плагіна XHR ми зможемо побачити ще три додаткових запити на вкладці Network інструменту Dev Tools.
http://app.com/locales/en-US/translation.json
http://app.com/locales/en/translation.json
http://app.com/locales/dev/translation.json
// pattern looks like
/locales/{language}/{namespace}.json
Перше питання, яке я задав собі: «Що таке мова dev?» Але після невеликого дослідження виявилося, що це ще одна нерозкрита можливість i18next.
- Save Missing — i18n може зберігати відсутні переклади на мові dev, щоб пізніше ваші перекладачі змогли працювати з цими файлами для створення правильного адаптованих англійських фраз, а також інших перекладів.
- Language (та Namespace) Fallback — у випадку, якщо ваш переклад не був знайдений у мовному файлі en-US, пошук продовжиться в en. (А потім у dev).
Краще не використовувати мову dev для production-коду. Ви можете забезпечити це, увівши додатковий параметр i18n.init({ fallbackLng: 'en' })
. Таким чином ви також мінімізуєте кількість запитів мовних файлів до двох.
Бібліотека пропонує ще більше можливостей для налаштування. Наприклад, багато проектів працюють в кількох локалях і використовують лише мовний код "en", пропускаючи регіональний код "US". Якщо це ваш випадок, використовуйте i18n.init({ load: 'languageOnly' })
, і ви побачите лише один http-запит на мовний файл.
Звичайно, ви можете змінити шлях завантаження файлів перекладу:
// i18n.js
const backendOpts = {
loadPath: `myCusomPathToLocales/{{lng}}/{{ns}}.json`
}
i18n.init({
backend: backendOpts
})
Порада. Ви можете використовувати publicPath
is webpack, щоб задати правильний шлях до файлів перекладів. Наприклад, до CDN.
Написання файлів перекладу
Ваші файли з перекладом є чистими .json
файлами. Найцікавіше полягає у тому, що i18next підтримує вкладений код за замовчуванням. Таким чином, ви можете створити ефективну структуру всередині файлів перекладу замість довгого списку одного рівня глибини.
// locales/en-US/translation.json
{
"common": {
"confirm": "Confirm",
"cancel": "Cancel"
},
"question": "Use i18next?"
}
// App.jsx
@translate()
class App extends React.Component {
render() {
const { t } = this.props;
return (
<span>{t('question')}</span>
<button>{t('common.confirm')}</button>
<button>{t('common.cancel')}</button>
);
}
}
Використання Mobx для зміни локалі
Mobx надає гарну можливість для створення реакцій (reaction), щоб виконати побічні ефекти в застосунку після змін певної властивості. Ми будемо використовувати ці реакції для зміни мови. Припустимо, що ми зберігаємо обрану користувачем локаль у властивості appStore.locale
.
Тоді реакція буде виглядати так:
// index.jsx
reaction(
() => appStore.locale,
locale => {
i18n.changeLanguage(locale);
}
);
Найкраще в цьому те, що новий переклад буде завантажено асинхронно. І всі ваші компоненти React будуть відображені вже новою мовою. Тож тепер ніяких жахливих перезавантажень всієї сторінки!
Реальний приклад
Перейдіть сюди, щоб дізнатись більше про приклад реального використання. Не забудьте відкрити консоль, щоб стежити за тим, що відбувається.
Підсумуємо
i18ext — дійсно цікаве та потужне рішення для локалізації вашої програми. Фреймворк дозволяє здійснювати складні налаштування, має розгалужену інфраструктуру з безліччю плагінів та інструментів. Стаття охоплює лише невелику основну частину з широкого діапазону можливостей i18next. Якщо ви хочете дізнатись більше, пропоную вам переглянути офіційну документацію.
Ще немає коментарів