Para hacer esto, tuvimos que resolver 2 problemas:
- Determine el idioma actual de la aplicación.
- 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!)