最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to internationalize a React Native Expo App? - Stack Overflow

programmeradmin1浏览0评论

Which technique is remended to use when making a React App International?

I am thinking of coding the following:

  1. Create a React Context "Languages"
  2. Create different modules exporting a map with all strings for each language "pt", "en", "fr", "sp", "it"...
  3. Try to load the default language from AsyncStorage in my Splash Screen, using a method provided by the Languages Context Provider.
  4. If not found, get the user language based on his location.

For me this has sense, but there might be other easier ways to achieve the same, feeling more profesional.

// This is my Languages Context
import React, { createContext, useState } from "react";
import * as Localization from "expo-localization";

import { VALID_LANGUAGES } from "../../utils/languages/constants"; // "English", "Spanish", "Portuguese"...
import languages from "../../languages"; // en, sp, pt, ... modules with all texts (key, value)
import AsyncStorage from "../../lib/async-storage/AsyncStorage";

const LanguagesContext = createContext(null);

export default LanguagesContext;

export function LanguagesProvider({ children }) {
  const [language, setLanguage] = useState(undefined);

  const loadLanguage = async () => {
    // Get the default language from async storage
    const language = await AsyncStorage.getData("language");

    // TODO - if undefined -> get user location and use the corresponding lang
    // TODO - if not supported, use english

    if (!language) {
      setLanguage(VALID_LANGUAGES[0]);
    }
  };

  const changeLanguage = async (language) => {
    // Update state
    setLanguage(language);

    // Save language in async storage
    await AsyncStorage.storeData("language", language);
  };

  return (
    <LanguagesContext.Provider
      value={{ language, loadLanguage, changeLanguage }}
    >
      {children}
    </LanguagesContext.Provider>
  );
}

export { LanguagesProvider };

What I am doing is using the method "loadLanguage" in my Splash Screen Component, something that ensures the app to be ready for use before rendering any content. What do you think about this technique?

How can I use the texts in the app? I thought to make another method getAppTexts() in the Context provider, just to return the correct map from my languages modules ("en", "it", "pt", ...)

Any library and an example?

Thanks you.

Which technique is remended to use when making a React App International?

I am thinking of coding the following:

  1. Create a React Context "Languages"
  2. Create different modules exporting a map with all strings for each language "pt", "en", "fr", "sp", "it"...
  3. Try to load the default language from AsyncStorage in my Splash Screen, using a method provided by the Languages Context Provider.
  4. If not found, get the user language based on his location.

For me this has sense, but there might be other easier ways to achieve the same, feeling more profesional.

// This is my Languages Context
import React, { createContext, useState } from "react";
import * as Localization from "expo-localization";

import { VALID_LANGUAGES } from "../../utils/languages/constants"; // "English", "Spanish", "Portuguese"...
import languages from "../../languages"; // en, sp, pt, ... modules with all texts (key, value)
import AsyncStorage from "../../lib/async-storage/AsyncStorage";

const LanguagesContext = createContext(null);

export default LanguagesContext;

export function LanguagesProvider({ children }) {
  const [language, setLanguage] = useState(undefined);

  const loadLanguage = async () => {
    // Get the default language from async storage
    const language = await AsyncStorage.getData("language");

    // TODO - if undefined -> get user location and use the corresponding lang
    // TODO - if not supported, use english

    if (!language) {
      setLanguage(VALID_LANGUAGES[0]);
    }
  };

  const changeLanguage = async (language) => {
    // Update state
    setLanguage(language);

    // Save language in async storage
    await AsyncStorage.storeData("language", language);
  };

  return (
    <LanguagesContext.Provider
      value={{ language, loadLanguage, changeLanguage }}
    >
      {children}
    </LanguagesContext.Provider>
  );
}

export { LanguagesProvider };

What I am doing is using the method "loadLanguage" in my Splash Screen Component, something that ensures the app to be ready for use before rendering any content. What do you think about this technique?

How can I use the texts in the app? I thought to make another method getAppTexts() in the Context provider, just to return the correct map from my languages modules ("en", "it", "pt", ...)

Any library and an example?

Thanks you.

Share Improve this question asked May 29, 2021 at 12:06 Victor MolinaVictor Molina 2,6613 gold badges28 silver badges59 bronze badges 1
  • Why not use the device's OS language instead of location to set your default language? – Amr Commented Jun 3, 2021 at 6:11
Add a ment  | 

4 Answers 4

Reset to default 5

First you just init a new react-native project

$ npx react-native init rn_example_translation

i would like to prefer to create a src folder to put all our JS code so we modify the index.js at the project root dir below like this:

import {AppRegistry} from 'react-native';
import App from './src/App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

Manage translations

We will translate the app using 'i18n-js' module so we install it with:

$ npm install --save i18n-js

Then create a file 'i18n.js' with:

import {I18nManager} from 'react-native';
import i18n from 'i18n-js';
import memoize from 'lodash.memoize';

export const DEFAULT_LANGUAGE = 'en';

