Basic

PPG007 ... 2023-11-29 About 4 min

# Basic

# 起步

安装 i18next:

yarn add i18next
1

然后定义国际化文件:

// i18n/locales/zh_cn.js
const translation = {
  click: '点击'
}

export default translation;
1
2
3
4
5
6

然后配置 i18next:

import i18next from "i18next";
import en from "./locales/en_us";
import zh from "./locales/zh_cn";

const config = {
  debug: true,
  resources: {
    zh: {
      translation: zh,
    },
    en: {
      translation: en,
    },
  },
  lng: 'zh'
};

i18next.init(config);

export default i18next;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

最后可以调用 translate 方法:

import { log } from "console";
import i18n from '../i18n';
log(i18n.t('click'))
1
2
3

# 命名空间

命名空间是 i18next 国际化框架中的一个功能,它允许将多个文件中的翻译分开。在较小的项目中,将所有内容放在一个文件中可能是合理的,当项目逐渐增大,多个模块之间有不同的、相同的国际化定义,这时就需要拆分。例如将通用的部分放置在一个 common 命名空间,其他模块放置在自己的命名空间中,例如:

// i18n/locales/modules/member/zh_cn.js
const translation = {
  member: '客户'
};

export default translation;
// i18n/locales/modules/account/zh_cn.js
const translation = {
  account: '账户'
};

export default translation;
// i18n/locales/modules/index.js
import member_zh from './member/zh_cn';
import member_en from './member/en_us';
import account_zh from './account/zh_cn';
import account_en from './account/en_us';

export default {
  zh: {
    member: member_zh,
    account: account_zh,
  },
  en: {
    member: member_en,
    account: account_en,
  }
}
// i18n/index.js
import i18next from "i18next";
import common_en from "./locales/en_us";
import common_zh from "./locales/zh_cn";
import modules from "./locales/modules";

const config = {
  debug: true,
  resources: {
    zh: {
      common: common_zh,
      ...modules.zh,
    },
    en: {
      common: common_en,
      ...modules.en,
    },
  },
  lng: 'zh',
  defaultNS: 'common',
  ns: ['common', 'member', 'account']
};

i18next.init(config);

export default i18next;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

现在就可以根据不同的命名空间来访问国际化文件了,以访问 account 模块为例,由于默认命名空间是 common,因此要访问 account 模块需要明确指定命名空间,可以有以下几种写法:

import { log } from "console";
import i18n from '../i18n';
log(i18n.t('account:account'))
log(i18n.t('account', {ns: 'account'}))
1
2
3
4

# 嵌套、上下文与动态内容

如果国际化中包含对象嵌套,可以通过 . 来访问:

const translation = {
  click: '点击',
  a: {
    b: '嵌套'
  }
}

log(i18n.t('a.b'))
1
2
3
4
5
6
7
8

对于一些场景,根据上下文的不同,国际化文本会有一些差异,可以使用上下文实现,例如:

const translation = {
  number: '数字',
  number_odd: '奇数',
  number_even: '偶数'
}

log(i18n.t('number', {context: 'even'}))
1
2
3
4
5
6
7

默认情况下是使用 _ 分隔上下文和国际化键的。

有时需要将动态内容嵌入国际化文本中,这时可以使用动态替换:

const translation = {
  click: '点击',
  a: {
    b: '嵌套'
  },
  count: '当前数字是 {{count}}'
}

log(i18n.t('count', {count: 123}))
1
2
3
4
5
6
7
8
9

默认情况下,动态内容要使用双括号包裹起来,这个符号可以通过 i18next 初始化配置设置:

const config: InitOptions = {
  // ...
  interpolation: {
    prefix: '<<',
    suffix: '>>'
  }
};
1
2
3
4
5
6
7

现在将国际化文件中的双大括号替换为双尖括号即可,除了动态参数标志字符可以配置外,命名空间等均可配置。

也可以通过对象的形式访问动态数据:

const translation = {
  click: '点击',
  author: {
    name: '当前作者是:<<author.name>>',
    age: '当前作者年龄是:<<author.age>>'
  }
}

log(i18n.t('author.name', {author: {name: 'PPG007'}}))
1
2
3
4
5
6
7
8
9

国际化文件中也可以嵌套其他国际化文本:

const translation = {
  click: '点击 {{age}}',
  author: {
    name: '当前作者是:{{author.name}}, $t(click, {\"author\": {{author.age}} })',
    age: '当前作者年龄是:{{author.age}}'
  }
}
1
2
3
4
5
6
7

# TypeScript

对于 TypeScript 项目,如果是编译为 CommonJS,可以参考下面的 TypeScript 配置:

{
    "compilerOptions": {
        "strict": true,
        "module": "CommonJS",
        "target": "ES6",
        "outDir": "lib",
        "moduleResolution": "Node10",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true
    },
    "include": [
        "src",
        "@types",
        "i18n"
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这种情况下,esModuleInterop 和 allowSyntheticDefaultImports 都要设置为 true。

为了实现对国际化键的 TypeScript 支持,需要自定义类型声明:

// @types/resources.ts
import common from "../i18n/locales/zh_cn";
import modules from "../i18n/locales/modules";

const resources = {
  common,
  ...modules.zh
} as const;

export default resources;

export const defaultNS = 'common';
// @types/i18next.d.ts
import resources, { defaultNS } from "./resources";

declare module 'i18next' {
  interface CustomTypeOptions {
    resources: typeof resources;
    defaultNS: typeof defaultNS,
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Tips

需要注意 i18next 的版本号,某些版本对 TypeScript 的支持有问题,上面代码基于 23.7.6。

# React

对于使用 React 的情况,直接使用 i18next-react:

yarn install react-i18next i18next
1

配置与普通用法完全一致,只是需要使用插件来提供 React Hooks 的支持:

i18n
    .use(I18nextBrowserLanguageDetector)
    .use(initReactI18next)
    .init(initOption);
1
2
3
4

initReactI18next 用于提供 Hooks 支持,I18nextBrowserLanguageDetector 会根据系统设置判断使用什么语言,在使用这个插件的情况下配置项中就不要指定语言了,这个插件对应的语言键参照Wiki (opens new window)

然后在组件中:

function App() {
  const {t} = useTranslation();
  const [count, setCount] = useState(0);
  return (
    <Fragment>
      <button onClick={() => {
        setCount(count+1)
      }}>
        {
          t('countTip', {myCount: count})
        }
      </button>
    </Fragment>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Last update: November 29, 2023 05:02
Contributors: Koston Zhuang