Para fazer isso, tivemos que resolver 2 problemas:
- Determine o idioma atual do aplicativo.
- 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!)