国际化React之i18next-scanner自动化

1,282 阅读1分钟

1、安装node包

npm install i18next react-i18next i18next-browser-languagedetector -S //安装国际化插件

npm install i18next-scanner -D // 扫描文件依赖

npm install crc -S // 中文通过crc32转码获得唯一的key, 也避免中文作为索引导致性能不佳和索取不准确问题

image.png

2、根目录新建 i18next-scaner.config.js 文件

const fs = require('fs');
const { crc32 } = require('crc');

const systemLanguage = {
  zh_CN: 'zh-CN',
  zh_HK: 'zh-HK',
  en_GB: 'en-GB'
};
// 读取已经存在在en.json文件的词条
const existJsonData = fs.readFileSync('./scan/en-GB.json');
let existData = {};
try {
  existData = JSON.parse(existJsonData);
} catch {
  existData = {};
}

const keyList = Object.keys(existData);

module.exports = {
  input: [
    'src/**/*.{js,jsx,tsx,ts}',
    // 不需要扫描的文件加!
    '!src/locales/**',
    '!**/node_modules/**'
  ],
  output: './scan', // 输出目录
  options: {
    debug: true,
    func: false,
    trans: false,
    lngs: [systemLanguage.zh_CN, systemLanguage.zh_HK, systemLanguage.en_GB],
    defaultLng: systemLanguage.zh_CN,
    resource: {
      loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
      savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key)
      jsonIndent: 2,
      lineEnding: '\n'
    },
    removeUnusedKeys: true,
    nsSeparator: false, // namespace separator
    keySeparator: false, // key separator
    interpolation: {
      prefix: '{{',
      suffix: '}}'
    }
  },
  // 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换.
  transform: function (file, enc, done) {
    // 自己通过该函数来加工key或value
    const { parser } = this;
    const content = fs.readFileSync(file.path, enc);
    parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
      options.defaultValue = key;
      const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
      // 如果词条不存在,则写入
      if (!keyList.includes(hashKey)) {
        parser.set(hashKey, options);
      }
    });
    done();
  }
};

3、根目录package.json配置扫描的运行命令

image.png

4、执行 npm run scan 扫描自动生成翻译文档

image.png

5、这个文件根据业务自行存放, 当前放在src/locales/index.js

import i18n from 'i18next';
import { initReactI18next, useTranslation } from 'react-i18next';
// i18next-browser-languagedetector插件 这是一个 i18next 语言检测插件,用于检测浏览器中的用户语言,
import LanguageDetector from 'i18next-browser-languagedetector';
import crc32 from 'crc/crc32';
// 引入需要实现国际化的简体、繁体、英文三种数据的json文件
import zhCN from 'antd/locale/zh_CN';
import zhHK from 'antd/locale/zh_HK';
import enGB from 'antd/locale/en_GB';
import localZh_CN from 'scan/zh-CN.json'; // 本地翻译中文文件
import localZh_HK from 'scan/zh-HK.json'; // 本地翻译中文粤语文件
import localEn_GB from 'scan/en-GB.json'; // 本地翻译英文文件
import config from '@/config';

const resources = {
  cn: {
    translation: localZh_CN,
    ...zhCN
  },
  hk: {
    translation: localZh_HK,
    ...zhHK
  },
  en: {
    translation: localEn_GB,
    ...enGB
  }
};

i18n
  .use(LanguageDetector) // 嗅探当前浏览器语言 zh-CN
  .use(initReactI18next) // 将 i18n 向下传递给 react-i18next
  .init({
    // 初始化
    resources, // 本地多语言数据
    fallbackLng: config.lang, // 默认当前环境的语言
    detection: {
      caches: ['localStorage', 'sessionStorage', 'cookie']
    }
  });

// --------这里是i18next-scanner新增的配置-------------
export const $t = (key: string, params?: any[]): string => {
  const hashKey = `K${crc32(key).toString(16)}`; // 将中文转换成crc32格式去匹配对应的json语言包
  let words = i18n.t(hashKey);
  // const { t } = useTranslation();  // 通过hooks
  // let words = t(hashKey);
  if (words === hashKey) {
    words = key;
  }

  // 配置传递参数的场景, 目前仅支持数组,可在此拓展
  if (Array.isArray(params)) {
    const reg = /\((\d)\)/g;
    words = words.replace(reg, (a: string, b: number) => {
      return params[b];
    });
  }
  return words;
};

export default i18n;

6、App.tsx里配置

image.png

7、页面中使用

image.png

8、切换语言组件

import Icon from '@/components/Icons';
import { Dropdown, Row, Col } from 'antd';
import { setLanguage, selectLanguage } from '@/store/reducer/langSlice';
import i18n from '@/locales';

const LanguageSetting = () => {
  const dispatch = useAppDispatch();
  const items = [
    {
      key: 'cn',
      label: $t('简体')
    },
    {
      key: 'hk',
      label: $t('繁体')
    },
    {
      key: 'en',
      label: $t('英文')
    }
  ];

  const lang = useAppSelector(selectLanguage);
  const langLabel = items.find((item) => item?.key === lang)?.label;
  return (
    <Dropdown
      trigger={['hover']}
      menu={{
        items,
        style: { width: 110 },
        onClick: (e) => {
          const { key } = e;
          dispatch(setLanguage(key));
          i18n.changeLanguage(key);
        }
      }}
    >
      <Row
        gutter={10}
        style={{
          cursor: 'pointer',
          marginTop: -2,
          userSelect: 'none',
          padding: '0 10px'
        }}
      >
        <Col>
          <Icon type="TranslationOutlined" />
        </Col>
        <Col>{langLabel}</Col>
      </Row>
    </Dropdown>
  );
};
export default memo(LanguageSetting);

9、效果图

image.png

image.png

image.png