ice/website/docs/guide/advanced/i18n.md

381 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 国际化
---
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 中间件一起使用才能生效。[详见](#路由自动重定向)