Creation:2026-03-20Last update:2026-05-31

    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


    </Step>

    </Steps>

    Declaring Content

    Create a *.content.ts file next to each component. Intlayer picks it up automatically during compilation.

    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

    "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 Internationalised Components

    With the IntlayerProvider decorator in place, your stories work exactly as before. The locale toolbar controls the active locale for the entire canvas:

    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:

    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");
      },
    };

    Additional Resources