likes
comments
collection
share

react-native 使用 react-i18next 实现国际化

作者站长头像
站长
· 阅读数 61

目录结构

├── locales
│   ├── zh-CN.json
│   ├── en-US.json
├── src
│   ├── utils
│   │   ├── i18next.ts
│   ├── App.js

国际化库配置

安装依赖

yarn add react-i18next i18next
yarn add react-native-localize

iOS

npx pod-install

提示:如果是 react web 项目,可以换一个系统语言检测插件,如: i18next-browser-languagedetector

准备好资源文件

新建 en-US.json

{
    "语言key": "language"
}

zh-CN.json

{
    "语言key": "语言"
}

配置 i18next

新建文件 i18next.ts

import i18next, { ModuleType } from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from "react-native-localize";
import storage from './storage';
import { reset } from './RootNavigation';

export const lngKey =  '@lng';

const languageDetector = {
  type: 'languageDetector' as ModuleType,
  async: true,
  detect: function (callback) {
    // 获取上次选择的语言
    storage.get(lngKey, 'locale').then(lng => {
      // 如果是跟随本地,则获取系统语言
      if (lng === 'locale') {
        callback(getSystemLanguage());
      } else {
        callback(lng);
      }
    });
  },
};

// 初始化i18next配置
i18next
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'zh', // 切换语言失败时的使用的语言
    debug: __DEV__, // 开发环境开启调试
    // 资源文件
    resources: {
      en: {
        translation: require('./../../locales/en-US.json'),
      },
      zh: {
        translation: require('./../../locales/zh-CN.json'),
      },
    },
    react: {
      useSuspense: false,
    },
  });

/**
 * 获取当前系统语言
 * @returns
 */
export const getSystemLanguage = (): string => {
  const locales = RNLocalize.getLocales();
  return locales[0].languageCode;
};

/**
 * 切换语言
 * @param lng
 */
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
  // 切换语言
  await i18next.changeLanguage(lng === 'locale' ? getSystemLanguage() : lng);
  // 持久化当前选择
  await storage.set(lngKey, lng);
};

export default i18next;

在入口文件 App.js 导入:

import 'i18next';

在页面中使用

不管那种类型的使用方式,都是通过注入 t 函数实现的,参数为资源文件的 key。

类组件 (HOC)

import React from 'react';
import { Text } from 'react-native';
import { withTranslation } from 'react-i18next';

@withTranslation
function MyComponent({ t, i18n }) {
  return <Text>{t('语言key')}</Text>
}

export default MyComponent;

函数组件 (hook)

import React from 'react';
import { Text } from 'react-native';
import { useTranslation } from 'react-i18next';

export function MyComponent() {
  const { t, i18n } = useTranslation();
  // or const [t, i18n] = useTranslation();

  return <Text>{t('语言key')}</Text>
}

Translation 组件(render props)

import React from 'react';
import { Text } from 'react-native';
import { Translation } from 'react-i18next';

export function MyComponent() {
  return (
    <Translation>
      {
        (t, { i18n }) => <Text>{t('语言key')}</Text>
      }
    </Translation>
  )
}

字符串中有变量

资源文件:

{
    使用次数为:"使用次数为 {{count}}"
}

使用:

t('使用次数为', { count: 10 });

更多字符串格式化用法可以查看:Formatting - i18next documentation

替换文本

配置好了,接下来就是文本替换,需要将所有页面的文本替换掉了,如果一个一个页面人工扫描,出错率会很高,这里推荐两个工具。

di18n

一个自动转换、基于配置的前端国际化方案。

用法也很简单,安装:

yarn add  -D di18n-cli

初始化:

npx di18n init

会生成一个配置文件,根据项目实际情况修改。

同步:

npx di18n sync

会替换掉源码的中文,换成指定格式,如:t('中文key')

注意:这个库运行 init 的时候,依赖有缺失,安装缺失依赖可解决,issues

i18n-ally

vscode 插件,在 vscode 插件中搜索安装即可。这个插件具备回显国际化内容,检查当前文件的 hard code 代码,一键抽取当前文本等功能。虽然不能批量替换文件,需要手动一个个文件检查,但是后期维护起来用这个工具进行可视化管理会很方便,文档链接 在文末的参考资料中。

问题记录

部分文本在切换语言后不变