export const translationGetters = {
  // lazy requires (metro bundler does not support symlinks)
  en: () => require('./assets/locales/en/translations.json'),
  fr: () => require('./assets/locales/fr/translations.json'),
};

export const translate = memoize(
  (key, config) => i18n.t(key, config),
  (key, config) => (config ? key + JSON.stringify(config) : key),
);

export const t = translate;

export const setI18nConfig = (codeLang = null) => {
  // fallback if no available language fits
  const fallback = {languageTag: DEFAULT_LANGUAGE, isRTL: false};
  const lang = codeLang ? {languageTag: codeLang, isRTL: false} : null;

  const {languageTag, isRTL} = lang ? lang : fallback;

  // clear translation cache
  translate.cache.clear();
  // update layout direction
  I18nManager.forceRTL(isRTL);
  // set i18n-js config
  i18n.translations = {[languageTag]: translationGetters[languageTag]()};
  i18n.locale = languageTag;

  return languageTag;
};

Then Create Empty Translations files like below:

 './src/assets/locales/en/translations.json' and './src/assets/locales/fr/translations.json'

now we can translate app JS string in french and english just like below:

i18n.t('Hello world!')

Switch locale in app

Now we'll setup a react context to keep the current user language and a switch to give the option to the user to change the language. It is cool to translate the strings but the translated strings have to match with user language.

To keep current user language along the app with a react context, we need to create a file 'LocalisationContext.js' in context folder with:

 import React from 'react';

const LocalizationContext = React.createContext();

export default LocalizationContext;

Now in your App.js

import React, {useEffect, useCallback} from 'react';
import {StyleSheet} from 'react-native';

import * as i18n from './i18n';
import LocalizationContext from './context/LocalizationContext';
import HomeScreen from './HomeScreen';

const App: () => React$Node = () => {
  const [locale, setLocale] = React.useState(i18n.DEFAULT_LANGUAGE);
  const localizationContext = React.useMemo(
    () => ({
      t: (scope, options) => i18n.t(scope, {locale, ...options}),
      locale,
      setLocale,
    }),
    [locale],
  );

  return (
    <>
      <LocalizationContext.Provider value={localizationContext}>
        <HomeScreen localizationChange={handleLocalizationChange} />
      </LocalizationContext.Provider>
    </>
  );
};

and create the 'HomeScreen.js' file:

import React, {useContext} from 'react';
import {StyleSheet, SafeAreaView, Text, Button} from 'react-native';

import LocalizationContext from './context/LocalizationContext';

function HomeScreen(props) {
  const {localizationChange} = props;
  const {t, locale, setLocale} = useContext(LocalizationContext);

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.title}>React-Native example translation</Text>
      <Text style={styles.subtitle}>{t('Home screen')}</Text>
      <Text style={styles.paragraph}>Locale: {locale}</Text>

      {locale === 'en' ? (
        <Button title="FR" onPress={() => localizationChange('fr')} />
      ) : (
        <Button title="EN" onPress={() => localizationChange('en')} />
      )}
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    textAlign: 'center',
    fontSize: 22,
    marginBottom: 40,
  },
  subtitle: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
  },
  paragraph: {
    fontSize: 14,
    marginBottom: 10,
  },
  langButton: {
    flex: 1,
  },
});

export default HomeScreen;

here we can translate the strings in js.

Handle localization system change

now we have to install localization module now:

$ npm install --save react-native-localize

then modify the app.js to this

import React, {useEffect, useCallback} from 'react';
import {StyleSheet} from 'react-native';
import * as RNLocalize from 'react-native-localize';

import * as i18n from './i18n';
import LocalizationContext from './context/LocalizationContext';
import HomeScreen from './HomeScreen';

const App: () => React$Node = () => {
  const [locale, setLocale] = React.useState(i18n.DEFAULT_LANGUAGE);
  const localizationContext = React.useMemo(
    () => ({
      t: (scope, options) => i18n.t(scope, {locale, ...options}),
      locale,
      setLocale,
    }),
    [locale],
  );

  const handleLocalizationChange = useCallback(
    (newLocale) => {
      const newSetLocale = i18n.setI18nConfig(newLocale);
      setLocale(newSetLocale);
    },
    [locale],
  );

  useEffect(() => {
    handleLocalizationChange();

    RNLocalize.addEventListener('change', handleLocalizationChange);
    return () => {
      RNLocalize.removeEventListener('change', handleLocalizationChange);
    };
  }, []);

  return (
    <>
      <LocalizationContext.Provider value={localizationContext}>
        <HomeScreen localizationChange={handleLocalizationChange} />
      </LocalizationContext.Provider>
    </>
  );
};

then 'i18n.js' file like this:

import {I18nManager} from 'react-native';
import * as RNLocalize from 'react-native-localize';
import i18n from 'i18n-js';
import memoize from 'lodash.memoize';

export const DEFAULT_LANGUAGE = 'en';

