Локализуем приложение на React Native

от автора

В ходе разработки одного из наших приложений нам понадобилось сделать поддержку мультиязычности. Задача была дать пользователю возможность менять язык(русский и английский) интерфейса приложения. При этом текста и контент должны переводиться «на лету».

Для этого нам нужно было решить 2 задачи:
1. Определить текущий язык приложения.
2. Использование глобального состояния для перевода «на лету».

В этой статья попробую подробно расписать как мы решили данные задачи. И так поехали.

Определяем текущий язык устройства


Для определения текущего языка можно, конечно, воспользоваться библиотекой react-native-i18n, но мы решили обойтись без него, так как для решения этой задачи можно и без сторонних библиотек. Для этого пишем следующее:

import {NativeModules, Platform} from 'react-native';  let deviceLanguage = (Platform.OS === 'ios'         ? NativeModules.SettingsManager.settings.AppleLocale ||           NativeModules.SettingsManager.settings.AppleLanguages[0] // iOS 13         : NativeModules.I18nManager.localeIdentifier 

Для ios мы извлекаем язык приложения через SettingsManager, а для android через нативный I18nManager.

Теперь когда мы получили текущий язык устройства, мы можем сохранить его в AsyncStorage и приступить ко второй задаче.

Переводим «на лету»

Для управления глобальным состоянием мы используем MobX, но вы можете использовать другое решение.

И так мы должны создать класс(мне нравится называть «модель»), который будет отвечать за глобальное состояния текущей локализации. Создаем:

// ключ локального хранилища, в котором будем записывать текущий lang const STORE = '@lang-store'; // список русскоязычных стран  const RU_LANGS = [   'ru',   'az',   'am',   'by',   'ge',   'kz',   'kg',   'md',   'tj',   'tm',   'uz',   'ua', ];  class LangModel {   @observable   lang = 'ru'; // по умолчанию    constructor() {     this.init();   }    @action   async init() {     const lang = await AsyncStorage.getItem(STORE);     if (lang) {       this.lang = lang;     } else {       let deviceLanguage: string = (Platform.OS === 'ios'         ? NativeModules.SettingsManager.settings.AppleLocale ||           NativeModules.SettingsManager.settings.AppleLanguages[0] // iOS 13         : NativeModules.I18nManager.localeIdentifier       ).toLowerCase();        if (         RU_LANGS.findIndex((rulang) => deviceLanguage.includes(rulang)) === -1       ) {         this.lang = 'en';       }       AsyncStorage.setItem(STORE, this.lang);     } }  export default new LangModel(); 

При инициализации нашей модели мы вызываем метод init, который берет локаль либо из AsyncStorage, если там есть, либо извлекаем текущий язык устройства и кладет в AsyncStorage.

Далее нам нужно написать метод(action), который будет менять язык:

  @action   changeLang(lang: string) {     this.lang = lang;     AsyncStorage.setItem(STORE, lang);   } 

Думаю, что тут все понятно.

Теперь самое интересное. Сами переводы мы решили хранить простым словарем. Для этого создадим js файл рядом с нашей LangModel, в котором мы поместим наши переводы:

// translations.js // Да, за основу мы взяли русский.  export default const translations = {   "Привет, Мир!": {en: "Hello, World!"}, } 

Далее реализуем еще один метод в LangModel, который будет принимать на вход текст и возвращать текст текущей локализации:

import translations from './translations';    ...   rk(text) {     if (!text) {       return text;     }     // если локаль ru, то переводить не нужно     if (this.lang === 'ru') {       return text;     }     // если перевода нет, кинем предупреждение      if (translations[text] === undefined || translations[text][this.lang] === undefined) {       console.warn(text);       return text;     }     return translations[text][this.lang];   } 

Все, наш LangModel готов.

Полный код LangModel

import {NativeModules, Platform} from 'react-native'; import {observable, action} from 'mobx'; import AsyncStorage from '@react-native-community/async-storage'; import translations from './translations';  const STORE = '@lang-store'; // список ru локали  const RU_LANGS = [   'ru',   'az',   'am',   'by',   'ge',   'kz',   'kg',   'md',   'tj',   'tm',   'uz',   'ua', ];  class LangModel {   @observable   lang = 'en';    constructor() {     this.init();   }    @action   async init() {     // Берем текущую локаль из AsyncStorage     const lang = await AsyncStorage.getItem(STORE);     if (lang) {       this.lang = lang;     } else {       let deviceLanguage: string = (Platform.OS === 'ios'         ? NativeModules.SettingsManager.settings.AppleLocale ||           NativeModules.SettingsManager.settings.AppleLanguages[0] // iOS 13         : NativeModules.I18nManager.localeIdentifier       ).toLowerCase();        if (         RU_LANGS.findIndex((rulang) => deviceLanguage.includes(rulang)) > -1       ) {         this.lang = 'ru';       }       AsyncStorage.setItem(STORE, this.lang);   }    @action   changeLang(lang: string) {     this.lang = lang;     AsyncStorage.setItem(STORE, lang);   }    rk(text) {     if (!text) {       return text;     }     // если локаль ru, то переводить не нужно     if (this.lang === 'ru') {       return text;     }     // если перевода нет, кинем предупреждение      if (translations[text] === undefined || translations[text][this.lang] === undefined) {       console.warn(text);       return text;     }     return translations[text][this.lang];   } } export default new LangModel(); 

Теперь мы можем использовать метод rk для локализация текста:

<Text>{LangModel.rk("Привет, Мир!")}</Text> 

Посмотреть как это работает можно в нашем приложении в AppStore и Google Play (Нажать на иконку (!) справа вверху, пролистать вниз)

Бонус

Конечно, писать каждый раз LangModel.rk это не круто. Поэтому мы можем создать собственный компонент Text и в нем уже использовать LangModel.rk

//components/text.js import React from 'react'; import {Text} from 'react-native'; import {observer} from 'mobx-react'; import {LangModel} from 'models';  export const MyText = observer((props) => (    <Text {...props}>{props.notTranslate ? props.children : LangModel.rk(props.children)}</Text> )); 

Так же нам может понадобиться, например, менять логотип приложения в зависимости от текущей локализации. Для этого можно просто менять контент в зависимости от LangModel.lang (не забудьте обернуть ваш компонент в observer(MobX))

P.S.: Возможно такой подход вам покажется не стандартным, но он нам понравился больше чем то, что предлагает react-native-i18n

На этом у меня все. Всем спасибо!)

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


Комментарии

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

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