From b17b5a169fed3ffaf661257950ca335fcc2921d9 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Fri, 11 Apr 2025 17:28:26 -0400 Subject: [PATCH] perf: make `useLocale` hook to use server-fetched i18n if in App router context (#20597) * write I18nProvider * rename * update useLocale * fix * use a singleton * safer * better * fix e2e test 1 * fix e2e test 2 * fix e2e test 3 * fix e2e test 4 * fix * refactor * address comments * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix flaky test * add more caching * revert changes to e2e tests * add jsdoc * memoize * update window object with new translations in /settings/my-account/general * remove dead code * only use locale and ns as deps * fix --- apps/web/app/AppRouterI18nProvider.tsx | 33 +++++++ apps/web/app/layout.tsx | 20 ++++- apps/web/components/PageWrapperAppDir.tsx | 4 - apps/web/lib/app-providers-app-dir.tsx | 89 +++---------------- packages/lib/hooks/useLocale.ts | 63 +++++++++++-- packages/lib/server/i18n.ts | 73 ++++++++++++--- .../unpublished-entity/UnpublishedEntity.tsx | 5 ++ 7 files changed, 184 insertions(+), 103 deletions(-) create mode 100644 apps/web/app/AppRouterI18nProvider.tsx diff --git a/apps/web/app/AppRouterI18nProvider.tsx b/apps/web/app/AppRouterI18nProvider.tsx new file mode 100644 index 0000000000..39876e93b6 --- /dev/null +++ b/apps/web/app/AppRouterI18nProvider.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { createContext, useMemo } from "react"; +import type { ReactNode } from "react"; + +type AppRouterI18nContextType = { + translations: Record; + ns: string; + locale: string; +}; + +export const AppRouterI18nContext = createContext(null); + +export function AppRouterI18nProvider({ + children, + translations, + locale, + ns, +}: AppRouterI18nContextType & { + children: ReactNode; +}) { + // Memoize the value to prevent re-renders unless the data changes + const value = useMemo( + () => ({ + translations, + locale, + ns, + }), + [locale, ns] + ); + + return {children}; +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 77c67a5d7e..8725a7b41b 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -5,10 +5,12 @@ import { headers, cookies } from "next/headers"; import React from "react"; import { getLocale } from "@calcom/features/auth/lib/getLocale"; +import { loadTranslations } from "@calcom/lib/server/i18n"; import { IconSprites } from "@calcom/ui/components/icon"; import { NotificationSoundHandler } from "@calcom/web/components/notification-sound-handler"; import "../styles/globals.css"; +import { AppRouterI18nProvider } from "./AppRouterI18nProvider"; import { SpeculationRules } from "./SpeculationRules"; import { Providers } from "./providers"; @@ -83,10 +85,15 @@ const getInitialProps = async (url: string) => { const embedColorScheme = searchParams?.get("ui.color-scheme"); const req = { headers: await headers(), cookies: await cookies() }; - const newLocale = await getLocale(req); + const newLocale = (await getLocale(req)) ?? "en"; const direction = dir(newLocale); - return { isEmbed, embedColorScheme, locale: newLocale, direction }; + return { + isEmbed, + embedColorScheme, + locale: newLocale, + direction, + }; }; const getFallbackProps = () => ({ @@ -107,6 +114,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo const { locale, direction, isEmbed, embedColorScheme } = isSSG ? getFallbackProps() : await getInitialProps(fullUrl); + const ns = "common"; + const translations = await loadTranslations(locale, ns); return ( - {children} + + + + {children} + + {!isEmbed && } diff --git a/apps/web/components/PageWrapperAppDir.tsx b/apps/web/components/PageWrapperAppDir.tsx index 53c9850fc6..20b6507911 100644 --- a/apps/web/components/PageWrapperAppDir.tsx +++ b/apps/web/components/PageWrapperAppDir.tsx @@ -1,7 +1,5 @@ "use client"; -import type { SSRConfig } from "next-i18next/dist/types/types"; -// import I18nLanguageHandler from "@components/I18nLanguageHandler"; import { usePathname } from "next/navigation"; import Script from "next/script"; @@ -16,7 +14,6 @@ export type PageWrapperProps = Readonly<{ requiresLicense: boolean; nonce: string | undefined; isBookingPage?: boolean; - i18n?: SSRConfig; }>; function PageWrapper(props: PageWrapperProps) { @@ -45,7 +42,6 @@ function PageWrapper(props: PageWrapperProps) { return ( - {/* */} <>