Localização do aplicativo em React Native

Durante o desenvolvimento de uma de nossas aplicações, foi necessário fazer suporte multilíngue. A tarefa era dar ao usuário a capacidade de alterar o idioma (russo e inglês) da interface do aplicativo. Ao mesmo tempo, o texto e o conteúdo devem ser traduzidos na hora.



Para fazer isso, tivemos que resolver 2 problemas:



  1. Determine o idioma atual do aplicativo.
  2. Usando o estado global para tradução imediata.


Neste artigo, tentarei descrever em detalhes como resolvemos esses problemas. E assim fomos.



Determine o idioma atual do dispositivo



Para determinar o idioma atual, você pode, é claro, usar a biblioteca react-native-i18n, mas decidimos fazer sem ela, já que você pode fazer isso sem bibliotecas de terceiros. Para fazer isso, escreva o seguinte:



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


Para ios, extraímos o idioma do aplicativo por meio do SettingsManager, e para o Android por meio do I18nManager nativo.



Agora que recebemos o idioma atual do dispositivo, podemos salvá-lo no AsyncStorage e prosseguir para a segunda tarefa.



Traduzimos "na hora"



Usamos o MobX para gerenciar o estado global, mas você pode usar uma solução diferente.



E então temos que criar uma classe (gosto de chamá-la de "modelo") que será responsável pelo estado global da localização atual. Nós criamos:



//   ,      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();


Ao inicializar nosso modelo, chamamos o método init, que obtém a localidade do AsyncStorage, se houver, ou extrai o idioma do dispositivo atual e o coloca no AsyncStorage.



Em seguida, precisamos escrever um método (ação) que mudará o idioma:



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


Acho que tudo está claro aqui.



Agora vem a parte divertida. Decidimos armazenar as próprias traduções em um dicionário simples. Para fazer isso, crie um arquivo js próximo ao nosso LangModel, no qual colocaremos nossas traduções:



// translations.js
// ,     . 
export default const translations = {
  ", !": {en: "Hello, World!"},
}


A seguir, implementaremos mais um método em LangModel, que aceitará texto como entrada e retornará o texto da localização atual:



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];
  }


É isso, nosso LangModel está pronto.



Código LangModel completo
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();




Agora podemos usar o método rk para localizar o texto:



<Text>{LangModel.rk(", !")}</Text>


Você pode ver como funciona em nosso aplicativo na AppStore e no Google Play (clique no ícone (!) No canto superior direito, role para baixo)



Bônus



Obviamente, escrever LangModel.rk sempre não é legal. Portanto, podemos criar nosso próprio componente de texto e já usar LangModel.rk nele



//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>
));


Também podemos precisar, por exemplo, alterar o logotipo do aplicativo dependendo da localização atual. Para fazer isso, você pode simplesmente alterar o conteúdo dependendo de LangModel.lang (não se esqueça de embrulhar seu componente no observador (MobX))



PS: Talvez esta abordagem pareça não ser um padrão, mas gostamos mais dela do que a oferecida por react-native-i18n



On isso é tudo para mim. Obrigado a todos!)



All Articles