chore: add tests for locale and directionality; set html.dir when the session locale is available (#11853)
parent
59faffe0d5
commit
a5fa2ef8d0
|
@ -8,6 +8,7 @@ import { ThemeProvider } from "next-themes";
|
|||
import type { AppProps as NextAppProps, AppProps as NextJsAppProps } from "next/app";
|
||||
import type { ParsedUrlQuery } from "querystring";
|
||||
import type { PropsWithChildren, ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
|
||||
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
|
||||
|
@ -75,6 +76,22 @@ const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
|
|||
const session = useSession();
|
||||
const locale = session?.data?.user.locale ?? props.pageProps.newLocale;
|
||||
|
||||
useEffect(() => {
|
||||
window.document.documentElement.lang = locale;
|
||||
|
||||
let direction = window.document.dir || "ltr";
|
||||
|
||||
try {
|
||||
const intlLocale = new Intl.Locale(locale);
|
||||
// @ts-expect-error INFO: Typescript does not know about the Intl.Locale textInfo attribute
|
||||
direction = intlLocale.textInfo?.direction;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
window.document.dir = direction;
|
||||
}, [locale]);
|
||||
|
||||
const clientViewerI18n = useViewerI18n(locale);
|
||||
const i18n = clientViewerI18n.data?.i18n;
|
||||
|
||||
|
@ -82,6 +99,7 @@ const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
|
|||
...props,
|
||||
pageProps: {
|
||||
...props.pageProps,
|
||||
|
||||
...i18n,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,7 +2,6 @@ import type { IncomingMessage } from "http";
|
|||
import type { AppContextType } from "next/dist/shared/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import type { AppProps } from "@lib/app-providers";
|
||||
|
@ -28,6 +27,7 @@ MyApp.getInitialProps = async (ctx: AppContextType) => {
|
|||
let newLocale = "en";
|
||||
|
||||
if (req) {
|
||||
const { getLocale } = await import("@calcom/features/auth/lib/getLocale");
|
||||
newLocale = await getLocale(req as IncomingMessage & { cookies: Record<string, any> });
|
||||
} else if (typeof window !== "undefined" && window.calNewLocale) {
|
||||
newLocale = window.calNewLocale;
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { DocumentContext, DocumentProps } from "next/document";
|
|||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
import { csp } from "@lib/csp";
|
||||
|
@ -28,9 +27,12 @@ class MyDocument extends Document<Props> {
|
|||
setHeader(ctx, "x-csp", "initialPropsOnly");
|
||||
}
|
||||
|
||||
const newLocale = ctx.req
|
||||
? await getLocale(ctx.req as IncomingMessage & { cookies: Record<string, any> })
|
||||
: "en";
|
||||
const getLocaleModule = ctx.req ? await import("@calcom/features/auth/lib/getLocale") : null;
|
||||
|
||||
const newLocale =
|
||||
ctx.req && getLocaleModule
|
||||
? await getLocaleModule.getLocale(ctx.req as IncomingMessage & { cookies: Record<string, any> })
|
||||
: "en";
|
||||
|
||||
const asPath = ctx.asPath || "";
|
||||
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
test.describe("unauthorized user sees correct translations (de)", async () => {
|
||||
test.use({
|
||||
locale: "de",
|
||||
});
|
||||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Willkommen zurück", { exact: true });
|
||||
expect(await locator.count()).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Welcome back", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("unauthorized user sees correct translations (ar)", async () => {
|
||||
test.use({
|
||||
locale: "ar",
|
||||
});
|
||||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("أهلاً بك من جديد", { exact: true });
|
||||
expect(await locator.count()).toEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Welcome back", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("authorized user sees correct translations (de) [locale1]", async () => {
|
||||
test.use({
|
||||
locale: "en",
|
||||
});
|
||||
|
||||
test("should return correct translations and html attributes", async ({ page, users }) => {
|
||||
await test.step("should create a de user", async () => {
|
||||
const user = await users.create({
|
||||
locale: "de",
|
||||
});
|
||||
await user.apiLogin();
|
||||
});
|
||||
|
||||
await test.step("should navigate to /event-types and show German translations", async () => {
|
||||
await page.goto("/event-types");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Ereignistypen", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Event Types", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should navigate to /bookings and show German translations", async () => {
|
||||
await page.goto("/bookings");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Buchungen", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload the /bookings and show German translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Buchungen", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("authorized user sees correct translations (ar)", async () => {
|
||||
test.use({
|
||||
locale: "en",
|
||||
});
|
||||
|
||||
test("should return correct translations and html attributes", async ({ page, users }) => {
|
||||
await test.step("should create a de user", async () => {
|
||||
const user = await users.create({
|
||||
locale: "ar",
|
||||
});
|
||||
await user.apiLogin();
|
||||
});
|
||||
|
||||
await test.step("should navigate to /event-types and show German translations", async () => {
|
||||
await page.goto("/event-types");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("أنواع الحدث", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Event Types", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should navigate to /bookings and show German translations", async () => {
|
||||
await page.goto("/bookings");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عمليات الحجز", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload the /bookings and show German translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عمليات الحجز", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("authorized user sees changed translations (de->ar)", async () => {
|
||||
test.use({
|
||||
locale: "en",
|
||||
});
|
||||
|
||||
test("should return correct translations and html attributes", async ({ page, users }) => {
|
||||
await test.step("should create a de user", async () => {
|
||||
const user = await users.create({
|
||||
locale: "de",
|
||||
});
|
||||
await user.apiLogin();
|
||||
});
|
||||
|
||||
await test.step("should change the language and show Arabic translations", async () => {
|
||||
await page.goto("/settings/my-account/general");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator(".bg-default > div > div:nth-child(2)").first().click();
|
||||
await page.locator("#react-select-2-option-0").click();
|
||||
|
||||
await page.getByRole("button", { name: "Aktualisieren" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Einstellungen erfolgreich aktualisiert" })
|
||||
.waitFor({ state: "visible" });
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عام", { exact: true }); // "general"
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Allgemein", { exact: true }); // "general"
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload and show Arabic translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عام", { exact: true }); // "general"
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Allgemein", { exact: true }); // "general"
|
||||
expect(await locator.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue