Localización de la aplicación en React Native

Durante el desarrollo de una de nuestras aplicaciones, necesitábamos realizar soporte multilingüe. La tarea consistía en brindar al usuario la posibilidad de cambiar el idioma (ruso e inglés) de la interfaz de la aplicación. Al mismo tiempo, el texto y el contenido deben traducirse "sobre la marcha".



Para hacer esto, tuvimos que resolver 2 problemas:



  1. Determine el idioma actual de la aplicación.
  2. Usando el estado global para la traducción sobre la marcha.


En este artículo intentaré describir en detalle cómo solucionamos estos problemas. Y así fuimos.



Determinar el idioma actual del dispositivo



Para determinar el idioma actual, puede, por supuesto, usar la biblioteca react-native-i18n, pero decidimos prescindir de ella, ya que puede hacerlo sin bibliotecas de terceros. Para hacer esto, escriba lo siguiente:



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, recuperamos el idioma de la aplicación a través de SettingsManager y para Android a través del I18nManager nativo.



Ahora que hemos recibido el idioma actual del dispositivo, podemos guardarlo en AsyncStorage y pasar a la segunda tarea.



Traducimos "sobre la marcha"



Usamos MobX para administrar el estado global, pero puede usar una solución diferente.



Y entonces tenemos que crear una clase (me gusta llamarla "modelo") que será responsable del estado global de la localización actual. Nosotros creamos:



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


Cuando inicializamos nuestro modelo, llamamos al método init, que toma la configuración regional del AsyncStorage, si hay uno, o extraemos el idioma actual del dispositivo y lo colocamos en AsyncStorage.



A continuación, necesitamos escribir un método (acción) que cambiará el idioma:



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


Creo que aquí todo está claro.



Ahora viene la parte divertida. Decidimos almacenar las traducciones en un diccionario simple. Para ello, creemos un archivo js junto a nuestro LangModel, en el que pondremos nuestras traducciones:



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


A continuación, implementaremos un método más en LangModel, que aceptará texto como entrada y devolverá el texto de la localización actual:



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


Eso es todo, nuestro LangModel está listo.



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




Ahora podemos usar el método rk para localizar el texto:



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


Puedes ver cómo funciona en nuestra aplicación en la AppStore y Google Play (Click en el icono (!) En la parte superior derecha, desplázate hacia abajo)



Prima



Por supuesto, escribir LangModel.rk cada vez no es genial. Por lo tanto, podemos crear nuestro propio componente Text y ya usar LangModel.rk en él



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


También es posible que necesitemos, por ejemplo, cambiar el logotipo de la aplicación según la localización actual. Para hacer esto, simplemente puede cambiar el contenido dependiendo de LangModel.lang (no olvide envolver su componente en el observador (MobX))



PD: Quizás este enfoque no parecerá un estándar, pero nos gustó más que el ofrecido por react-native-i18n



On eso es todo para mi. ¡Gracias a todos!)



All Articles