fix: build locale based on validated codes and regions

gpp/localeRegions
Greg Pabian 2023-10-16 11:30:18 +02:00
parent 899b59620d
commit 5ffdcbc844
7 changed files with 484 additions and 3852 deletions

View File

@ -1,4 +1,5 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { dir } from "i18next";
import type { Session } from "next-auth";
import { SessionProvider, useSession } from "next-auth/react";
import { EventCollectionProvider } from "next-collect/client";
@ -78,18 +79,7 @@ const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
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;
window.document.dir = dir(locale);
}, [locale]);
const clientViewerI18n = useViewerI18n(locale);

View File

@ -1,4 +1,5 @@
import type { IncomingMessage } from "http";
import { dir } from "i18next";
import type { NextPageContext } from "next";
import type { DocumentContext, DocumentProps } from "next/document";
import Document, { Head, Html, Main, NextScript } from "next/document";
@ -50,21 +51,15 @@ class MyDocument extends Document<Props> {
render() {
const { isEmbed, embedColorScheme } = this.props;
const newLocale = this.props.newLocale || "en";
const newDir = dir(newLocale);
const nonceParsed = z.string().safeParse(this.props.nonce);
const nonce = nonceParsed.success ? nonceParsed.data : "";
const intlLocale = new Intl.Locale(newLocale);
// @ts-expect-error INFO: Typescript does not know about the Intl.Locale textInfo attribute
const direction = intlLocale.textInfo?.direction;
if (!direction) {
throw new Error("NodeJS major breaking change detected, use getTextInfo() instead.");
}
return (
<Html
lang={newLocale}
dir={direction}
dir={newDir}
style={embedColorScheme ? { colorScheme: embedColorScheme as string } : undefined}>
<Head nonce={nonce}>
<script

View File

@ -52,7 +52,151 @@ test.describe("unauthorized user sees correct translations (ar)", async () => {
});
});
test.describe("authorized user sees correct translations (de) [locale1]", async () => {
test.describe("unauthorized user sees correct translations (zh)", async () => {
test.use({
locale: "zh",
});
test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("load");
await page.locator("html[lang=zh]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").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("unauthorized user sees correct translations (zh-cn)", async () => {
test.use({
locale: "zh-cn",
});
test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("load");
await page.locator("html[lang=zh-cn]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").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("unauthorized user sees correct translations (zh-tw)", async () => {
test.use({
locale: "zh-tw",
});
test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("load");
await page.locator("html[lang=zh-tw]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").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("unauthorized user sees correct translations (pt)", async () => {
test.use({
locale: "pt",
});
test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("load");
await page.locator("html[lang=pt]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{
const locator = page.getByText("Olá novamente", { 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 (pt-br)", async () => {
test.use({
locale: "pt-br",
});
test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("load");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{
const locator = page.getByText("Bem-vindo(a) novamente", { 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 (es-419)", async () => {
test.use({
locale: "es-419",
});
test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("load");
await page.locator("html[lang=es-419]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{
const locator = page.getByText("Bienvenido de nuevo", { 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)", async () => {
test.use({
locale: "en",
});
@ -124,6 +268,78 @@ test.describe("authorized user sees correct translations (de) [locale1]", async
});
});
test.describe("authorized user sees correct translations (pt-br)", async () => {
test.use({
locale: "en",
});
test("should return correct translations and html attributes", async ({ page, users }) => {
await test.step("should create a pt-br user", async () => {
const user = await users.create({
locale: "pt-br",
});
await user.apiLogin();
});
await test.step("should navigate to /event-types and show Brazil-Portuguese translations", async () => {
await page.goto("/event-types");
await page.waitForLoadState("networkidle");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{
const locator = page.getByText("Tipos de Eventos", { 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 Brazil-Portuguese translations", async () => {
await page.goto("/bookings");
await page.waitForLoadState("networkidle");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{
const locator = page.getByText("Reservas", { 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 Brazil-Portuguese translations", async () => {
await page.reload();
await page.waitForLoadState("networkidle");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{
const locator = page.getByText("Reservas", { 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",
@ -137,7 +353,7 @@ test.describe("authorized user sees correct translations (ar)", async () => {
await user.apiLogin();
});
await test.step("should navigate to /event-types and show German translations", async () => {
await test.step("should navigate to /event-types and show Arabic translations", async () => {
await page.goto("/event-types");
await page.waitForLoadState("networkidle");
@ -156,7 +372,7 @@ test.describe("authorized user sees correct translations (ar)", async () => {
}
});
await test.step("should navigate to /bookings and show German translations", async () => {
await test.step("should navigate to /bookings and show Arabic translations", async () => {
await page.goto("/bookings");
await page.waitForLoadState("networkidle");
@ -175,7 +391,7 @@ test.describe("authorized user sees correct translations (ar)", async () => {
}
});
await test.step("should reload the /bookings and show German translations", async () => {
await test.step("should reload the /bookings and show Arabic translations", async () => {
await page.reload();
await page.waitForLoadState("networkidle");

View File

@ -100,6 +100,7 @@
"dependencies": {
"city-timezones": "^1.2.1",
"eslint": "^8.34.0",
"i18next": "^23.5.1",
"lucide-react": "^0.171.0",
"turbo": "^1.10.1"
},

View File

@ -33,6 +33,10 @@ const config = {
"zh-TW",
],
},
fallbackLng: {
default: ["en"],
zh: ["zh-CN"],
},
reloadOnPrerender: process.env.NODE_ENV !== "production",
};

View File

@ -29,5 +29,11 @@ export const getLocale = async (req: GetTokenParams["req"]): Promise<string> =>
const languages = acceptLanguage ? parse(acceptLanguage) : [];
return languages[0]?.code || "en";
const code: string = languages[0]?.code ?? "";
const region: string = languages[0]?.region ?? "";
const testedCode = /^[a-zA-Z]+$/.test(code) ? code : "en";
const testedRegion = /^[a-zA-Z0-9]+$/.test(region) ? region : "";
return `${testedCode}${testedRegion !== "" ? "-" : ""}${testedRegion}`;
};

4074
yarn.lock

File diff suppressed because it is too large Load Diff