原因: 那部分的文本没有办法触发 react 的刷新。例如下面的写法:

import { Text } from 'react-native';
import i18next from '@utils/i18next';

const data = { name: i18next.t("姓名") };

function Comp(){
	return <Text>{data.name}</Text>
}

解决方案:

  1. 换个写法。
import { Text } from 'react-native';
import { useTranslation } from 'react-i18next';

const data = { name: 姓名 };

function Comp(){
    const { t } = useTranslation();
    return <Text>{t(data.name)}</Text>
}

但是这种写法可能很多,每个地方都要改一遍,工作量大,也容易引入新的风险。

  1. 每次切换语言都重新加载 js bundle。

使用 react-native-restart 库:

 yarn add react-native-restart
 npx pod-install

修改切换语言的函数:

import RNRestart from 'react-native-restart';

/**
 * 切换语言,切换完成自动重启
 * @param lng
 */
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
  // 持久化当前选择
  await storage.set('@lng', lng);
  // 重启app
  RNRestart.Restart();
};
  1. 重新渲染整个 app。
import { reset } from './RootNavigation';

/**
 * 切换语言
 * @param lng
 */
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
  // 切换语言
  await i18next.changeLanguage(lng);
  // 持久化当前选择
  await storage.set('@lng', lng);
  // 跳转回首页
  reset({
    index: 0,
    routes: [{ name: 'Main' }],
  });
};

由于我们的 app 是使用 react-navigation 作为导航库的,所以就通过跳转回首页的方式让所有页面重新渲染。这里也可以使用其他方式,能达到强制重新渲染页面的效果就行。

最后选择了方案3,最终的完整配置代码如下:

import i18next, { ModuleType } from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from "react-native-localize";
import storage from './storage';
import { reset } from './RootNavigation';

export const lngKey =  '@lng';

const languageDetector = {
  type: 'languageDetector' as ModuleType,
  async: true,
  detect: function (callback) {
    // 获取上次选择的语言
    storage.get(lngKey, 'locale').then(lng => {
      // 如果是跟随本地,则获取系统语言
      if (lng === 'locale') {
        callback(getSystemLanguage());
      } else {
        callback(lng);
      }
    });
  },
};

// 初始化i18next配置
i18next
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'zh', // 切换语言失败时的使用的语言
    debug: __DEV__, // 开发环境开启调试
    // 资源文件
    resources: {
      en: {
        translation: require('./../../locales/en-US.json'),
      },
      zh: {
        translation: require('./../../locales/zh-CN.json'),
      },
    },
    react: {
      useSuspense: false,
    },
  });

/**
 * 获取当前系统语言
 * @returns
 */
export const getSystemLanguage = (): string => {
  const locales = RNLocalize.getLocales();
  return locales[0].languageCode;
};

/**
 * 切换语言
 * @param lng
 */
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
  // 切换语言
  await i18next.changeLanguage(lng === 'locale' ? getSystemLanguage() : lng);
  // 持久化当前选择
  await storage.set(lngKey, lng);
  // 跳转回首页
  reset({
    index: 0,
    routes: [{ name: 'Main' }],
  });
};

export default i18next;

android端 App 中选择跟随系统,然后去系统设置页面切换语言,再次进入 App,语言没有切换成功

原因: 和 iOS不同,android 切换语言的时候,不会杀掉 App 的进程,所以 jsbundle 不会重新加载,导致再次进入 App 语言没有切换成功。

解决方案: 每次进入 App 的时候判断一下持久化存储的语言是不是跟随系统,如果是,判断系统语言和当前 App 使用的语言是否一致,不一致则切换成正确的语言。

可以封装成一个 hook ,在入口文件引入,监听每一次系统语言的变化:

import { Platform } from 'react-native';
import i18next, { changeLanguage, getSystemLanguage, lngKey } from '@utils/i18next';
import storage from '@utils/storage';
import { useCallback, useEffect } from 'react';
import * as RNLocalize from 'react-native-localize';

export default function useLanguageChange() {
  const handleLocalizationChange = useCallback(() => {
    if (Platform.OS === 'android') {
      storage.get(lngKey).then(res => {
        if (res === 'locale' && getSystemLanguage() !== i18next.language) {
          changeLanguage('locale');
        }
      });
    }
  }, []);

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

参考资料

转载自:https://juejin.cn/post/7204731868137979941
评论
请登录