[Разработка мобильных приложений, Локализация продуктов] Локализуем приложение на React Native

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
11-Сен-2020 04:30

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

SPL
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
На этом у меня все. Всем спасибо!)
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_lokalizatsija_produktov (Локализация продуктов), #_react_native, #_reactnative, #_mobile_development, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
, #_lokalizatsija_produktov (
Локализация продуктов
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 17:58
Часовой пояс: UTC + 5