Ask your question and get a summary of the document by referencing this page and the AI provider of your choice
Version History
- "Update Solid useIntlayer API usage to direct property access"v8.9.05/4/2026
- "Init doc"v8.4.53/20/2026
If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
Intlayer with Storybook
Table of Contents
Why Intlayer over alternatives?
Compared to main solutions like storybook-react-i18next or i18next, Intlayer is a solution that comes with integrated optimizations such as:
Intlayer is optimized to work perfectly with Storybook by offering multilingual story decorators, locale switching, and all the features needed for scaling internationalization (i18n) across your design system.
Instead of loading massive JSON files into your pages, load only the necessary content. Intlayer helps reduce your bundle and page sizes by up to 50%.
Scoping your application's content facilitates maintenance for large-scale applications. You can duplicate or delete a single feature folder without the mental burden of reviewing your entire content codebase. Additionally, Intlayer is fully typed to ensure your content's accuracy.
Co-locating content reduces the context needed by Large Language Models (LLMs). Intlayer also comes with a suite of tools, such as a CLI to test for missing translations,LSP, MCP, and agent skills, to make the developer experience (DX) even smoother for AI agents.
Use automation to translate in your CI/CD pipeline using the LLM of your choice at the cost of your AI provider. Intlayer also offers a compiler to automate content extraction, as well as a web platform to help translate in the background.
Connecting massive JSON files to components can lead to performance and reactivity issues. Intlayer optimizes your content loading at build time.
More than just an i18n solution, Intlayer provides an self-hosted visual editor and a full CMS to help you manage your multilingual content in real-time, making collaboration with translators, copywriters, and other team members seamless. Content can be stored locally and/or remotely.
Why use Intlayer with Storybook?
Storybook is the industry-standard tool for developing and documenting UI components in isolation. Combining it with Intlayer lets you:
- Preview every locale directly inside the Storybook canvas using a toolbar switcher.
- Catch missing translations before they reach production.
- Document multilingual components with real, type-safe content rather than hard-coded strings.
Step-by-Step Setup
Install Dependencies
Copy the code to the clipboard
npm install intlayer react-intlayernpm install vite-intlayer --save-devOpen the table in a modal to view all data content clearly
| Package | Role |
|---|---|
intlayer | Core - config, content compilation, CLI |
react-intlayer | React bindings - IntlayerProvider, useIntlayer hook |
vite-intlayer | Vite plugin - watches and compiles content declaration files |
Create an Intlayer Configuration
Create intlayer.config.ts at the root of your project (or inside your design-system package):
Copy the code to the clipboard
import { Locales, type IntlayerConfig } from "intlayer";const config: IntlayerConfig = { internationalization: { locales: [ Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, // add more locales as needed ], defaultLocale: Locales.ENGLISH, }, content: { contentDir: ["./src"], // where your *.content.ts files live },};export default config;For the full list of options see the configuration reference.
Add the Vite Plugin to Storybook
Storybook's viteFinal hook lets you extend the internal Vite config. Import and add the intlayer() plugin there:
Copy the code to the clipboard
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", // …other addons ], 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()], })); return mergeConfig(baseConfig, viteConfig(env)); },};export default config;The intlayer() plugin watches your *.content.ts files and rebuilds dictionaries automatically whenever they change during Storybook development.
Add the `IntlayerProvider` Decorator and a Locale Toolbar
Storybook's preview file is the right place to wrap every story with the IntlayerProvider and expose a locale switcher in the toolbar:
Copy the code to the clipboard
import type { Preview, StoryContext } from "@storybook/react";import { IntlayerProvider } from "react-intlayer";const preview: Preview = { // Wrap every story inside the IntlayerProvider decorators: [ (Story, context: StoryContext) => { const locale = context.globals.locale ?? "en"; return ( <IntlayerProvider locale={locale}> <Story /> </IntlayerProvider> ); }, ], // Expose a locale switcher in the Storybook toolbar globalTypes: { locale: { description: "Active locale", defaultValue: "en", toolbar: { title: "Locale", 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;Thelocalevalues must match the locales declared in yourintlayer.config.ts.
</Step>
</Steps>
Declaring Content
Create a *.content.ts file next to each component. Intlayer picks it up automatically during compilation.
Copy the code to the clipboard
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;For more content declaration formats and features see the content declaration documentation.
Using useIntlayer in a Component
Copy the code to the clipboard
"use client";import { type FC } from "react";import { useIntlayer } from "react-intlayer";type CopyButtonProps = { content: string;};export const CopyButton: FC<CopyButtonProps> = ({ content }) => { const { label } = useIntlayer("copy-button"); return ( <button onClick={() => navigator.clipboard.writeText(content)} aria-label={label.value} title={label.value} > Copy </button> );};useIntlayer returns the compiled dictionary for the current locale provided by the nearest IntlayerProvider. Switching the locale in the Storybook toolbar automatically re-renders the story with updated translations.
Writing Stories for Internationalized Components
With the IntlayerProvider decorator in place, your stories work exactly as before. The locale toolbar controls the active locale for the entire canvas:
Copy the code to the clipboard
import type { Meta, StoryObj } from "@storybook/react";import { CopyButton } from ".";const meta: Meta<typeof CopyButton> = { title: "Components/CopyButton", component: CopyButton, tags: ["autodocs"], argTypes: { content: { control: "text" }, },};export default meta;type Story = StoryObj<typeof CopyButton>;/** Default story - switch the locale in the toolbar to preview translations. */export const Default: Story = { args: { content: "npm install intlayer react-intlayer", },};/** Renders the button inside a code block, a common real-world use case. */export const InsideCodeBlock: Story = { render: (args) => ( <div style={{ position: "relative", display: "inline-block" }}> <pre style={{ background: "#1e1e1e", color: "#fff", padding: "1rem" }}> <code>{args.content}</code> </pre> <CopyButton content={args.content} style={{ position: "absolute", top: 8, right: 8 }} /> </div> ), args: { content: "npx intlayer init", },};Each story inherits the locale global from the toolbar, so you can verify every locale without changing any story code.
Testing Translations in Stories
Use Storybook's play functions to assert that the correct translated text is rendered for a given locale:
Copy the code to the clipboard
import type { Meta, StoryObj } from "@storybook/react";import { expect, within } from "@storybook/test";import { CopyButton } from ".";const meta: Meta<typeof CopyButton> = { title: "Components/CopyButton", component: CopyButton, tags: ["autodocs"],};export default meta;type Story = StoryObj<typeof CopyButton>;export const AccessibleLabel: Story = { args: { content: "Hello World" }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); const button = canvas.getByRole("button"); // Verify the button has a non-empty accessible name await expect(button).toHaveAccessibleName(); // Verify the button is not disabled await expect(button).not.toBeDisabled(); // Verify keyboard accessibility await expect(button).toHaveAttribute("tabindex", "0"); },};