chore: add tests for locale and directionality; set html.dir when the session locale is available (#11853)

This commit is contained in:
Greg Pabian 2023-10-13 19:27:10 +02:00 committed by GitHub
parent 59faffe0d5
commit a5fa2ef8d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 284 additions and 5 deletions

View File

@ -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,
},
};

View File

@ -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;

View File

@ -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

View File

@ -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);
}
});
});
});