From 7f51bb6a69c4b717dcb72c09a6a68b78fc5e0efd Mon Sep 17 00:00:00 2001 From: behnam Date: Sat, 2 Nov 2024 20:41:38 +0300 Subject: [PATCH] Add i18n --- package.json | 4 + src/app/[lang]/dashboard/page.tsx | 7 +- .../vm/create-random-invoice-button-vm.ts | 6 +- src/app/[lang]/layout.tsx | 9 ++ src/app/layout.tsx | 2 +- src/bootstrap/helpers/global-helpers.ts | 2 + src/bootstrap/helpers/lib/actions.ts | 117 ------------------ src/bootstrap/i18n/dictionaries/en.ts | 1 + src/bootstrap/i18n/dictionaries/lang-key.ts | 7 +- src/bootstrap/i18n/dictionaries/ru.ts | 1 + src/bootstrap/i18n/i18n-provider.tsx | 13 ++ src/bootstrap/i18n/i18n.ts | 29 +++-- src/bootstrap/i18n/settings.ts | 1 + src/middleware.ts | 55 ++++---- yarn.lock | 85 ++++++++++++- 15 files changed, 183 insertions(+), 156 deletions(-) create mode 100644 src/app/[lang]/layout.tsx delete mode 100644 src/bootstrap/helpers/lib/actions.ts create mode 100644 src/bootstrap/i18n/i18n-provider.tsx diff --git a/package.json b/package.json index 8fee4df..818e3a6 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,19 @@ "@heroicons/react": "^2.1.5", "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-slot": "^1.1.0", + "accept-language": "^3.0.20", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "fp-ts": "^2.16.9", "i18next": "^23.16.4", + "i18next-browser-languagedetector": "^8.0.0", "i18next-resources-to-backend": "^1.2.1", "lucide-react": "^0.454.0", "next": "15.0.2", + "next-i18n-router": "^5.5.1", "postgres": "^3.4.5", "react": "19.0.0-rc-69d4b800-20241021", + "react-cookie": "^7.2.2", "react-dom": "19.0.0-rc-69d4b800-20241021", "react-i18next": "^15.1.0", "reflect-metadata": "^0.2.2", diff --git a/src/app/[lang]/dashboard/page.tsx b/src/app/[lang]/dashboard/page.tsx index a77f734..0035a7b 100644 --- a/src/app/[lang]/dashboard/page.tsx +++ b/src/app/[lang]/dashboard/page.tsx @@ -3,15 +3,16 @@ import CardWrapper from "@/app/[lang]/dashboard/components/server/cards/cards"; import LatestInvoices from "@/app/[lang]/dashboard/components/server/latest-invoices/latest-invoices"; import RevenueChart from "@/app/[lang]/dashboard/components/server/revenue-chart/revenue-chart"; import { Suspense } from "react"; -import { getTranslation } from "@/bootstrap/i18n/i18n"; +import { getServerTranslation } from "@/bootstrap/i18n/i18n"; +import langKey from "@/bootstrap/i18n/dictionaries/lang-key"; export default async function Dashboard(props: {params: Promise<{lang: string}>}) { const {lang} = await props.params - const { t } = await getTranslation(lang) + const { t } = await getServerTranslation(lang) return (

- {t("global.dashboard")} + {t(langKey.global.dashboard)}

diff --git a/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts b/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts index c5ceb13..136f5b0 100644 --- a/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts +++ b/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts @@ -2,10 +2,12 @@ import ButtonVm from "@/app/components/button/button-vm"; import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action"; import useThrottle from "@/bootstrap/helpers/hooks/use-throttle"; import BaseVM from "@/bootstrap/helpers/vm/base-vm"; +import langKey from "@/bootstrap/i18n/dictionaries/lang-key"; import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param"; import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase"; import { faker } from "@faker-js/faker"; import { useRouter } from "next/navigation"; +import { useTranslation } from "react-i18next"; export default class CreateRandomInvoiceButtonVM extends BaseVM { private createInvoice: typeof createInvoiceUsecase @@ -19,9 +21,11 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM { const [action, isPending] = useServerAction(() => this.onClickHandler(router.refresh)) const throttledOnClick = useThrottle(action, 5000) + const {t} = useTranslation() + return { props: { - title: isPending ? "Loading" : "Create Random Invoice", + title: t(isPending ? langKey.global.loading : langKey.dashboard.invoice.createButton), isDisable: isPending ? true : false }, onClick: throttledOnClick.bind(this) diff --git a/src/app/[lang]/layout.tsx b/src/app/[lang]/layout.tsx new file mode 100644 index 0000000..dedc58e --- /dev/null +++ b/src/app/[lang]/layout.tsx @@ -0,0 +1,9 @@ +import { initI18next } from "@/bootstrap/i18n/i18n"; +import TranslationsProvider from "@/bootstrap/i18n/i18n-provider"; +import { PropsWithChildren } from "react"; + +export default async function layout(props: PropsWithChildren & {params: Promise<{lang: string}>}) { + const lang = (await props.params).lang + const { resources} = await initI18next({lng: lang}) + return {props.children} +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ba8b4a7..8a8df4e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -23,7 +23,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/src/bootstrap/helpers/global-helpers.ts b/src/bootstrap/helpers/global-helpers.ts index e69de29..cf37914 100644 --- a/src/bootstrap/helpers/global-helpers.ts +++ b/src/bootstrap/helpers/global-helpers.ts @@ -0,0 +1,2 @@ + +export const isServer = typeof window === 'undefined' \ No newline at end of file diff --git a/src/bootstrap/helpers/lib/actions.ts b/src/bootstrap/helpers/lib/actions.ts deleted file mode 100644 index a408241..0000000 --- a/src/bootstrap/helpers/lib/actions.ts +++ /dev/null @@ -1,117 +0,0 @@ -'use server'; - -import { z } from 'zod'; -import { revalidatePath } from 'next/cache'; -import { redirect } from 'next/navigation'; -import { sql } from '@/bootstrap/boundaries/db/db'; - -const FormSchema = z.object({ - id: z.string(), - customerId: z.string({ - invalid_type_error: 'Please select a customer.', - }), - amount: z.coerce.number().gt(0, { message: 'Please enter an amount greater than $0.' }), - status: z.enum(['pending', 'paid'], { - invalid_type_error: 'Please select an invoice status.', - }), - date: z.string(), -}); -const CreateInvoice = FormSchema.omit({ id: true, date: true }); - - - -// This is temporary -export type State = { - errors?: { - customerId?: string[]; - amount?: string[]; - status?: string[]; - }; - message?: string | null; -}; - -export async function createInvoice(_prevState: State, formData: FormData) { - // Validate form fields using Zod - console.log('ffor', formData) - const validatedFields = CreateInvoice.safeParse({ - customerId: formData?.get('customerId') || undefined, - amount: formData?.get('amount') || undefined, - status: formData?.get('status') || undefined, - }); - - // If form validation fails, return errors early. Otherwise, continue. - if (!validatedFields.success) { - return { - errors: validatedFields.error.flatten().fieldErrors, - message: 'Missing Fields. Failed to Create Invoice.', - }; - } - - // Prepare data for insertion into the database - const { customerId, amount, status } = validatedFields.data; - const amountInCents = amount * 100; - const date = new Date().toISOString().split('T')[0]; - - // Insert data into the database - try { - await sql` - INSERT INTO invoices (customer_id, amount, status, date) - VALUES (${customerId}, ${amountInCents}, ${status}, ${date}) - `; - } catch { - // If a database error occurs, return a more specific error. - return { - message: 'Database Error: Failed to Create Invoice.', - }; - } - - // Revalidate the cache for the invoices page and redirect the user. - revalidatePath('/dashboard/invoices'); - redirect('/dashboard/invoices'); -} - -const UpdateInvoice = FormSchema.omit({ id: true, date: true }); -export async function updateInvoice( - id: string, - _prevState: State, - formData: FormData, -) { - const validatedFields = UpdateInvoice.safeParse({ - customerId: formData.get('customerId'), - amount: formData.get('amount'), - status: formData.get('status'), - }); - - if (!validatedFields.success) { - return { - errors: validatedFields.error.flatten().fieldErrors, - message: 'Missing Fields. Failed to Update Invoice.', - }; - } - - const { customerId, amount, status } = validatedFields.data; - const amountInCents = amount * 100; - - try { - await sql` - UPDATE invoices - SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status} - WHERE id = ${id} - `; - } catch { - return { message: 'Database Error: Failed to Update Invoice.' }; - } - - revalidatePath('/dashboard/invoices'); - redirect('/dashboard/invoices'); -} -export async function deleteInvoice(id: string) { - try { - await sql`DELETE FROM invoices WHERE id = ${id}`; - revalidatePath('/dashboard/invoices'); - return { message: 'Deleted Invoice.' }; - } catch { - return { message: 'Database Error: Failed to Delete Invoice.' }; - } -} - diff --git a/src/bootstrap/i18n/dictionaries/en.ts b/src/bootstrap/i18n/dictionaries/en.ts index 3bd88ce..3f0f60c 100644 --- a/src/bootstrap/i18n/dictionaries/en.ts +++ b/src/bootstrap/i18n/dictionaries/en.ts @@ -3,6 +3,7 @@ import langKey from "@/bootstrap/i18n/dictionaries/lang-key" const en: typeof langKey = { global: { home: "Home", + loading: "Loading", dashboard: "Dashboard" }, dashboard: { diff --git a/src/bootstrap/i18n/dictionaries/lang-key.ts b/src/bootstrap/i18n/dictionaries/lang-key.ts index aed439c..69ca04e 100644 --- a/src/bootstrap/i18n/dictionaries/lang-key.ts +++ b/src/bootstrap/i18n/dictionaries/lang-key.ts @@ -1,11 +1,12 @@ const langKey = { global: { - home: "Дом", - dashboard: "Панель приборов" + home: "global.home", + dashboard: "global.dashboard", + loading: "global.loading" }, dashboard: { invoice: { - createButton: "Создать случайный счет-фактуру" + createButton: "dashboard.invoice.createButton" } } } diff --git a/src/bootstrap/i18n/dictionaries/ru.ts b/src/bootstrap/i18n/dictionaries/ru.ts index 5b6c4b4..9c8e561 100644 --- a/src/bootstrap/i18n/dictionaries/ru.ts +++ b/src/bootstrap/i18n/dictionaries/ru.ts @@ -3,6 +3,7 @@ import langKey from "@/bootstrap/i18n/dictionaries/lang-key" const ru: typeof langKey = { global: { home: "Дом", + loading: "Загрузка", dashboard: "Панель приборов" }, dashboard: { diff --git a/src/bootstrap/i18n/i18n-provider.tsx b/src/bootstrap/i18n/i18n-provider.tsx new file mode 100644 index 0000000..d15b347 --- /dev/null +++ b/src/bootstrap/i18n/i18n-provider.tsx @@ -0,0 +1,13 @@ +"use client" +import { I18nextProvider } from "react-i18next" +import { initI18next } from "@/bootstrap/i18n/i18n"; +import { createInstance, Resource } from "i18next"; +import { PropsWithChildren } from "react"; + +export default function TranslationsProvider({children, lng, resources}: PropsWithChildren & {lng: string; resources: Resource}) { + const i18n = createInstance() + + initI18next({lng, i18n, resources}) + + return {children} +} \ No newline at end of file diff --git a/src/bootstrap/i18n/i18n.ts b/src/bootstrap/i18n/i18n.ts index 9cc5fb0..edd45d5 100644 --- a/src/bootstrap/i18n/i18n.ts +++ b/src/bootstrap/i18n/i18n.ts @@ -1,19 +1,32 @@ -import { getOptions } from '@/bootstrap/i18n/settings' -import { createInstance } from 'i18next' +import { getOptions, languages } from '@/bootstrap/i18n/settings' +import { createInstance, i18n, Resource } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' import { initReactI18next } from 'react-i18next/initReactI18next' -const initI18next = async (lng: string, ns?: string) => { - const i18nInstance = createInstance() +export const initI18next = async (params: {lng: string, i18n?: i18n, resources?: Resource, ns?: string}) => { + const { lng, i18n, ns, resources } = params + const i18nInstance = i18n ? i18n : createInstance() await i18nInstance .use(initReactI18next) .use(resourcesToBackend((language: string) => import(`./dictionaries/${language}.ts`))) - .init(getOptions(lng, ns)) - return i18nInstance + .init({ + ...getOptions(lng, ns), + resources, + preload: resources ? [] : languages + },) + + await i18nInstance.init() + + return { + i18n: i18nInstance, + resources: i18nInstance.services.resourceStore.data, + t: i18nInstance.t + } } -export async function getTranslation(lng: string, ns?: string, options: {keyPrefix?: string} = {}) { - const i18nextInstance = await initI18next(lng, ns) +export async function getServerTranslation(lng: string, ns?: string, options: {keyPrefix?: string} = {}) { + const i18nextInstance = (await initI18next({lng, ns})).i18n + return { t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns, options?.keyPrefix), i18n: i18nextInstance diff --git a/src/bootstrap/i18n/settings.ts b/src/bootstrap/i18n/settings.ts index 90f4fa0..7e3030b 100644 --- a/src/bootstrap/i18n/settings.ts +++ b/src/bootstrap/i18n/settings.ts @@ -1,6 +1,7 @@ export const fallbackLng = 'en' export const languages = [fallbackLng, 'ru'] export const defaultNS = 'translation' +export const cookieName = 'i18next' export function getOptions (lng = fallbackLng, ns = defaultNS) { return { diff --git a/src/middleware.ts b/src/middleware.ts index ee54225..8c6f760 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,23 +1,36 @@ -import { fallbackLng, languages } from "@/bootstrap/i18n/settings"; -import { NextRequest, NextResponse } from "next/server"; - -export function middleware(request: NextRequest) { - const { pathname } = request.nextUrl - const pathnameHasLocale = languages.some( - (lang) => pathname.startsWith(`/${lang}/`) || pathname === `/${lang}` - ) - - if (pathnameHasLocale) return - - request.nextUrl.pathname = `/${fallbackLng}${pathname}` - // e.g. incoming request is /products - // The new URL is now /en-US/products - return NextResponse.redirect(request.nextUrl) -} - +import { NextRequest, NextResponse } from 'next/server' +import acceptLanguage from 'accept-language' +import { cookieName, fallbackLng, languages } from '@/bootstrap/i18n/settings' + +acceptLanguage.languages(languages) + export const config = { - matcher: [ - // Skip all internal paths (_next) - '/((?!api|_next/static|_next/image|favicon.ico).*)' - ], + matcher: ["/((?!api|static|.*\\..*|_next).*)"] +} + +export function middleware(req: NextRequest) { + let lng + if (req.cookies.has(cookieName)) lng = acceptLanguage.get(req?.cookies?.get(cookieName)?.value) + if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language')) + if (!lng) lng = fallbackLng + + // Redirect if lng in path is not supported + if ( + !languages.some(loc => req.nextUrl.pathname.startsWith(`/${loc}`)) && + !req.nextUrl.pathname.startsWith('/_next') + ) { + return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url)) + } + + if (req.headers.has('referer')) { + const refererUrl = new URL(req?.headers?.get('referer') ?? "") + const lngInReferer = languages.find((l) => refererUrl.pathname.startsWith(`/${l}`)) + const response = NextResponse.next() + if (lngInReferer) { + response.cookies.set(cookieName, lngInReferer) + } + return response + } + + return NextResponse.next() } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4fc4079..cac9504 100644 --- a/yarn.lock +++ b/yarn.lock @@ -334,6 +334,13 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.1.0.tgz#5d7957df87e2fb0eee5dcfd311ba83b34ec8eead" integrity sha512-GJvX9iM9PBtKScJVlXQ0tWpihK3i0pha/XAhzQa1hPK/ILLa1Wq3I63Ij7lRtqTwmdTxRCyrUhLC5Sly9SLbug== +"@formatjs/intl-localematcher@^0.5.2": + version "0.5.6" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.6.tgz#cd0cd99483673d3196a15b4e2c924cfda7f002f8" + integrity sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA== + dependencies: + tslib "2" + "@heroicons/react@^2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.1.5.tgz#1e13f34976cc542deae92353c01c8b3d7942e9ba" @@ -801,11 +808,24 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + "@types/estree@1.0.6", "@types/estree@^1.0.0": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== +"@types/hoist-non-react-statics@^3.3.5": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -999,6 +1019,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +accept-language@^3.0.20: + version "3.0.20" + resolved "https://registry.yarnpkg.com/accept-language/-/accept-language-3.0.20.tgz#e825601d3b59f5ac7487698569b6640e80240cda" + integrity sha512-xklPzRma4aoDEPk0ZfMjeuxB2FP4JBYlAR25OFUqCoOYDjYo6wGwAs49SnTN/MoB5VpnNX9tENfZ+vEIFmHQMQ== + dependencies: + bcp47 "^1.1.2" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1234,6 +1261,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bcp47@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/bcp47/-/bcp47-1.1.2.tgz#354be3307ffd08433a78f5e1e2095845f89fc7fe" + integrity sha512-JnkkL4GUpOvvanH9AZPX38CxhiLsXMBicBY2IAtqiVN8YulGDQybUydWA4W6yAMtw6iShtw+8HEF6cfrTHU+UQ== + bcrypt@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" @@ -1443,6 +1475,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2335,6 +2372,13 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + html-encoding-sniffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" @@ -2373,6 +2417,13 @@ https-proxy-agent@^7.0.5: agent-base "^7.0.2" debug "4" +i18next-browser-languagedetector@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz#b6fdd9b43af67c47f2c26c9ba27710a1eaf31e2f" + integrity sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw== + dependencies: + "@babel/runtime" "^7.23.2" + i18next-resources-to-backend@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.1.tgz#fded121e63e3139ce839c9901b9449dbbea7351d" @@ -2956,6 +3007,19 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +negotiator@^0.6.3: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + +next-i18n-router@^5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/next-i18n-router/-/next-i18n-router-5.5.1.tgz#3d4c34d431e38aa4ba8f52cd54d4387fc70db6d2" + integrity sha512-uJGYUAQS33LbRT3Jx+kurR/E79iPQo1jWZUYmc+614UkPt58k2XYyGloSvHR74b21i4K/d6eksdBj6T2WojjdA== + dependencies: + "@formatjs/intl-localematcher" "^0.5.2" + negotiator "^0.6.3" + next@15.0.2: version "15.0.2" resolved "https://registry.yarnpkg.com/next/-/next-15.0.2.tgz#4a2224c007856118010b8cef5e9b2383cd743388" @@ -3305,6 +3369,15 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +react-cookie@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-7.2.2.tgz#a7559e552ea9cca39a4b3686723a5acf504b8f84" + integrity sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.5" + hoist-non-react-statics "^3.3.2" + universal-cookie "^7.0.0" + react-dom@19.0.0-rc-69d4b800-20241021: version "19.0.0-rc-69d4b800-20241021" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0-rc-69d4b800-20241021.tgz#e27b4f2c962236e9ece496a0ea1c9c7161608ea0" @@ -3320,7 +3393,7 @@ react-i18next@^15.1.0: "@babel/runtime" "^7.25.0" html-parse-stringify "^3.0.1" -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -3968,7 +4041,7 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@*, tslib@^2.4.0: +tslib@*, tslib@2, tslib@^2.4.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -4061,6 +4134,14 @@ undici-types@~6.19.2: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +universal-cookie@^7.0.0: + version "7.2.2" + resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-7.2.2.tgz#93ae9ec55baab89b24300473543170bb8112773c" + integrity sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ== + dependencies: + "@types/cookie" "^0.6.0" + cookie "^0.7.2" + update-browserslist-db@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5"