国际化
ice.js 官方提供 i18n 国际化插件,支持在应用快速开启国际化能力。核心特性包括:
- 支持自动处理和生成国际化路由
- 完美支持 SSR 和 SSG,以获得更好的 SEO 优化
- 支持自动重定向到偏好语言对应的页面
- 不耦合任何一个 i18n 库(流行的 React i18n 库有 react-intl、react-i18next 等),你可以选择任一国际化的库来为你的应用设置国际化
使用国际化插件的示例
如果应用不需要使用国际化路由,你可以参考以下例子来让你的项目支持国际化:
快速开始
首先,我们需要在终端执行以下命令安装插件:
$ npm i @ice/plugin-i18n -D
然后在 ice.config.mts
中添加插件和选项:
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 语言标识符。比如:
zh-CN
:中文(中国)zh-HK
:中文(香港)en-US
:英文(美国)de
: 德文
国际化路由
国际化路由是指在页面路由地址中包含了当前页面的语言,一个国际化路由对应一个语言。
假设现在插件的选项配置是:
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()
用于获取当前应用支持的所有语言。
import { getAllLocales } from 'ice';
console.log(getAllLocales()); // ['zh-CN', 'en-US']
getDefaultLocale()
getDefaultLocale()
用于获取应用配置的默认语言。
import { getDefaultLocale } from 'ice';
console.log(getDefaultLocale()); // 'zh-CN'
useLocale()
在 Function 组件中使用 useLocale()
Hook API,它的返回值是一个数组,包含两个值:
- 当前页面的语言
- 一个 set 函数用于更新当前页面的语言。注意,默认情况下调用此 set 函数时候,同时会更新 Cookie 中
ice_locale
的值为当前页面的语言。这样,再次访问该页面时,从服务端请求能得知当前用户的之前设置的偏好语言,以便返回对应语言的页面内容。
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
的值为当前页面的语言。这样,再次访问该页面时,从服务端请求能得知当前用户的之前设置的偏好语言,以便返回对应语言的页面内容。
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()
方法进行语言切换:
- 使用 <Link />
- 使用 useNavigate()
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>
);
}
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>
);
}
路由自动重定向
路由自动重定向是指,如果当前访问的页面是根路由(/
),将会根据当前语言环境自动跳转到对应的国际化路由。
默认情况下,路由自动重定向的功能是关闭的。如果需要开启,则需要加入以下内容:
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 中间件使用才能生效。比如:
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:
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
对象中显式传入即将要切换的新语言的值:
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
两个页面:
├── src/pages
| ├── about.tsx
| └── index.tsx
假如插件的配置是:
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 文件:
├── build
| ├── about
| | └── index.html
| ├── en-US
| | ├── about
| | | └── index.html
| | └── index.html
| ├── index.html
插件选项
locales
- 类型:
string[]
用于声明该应用支持的语言。
defaultLocale
- 类型:
string
声明该应用默认的语言。需要注意的是, locales
数组必须包含 defaultLocale
的值。
autoRedirect
- 类型:
boolean
- 默认值:
false
默认不会自动重定向到用户偏好语言对应的页面。如果设置为 true
,在生产环境下,一般需要配合 Node 中间件一起使用才能生效。详见