export const translationGetters = {
  // lazy requires (metro bundler does not support symlinks)
  en: () => require('./assets/locales/en/translations.json'),
  fr: () => require('./assets/locales/fr/translations.json'),
};

export const translate = memoize(
  (key, config) => i18n.t(key, config),
  (key, config) => (config ? key + JSON.stringify(config) : key),
);

export const t = translate;

export const setI18nConfig = (codeLang = null) => {
  // fallback if no available language fits
  const fallback = {languageTag: DEFAULT_LANGUAGE, isRTL: false};
  const lang = codeLang ? {languageTag: codeLang, isRTL: false} : null;

# Use RNLocalize to detect the user system language
  const {languageTag, isRTL} = lang
    ? lang
    : RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
      fallback;

  // clear translation cache
  translate.cache.clear();
  // update layout direction
  I18nManager.forceRTL(isRTL);
  // set i18n-js config
  i18n.translations = {[languageTag]: translationGetters[languageTag]()};
  i18n.locale = languageTag;

  return languageTag;
};

Generate translations

in order to generate language files you can use i18next-scanner. we need to install it globally

npm install -g i18next-scanner

create a 'i18next-scanner.config.js' file at your project dir root with:

  const fs = require('fs');
const chalk = require('chalk');

module.exports = {
  input: [
    'src/**/*.{js,jsx}',
    // Use ! to filter out files or directories
    '!app/**/*.spec.{js,jsx}',
    '!app/i18n/**',
    '!**/node_modules/**',
  ],
  output: './',
  options: {
    debug: false,
    removeUnusedKeys: true,
    func: {
      list: ['i18next.t', 'i18n.t', 't'],
      extensions: ['.js', '.jsx'],
    },
    trans: {
      ponent: 'Trans',
      i18nKey: 'i18nKey',
      defaultsKey: 'defaults',
      extensions: [],
      fallbackKey: function (ns, value) {
        return value;
      },
      acorn: {
        ecmaVersion: 10, // defaults to 10
        sourceType: 'module', // defaults to 'module'
        // Check out https://github./acornjs/acorn/tree/master/acorn#interface for additional options
      },
    },
    lngs: ['en', 'fr'],
    ns: ['translations'],
    defaultLng: 'en',
    defaultNs: 'translations',
    defaultValue: '__STRING_NOT_TRANSLATED__',
    resource: {
      loadPath: 'src/assets/locales/{{lng}}/{{ns}}.json',
      savePath: 'src/assets/locales/{{lng}}/{{ns}}.json',
      jsonIndent: 2,
      lineEnding: '\n',
    },
    nsSeparator: false, // namespace separator
    keySeparator: false, // key separator
    interpolation: {
      prefix: '{{',
      suffix: '}}',
    },
  },
  transform: function customTransform(file, enc, done) {
    'use strict';
    const parser = this.parser;

    const options = {
      presets: ['@babel/preset-flow'],
      plugins: [
        '@babel/plugin-syntax-jsx',
        '@babel/plugin-proposal-class-properties',
      ],
      configFile: false,
    };

    const content = fs.readFileSync(file.path, enc);
    let count = 0;

    const code = require('@babel/core').transform(content, options);
    parser.parseFuncFromString(
      code.code,
      {list: ['i18next._', 'i18next.__']},
      (key, options) => {
        parser.set(
          key,
          Object.assign({}, options, {
            nsSeparator: false,
            keySeparator: false,
          }),
        );
        ++count;
      },
    );

    if (count > 0) {
      console.log(
        `i18next-scanner: count=${chalk.cyan(count)}, file=${chalk.yellow(
          JSON.stringify(file.relative),
        )}`,
      );
    }

    done();
  },
};

Here we can use the mand :

$ i18next-scanner

now it will generate prefill translation files

'./src/assets/locales/en/translations.json' and './src/assets/locales/fr/translations.json'.

we just need to change in these files by right translation

now run the App;

npx react-native run-android

It will run succesfully.

if you want to switch language then This is the top Solution. try this one.

i18n.locale = "ar";
i18n.reset();

Late to the party but there is a section in the expo doc about app localization

Here is a short example taken from the doc:

import { getLocales } from 'expo-localization'
import { I18n } from 'i18n-js'

// Set the key-value pairs for the different languages you want to support.
const i18n = new I18n({
  en: { wele: 'Hello' },
  ja: { wele: 'こんにちは' },
})

// Set the locale once at the beginning of your app.
i18n.locale = getLocales()[0].languageCode

console.log(i18n.t('wele'))

first install these two packages:

 npx expo install expo-localization
 npx expo install i18n-js

Then open your .jsx file and type:

import { View, Text } from 'react-native';
import { I18n } from 'i18n-js';

const translations = {
  en: { wele: 'Hello'},
  ja: { wele: 'こんにちは' },
};
const i18n = new I18n(translations);
i18n.locale = 'ja';

export default function App() {
  return (
    <View>
      <Text>
        {i18n.t('wele')}
      </Text>
    </View>
  );
}
发布评论

评论列表(0)

  1. 暂无评论