javascript - 如何国际化 React Native Expo 应用程序?

标签 javascript reactjs react-native internationalization expo

制作 React App International 时建议使用哪种技术?

我正在考虑编写以下代码:

  1. 创建 React 上下文“语言”
  2. 创建不同的模块,导出包含每种语言“pt”、“en”、“fr”、“sp”、“it”...的所有字符串的 map
  3. 尝试使用语言上下文提供程序提供的方法从我的启动屏幕中的 AsyncStorage 加载默认语言。
  4. 如果未找到,请根据用户所在位置获取用户语言。

对我来说,这是有道理的,但可能还有其他更简单的方法来实现相同的目标,感觉更专业。

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

我正在做的是在我的启动屏幕组件中使用“loadLanguage”方法,以确保应用程序在渲染任何内容之前准备好使用。您对这项技术有何看法?

如何使用应用程序中的文本? 我想在 Context 提供程序中创建另一个方法 getAppTexts(),只是为了从我的语言模块返回正确的 map (“en”、“它”,“pt”,...)

有库和示例吗?

谢谢你。

最佳答案

首先,您只需启动一个新的react-native项目

$ npx react-native init rn_example_translation

我想创建一个 src 文件夹来放置所有 JS 代码,因此我们修改下面项目根目录中的 index.js,如下所示:

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

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

管理翻译

我们将使用“i18n-js”模块翻译应用程序,因此我们安装它:

$ npm install --save i18n-js

然后创建一个文件“i18n.js”:

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

然后创建空翻译文件,如下所示:

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

现在我们可以将应用程序 JS 字符串翻译成法语和英语,如下所示:

i18n.t('Hello world!')

在应用程序中切换区域设置

现在我们将设置一个 react 上下文来保留当前的用户语言,并设置一个开关来为用户提供更改语言的选项。翻译字符串很酷,但翻译后的字符串必须与用户语言匹配。

为了将当前用户语言与 react 上下文一起保留在应用程序中,我们需要在上下文文件夹中创建一个文件“LocalizationContext.js”:

 import React from 'react';

const LocalizationContext = React.createContext();

export default LocalizationContext;

现在在您的 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>
    </>
  );
};

并创建“HomeScreen.js”文件:

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;

这里我们可以翻译js中的字符串。

处理本地化系统变更

现在我们必须安装本地化模块:

$ npm install --save react-native-localize

然后将app.js修改为此

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

然后像这样的“i18n.js”文件:

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

生成翻译

为了生成语言文件,您可以使用 i18next-scanner。 我们需要全局安装它

npm install -g i18next-scanner

在项目目录根目录中创建一个“i18next-scanner.config.js”文件:

  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: {
      component: '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.com/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();
  },
};

这里我们可以使用命令:

$ i18next-scanner

现在它将生成预填充翻译文件

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

我们只需要通过正确的翻译来更改这些文件

现在运行应用程序;

npx react-native run-android

它将成功运行。

关于javascript - 如何国际化 React Native Expo 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67751187/

相关文章:

javascript - Browserify多次解析同一个文件

android - react native : clickable list of items

ReactJS - 找不到模块 : Error: Can't resolve 'App'

javascript - React Native - 使背景图像避免键盘

react-native - 未强制执行 ReactiveSearch 默认查询

javascript - Python从&lt;script&gt; html标签内获取数据值

javascript - 在 Angular Js 中选择日期后,不要丢失 Rzslider 的先前位置?

javascript - 如何单击 Flash 对象

css - 警告 : a div tag was passed a numeric string value for CSS property fontSize which will be treated as a unitless number in a future version of React

javascript - React( native )导航切换搜索栏