From f2ecd9818a60d2417c683fed95542f87e771785b Mon Sep 17 00:00:00 2001 From: Greg Pabian <35925521+grzpab@users.noreply.github.com> Date: Mon, 16 Oct 2023 18:29:35 +0200 Subject: [PATCH] fix: build locale based on validated codes and regions (#11912) * fix: build locale based on validated codes and regions * keep html lang stable * fix type error --- apps/web/lib/app-providers.tsx | 31 ++- apps/web/pages/_document.tsx | 11 +- apps/web/playwright/locale.e2e.ts | 286 +++++++++++++++++++++++- packages/config/next-i18next.config.js | 4 + packages/features/auth/lib/getLocale.ts | 13 +- 5 files changed, 324 insertions(+), 21 deletions(-) diff --git a/apps/web/lib/app-providers.tsx b/apps/web/lib/app-providers.tsx index 51d3bfd0ea..a7bfcba9ea 100644 --- a/apps/web/lib/app-providers.tsx +++ b/apps/web/lib/app-providers.tsx @@ -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"; @@ -77,19 +78,33 @@ const CustomI18nextProvider = (props: AppPropsWithoutNonce) => { 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; + // @ts-expect-error TS2790: The operand of a 'delete' operator must be optional. + delete window.document.documentElement["lang"]; + + window.document.documentElement.lang = locale; + + // Next.js writes the locale to the same attribute + // https://github.com/vercel/next.js/blob/1609da2d9552fed48ab45969bdc5631230c6d356/packages/next/src/shared/lib/router/router.ts#L1786 + // which can result in a race condition + // this property descriptor ensures this never happens + Object.defineProperty(window.document.documentElement, "lang", { + configurable: true, + // value: locale, + set: function (this) { + // empty setter on purpose + }, + get: function () { + return locale; + }, + }); } catch (error) { console.error(error); + + window.document.documentElement.lang = locale; } - window.document.dir = direction; + window.document.dir = dir(locale); }, [locale]); const clientViewerI18n = useViewerI18n(locale); diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index e8c73a21b7..6ffa6f293d 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -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 { 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 (