2022-07-01 16:47:25 +08:00
|
|
|
|
---
|
|
|
|
|
title: 国际化
|
|
|
|
|
---
|
|
|
|
|
|
2023-04-24 10:18:53 +08:00
|
|
|
|
import Tabs from '@theme/Tabs';
|
|
|
|
|
import TabItem from '@theme/TabItem';
|
|
|
|
|
|
|
|
|
|
ice.js 官方提供 i18n 国际化插件,支持在应用快速开启国际化能力。核心特性包括:
|
|
|
|
|
|
|
|
|
|
1. 支持自动处理和生成国际化路由
|
|
|
|
|
2. 完美支持 SSR 和 SSG,以获得更好的 SEO 优化
|
|
|
|
|
3. 支持自动重定向到偏好语言对应的页面
|
|
|
|
|
4. 不耦合任何一个 i18n 库(流行的 React i18n 库有 [react-intl](https://formatjs.io/docs/getting-started/installation/)、[react-i18next](https://react.i18next.com/) 等),你可以选择任一国际化的库来为你的应用设置国际化
|
|
|
|
|
|
|
|
|
|
<details open>
|
|
|
|
|
<summary>使用国际化插件的示例</summary>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>
|
|
|
|
|
<a href="https://github.com/alibaba/ice/tree/master/examples/with-i18n" target="_blank" rel="noopener noreferrer">
|
|
|
|
|
with-i18n
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</details>
|
|
|
|
|
|
|
|
|
|
:::tip
|
|
|
|
|
|
|
|
|
|
如果应用不需要使用国际化路由,你可以参考以下例子来让你的项目支持国际化:
|
|
|
|
|
|
|
|
|
|
- [with-antd5](https://github.com/alibaba/ice/tree/master/examples/with-antd5)
|
|
|
|
|
- [with-fusion](https://github.com/alibaba/ice/tree/master/examples/with-fusion)
|
|
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
|
|
|
首先,我们需要在终端执行以下命令安装插件:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
$ npm i @ice/plugin-i18n -D
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
然后在 `ice.config.mts` 中添加插件和选项:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { defineConfig } from '@ice/app';
|
|
|
|
|
import i18n from '@ice/plugin-i18n';
|
|
|
|
|
|
|
|
|
|
export default defineConfig({
|
|
|
|
|
plugins: [
|
|
|
|
|
i18n({
|
|
|
|
|
locales: ['zh-CN', 'en-US', 'de'],
|
|
|
|
|
defaultLocale: 'zh-CN',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上面的 `en-US` 和 `zh-CN` 是国际化语言的缩写,它们均遵循标准的 [UTS 语言标识符](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers)。比如:
|
|
|
|
|
|
|
|
|
|
- `zh-CN`:中文(中国)
|
|
|
|
|
- `zh-HK`:中文(香港)
|
|
|
|
|
- `en-US`:英文(美国)
|
|
|
|
|
- `de`: 德文
|
|
|
|
|
|
|
|
|
|
## 国际化路由
|
|
|
|
|
|
|
|
|
|
国际化路由是指在页面路由地址中包含了当前页面的语言,一个国际化路由对应一个语言。
|
|
|
|
|
|
|
|
|
|
假设现在插件的选项配置是:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { defineConfig } from '@ice/app';
|
|
|
|
|
import i18n from '@ice/plugin-i18n';
|
|
|
|
|
|
|
|
|
|
export default defineConfig({
|
|
|
|
|
plugins: [
|
|
|
|
|
i18n({
|
|
|
|
|
locales: ['zh-CN', 'en-US', 'nl-NL'],
|
|
|
|
|
defaultLocale: 'zh-CN',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
假设我们有一个页面 `src/pages/home.tsx`,那么将会一一对应自动生成以下的路由:
|
|
|
|
|
|
|
|
|
|
- `/home`:显示 `zh-CN` 语言,默认语言对应的路由不包含语言前缀
|
|
|
|
|
- `/en-US/home`:显示 `en-US` 语言
|
|
|
|
|
- `/nl-NL/home`:显示 `nl-NL` 语言
|
|
|
|
|
|
|
|
|
|
访问不同的路由,将会显示该语言对应页面内容。
|
|
|
|
|
|
|
|
|
|
## 获取语言信息
|
|
|
|
|
|
|
|
|
|
### `getLocales()`
|
|
|
|
|
|
|
|
|
|
`getAllLocales()` 用于获取当前应用支持的所有语言。
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { getAllLocales } from 'ice';
|
|
|
|
|
|
|
|
|
|
console.log(getAllLocales()); // ['zh-CN', 'en-US']
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `getDefaultLocale()`
|
|
|
|
|
|
|
|
|
|
`getDefaultLocale()` 用于获取应用配置的默认语言。
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { getDefaultLocale } from 'ice';
|
|
|
|
|
|
|
|
|
|
console.log(getDefaultLocale()); // 'zh-CN'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `useLocale()`
|
|
|
|
|
|
|
|
|
|
在 Function 组件中使用 `useLocale()` Hook API,它的返回值是一个数组,包含两个值:
|
|
|
|
|
|
|
|
|
|
1. 当前页面的语言
|
|
|
|
|
2. 一个 set 函数用于更新当前页面的语言。注意,默认情况下调用此 set 函数时候,同时会更新 Cookie 中 `ice_locale` 的值为当前页面的语言。这样,再次访问该页面时,从服务端请求能得知当前用户的之前设置的偏好语言,以便返回对应语言的页面内容。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { useLocale } from 'ice';
|
|
|
|
|
|
|
|
|
|
export default function Home() {
|
|
|
|
|
const [locale, setLocale] = useLocale();
|
|
|
|
|
|
|
|
|
|
console.log('locale: ', locale); // 'en-US'
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* 切换语言为 zh-CN */}
|
|
|
|
|
<div onClick={() => setLocale('zh-CN')}>Set zh-CN</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `withLocale()`
|
|
|
|
|
|
|
|
|
|
使用 `withLocale()` 方法包裹的 Class 组件,组件的 Props 会包含 `locale` 和 `setLocale()` 函数,可以查看和修改当前页面的语言。注意,默认情况下调用 `setLocale()`,会更新 Cookie 中 `ice_locale` 的值为当前页面的语言。这样,再次访问该页面时,从服务端请求能得知当前用户的之前设置的偏好语言,以便返回对应语言的页面内容。
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { withLocale } from 'ice';
|
|
|
|
|
|
|
|
|
|
function Home({ locale, setLocale }) {
|
|
|
|
|
console.log('locale: ', locale); // 'en-US'
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* 切换语言为 zh-CN */}
|
|
|
|
|
<div onClick={() => setLocale('zh-CN')}>Set zh-CN</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default withLocale(Home);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 切换语言
|
|
|
|
|
|
|
|
|
|
推荐使用 `setLocale()` 方法配合 `<Link>` 组件或者 `useNavigate()` 方法进行语言切换:
|
|
|
|
|
|
|
|
|
|
<Tabs>
|
|
|
|
|
<TabItem value="a" label="使用 <Link />">
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { useLocale, getAllLocales, Link, useLocation } from 'ice';
|
|
|
|
|
|
|
|
|
|
export default function Layout() {
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const [activeLocale, setLocale] = useLocale();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<main>
|
|
|
|
|
<p><b>Current locale: </b>{activeLocale}</p>
|
|
|
|
|
|
|
|
|
|
<b>Choose language: </b>
|
|
|
|
|
<ul>
|
|
|
|
|
{
|
|
|
|
|
getAllLocales().map((locale: string) => {
|
|
|
|
|
return (
|
|
|
|
|
<li key={locale}>
|
|
|
|
|
<Link
|
|
|
|
|
to={location.pathname}
|
|
|
|
|
onClick={() => setLocale(locale)}
|
|
|
|
|
>
|
|
|
|
|
{locale}
|
|
|
|
|
</Link>
|
|
|
|
|
</li>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</ul>
|
|
|
|
|
</main>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
</TabItem>
|
|
|
|
|
<TabItem value="b" label="使用 useNavigate()">
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { useLocale, useNavigate, useLocation } from 'ice';
|
|
|
|
|
|
|
|
|
|
export default function Layout() {
|
|
|
|
|
const [, setLocale] = useLocale();
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const switchToZHCN = () => {
|
|
|
|
|
setLocale('zh-CN');
|
|
|
|
|
navigate(location.pathname);
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<main>
|
|
|
|
|
<div onClick={switchToZHCN}>
|
|
|
|
|
点我切换到中文
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
</TabItem>
|
|
|
|
|
</Tabs>
|
|
|
|
|
|
|
|
|
|
## 路由自动重定向
|
|
|
|
|
|
|
|
|
|
路由自动重定向是指,如果当前访问的页面是根路由(`/`),将会根据当前语言环境自动跳转到对应的国际化路由。
|
|
|
|
|
|
|
|
|
|
默认情况下,路由自动重定向的功能是关闭的。如果需要开启,则需要加入以下内容:
|
|
|
|
|
|
|
|
|
|
```diff
|
|
|
|
|
import { defineConfig } from '@ice/app';
|
|
|
|
|
import i18n from '@ice/plugin-i18n';
|
|
|
|
|
|
|
|
|
|
export default defineConfig({
|
|
|
|
|
plugins: [
|
|
|
|
|
i18n({
|
|
|
|
|
locales: ['zh-CN', 'en-US', 'de'],
|
|
|
|
|
defaultLocale: 'zh-CN',
|
|
|
|
|
+ autoRedirect: true,
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
其中,语言环境的识别顺序如下:
|
|
|
|
|
|
|
|
|
|
- `CSR`:cookie 中 `ice_locale` 的值 > `window.navigator.language` > `defaultLocale`
|
|
|
|
|
- `SSR`:cookie 中 `ice_locale` 的值 > `Request Header` 中的 `Accept-Language` > `defaultLocale`
|
|
|
|
|
|
|
|
|
|
在部署阶段,路由自动重定向的功能需要配合 Node 中间件使用才能生效。比如:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import express from 'express';
|
|
|
|
|
import { renderToHTML } from './build/server/index.mjs';
|
|
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
|
|
|
|
|
app.use(express.static('build', {}));
|
|
|
|
|
|
|
|
|
|
app.use(async (req, res) => {
|
|
|
|
|
const { statusCode, statusText, headers, value: body } = await renderToHTML({ req, res });
|
|
|
|
|
res.statusCode = statusCode;
|
|
|
|
|
res.statusMessage = statusText;
|
|
|
|
|
Object.entries((headers || {}) as Record<string, string>).forEach(([name, value]) => {
|
|
|
|
|
res.setHeader(name, value);
|
|
|
|
|
});
|
|
|
|
|
if (body && req.method !== 'HEAD') {
|
|
|
|
|
res.end(body);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 禁用 Cookie
|
|
|
|
|
|
|
|
|
|
在上面的章节中提到,用户设置的偏好语言是存放在 Cookie 中的 `ice_locale`,调用 `setLocale()` 时会更新到 Cookie 中,并且路由重定向和路由跳转的时候依赖 `ice_locale` 的值。
|
|
|
|
|
|
|
|
|
|
假设有这么一个场景,用户拒绝接受 Cookie,为了保护隐私,这样就不能把偏好语言写到 Cookie 中了。因此需要做以下的配置来禁用 Cookie:
|
|
|
|
|
|
|
|
|
|
```ts title="src/app.ts"
|
|
|
|
|
import { defineI18nConfig } from '@ice/plugin-i18n/types';
|
|
|
|
|
|
|
|
|
|
export const i18nConfig = defineI18nConfig(() => ({
|
|
|
|
|
// 可以是一个 function
|
|
|
|
|
disabledCookie: () => {
|
|
|
|
|
if (import.meta.renderer === 'client') {
|
|
|
|
|
return window.localStorage.getItem('acceptCookie') === 'yes';
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
// 也可以是 boolean 值
|
|
|
|
|
// disabledCookie: true,
|
|
|
|
|
}));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这样,就禁用掉了 Cookie 的写入了。在切换语言的时候需要在 `state` 对象中显式传入即将要切换的新语言的值:
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
import { Link, useLocale } from 'ice';
|
|
|
|
|
|
|
|
|
|
export default function Home() {
|
|
|
|
|
const [, setLocale] = useLocale();
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Link
|
|
|
|
|
to="/"
|
|
|
|
|
onClick={() => setLocale('zh-CN')}
|
|
|
|
|
state={{ locale: 'zh-CN' }}
|
|
|
|
|
>
|
|
|
|
|
切换到 zh-CN
|
|
|
|
|
</Link>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## SSG
|
|
|
|
|
|
|
|
|
|
在开启 SSG 功能后,将根据配置的 `locales` 的值,在 `build` 阶段会生成不同语言对应的 HTML。
|
|
|
|
|
|
|
|
|
|
比如我们有以下的目录结构,包含 `about` 和 `index` 两个页面:
|
|
|
|
|
|
|
|
|
|
```md
|
|
|
|
|
├── src/pages
|
|
|
|
|
| ├── about.tsx
|
|
|
|
|
| └── index.tsx
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
假如插件的配置是:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { defineConfig } from '@ice/app';
|
|
|
|
|
import i18n from '@ice/plugin-i18n';
|
|
|
|
|
|
|
|
|
|
export default defineConfig({
|
|
|
|
|
plugins: [
|
|
|
|
|
i18n({
|
|
|
|
|
locales: ['zh-CN', 'en-US'],
|
|
|
|
|
defaultLocale: 'zh-CN',
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
那么将会生成 4 个 HTML 文件:
|
|
|
|
|
|
|
|
|
|
```md
|
|
|
|
|
├── build
|
|
|
|
|
| ├── about
|
|
|
|
|
| | └── index.html
|
|
|
|
|
| ├── en-US
|
|
|
|
|
| | ├── about
|
|
|
|
|
| | | └── index.html
|
|
|
|
|
| | └── index.html
|
|
|
|
|
| ├── index.html
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 插件选项
|
|
|
|
|
|
|
|
|
|
### `locales`
|
|
|
|
|
|
|
|
|
|
- **类型:**`string[]`
|
|
|
|
|
|
|
|
|
|
用于声明该应用支持的语言。
|
|
|
|
|
|
|
|
|
|
### `defaultLocale`
|
|
|
|
|
|
|
|
|
|
- **类型:**`string`
|
|
|
|
|
|
|
|
|
|
声明该应用默认的语言。需要注意的是, `locales` 数组必须包含 `defaultLocale` 的值。
|
|
|
|
|
|
|
|
|
|
### `autoRedirect`
|
|
|
|
|
|
|
|
|
|
- **类型:**`boolean`
|
|
|
|
|
- **默认值:**`false`
|
|
|
|
|
|
|
|
|
|
默认不会自动重定向到用户偏好语言对应的页面。如果设置为 `true`,在生产环境下,一般需要配合 Node 中间件一起使用才能生效。[详见](#路由自动重定向)
|