diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts
index d895a0bc9..b4ffa1976 100644
--- a/frontend/.storybook/main.ts
+++ b/frontend/.storybook/main.ts
@@ -9,7 +9,7 @@ import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../{src,stories}/**/*.stories.@(js|jsx|ts|tsx)"],
- addons: ["storybook-react-i18next", "@storybook/addon-docs"],
+ addons: ["@storybook/addon-docs"],
framework: "@storybook/react-vite",
diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx
index a9abadc7d..7ba9e4218 100644
--- a/frontend/.storybook/preview.tsx
+++ b/frontend/.storybook/preview.tsx
@@ -4,15 +4,11 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
-import type {
- ArgTypes,
- Decorator,
- Parameters,
- Preview,
-} from "@storybook/react-vite";
+import type { Decorator, Preview } from "@storybook/react-vite";
import { TooltipProvider } from "@vector-im/compound-web";
import { initialize, mswLoader } from "msw-storybook-addon";
-import { useLayoutEffect } from "react";
+import { useEffect, useLayoutEffect } from "react";
+import { I18nextProvider } from "react-i18next";
import "../src/shared.css";
import i18n, { setupI18n } from "../src/i18n";
import { DummyRouter } from "../src/test-utils/router";
@@ -31,37 +27,12 @@ initialize(
setupI18n();
-export const parameters: Parameters = {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/,
- },
- },
-};
-
-export const globalTypes = {
- theme: {
- name: "Theme",
- defaultValue: "system",
- description: "Global theme for components",
- toolbar: {
- icon: "circlehollow",
- title: "Theme",
- items: [
- { title: "System", value: "system", icon: "browser" },
- { title: "Light", value: "light", icon: "sun" },
- { title: "Light (high contrast)", value: "light-hc", icon: "sun" },
- { title: "Dark", value: "dark", icon: "moon" },
- { title: "Dark (high contrast)", value: "dark-hc", icon: "moon" },
- ],
- },
- },
-} satisfies ArgTypes;
-
-const allThemesClasses = globalTypes.theme.toolbar.items.map(
- ({ value }) => `cpd-theme-${value}`,
-);
+const allThemesClasses = [
+ "cpd-theme-light",
+ "cpd-theme-light-hc",
+ "cpd-theme-dark",
+ "cpd-theme-dark-hc",
+];
const ThemeSwitcher: React.FC<{
theme: string;
@@ -86,6 +57,27 @@ const withThemeProvider: Decorator = (Story, context) => {
);
};
+const LocaleSwitcher: React.FC<{
+ locale: string;
+}> = ({ locale }) => {
+ useEffect(() => {
+ i18n.changeLanguage(locale);
+ }, [locale]);
+
+ return null;
+};
+
+const withI18nProvider: Decorator = (Story, context) => {
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
const withDummyRouter: Decorator = (Story, _context) => {
return (
@@ -102,28 +94,58 @@ const withTooltipProvider: Decorator = (Story, _context) => {
);
};
-export const decorators: Decorator[] = [
- withThemeProvider,
- withDummyRouter,
- withTooltipProvider,
-];
-
-const locales = Object.fromEntries(
- localazyMetadata.languages.map(({ language, name, localizedName }) => [
- language,
- `${localizedName} (${name})`,
- ]),
-);
-
const preview: Preview = {
+ loaders: [mswLoader],
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
+ },
+ decorators: [
+ withI18nProvider,
+ withThemeProvider,
+ withDummyRouter,
+ withTooltipProvider,
+ ],
+ globalTypes: {
+ theme: {
+ name: "Theme",
+ description: "Global theme for components",
+ toolbar: {
+ icon: "circlehollow",
+ title: "Theme",
+ items: [
+ { title: "System", value: "system", icon: "browser" },
+ { title: "Light", value: "light", icon: "sun" },
+ { title: "Light (high contrast)", value: "light-hc", icon: "sun" },
+ { title: "Dark", value: "dark", icon: "moon" },
+ { title: "Dark (high contrast)", value: "dark-hc", icon: "moon" },
+ ],
+ },
+ },
+
+ locale: {
+ name: "Locale",
+ description: "Locale for the app",
+ toolbar: {
+ title: "Language",
+ icon: "globe",
+ items: localazyMetadata.languages.map(
+ ({ language, localizedName, name }) => ({
+ title: `${localizedName} (${name})`,
+ value: language,
+ }),
+ ),
+ },
+ },
+ },
initialGlobals: {
locale: localazyMetadata.baseLocale,
- locales,
- },
- parameters: {
- i18n,
+ theme: "system",
},
- loaders: [mswLoader],
tags: ["autodocs"],
};
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a90aae4eb..ceb3534f4 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -23,7 +23,7 @@
"i18next": "^25.5.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
- "react-i18next": "^15.7.3",
+ "react-i18next": "^16.0.0",
"swagger-ui-dist": "^5.29.0",
"valibot": "^1.1.0",
"vaul": "^1.1.2"
@@ -63,7 +63,6 @@
"postcss-nesting": "^13.0.2",
"rimraf": "^6.0.1",
"storybook": "^9.1.5",
- "storybook-react-i18next": "4.0.11",
"tailwindcss": "^3.4.17",
"typescript": "^5.9.2",
"vite": "7.1.7",
@@ -8804,39 +8803,6 @@
}
}
},
- "node_modules/i18next-browser-languagedetector": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
- "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/runtime": "^7.23.2"
- }
- },
- "node_modules/i18next-http-backend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz",
- "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "cross-fetch": "4.0.0"
- }
- },
- "node_modules/i18next-http-backend/node_modules/cross-fetch": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
- "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "node-fetch": "^2.6.12"
- }
- },
"node_modules/i18next-parser": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/i18next-parser/-/i18next-parser-9.3.0.tgz",
@@ -11245,16 +11211,16 @@
}
},
"node_modules/react-i18next": {
- "version": "15.7.3",
- "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.3.tgz",
- "integrity": "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==",
+ "version": "16.0.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.0.tgz",
+ "integrity": "sha512-JQ+dFfLnFSKJQt7W01lJHWRC0SX7eDPobI+MSTJ3/gP39xH2g33AuTE7iddAfXYHamJdAeMGM0VFboPaD3G68Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
- "i18next": ">= 25.4.1",
+ "i18next": ">= 25.5.2",
"react": ">= 16.8.0",
"typescript": "^5"
},
@@ -12158,37 +12124,6 @@
}
}
},
- "node_modules/storybook-i18n": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/storybook-i18n/-/storybook-i18n-4.0.5.tgz",
- "integrity": "sha512-uy6k7N5VU8PRSoMo6tVYo1WNSDRd8Z3goSku7J1Cz8A8WseBN5xAnGZ/IbO5DLUOVBetLZdaKHBVoLKbYidHjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@storybook/icons": "^1.4.0"
- },
- "peerDependencies": {
- "storybook": "^9.0.0"
- }
- },
- "node_modules/storybook-react-i18next": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/storybook-react-i18next/-/storybook-react-i18next-4.0.11.tgz",
- "integrity": "sha512-p6gcz8//n7mtBaP75yZx910/t9Z4aIwOP+xzCvxwTzWL19NT1YGTR4GyR0ybzbEebqlPJtJVHnpGKQQD4wRyYg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "storybook-i18n": "^4.0.5"
- },
- "peerDependencies": {
- "i18next": "^22.0.0 || ^23.0.0 || ^24.0.0 || ^25.0.0",
- "i18next-browser-languagedetector": "^7.0.0 || ^8.0.0",
- "i18next-http-backend": "^2.0.0 || ^3.0.0",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-i18next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0",
- "storybook": "^9.0.0"
- }
- },
"node_modules/storybook/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 34a6d7cd4..af23fc784 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -33,7 +33,7 @@
"i18next": "^25.5.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
- "react-i18next": "^15.7.3",
+ "react-i18next": "^16.0.0",
"swagger-ui-dist": "^5.29.0",
"valibot": "^1.1.0",
"vaul": "^1.1.2"
@@ -73,7 +73,6 @@
"postcss-nesting": "^13.0.2",
"rimraf": "^6.0.1",
"storybook": "^9.1.5",
- "storybook-react-i18next": "4.0.11",
"tailwindcss": "^3.4.17",
"typescript": "^5.9.2",
"vite": "7.1.7",