diff --git a/frontend/.storybook/I18nStoryWrapper.tsx b/frontend/.storybook/I18nStoryWrapper.tsx new file mode 100644 index 000000000..f05ca54a0 --- /dev/null +++ b/frontend/.storybook/I18nStoryWrapper.tsx @@ -0,0 +1,30 @@ +/** + * @file Storybook decorator, enabling internationalization for each story. + * @see https://storybook.js.org/docs/writing-stories/decorators + */ +import { StoryContext } from "@storybook/react"; + +import { NextIntlClientProvider } from "next-intl"; +import React from "react"; + +import { defaultLocale, formats, timeZone } from "../src/i18n/config"; + +const I18nStoryWrapper = ( + Story: React.ComponentType, + context: StoryContext, +) => { + const locale = (context.globals.locale as string) ?? defaultLocale; + + return ( + + + + ); +}; + +export default I18nStoryWrapper; diff --git a/frontend/.storybook/i18next.js b/frontend/.storybook/i18next.js deleted file mode 100644 index a3eeb9d9c..000000000 --- a/frontend/.storybook/i18next.js +++ /dev/null @@ -1,22 +0,0 @@ -// Configure i18next for Storybook -// See https://storybook.js.org/addons/storybook-react-i18next -import i18nConfig from "../next-i18next.config"; -import i18next from "i18next"; -import LanguageDetector from "i18next-browser-languagedetector"; -import Backend from "i18next-http-backend"; -import { initReactI18next } from "react-i18next"; - -i18next - .use(initReactI18next) - .use(LanguageDetector) - .use(Backend) - .init({ - ...i18nConfig, - backend: { - loadPath: `${ - process.env.NEXT_PUBLIC_BASE_PATH ?? "" - }/locales/{{lng}}/{{ns}}.json`, - }, - }); - -export default i18next; diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js index 6ef68f43b..a673aa99b 100644 --- a/frontend/.storybook/main.js +++ b/frontend/.storybook/main.js @@ -25,11 +25,7 @@ function blockSearchEnginesInHead(head) { */ const config = { stories: ["../stories/**/*.stories.@(mdx|js|jsx|ts|tsx)"], - addons: [ - "@storybook/addon-essentials", - "storybook-react-i18next", - "@storybook/addon-designs", - ], + addons: ["@storybook/addon-essentials", "@storybook/addon-designs"], framework: { name: "@storybook/nextjs", options: { diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js deleted file mode 100644 index d75bcc900..000000000 --- a/frontend/.storybook/preview.js +++ /dev/null @@ -1,43 +0,0 @@ -// @ts-check -import i18nConfig from "../next-i18next.config"; - -// Apply global styling to our stories -import "../src/styles/styles.scss"; - -// Import i18next config. -import i18n from "./i18next.js"; - -// Generate the options for the Language menu using the locale codes. -// Teams can override these labels, but this helps ensure that the language -// is at least exposed in the list. -const initialLocales = {}; -i18nConfig.i18n.locales.forEach((locale) => (initialLocales[locale] = locale)); - -const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - // Configure i18next and locale/dropdown options. - i18n, -}; - -/** - * @type {import("@storybook/react").Preview} - */ -const preview = { - parameters, - globals: { - locale: "en", - locales: { - ...initialLocales, - en: "English", - es: "EspaƱol", - }, - }, -}; - -export default preview; diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx new file mode 100644 index 000000000..9b135fadc --- /dev/null +++ b/frontend/.storybook/preview.tsx @@ -0,0 +1,64 @@ +/** + * @file Setup the toolbar, styling, and global context for each Storybook story. + * @see https://storybook.js.org/docs/configure#configure-story-rendering + */ +import { Loader, Preview } from "@storybook/react"; + +import "../src/styles/styles.scss"; + +import { defaultLocale, locales } from "../src/i18n/config"; +import { getMessagesWithFallbacks } from "../src/i18n/getMessagesWithFallbacks"; +import I18nStoryWrapper from "./I18nStoryWrapper"; + +const parameters = { + nextjs: { + appDirectory: true, + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + options: { + storySort: { + method: "alphabetical", + order: [ + "Welcome", + "Core", + // Storybook infers the title when not explicitly set, but is case-sensitive + // so we need to explicitly set both casings here for this to properly sort. + "Components", + "components", + "Templates", + "Pages", + "pages", + ], + }, + }, +}; + +const i18nMessagesLoader: Loader = async (context) => { + const messages = await getMessagesWithFallbacks( + context.globals.locale as string, + ); + return { messages }; +}; + +const preview: Preview = { + loaders: [i18nMessagesLoader], + decorators: [I18nStoryWrapper], + parameters, + globalTypes: { + locale: { + description: "Active language", + defaultValue: defaultLocale, + toolbar: { + icon: "globe", + items: locales, + }, + }, + }, +}; + +export default preview;