Allow loading Storybook in other locales
This commit is contained in:
Родитель
971ce2cfb7
Коммит
31397d0b02
|
@ -9,14 +9,11 @@ import { action } from "@storybook/addon-actions";
|
|||
import { linkTo } from "@storybook/addon-links";
|
||||
import "../src/app/globals.css";
|
||||
import { metropolis } from "../src/app/fonts/Metropolis/metropolis";
|
||||
import { getEnL10nBundlesSync } from "../src/app/functions/server/mockL10n";
|
||||
import { TestComponentWrapper } from "../src/TestComponentWrapper";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
||||
|
||||
const AppDecorator: Preview["decorators"] = (storyFn) => {
|
||||
const l10nBundles = getEnL10nBundlesSync();
|
||||
|
||||
useEffect(() => {
|
||||
// We have to add these classes to the body, rather than simply wrapping the
|
||||
// storyFn in a container, because some components (most notably, the ones
|
||||
|
|
|
@ -7,9 +7,9 @@ import { L10nProvider } from "./contextProviders/localization";
|
|||
import { PublicEnvProvider } from "./contextProviders/public-env";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { ReactAriaI18nProvider } from "./contextProviders/react-aria";
|
||||
import { getEnL10nBundlesSync } from "./app/functions/server/mockL10n";
|
||||
import { getOneL10nBundleSync } from "./app/functions/server/mockL10n";
|
||||
|
||||
const l10nBundles = getEnL10nBundlesSync();
|
||||
const l10nBundles = getOneL10nBundleSync();
|
||||
|
||||
export const TestComponentWrapper = (props: { children: ReactNode }) => {
|
||||
return (
|
||||
|
|
|
@ -7,7 +7,7 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||
import { OnerepScanResultRow, OnerepScanRow } from "knex/types/tables";
|
||||
import { View as DashboardEl } from "./View";
|
||||
import { Shell } from "../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../functions/server/mockL10n";
|
||||
import {
|
||||
createRandomScanResult,
|
||||
createRandomBreach,
|
||||
|
@ -148,7 +148,7 @@ const DashboardWrapper = (props: DashboardWrapperProps) => {
|
|||
|
||||
return (
|
||||
<CountryCodeProvider countryCode={props.countryCode}>
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<DashboardEl
|
||||
user={user}
|
||||
userBreaches={breaches}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { LatestOnerepScanData } from "../../../../../../../../../../db/tables/onerep_scans";
|
||||
|
||||
const mockedScan: OnerepScanRow = {
|
||||
|
@ -50,7 +50,7 @@ export const AutomaticRemoveViewStory: Story = {
|
|||
name: "1d. Automatically resolve brokers",
|
||||
render: () => {
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<AutomaticRemoveView
|
||||
data={{
|
||||
countryCode: "us",
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { LatestOnerepScanData } from "../../../../../../../../../../db/tables/onerep_scans";
|
||||
import { hasPremium } from "../../../../../../../../../functions/universal/user";
|
||||
|
||||
|
@ -51,7 +51,7 @@ export const ManualRemoveViewStory: Story = {
|
|||
name: "1c. Manually resolve brokers",
|
||||
render: () => {
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<ManualRemoveView
|
||||
scanData={mockedScanData}
|
||||
breaches={mockedBreaches}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { LatestOnerepScanData } from "../../../../../../../../../../db/tables/onerep_scans";
|
||||
|
||||
const mockedScan: OnerepScanRow = {
|
||||
|
@ -50,7 +50,7 @@ export const StartFreeScanViewStory: Story = {
|
|||
name: "1a. Free scan",
|
||||
render: () => {
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<StartFreeScanView
|
||||
data={{
|
||||
countryCode: "us",
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { LatestOnerepScanData } from "../../../../../../../../../../db/tables/onerep_scans";
|
||||
|
||||
const brokerOptions = {
|
||||
|
@ -93,7 +93,7 @@ const ViewWrapper = (props: ViewWrapperProps) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<ViewDataBrokersView
|
||||
data={{
|
||||
latestScanData: scanData,
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { LatestOnerepScanData } from "../../../../../../../../../../db/tables/onerep_scans";
|
||||
|
||||
const mockedScan: OnerepScanRow = {
|
||||
|
@ -50,7 +50,7 @@ export const ManualRemoveViewStory: Story = {
|
|||
name: "1e. Welcome to Premium",
|
||||
render: () => {
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<WelcomeToPremiumView
|
||||
data={{
|
||||
countryCode: "us",
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { HighRiskBreachLayout } from "../HighRiskBreachLayout";
|
||||
import {
|
||||
HighRiskBreachTypes,
|
||||
|
@ -102,7 +102,7 @@ const HighRiskBreachWrapper = (props: {
|
|||
};
|
||||
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<HighRiskBreachLayout
|
||||
subscriberEmails={[]}
|
||||
type={props.type}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { LeakedPasswordsLayout } from "../LeakedPasswordsLayout";
|
||||
import {
|
||||
LeakedPasswordsTypes,
|
||||
|
@ -64,7 +64,7 @@ const LeakedPasswordsWrapper = (props: {
|
|||
}
|
||||
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<LeakedPasswordsLayout
|
||||
subscriberEmails={[]}
|
||||
type={props.type}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
createUserWithPremiumSubscription,
|
||||
} from "../../../../../../../../../../apiMocks/mockData";
|
||||
import { Shell } from "../../../../../../../Shell";
|
||||
import { getEnL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../../../../functions/server/mockL10n";
|
||||
import { SecurityRecommendationsLayout } from "../SecurityRecommendationsLayout";
|
||||
import {
|
||||
SecurityRecommendationTypes,
|
||||
|
@ -42,7 +42,7 @@ const SecurityRecommendationsWrapper = (props: {
|
|||
type: SecurityRecommendationTypes;
|
||||
}) => {
|
||||
return (
|
||||
<Shell l10n={getEnL10nSync()} session={mockedSession} nonce="">
|
||||
<Shell l10n={getOneL10nSync()} session={mockedSession} nonce="">
|
||||
<SecurityRecommendationsLayout
|
||||
subscriberEmails={[]}
|
||||
type={props.type}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { render, screen } from "@testing-library/react";
|
|||
import { axe } from "jest-axe";
|
||||
import { Session } from "next-auth";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { getEnL10nSync } from "../../../../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../../../../functions/server/mockL10n";
|
||||
import { TestComponentWrapper } from "../../../../../../../TestComponentWrapper";
|
||||
import { EmailRow } from "../../../../../../../db/tables/emailAddresses";
|
||||
import { SerializedSubscriber } from "../../../../../../../next-auth";
|
||||
|
@ -61,7 +61,7 @@ it("passes the axe accessibility audit", async () => {
|
|||
const { container } = render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={mockedUser}
|
||||
breachCountByEmailAddress={{
|
||||
[mockedUser.email]: 42,
|
||||
|
@ -85,7 +85,7 @@ it("preselects 'Send all breach alerts to the primary email address' if that's t
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={{
|
||||
...mockedUser,
|
||||
subscriber: {
|
||||
|
@ -120,7 +120,7 @@ it("preselects 'Send breach alerts to the affected email address' if that's the
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={{
|
||||
...mockedUser,
|
||||
subscriber: {
|
||||
|
@ -157,7 +157,7 @@ it("sends a call to the API to change the email alert preferences when changing
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={{
|
||||
...mockedUser,
|
||||
subscriber: {
|
||||
|
@ -203,7 +203,7 @@ it("refreshes the session token after changing email alert preferences, to ensur
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={{
|
||||
...mockedUser,
|
||||
subscriber: {
|
||||
|
@ -235,7 +235,7 @@ it("marks unverified email addresses as such", () => {
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={mockedUser}
|
||||
breachCountByEmailAddress={{
|
||||
[mockedUser.email]: 42,
|
||||
|
@ -266,7 +266,7 @@ it("calls the API to resend a verification email if requested to", async () => {
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={mockedUser}
|
||||
breachCountByEmailAddress={{
|
||||
[mockedUser.email]: 42,
|
||||
|
@ -307,7 +307,7 @@ it("calls the 'remove' action when clicking the rubbish bin icon", async () => {
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={mockedUser}
|
||||
breachCountByEmailAddress={{
|
||||
[mockedUser.email]: 42,
|
||||
|
@ -342,7 +342,7 @@ it.skip("calls the 'add' action when adding another email address", async () =>
|
|||
render(
|
||||
<TestComponentWrapper>
|
||||
<SettingsView
|
||||
l10n={getEnL10nSync()}
|
||||
l10n={getOneL10nSync()}
|
||||
user={mockedUser}
|
||||
breachCountByEmailAddress={{
|
||||
[mockedUser.email]: 42,
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { View } from "./LandingView";
|
||||
import { getEnL10nSync } from "../../../functions/server/mockL10n";
|
||||
import { getOneL10nSync } from "../../../functions/server/mockL10n";
|
||||
|
||||
const meta: Meta<typeof View> = {
|
||||
title: "Pages/Public landing page",
|
||||
component: View,
|
||||
args: {
|
||||
l10n: getEnL10nSync(),
|
||||
l10n: getOneL10nSync(),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -48,6 +48,7 @@ export const LandingNonUsDe: Story = {
|
|||
args: {
|
||||
eligibleForPremium: false,
|
||||
countryCode: "de",
|
||||
l10n: getOneL10nSync("de"),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -56,5 +57,6 @@ export const LandingNonUsFr: Story = {
|
|||
args: {
|
||||
eligibleForPremium: false,
|
||||
countryCode: "fr",
|
||||
l10n: getOneL10nSync("fr"),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { getEnL10nBundlesInNodeContext, getEnL10nSync } from "../mockL10n";
|
||||
import { getOneL10nBundleInNodeContext, getOneL10nSync } from "../mockL10n";
|
||||
|
||||
export const getL10nBundles = getEnL10nBundlesInNodeContext;
|
||||
export const getL10nBundles = getOneL10nBundleInNodeContext;
|
||||
|
||||
export const getL10n = getEnL10nSync;
|
||||
export const getL10n = getOneL10nSync;
|
||||
|
|
|
@ -17,22 +17,29 @@ import type { LocaleData } from "./l10n";
|
|||
// Code in this file is only used in tests and Storybook, not in production:
|
||||
/* c8 ignore start */
|
||||
|
||||
export function getEnL10nBundlesSync(): LocaleData[] {
|
||||
export function getOneL10nBundleSync(locale = detectLocale()): LocaleData[] {
|
||||
return process.env.STORYBOOK === "true"
|
||||
? getEnL10nBundlesInWebpackContext()
|
||||
: getEnL10nBundlesInNodeContext();
|
||||
? getOneL10nBundleInWebpackContext(locale)
|
||||
: getOneL10nBundleInNodeContext(locale);
|
||||
}
|
||||
|
||||
export function getEnL10nBundlesInWebpackContext(): LocaleData[] {
|
||||
const referenceStringsContext: { keys: () => string[] } & ((
|
||||
export function getOneL10nBundleInWebpackContext(
|
||||
locale = detectLocale(),
|
||||
): LocaleData[] {
|
||||
const allStringsContext: { keys: () => string[] } & ((
|
||||
path: string,
|
||||
// `require` isn't usually valid JS, so skip type checking for that:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => string) = (require as any).context(
|
||||
"../../../../locales/en",
|
||||
"../../../../locales/",
|
||||
true,
|
||||
/\.ftl$/,
|
||||
);
|
||||
const allStringFilenames = allStringsContext.keys();
|
||||
const localeStringFilenames = allStringFilenames.filter((filepath) =>
|
||||
filepath.startsWith(`./${locale}/`),
|
||||
);
|
||||
|
||||
const pendingTranslationsContext: { keys: () => string[] } & ((
|
||||
path: string,
|
||||
// `require` isn't usually valid JS, so skip type checking for that:
|
||||
|
@ -42,10 +49,9 @@ export function getEnL10nBundlesInWebpackContext(): LocaleData[] {
|
|||
true,
|
||||
/\.ftl$/,
|
||||
);
|
||||
const referenceSourceFilenames = referenceStringsContext.keys();
|
||||
const pendingSourceFilenames = pendingTranslationsContext.keys();
|
||||
const bundleSources: string[] = referenceSourceFilenames
|
||||
.map((filePath) => referenceStringsContext(filePath))
|
||||
const bundleSources: string[] = localeStringFilenames
|
||||
.map((filePath) => allStringsContext(filePath))
|
||||
.concat(
|
||||
pendingSourceFilenames.map((filePath) =>
|
||||
pendingTranslationsContext(filePath),
|
||||
|
@ -54,13 +60,15 @@ export function getEnL10nBundlesInWebpackContext(): LocaleData[] {
|
|||
|
||||
return [
|
||||
{
|
||||
locale: "en",
|
||||
locale: locale,
|
||||
bundleSources: bundleSources,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getEnL10nBundlesInNodeContext(): LocaleData[] {
|
||||
export function getOneL10nBundleInNodeContext(
|
||||
locale = detectLocale(),
|
||||
): LocaleData[] {
|
||||
const {
|
||||
readdirSync: nodeReaddirSync,
|
||||
readFileSync: nodeReadFileSync,
|
||||
|
@ -71,7 +79,7 @@ export function getEnL10nBundlesInNodeContext(): LocaleData[] {
|
|||
const { resolve: resolvePath }: { resolve: typeof resolve } = require("path");
|
||||
const referenceStringsPath = resolvePath(
|
||||
__dirname,
|
||||
"../../../../locales/en/",
|
||||
`../../../../locales/${locale}/`,
|
||||
);
|
||||
const pendingStringsPath = resolvePath(
|
||||
__dirname,
|
||||
|
@ -91,7 +99,7 @@ export function getEnL10nBundlesInNodeContext(): LocaleData[] {
|
|||
|
||||
return [
|
||||
{
|
||||
locale: "en",
|
||||
locale: locale,
|
||||
bundleSources: bundleSources,
|
||||
},
|
||||
];
|
||||
|
@ -122,15 +130,19 @@ function getBundle(localeData: LocaleData): FluentBundle {
|
|||
}
|
||||
|
||||
/**
|
||||
* This function loads a ReactLocalization instance with the `en` and pending strings.
|
||||
* This function loads a ReactLocalization instance with the given locale (`en` by default) and pending strings.
|
||||
*
|
||||
* This is useful in tests and Storybook, where we can't call `headers` from
|
||||
* `next/headers` to determine the user's locale, and even just importing from a
|
||||
* module that references it can result in an error about only being able to use
|
||||
* it in Server Components.
|
||||
*
|
||||
* @param locale {string} Locale to load; `en` by default.
|
||||
*/
|
||||
export function getEnL10nSync(): ExtendedReactLocalization {
|
||||
const localeData: LocaleData[] = getEnL10nBundlesSync();
|
||||
export function getOneL10nSync(
|
||||
locale = detectLocale(),
|
||||
): ExtendedReactLocalization {
|
||||
const localeData: LocaleData[] = getOneL10nBundleSync(locale);
|
||||
const bundles: FluentBundle[] = localeData.map((data) => getBundle(data));
|
||||
|
||||
// The ReactLocalization instance stores and caches the sequence of generated
|
||||
|
@ -151,3 +163,12 @@ export function getEnL10nSync(): ExtendedReactLocalization {
|
|||
|
||||
return extendedL10n;
|
||||
}
|
||||
|
||||
function detectLocale() {
|
||||
const locale =
|
||||
typeof document !== "undefined"
|
||||
? new URLSearchParams(document.location.search).get("locale") ?? undefined
|
||||
: undefined;
|
||||
|
||||
return locale ?? "en";
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче