mirror of https://github.com/alibaba/ice.git
381 lines
9.8 KiB
Markdown
381 lines
9.8 KiB
Markdown
---
|
||
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 中间件一起使用才能生效。[详见](#路由自动重定向)
|