From a5af0cae4d643dc29815803d848e3bda8da57cce Mon Sep 17 00:00:00 2001 From: peintnermax Date: Tue, 8 Oct 2024 09:31:56 +0200 Subject: [PATCH] i18n --- apps/login/i18nConfig.js | 7 ++ apps/login/locales/en/loginname.json | 0 apps/login/package.json | 4 + .../(login)/{ => [locale]}/accounts/page.tsx | 0 .../src/app/(login)/{ => [locale]}/error.tsx | 0 .../idp/[provider]/failure/page.tsx | 0 .../idp/[provider]/success/page.tsx | 0 .../app/(login)/{ => [locale]}/idp/page.tsx | 0 .../(login)/{ => [locale]}/loginname/page.tsx | 0 .../me/change-password/page.tsx | 0 .../app/(login)/{ => [locale]}/mfa/page.tsx | 0 .../(login)/{ => [locale]}/mfa/set/page.tsx | 0 .../{ => [locale]}/otp/[method]/page.tsx | 0 .../{ => [locale]}/otp/[method]/set/page.tsx | 0 .../(login)/{ => [locale]}/overview/page.tsx | 0 .../(login)/{ => [locale]}/passkey/page.tsx | 0 .../{ => [locale]}/passkey/set/page.tsx | 0 .../(login)/{ => [locale]}/password/page.tsx | 0 .../(login)/{ => [locale]}/register/page.tsx | 0 .../(login)/{ => [locale]}/signedin/page.tsx | 0 .../app/(login)/{ => [locale]}/u2f/page.tsx | 0 .../(login)/{ => [locale]}/u2f/set/page.tsx | 0 .../(login)/{ => [locale]}/verify/page.tsx | 0 apps/login/src/app/i18n.js | 40 +++++++++ apps/login/src/app/layout.tsx | 82 ++++++++++------- .../src/components/translations-provider.tsx | 26 ++++++ apps/login/src/middleware.ts | 1 + pnpm-lock.yaml | 87 ++++++++++++++++++- 28 files changed, 216 insertions(+), 31 deletions(-) create mode 100644 apps/login/i18nConfig.js create mode 100644 apps/login/locales/en/loginname.json rename apps/login/src/app/(login)/{ => [locale]}/accounts/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/error.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/idp/[provider]/failure/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/idp/[provider]/success/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/idp/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/loginname/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/me/change-password/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/mfa/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/mfa/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/otp/[method]/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/otp/[method]/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/overview/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/passkey/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/passkey/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/password/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/register/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/signedin/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/u2f/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/u2f/set/page.tsx (100%) rename apps/login/src/app/(login)/{ => [locale]}/verify/page.tsx (100%) create mode 100644 apps/login/src/app/i18n.js create mode 100644 apps/login/src/components/translations-provider.tsx diff --git a/apps/login/i18nConfig.js b/apps/login/i18nConfig.js new file mode 100644 index 00000000000..f47cf77f79b --- /dev/null +++ b/apps/login/i18nConfig.js @@ -0,0 +1,7 @@ +const i18nConfig = { + locales: ["en", "de", "it"], + defaultLocale: "en", + prefixDefault: false, +}; + +module.exports = i18nConfig; diff --git a/apps/login/locales/en/loginname.json b/apps/login/locales/en/loginname.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/login/package.json b/apps/login/package.json index 0670d5bf496..208ab3b8bac 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -42,14 +42,18 @@ "@zitadel/proto": "workspace:*", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", + "i18next": "^23.15.2", + "i18next-resources-to-backend": "^1.2.1", "moment": "^2.29.4", "next": "14.2.10", + "next-i18n-router": "^5.5.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", "qrcode.react": "^3.1.0", "react": "^18.3.1", "react-dom": "18.3.1", "react-hook-form": "7.39.5", + "react-i18next": "^15.0.2", "swr": "^2.2.0", "tinycolor2": "1.4.2" }, diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/[locale]/accounts/page.tsx similarity index 100% rename from apps/login/src/app/(login)/accounts/page.tsx rename to apps/login/src/app/(login)/[locale]/accounts/page.tsx diff --git a/apps/login/src/app/(login)/error.tsx b/apps/login/src/app/(login)/[locale]/error.tsx similarity index 100% rename from apps/login/src/app/(login)/error.tsx rename to apps/login/src/app/(login)/[locale]/error.tsx diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/[locale]/idp/[provider]/failure/page.tsx similarity index 100% rename from apps/login/src/app/(login)/idp/[provider]/failure/page.tsx rename to apps/login/src/app/(login)/[locale]/idp/[provider]/failure/page.tsx diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/[locale]/idp/[provider]/success/page.tsx similarity index 100% rename from apps/login/src/app/(login)/idp/[provider]/success/page.tsx rename to apps/login/src/app/(login)/[locale]/idp/[provider]/success/page.tsx diff --git a/apps/login/src/app/(login)/idp/page.tsx b/apps/login/src/app/(login)/[locale]/idp/page.tsx similarity index 100% rename from apps/login/src/app/(login)/idp/page.tsx rename to apps/login/src/app/(login)/[locale]/idp/page.tsx diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/[locale]/loginname/page.tsx similarity index 100% rename from apps/login/src/app/(login)/loginname/page.tsx rename to apps/login/src/app/(login)/[locale]/loginname/page.tsx diff --git a/apps/login/src/app/(login)/me/change-password/page.tsx b/apps/login/src/app/(login)/[locale]/me/change-password/page.tsx similarity index 100% rename from apps/login/src/app/(login)/me/change-password/page.tsx rename to apps/login/src/app/(login)/[locale]/me/change-password/page.tsx diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/[locale]/mfa/page.tsx similarity index 100% rename from apps/login/src/app/(login)/mfa/page.tsx rename to apps/login/src/app/(login)/[locale]/mfa/page.tsx diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/[locale]/mfa/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/mfa/set/page.tsx rename to apps/login/src/app/(login)/[locale]/mfa/set/page.tsx diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/[locale]/otp/[method]/page.tsx similarity index 100% rename from apps/login/src/app/(login)/otp/[method]/page.tsx rename to apps/login/src/app/(login)/[locale]/otp/[method]/page.tsx diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/[locale]/otp/[method]/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/otp/[method]/set/page.tsx rename to apps/login/src/app/(login)/[locale]/otp/[method]/set/page.tsx diff --git a/apps/login/src/app/(login)/overview/page.tsx b/apps/login/src/app/(login)/[locale]/overview/page.tsx similarity index 100% rename from apps/login/src/app/(login)/overview/page.tsx rename to apps/login/src/app/(login)/[locale]/overview/page.tsx diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/[locale]/passkey/page.tsx similarity index 100% rename from apps/login/src/app/(login)/passkey/page.tsx rename to apps/login/src/app/(login)/[locale]/passkey/page.tsx diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/[locale]/passkey/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/passkey/set/page.tsx rename to apps/login/src/app/(login)/[locale]/passkey/set/page.tsx diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/[locale]/password/page.tsx similarity index 100% rename from apps/login/src/app/(login)/password/page.tsx rename to apps/login/src/app/(login)/[locale]/password/page.tsx diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/[locale]/register/page.tsx similarity index 100% rename from apps/login/src/app/(login)/register/page.tsx rename to apps/login/src/app/(login)/[locale]/register/page.tsx diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/[locale]/signedin/page.tsx similarity index 100% rename from apps/login/src/app/(login)/signedin/page.tsx rename to apps/login/src/app/(login)/[locale]/signedin/page.tsx diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/[locale]/u2f/page.tsx similarity index 100% rename from apps/login/src/app/(login)/u2f/page.tsx rename to apps/login/src/app/(login)/[locale]/u2f/page.tsx diff --git a/apps/login/src/app/(login)/u2f/set/page.tsx b/apps/login/src/app/(login)/[locale]/u2f/set/page.tsx similarity index 100% rename from apps/login/src/app/(login)/u2f/set/page.tsx rename to apps/login/src/app/(login)/[locale]/u2f/set/page.tsx diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/[locale]/verify/page.tsx similarity index 100% rename from apps/login/src/app/(login)/verify/page.tsx rename to apps/login/src/app/(login)/[locale]/verify/page.tsx diff --git a/apps/login/src/app/i18n.js b/apps/login/src/app/i18n.js new file mode 100644 index 00000000000..b6dae438b70 --- /dev/null +++ b/apps/login/src/app/i18n.js @@ -0,0 +1,40 @@ +import { createInstance } from "i18next"; +import { initReactI18next } from "react-i18next/initReactI18next"; +import resourcesToBackend from "i18next-resources-to-backend"; +import i18nConfig from "i18nConfig"; + +export default async function initTranslations( + locale, + namespaces, + i18nInstance, + resources +) { + i18nInstance = i18nInstance || createInstance(); + + i18nInstance.use(initReactI18next); + + if (!resources) { + i18nInstance.use( + resourcesToBackend((language, namespace) => + import(`/locales/${language}/${namespace}.json`) + ) + ); + } + + await i18nInstance.init({ + lng: locale, + resources, + fallbackLng: i18nConfig.defaultLocale, + supportedLngs: i18nConfig.locales, + defaultNS: namespaces[0], + fallbackNS: namespaces[0], + ns: namespaces, + preload: resources ? [] : i18nConfig.locales, + }); + + return { + i18n: i18nInstance, + resources: i18nInstance.services.resourceStore.data, + t: i18nInstance.t, + }; +} diff --git a/apps/login/src/app/layout.tsx b/apps/login/src/app/layout.tsx index 701409fd0f0..f8f0fab6a70 100644 --- a/apps/login/src/app/layout.tsx +++ b/apps/login/src/app/layout.tsx @@ -4,9 +4,13 @@ import { AddressBar } from "@/components/address-bar"; import { GlobalNav } from "@/components/global-nav"; import { Theme } from "@/components/theme"; import { ThemeProvider } from "@/components/theme-provider"; +import { TranslationsProvider } from "@/components/translations-provider"; import { Analytics } from "@vercel/analytics/react"; +import i18nConfig from "i18nConfig"; +import { dir } from "i18next"; import { Lato } from "next/font/google"; import { ReactNode } from "react"; +import initTranslations from "./i18n"; const lato = Lato({ weight: ["400", "700", "900"], @@ -15,11 +19,20 @@ const lato = Lato({ export const revalidate = 60; // revalidate every minute +export function generateStaticParams() { + return i18nConfig.locales.map((locale) => ({ locale })); +} + export default async function RootLayout({ children, + params: { locale }, }: { children: ReactNode; + params: { locale: string }; }) { + const i18nNamespaces = ["common", "footer"]; + const { t, resources } = await initTranslations(locale, i18nNamespaces); + // later only shown with dev mode enabled const showNav = process.env.DEBUG === "true"; @@ -27,45 +40,56 @@ export default async function RootLayout({ domain = domain ? domain.replace("https://", "") : "acme.com"; return ( - + -
- {showNav ? ( - - ) : ( -
- -
- )} -
-
- {showNav && ( -
-
- -
-
- )} + {showNav ? ( + + ) : ( +
+ +
+ )} - {children} +
+
+ {showNav && ( +
+
+ +
+
+ )} + + {children} +
-
- + + diff --git a/apps/login/src/components/translations-provider.tsx b/apps/login/src/components/translations-provider.tsx new file mode 100644 index 00000000000..d4692a5e6d9 --- /dev/null +++ b/apps/login/src/components/translations-provider.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { createInstance } from "i18next"; +import React from "react"; +import { I18nextProvider } from "react-i18next"; +import initTranslations from "../app/i18n"; + +type Props = { + locale: string; + children: React.ReactNode; + namespaces: string[]; + resources: Record>; +}; + +export function TranslationsProvider({ + children, + locale, + namespaces, + resources, +}: Props) { + const i18n = createInstance(); + + initTranslations(locale, namespaces, i18n, resources); + + return {children}; +} diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 3b9f879e30b..ee3a66eaf00 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -32,6 +32,7 @@ export function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Headers", "*"); request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`; + return NextResponse.rewrite(request.nextUrl, { request: { headers: requestHeaders, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6435a13f2f..ecfa984f27b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,12 +77,21 @@ importers: copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 + i18next: + specifier: ^23.15.2 + version: 23.15.2 + i18next-resources-to-backend: + specifier: ^1.2.1 + version: 1.2.1 moment: specifier: ^2.29.4 version: 2.30.1 next: specifier: 14.2.10 version: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) + next-i18n-router: + specifier: ^5.5.1 + version: 5.5.1 next-themes: specifier: ^0.2.1 version: 0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -101,6 +110,9 @@ importers: react-hook-form: specifier: 7.39.5 version: 7.39.5(react@18.3.1) + react-i18next: + specifier: ^15.0.2 + version: 15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) swr: specifier: ^2.2.0 version: 2.2.5(react@18.3.1) @@ -839,6 +851,9 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@formatjs/intl-localematcher@0.5.4': + resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} + '@grpc/grpc-js@1.11.1': resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} engines: {node: '>=12.10.0'} @@ -2141,6 +2156,7 @@ packages: eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -2491,6 +2507,9 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2522,6 +2541,12 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + i18next-resources-to-backend@1.2.1: + resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} + + i18next@23.15.2: + resolution: {integrity: sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -3102,6 +3127,13 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + next-i18n-router@5.5.1: + resolution: {integrity: sha512-uJGYUAQS33LbRT3Jx+kurR/E79iPQo1jWZUYmc+614UkPt58k2XYyGloSvHR74b21i4K/d6eksdBj6T2WojjdA==} + next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} peerDependencies: @@ -3603,6 +3635,19 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 + react-i18next@15.0.2: + resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4375,6 +4420,10 @@ packages: jsdom: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -5065,6 +5114,10 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@formatjs/intl-localematcher@0.5.4': + dependencies: + tslib: 2.7.0 + '@grpc/grpc-js@1.11.1': dependencies: '@grpc/proto-loader': 0.7.13 @@ -5136,14 +5189,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -6988,6 +7041,10 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 @@ -7023,6 +7080,14 @@ snapshots: human-signals@5.0.0: {} + i18next-resources-to-backend@1.2.1: + dependencies: + '@babel/runtime': 7.25.7 + + i18next@23.15.2: + dependencies: + '@babel/runtime': 7.25.7 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -7579,6 +7644,13 @@ snapshots: natural-compare@1.4.0: {} + negotiator@0.6.3: {} + + next-i18n-router@5.5.1: + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + negotiator: 0.6.3 + next-themes@0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1) @@ -7990,6 +8062,15 @@ snapshots: dependencies: react: 18.3.1 + react-i18next@15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.7 + html-parse-stringify: 3.0.1 + i18next: 23.15.2 + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-is@16.13.1: {} react-is@17.0.2: {} @@ -8834,6 +8915,8 @@ snapshots: - supports-color - terser + void-elements@3.1.0: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0