From c7b1e4dfa1d50514217572aa95130dbe82b4c3d9 Mon Sep 17 00:00:00 2001 From: DmytroHryshyn <125881252+DmytroHryshyn@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:43:48 +0300 Subject: [PATCH] chore: [app dir bootstrapping 6] server-side translations (#11995) Co-authored-by: zomars --- .../next-i18next-npm-13.3.0-bf25b0943c.patch | 26 +++++++++++++++++++ apps/web/playwright/locale.e2e.ts | 7 ++--- apps/web/server/lib/ssg.ts | 10 +++++-- apps/web/server/lib/ssr.ts | 14 +++++++--- package.json | 3 ++- packages/features/auth/lib/getLocale.ts | 10 ++++++- yarn.lock | 20 +++++++++++++- 7 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 .yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch diff --git a/.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch b/.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch new file mode 100644 index 0000000000..43667e8668 --- /dev/null +++ b/.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch @@ -0,0 +1,26 @@ +diff --git a/dist/commonjs/serverSideTranslations.js b/dist/commonjs/serverSideTranslations.js +index bcad3d02fbdfab8dacb1d85efd79e98623a0c257..fff668f598154a13c4030d1b4a90d5d9c18214ad 100644 +--- a/dist/commonjs/serverSideTranslations.js ++++ b/dist/commonjs/serverSideTranslations.js +@@ -36,7 +36,6 @@ var _fs = _interopRequireDefault(require("fs")); + var _path = _interopRequireDefault(require("path")); + var _createConfig = require("./config/createConfig"); + var _node = _interopRequireDefault(require("./createClient/node")); +-var _appWithTranslation = require("./appWithTranslation"); + var _utils = require("./utils"); + function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } +@@ -110,12 +109,8 @@ var serverSideTranslations = /*#__PURE__*/function () { + lng: initialLocale + })); + localeExtension = config.localeExtension, localePath = config.localePath, fallbackLng = config.fallbackLng, reloadOnPrerender = config.reloadOnPrerender; +- if (!reloadOnPrerender) { +- _context.next = 18; +- break; +- } + _context.next = 18; +- return _appWithTranslation.globalI18n === null || _appWithTranslation.globalI18n === void 0 ? void 0 : _appWithTranslation.globalI18n.reloadResources(); ++ return void 0; + case 18: + _createClient = (0, _node["default"])(_objectSpread(_objectSpread({}, config), {}, { + lng: initialLocale diff --git a/apps/web/playwright/locale.e2e.ts b/apps/web/playwright/locale.e2e.ts index c802116b67..f84a8ab85e 100644 --- a/apps/web/playwright/locale.e2e.ts +++ b/apps/web/playwright/locale.e2e.ts @@ -150,14 +150,14 @@ test.describe("unauthorized user sees correct translations (pt)", async () => { test.describe("unauthorized user sees correct translations (pt-br)", async () => { test.use({ - locale: "pt-br", + 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[lang=pt-BR]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { @@ -181,7 +181,8 @@ test.describe("unauthorized user sees correct translations (es-419)", async () = await page.goto("/"); await page.waitForLoadState("load"); - await page.locator("html[lang=es-419]").waitFor({ state: "attached" }); + // es-419 is disabled in i18n config, so es should be used as fallback + await page.locator("html[lang=es]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { diff --git a/apps/web/server/lib/ssg.ts b/apps/web/server/lib/ssg.ts index 2f06820ca7..469653da97 100644 --- a/apps/web/server/lib/ssg.ts +++ b/apps/web/server/lib/ssg.ts @@ -37,8 +37,14 @@ export async function ssgInit(opts: GetStat }, }); - // always preload i18n - await ssg.viewer.public.i18n.fetch({ locale, CalComVersion: CALCOM_VERSION }); + // i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch + // we can set query data directly to the queryClient + const queryKey = [ + ["viewer", "public", "i18n"], + { input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" }, + ]; + + ssg.queryClient.setQueryData(queryKey, { i18n: _i18n }); return ssg; } diff --git a/apps/web/server/lib/ssr.ts b/apps/web/server/lib/ssr.ts index bba174e6c8..08922d5fe5 100644 --- a/apps/web/server/lib/ssr.ts +++ b/apps/web/server/lib/ssr.ts @@ -25,11 +25,17 @@ export async function ssrInit(context: GetServerSidePropsContext, options?: { no ctx: { ...ctx, locale, i18n }, }); + // i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch + // we can set query data directly to the queryClient + const queryKey = [ + ["viewer", "public", "i18n"], + { input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" }, + ]; + if (!options?.noI18nPreload) { + ssr.queryClient.setQueryData(queryKey, { i18n }); + } + await Promise.allSettled([ - // always preload "viewer.public.i18n" - !options?.noI18nPreload - ? ssr.viewer.public.i18n.prefetch({ locale, CalComVersion: CALCOM_VERSION }) - : Promise.resolve({}), // So feature flags are available on first render ssr.viewer.features.map.prefetch(), // Provides a better UX to the users who have already upgraded. diff --git a/package.json b/package.json index 5c701e2e5a..7591e82361 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ "@types/node": "16.9.1", "@types/react": "18.0.26", "@types/react-dom": "^18.0.9", - "libphonenumber-js@^1.10.12": "patch:libphonenumber-js@npm%3A1.10.12#./.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch" + "libphonenumber-js@^1.10.12": "patch:libphonenumber-js@npm%3A1.10.12#./.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch", + "next-i18next@^13.2.2": "patch:next-i18next@npm%3A13.3.0#./.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch" }, "lint-staged": { "(apps|packages)/**/*.{js,ts,jsx,tsx}": [ diff --git a/packages/features/auth/lib/getLocale.ts b/packages/features/auth/lib/getLocale.ts index c561e3c3c7..6e6c2795a6 100644 --- a/packages/features/auth/lib/getLocale.ts +++ b/packages/features/auth/lib/getLocale.ts @@ -1,7 +1,11 @@ import { parse } from "accept-language-parser"; +import { lookup } from "bcp-47-match"; import type { GetTokenParams } from "next-auth/jwt"; import { getToken } from "next-auth/jwt"; +//@ts-expect-error no type definitions +import { i18n } from "@calcom/web/next-i18next.config"; + /** * This is a slimmed down version of the `getServerSession` function from * `next-auth`. @@ -40,5 +44,9 @@ export const getLocale = async (req: GetTokenParams["req"]): Promise => // the regex underneath is more permissive const testedRegion = /^[a-zA-Z0-9]+$/.test(region) ? region : ""; - return `${testedCode}${testedRegion !== "" ? "-" : ""}${testedRegion}`; + const requestedLocale = `${testedCode}${testedRegion !== "" ? "-" : ""}${testedRegion}`; + + // use fallback to closest supported locale. + // for instance, es-419 will be transformed to es + return lookup(i18n.locales, requestedLocale) ?? requestedLocale; }; diff --git a/yarn.lock b/yarn.lock index ea460c1250..9f1c424bd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29419,7 +29419,7 @@ __metadata: languageName: node linkType: hard -"next-i18next@npm:^13.2.2": +"next-i18next@npm:13.3.0": version: 13.3.0 resolution: "next-i18next@npm:13.3.0" dependencies: @@ -29437,6 +29437,24 @@ __metadata: languageName: node linkType: hard +"next-i18next@patch:next-i18next@npm%3A13.3.0#./.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch::locator=calcom-monorepo%40workspace%3A.": + version: 13.3.0 + resolution: "next-i18next@patch:next-i18next@npm%3A13.3.0#./.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch::version=13.3.0&hash=bcbde7&locator=calcom-monorepo%40workspace%3A." + dependencies: + "@babel/runtime": ^7.20.13 + "@types/hoist-non-react-statics": ^3.3.1 + core-js: ^3 + hoist-non-react-statics: ^3.3.2 + i18next-fs-backend: ^2.1.1 + peerDependencies: + i18next: ^22.0.6 + next: ">= 12.0.0" + react: ">= 17.0.2" + react-i18next: ^12.2.0 + checksum: 7dcb7e2ec14a0164e2c803b5eb4be3d3198ff0db266fecd6225dfa99ec53bf923fe50230c413f2e9b9a795266fb4e31f129572865181df1eadcf8721ad138b3e + languageName: node + linkType: hard + "next-seo@npm:^6.0.0": version: 6.1.0 resolution: "next-seo@npm:6.1.0"