---
createdAt: 2026-03-20
updatedAt: 2026-05-31
title: "Storybook i18n - 翻译你的应用的完整指南"
description: "告别 i18next。2026 年构建多语言 (i18n) Storybook 应用的完整指南。使用 AI 代理翻译并优化包体积、SEO 和性能。"
keywords:
- 国际化
- 文档
- Intlayer
- Storybook
- React
- i18n
- TypeScript
- Vite
- Webpack
slugs:
- doc
- storybook
history:
- version: 8.9.0
date: 2026-05-04
changes: "更新 Solid useIntlayer API 用法以直接访问属性"
- version: 8.4.5
date: 2026-03-20
changes: "Init doc"
author: aymericzip
---
# 在 Storybook 中使用 Intlayer
## 目录
## 为什么选择 Inlayer 而不是替代品?
与“storybook-react-i18next”或“i18next”等主要解决方案相比,Intlayer是一个具有集成优化的解决方案,例如:
Intlayer 经过优化,可与 Storybook 完美配合,提供**多语言故事装饰器**、**区域设置切换**以及在整个设计系统中扩展国际化 (i18n) 所需的所有功能。
不要将大量 JSON 文件加载到页面中,而只需加载必要的内容。 Intlayer 有助于**将捆绑包和页面大小减少多达 50%**。
确定应用程序内容的范围**有利于大型应用程序的维护**。您可以复制或删除单个功能文件夹,而无需承担检查整个内容代码库的精神负担。此外,Intlayer 具有**完全类型化 (fully typed)**,以确保您的内容的准确性。
共置内容**减少大型语言模型 (LLM) 所需的上下文**。 Intlayer 还附带了一套工具,例如用于测试缺失翻译的 **CLI**、**[LSP](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/lsp.md)**、**[MCP](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/mcp_server.md)** 和 **[agent skills](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/agent_skills.md)**,使 AI 代理的开发者体验 (DX) 更加流畅。
使用您选择的法学硕士,通过自动化在 CI/CD 管道中进行翻译,而费用由您的 AI 提供商承担。 Intlayer 还提供了一个**编译器**来自动提取内容,以及一个[网络平台](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md)来帮助**在后台翻译**。
将大量 JSON 文件连接到组件可能会导致性能和反应性问题。 Intlayer 可在构建时 (build time)优化您的内容加载。
Intlayer 不仅仅是一个 i18n 解决方案,还提供了一个**自托管的[可视化编辑器](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_visual_editor.md)**和一个**[完整的 CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md)** 来帮助您管理多语言内容**实时**,与译员、文案人员和其他团队成员无缝协作。内容可以本地和/或远程存储。
## 为什么要结合 Storybook 使用 Intlayer?
Storybook 是开发和记录 UI 组件的行业标准工具。通过将它与 Intlayer 结合使用,您可以:
- **直接在 Storybook 画布中预览每种语言**:使用工具栏切换器。
- **提前捕获缺失的翻译**:在进入生产环境之前修正问题。
- **记录多语言组件**:使用真实的、类型安全的内容,而不是硬编码的字符串。
## 逐步设置
```bash packageManager="npm"
npm install intlayer react-intlayer
npm install vite-intlayer --save-dev
```
```bash packageManager="pnpm"
pnpm add intlayer react-intlayer
pnpm add vite-intlayer --save-dev
```
```bash packageManager="yarn"
yarn add intlayer react-intlayer
yarn add vite-intlayer --save-dev
```
```bash packageManager="bun"
bun add intlayer react-intlayer
bun add vite-intlayer --dev
```
| Package | 作用 |
| ---------------- | --------------------------------------------------- |
| `intlayer` | 核心 - 配置、内容编译、CLI |
| `react-intlayer` | React 绑定 - `IntlayerProvider`、`useIntlayer` hook |
| `vite-intlayer` | Vite 插件 - 监视和编译内容声明文件 |
在项目的根目录(或在你的设计系统包内)创建 `intlayer.config.ts`:
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
import { Locales, type IntlayerConfig } from "intlayer";
const config: IntlayerConfig = {
internationalization: {
locales: [
Locales.ENGLISH,
Locales.FRENCH,
Locales.SPANISH,
// 根据需要添加更多语言
],
defaultLocale: Locales.ENGLISH,
},
content: {
contentDir: ["./src"], // 你的 *.content.ts 文件所在位置
},
};
export default config;
```
> 有关完整的选项列表,请参阅[配置参考](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/configuration.md)。
Storybook 的 `viteFinal` hook 可让你扩展内部 Vite 配置。在那里导入并添加 `intlayer()` 插件:
```typescript fileName=".storybook/main.ts" codeFormat="typescript"
import type { StorybookConfig } from "@storybook/react-vite";
import { defineConfig, mergeConfig } from "vite";
import { intlayer } from "vite-intlayer";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-essentials",
// …其他插件
],
framework: {
name: "@storybook/react-vite",
options: {},
},
async viteFinal(baseConfig, { configType }) {
const env = {
command: configType === "DEVELOPMENT" ? "serve" : "build",
mode: configType === "DEVELOPMENT" ? "development" : "production",
} as const;
const viteConfig = defineConfig(() => ({
plugins: [
intlayer({
proxy: {
ignore: (req) => req.url?.startsWith("/api"),
},
}),
],
}));
return mergeConfig(baseConfig, viteConfig(env));
},
};
export default config;
```
`intlayer()` 插件会监视你的 `*.content.ts` 文件,并在 Storybook 开发过程中任何更改时自动重建字典。
Storybook 的 `preview` 文件是用 `IntlayerProvider` 包装每个故事并在工具栏中公开语言切换器的合适位置:
```tsx fileName=".storybook/preview.tsx" codeFormat="typescript"
import type { Preview, StoryContext } from "@storybook/react";
import { IntlayerProvider } from "react-intlayer";
const preview: Preview = {
// 用 IntlayerProvider 包装每个故事
decorators: [
(Story, context: StoryContext) => {
const locale = context.globals.locale ?? "en";
return (
);
},
],
// 在 Storybook 工具栏中公开语言切换器
globalTypes: {
locale: {
description: "活跃的语言",
defaultValue: "en",
toolbar: {
title: "语言",
icon: "globe",
items: [
{ value: "en", title: "English" },
{ value: "fr", title: "Français" },
{ value: "es", title: "Español" },
],
dynamicTitle: true,
},
},
},
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
```
> `locale` 值必须与 `intlayer.config.ts` 中声明的语言匹配。
```bash packageManager="npm"
npm install intlayer react-intlayer
npm install @intlayer/webpack --save-dev
```
```bash packageManager="pnpm"
pnpm add intlayer react-intlayer
pnpm add @intlayer/webpack --save-dev
```
```bash packageManager="yarn"
yarn add intlayer react-intlayer
yarn add @intlayer/webpack --save-dev
```
```bash packageManager="bun"
bun add intlayer react-intlayer
bun add @intlayer/webpack --dev
```
在项目的根目录创建 `intlayer.config.ts`:
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
import { Locales, type IntlayerConfig } from "intlayer";
const config: IntlayerConfig = {
internationalization: {
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
defaultLocale: Locales.ENGLISH,
},
content: {
contentDir: ["./src"],
},
};
export default config;
```
对于基于 Webpack 的 Storybook 设置(例如 `@storybook/react-webpack5`),通过 `webpackFinal` 扩展 webpack 配置以添加 Intlayer 别名和加载器:
```typescript fileName=".storybook/main.ts" codeFormat="typescript"
import type { StorybookConfig } from "@storybook/react-webpack5";
import { IntlayerPlugin } from "@intlayer/webpack";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: ["@storybook/addon-essentials"],
framework: {
name: "@storybook/react-webpack5",
options: {},
},
webpackFinal: async (baseConfig) => {
baseConfig.plugins = [...(baseConfig.plugins ?? []), new IntlayerPlugin()];
return baseConfig;
},
};
export default config;
```
与 Vite 设置相同 - 在 `.storybook/preview.tsx` 中添加装饰器和全局语言类型:
```tsx fileName=".storybook/preview.tsx" codeFormat="typescript"
import type { Preview, StoryContext } from "@storybook/react";
import { IntlayerProvider } from "react-intlayer";
const preview: Preview = {
decorators: [
(Story, context: StoryContext) => {
const locale = context.globals.locale ?? "en";
return (
);
},
],
globalTypes: {
locale: {
description: "活跃的语言",
defaultValue: "en",
toolbar: {
title: "语言",
icon: "globe",
items: [
{ value: "en", title: "English" },
{ value: "fr", title: "Français" },
{ value: "es", title: "Español" },
],
dynamicTitle: true,
},
},
},
};
export default preview;
```
## 声明内容
在每个组件旁边创建一个 `*.content.ts` 文件。Intlayer 会在编译期间自动识别它。
```typescript fileName="src/components/CopyButton/CopyButton.content.ts" codeFormat={["typescript", "esm", "commonjs"]}
import { type Dictionary, t } from "intlayer";
const copyButtonContent = {
key: "copy-button",
content: {
label: t({
en: "Copy content",
fr: "Copier le contenu",
es: "Copiar contenido",
}),
},
} satisfies Dictionary;
export default copyButtonContent;
```
> 有关更多内容声明格式和功能,请参阅 [内容声明文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/dictionary/content_file.md)。
## 在组件中使用 `useIntlayer`
```tsx fileName="src/components/CopyButton/index.tsx" codeFormat="typescript"
"use client";
import { type FC } from "react";
import { useIntlayer } from "react-intlayer";
type CopyButtonProps = {
content: string;
};
export const CopyButton: FC = ({ content }) => {
const { label } = useIntlayer("copy-button");
return (
);
};
```
`useIntlayer` 会返回由最近的 `IntlayerProvider` 提供的当前语言的编译后的字典。在 Storybook 工具栏中切换语言会自动重新渲染对应的 story 并更新翻译。
## 为国际化组件编写 Story
在配置好 `IntlayerProvider` 装饰器之后,您的 story 工作方式与以前完全相同。语言工具栏控制整个画布的当前语言:
```tsx fileName="src/components/CopyButton/CopyButton.stories.tsx" codeFormat="typescript"
import type { Meta, StoryObj } from "@storybook/react";
import { CopyButton } from ".";
const meta: Meta = {
title: "Components/CopyButton",
component: CopyButton,
tags: ["autodocs"],
argTypes: {
content: { control: "text" },
},
};
export default meta;
type Story = StoryObj;
/** 默认 Story - 在工具栏中切换语言来预览翻译。 */
export const Default: Story = {
args: {
content: "npm install intlayer react-intlayer",
},
};
/** 在代码块内渲染该按钮,这是一个常见的现实用例。 */
export const InsideCodeBlock: Story = {
render: (args) => (
),
args: {
content: "npx intlayer init",
},
};
```
> 每个 story 都会从工具栏继承 `locale` 全局变量,因此您可以在不更改任何 story 代码的情况下验证每种语言。
## 在 Story 中测试翻译
使用 Storybook 的 `play` 函数来断言在指定语言下是否渲染了正确的翻译文本:
```tsx fileName="src/components/CopyButton/CopyButton.stories.tsx" codeFormat="typescript"
import type { Meta, StoryObj } from "@storybook/react";
import { expect, within } from "@storybook/test";
import { CopyButton } from ".";
const meta: Meta = {
title: "Components/CopyButton",
component: CopyButton,
tags: ["autodocs"],
};
export default meta;
type Story = StoryObj;
export const AccessibleLabel: Story = {
args: { content: "Hello World" },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button");
// 验证按钮是否具有非空的无障碍名称
await expect(button).toHaveAccessibleName();
// 验证按钮未被禁用
await expect(button).not.toBeDisabled();
// 验证键盘可访问性
await expect(button).toHaveAttribute("tabindex", "0");
},
};
```
## 其他资源
- [Intlayer 配置参考](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/configuration.md)
- [内容声明文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/dictionary/content_file.md)
- [Intlayer 命令行界面文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/cli/index.md)
- [Storybook 文档](https://storybook.js.org/docs)