diff --git a/app/about/page.tsx b/app/[locale]/about/page.tsx
similarity index 100%
rename from app/about/page.tsx
rename to app/[locale]/about/page.tsx
diff --git a/app/contact/page.tsx b/app/[locale]/contact/page.tsx
similarity index 100%
rename from app/contact/page.tsx
rename to app/[locale]/contact/page.tsx
diff --git a/app/faq/page.tsx b/app/[locale]/faq/page.tsx
similarity index 100%
rename from app/faq/page.tsx
rename to app/[locale]/faq/page.tsx
diff --git a/app/[locale]/home/page.tsx b/app/[locale]/home/page.tsx
new file mode 100644
index 0000000..71136c5
--- /dev/null
+++ b/app/[locale]/home/page.tsx
@@ -0,0 +1,25 @@
+import {
+ AboutUs,
+ Banner,
+ Blog,
+ Line,
+ OurService,
+ Statistics,
+ Testimonial,
+ Video,
+} from "@/components/pages/home";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/products/[slug]/page.tsx b/app/[locale]/products/[slug]/page.tsx
similarity index 100%
rename from app/products/[slug]/page.tsx
rename to app/[locale]/products/[slug]/page.tsx
diff --git a/app/products/page.tsx b/app/[locale]/products/page.tsx
similarity index 100%
rename from app/products/page.tsx
rename to app/[locale]/products/page.tsx
diff --git a/app/services/page.tsx b/app/[locale]/services/page.tsx
similarity index 100%
rename from app/services/page.tsx
rename to app/[locale]/services/page.tsx
diff --git a/app/layout.tsx b/app/layout.tsx
index 5fc847d..8703c8c 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,9 +4,8 @@ import { Geist, Geist_Mono } from "next/font/google";
import { Analytics } from "@vercel/analytics/next";
import "./globals.css";
import { Footer, Navbar } from "@/components/layout";
-
-const _geist = Geist({ subsets: ["latin"] });
-const _geistMono = Geist_Mono({ subsets: ["latin"] });
+import { NextIntlClientProvider } from "next-intl";
+import { getMessages } from "next-intl/server";
export const metadata: Metadata = {
title: "FireForce - Emergency Services",
@@ -32,18 +31,24 @@ export const metadata: Metadata = {
},
};
-export default function RootLayout({
+export default async function RootLayout({
children,
+ params,
}: Readonly<{
children: React.ReactNode;
+ params: any;
}>) {
+ const { locale } = await params;
+ const messages: any = await getMessages();
return (
-
+
-
- {children}
-
-
+
+
+ {children}
+
+
+
);
diff --git a/app/page.tsx b/app/page.tsx
index 71136c5..338fa12 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,25 +1,5 @@
-import {
- AboutUs,
- Banner,
- Blog,
- Line,
- OurService,
- Statistics,
- Testimonial,
- Video,
-} from "@/components/pages/home";
+import { redirect } from "next/navigation";
export default function Home() {
- return (
-
-
-
-
-
-
-
-
-
-
- );
+ return redirect('/uz/home')
}
diff --git a/components/languageSwitcher.tsx b/components/languageSwitcher.tsx
new file mode 100644
index 0000000..b25f4ae
--- /dev/null
+++ b/components/languageSwitcher.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter, usePathname } from "next/navigation";
+import { Check, ChevronDown, Globe } from "lucide-react";
+import { locales, localeFlags, localeNames, type Locale } from "@/i18n/config";
+import { useLocale } from "next-intl";
+
+export default function LanguageSelectRadix() {
+ const router = useRouter();
+ const pathname = usePathname();
+ const currentLocale = useLocale() as Locale;
+ const [isPending, startTransition] = useTransition();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const changeLanguage = (newLocale: Locale) => {
+ if (newLocale === currentLocale) {
+ setIsOpen(false);
+ return;
+ }
+
+ startTransition(() => {
+ // ✅ 1. Set cookie (middleware will sync this)
+ document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;
+
+ // ✅ 2. Build new path
+ const segments = pathname.split("/").filter(Boolean);
+
+ // Remove current locale if exists
+ const pathWithoutLocale =
+ segments[0] && locales.includes(segments[0] as Locale)
+ ? segments.slice(1)
+ : segments;
+
+ // Add new locale
+ const newPath = `/${newLocale}${
+ pathWithoutLocale.length ? "/" + pathWithoutLocale.join("/") : ""
+ }`;
+
+ // ✅ 3. Navigate (middleware will handle the rest)
+ router.push(newPath);
+
+ // ✅ 4. Force refresh after navigation completes
+ setTimeout(() => {
+ router.refresh();
+ }, 100); // Small delay ensures navigation completes
+ });
+
+ setIsOpen(false);
+ };
+
+ return (
+
+ {/* Trigger Button */}
+
+
+ {/* Dropdown */}
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
setIsOpen(false)}
+ aria-hidden="true"
+ />
+
+ {/* Menu */}
+
+
+ {locales.map((lang) => (
+
+ ))}
+
+
+ >
+ )}
+
+ );
+}
diff --git a/components/layout/navbar.tsx b/components/layout/navbar.tsx
index 1e5c303..730b380 100644
--- a/components/layout/navbar.tsx
+++ b/components/layout/navbar.tsx
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
import Link from "next/link";
import { ChevronDown, Phone, Menu, X } from "lucide-react";
import Image from "next/image";
+import LanguageSelectRadix from "../languageSwitcher";
export function Navbar() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@@ -122,6 +123,9 @@ export function Navbar() {
+ {/* Language Switcher */}
+
+
{/* Emergency Call Button - Hidden on mobile */}
diff --git a/i18n/config.ts b/i18n/config.ts
new file mode 100644
index 0000000..6d7de9b
--- /dev/null
+++ b/i18n/config.ts
@@ -0,0 +1,16 @@
+export const locales = ["uz", "ru", "en"] as const
+export type Locale = (typeof locales)[number]
+
+export const defaultLocale: Locale = "uz"
+
+export const localeNames: Record
= {
+ uz: "O'zbekcha",
+ ru: "Русский",
+ en: "English",
+}
+
+export const localeFlags: Record = {
+ uz: "🇺🇿",
+ ru: "🇷🇺",
+ en: "🇬🇧",
+}
diff --git a/i18n/request.ts b/i18n/request.ts
new file mode 100644
index 0000000..b9bbb99
--- /dev/null
+++ b/i18n/request.ts
@@ -0,0 +1,25 @@
+// i18n/request.ts
+import { getRequestConfig } from 'next-intl/server';
+import { headers } from 'next/headers';
+
+const LOCALES = ["uz", "ru", "en"] as const;
+const DEFAULT_LOCALE = "uz";
+
+type Locale = typeof LOCALES[number];
+
+export default getRequestConfig(async () => {
+ const headersList = await headers();
+
+ // ✅ Middleware allaqachon locale'ni set qilgan
+ const locale = headersList.get('x-locale') || DEFAULT_LOCALE;
+
+ // Validate
+ const validLocale = LOCALES.includes(locale as Locale)
+ ? locale
+ : DEFAULT_LOCALE;
+
+ return {
+ locale: validLocale,
+ messages: (await import(`../messages/${validLocale}.json`)).default,
+ };
+});
\ No newline at end of file
diff --git a/lib/i18n-utils.ts b/lib/i18n-utils.ts
new file mode 100644
index 0000000..dfa3f3a
--- /dev/null
+++ b/lib/i18n-utils.ts
@@ -0,0 +1,23 @@
+"use client"
+
+import { Locale } from "@/i18n/config"
+import { useRouter } from "next/navigation"
+
+export function useChangeLocale() {
+ const router = useRouter()
+
+ const changeLocale = (locale: Locale) => {
+ document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=31536000`
+ router.refresh()
+ }
+
+ return changeLocale
+}
+
+export function getLocaleFromCookie(): Locale {
+ if (typeof document === "undefined") return "uz"
+
+ const cookie = document.cookie.split("; ").find((row) => row.startsWith("NEXT_LOCALE="))
+
+ return (cookie?.split("=")[1] as Locale) || "uz"
+}
diff --git a/locales/en.json b/messages/en.json
similarity index 100%
rename from locales/en.json
rename to messages/en.json
diff --git a/locales/ru.json b/messages/ru.json
similarity index 100%
rename from locales/ru.json
rename to messages/ru.json
diff --git a/locales/uz.json b/messages/uz.json
similarity index 100%
rename from locales/uz.json
rename to messages/uz.json
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..52a363e
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,136 @@
+import { NextRequest, NextResponse } from "next/server";
+import { match as matchLocale } from "@formatjs/intl-localematcher";
+import Negotiator from "negotiator";
+const PUBLIC_PAGES = ["/login", "/register"];
+
+const LOCALES = ["uz", "ru", "en"];
+const DEFAULT_LOCALE = "uz";
+
+type Locale = (typeof LOCALES)[number];
+
+function getLocaleFromPathname(pathname: string): Locale | null {
+ const segments = pathname.split("/").filter(Boolean);
+ const firstSegment = segments[0];
+
+ if (firstSegment && LOCALES.includes(firstSegment as Locale)) {
+ return firstSegment as Locale;
+ }
+
+ return null;
+}
+
+function getLocaleFromCookie(request: NextRequest): Locale | null {
+ const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value;
+
+ if (cookieLocale && LOCALES.includes(cookieLocale as Locale)) {
+ return cookieLocale as Locale;
+ }
+
+ return null;
+}
+
+function getLocaleFromHeaders(request: NextRequest): Locale {
+ const negotiatorHeaders: Record = {};
+ request.headers.forEach((value, key) => {
+ negotiatorHeaders[key] = value;
+ });
+
+ const languages = new Negotiator({ headers: negotiatorHeaders }).languages();
+
+ try {
+ return matchLocale(
+ languages,
+ LOCALES as unknown as string[],
+ DEFAULT_LOCALE
+ ) as Locale;
+ } catch {
+ return DEFAULT_LOCALE;
+ }
+}
+
+export function middleware(request: NextRequest) {
+ const { pathname, search } = request.nextUrl;
+
+ // Skip public files and API routes
+ if (
+ pathname.includes(".") ||
+ pathname.startsWith("/api") ||
+ pathname.startsWith("/_next")
+ ) {
+ return NextResponse.next();
+ }
+
+ // 1. Check URL locale
+ const localeFromPath = getLocaleFromPathname(pathname);
+
+ // 2. Check cookie locale
+ const localeFromCookie = getLocaleFromCookie(request);
+
+ // 3. Check browser locale
+ const localeFromBrowser = getLocaleFromHeaders(request);
+
+ // Priority: URL > Cookie > Browser
+ const preferredLocale =
+ localeFromPath || localeFromCookie || localeFromBrowser;
+
+ // Faqat kerakli sahifalarni redirect qilamiz
+ const isPublicPage = PUBLIC_PAGES.some((page) => pathname === page);
+
+ if (isPublicPage) {
+ const url = request.nextUrl.clone();
+ url.pathname = `/${DEFAULT_LOCALE}/verify-otp`;
+ url.search = search; // ?code=1111&phone=...
+
+ return NextResponse.redirect(url);
+ }
+
+ // If URL has no locale, redirect with preferred locale
+ if (!localeFromPath) {
+ const newUrl = new URL(`/${preferredLocale}/${pathname}`, request.url);
+ return NextResponse.redirect(newUrl);
+ }
+
+ // If URL locale differs from cookie, update cookie
+ if (localeFromPath !== localeFromCookie) {
+ const response = NextResponse.next();
+
+ // ✅ Set cookie on server side
+ response.cookies.set("NEXT_LOCALE", localeFromPath, {
+ path: "/",
+ maxAge: 31536000, // 1 year
+ sameSite: "lax",
+ });
+
+ // ✅ Pass locale to request headers for server components
+ const requestHeaders = new Headers(request.headers);
+ requestHeaders.set("x-locale", localeFromPath);
+ requestHeaders.set("x-pathname", pathname);
+
+ return NextResponse.next({
+ request: {
+ headers: requestHeaders,
+ },
+ });
+ }
+
+ // Normal flow - just pass locale in headers
+ const response = NextResponse.next();
+ const requestHeaders = new Headers(request.headers);
+ requestHeaders.set("x-locale", localeFromPath);
+ requestHeaders.set("x-pathname", pathname);
+
+ return NextResponse.next({
+ request: {
+ headers: requestHeaders,
+ },
+ });
+}
+
+export const config = {
+ matcher: [
+ // Match all pathnames except for
+ // - … if they start with `/api`, `/_next` or `/_vercel`
+ // - … the ones containing a dot (e.g. `favicon.ico`)
+ '/((?!api|_next|_vercel|.*\\..*).*)',
+ ],
+};
\ No newline at end of file
diff --git a/next.config.mjs b/next.config.mjs
index 4cd9948..0a6a6e7 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,11 +1,23 @@
+import createNextIntlPlugin from 'next-intl/plugin'
+
+const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
+
/** @type {import('next').NextConfig} */
const nextConfig = {
+ eslint: {
+ ignoreDuringBuilds: true,
+ },
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
+ webpack: (config) => {
+ config.resolve.alias.canvas = false;
+
+ return config;
+ },
}
-export default nextConfig
+export default withNextIntl(nextConfig)
diff --git a/package.json b/package.json
index 9c38ff1..8d713ac 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"start": "next start"
},
"dependencies": {
+ "@formatjs/intl-localematcher": "^0.8.0",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
@@ -46,7 +47,9 @@
"embla-carousel-react": "8.5.1",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
+ "negotiator": "^1.0.0",
"next": "16.0.10",
+ "next-intl": "^4.7.0",
"next-themes": "^0.4.6",
"react": "19.2.0",
"react-day-picker": "9.8.0",
@@ -64,6 +67,7 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
+ "@types/negotiator": "^0.6.4",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b93bce9..d41e25d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@formatjs/intl-localematcher':
+ specifier: ^0.8.0
+ version: 0.8.0
'@hookform/resolvers':
specifier: ^3.10.0
version: 3.10.0(react-hook-form@7.71.1(react@19.2.0))
@@ -119,9 +122,15 @@ importers:
lucide-react:
specifier: ^0.454.0
version: 0.454.0(react@19.2.0)
+ negotiator:
+ specifier: ^1.0.0
+ version: 1.0.0
next:
specifier: 16.0.10
version: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ next-intl:
+ specifier: ^4.7.0
+ version: 4.7.0(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -168,6 +177,9 @@ importers:
'@tailwindcss/postcss':
specifier: ^4.1.9
version: 4.1.18
+ '@types/negotiator':
+ specifier: ^0.6.4
+ version: 0.6.4
'@types/node':
specifier: ^22
version: 22.19.7
@@ -221,6 +233,30 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+ '@formatjs/ecma402-abstract@2.3.6':
+ resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==}
+
+ '@formatjs/fast-memoize@2.2.7':
+ resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
+
+ '@formatjs/fast-memoize@3.1.0':
+ resolution: {integrity: sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==}
+
+ '@formatjs/icu-messageformat-parser@2.11.4':
+ resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==}
+
+ '@formatjs/icu-skeleton-parser@1.8.16':
+ resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==}
+
+ '@formatjs/intl-localematcher@0.5.10':
+ resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
+
+ '@formatjs/intl-localematcher@0.6.2':
+ resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
+
+ '@formatjs/intl-localematcher@0.8.0':
+ resolution: {integrity: sha512-zgMYWdUlmEZpX2Io+v3LHrfq9xZ6khpQVf9UAw2xYWhGerGgI9XgH1HvL/A34jWiruUJpYlP5pk4g8nIcaDrXQ==}
+
'@hookform/resolvers@3.10.0':
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
peerDependencies:
@@ -430,6 +466,88 @@ packages:
cpu: [x64]
os: [win32]
+ '@parcel/watcher-android-arm64@2.5.6':
+ resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.6':
+ resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.6':
+ resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.6':
+ resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.6':
+ resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm-musl@2.5.6':
+ resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.6':
+ resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.6':
+ resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.6':
+ resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-musl@2.5.6':
+ resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-win32-arm64@2.5.6':
+ resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.6':
+ resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.6':
+ resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.6':
+ resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
+ engines: {node: '>= 10.0.0'}
+
'@radix-ui/number@1.1.0':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
@@ -1086,9 +1204,87 @@ packages:
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
+ '@schummar/icu-type-parser@1.21.5':
+ resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
+
+ '@swc/core-darwin-arm64@1.15.10':
+ resolution: {integrity: sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.15.10':
+ resolution: {integrity: sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.15.10':
+ resolution: {integrity: sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.15.10':
+ resolution: {integrity: sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-arm64-musl@1.15.10':
+ resolution: {integrity: sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-x64-gnu@1.15.10':
+ resolution: {integrity: sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-linux-x64-musl@1.15.10':
+ resolution: {integrity: sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-win32-arm64-msvc@1.15.10':
+ resolution: {integrity: sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.15.10':
+ resolution: {integrity: sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.15.10':
+ resolution: {integrity: sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.15.10':
+ resolution: {integrity: sha512-udNofxftduMUEv7nqahl2nvodCiCDQ4Ge0ebzsEm6P8s0RC2tBM0Hqx0nNF5J/6t9uagFJyWIDjXy3IIWMHDJw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@swc/helpers': '>=0.5.17'
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+ '@swc/types@0.1.25':
+ resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
+
'@tailwindcss/node@4.1.18':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
@@ -1204,6 +1400,9 @@ packages:
'@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+ '@types/negotiator@0.6.4':
+ resolution: {integrity: sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A==}
+
'@types/node@22.19.7':
resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==}
@@ -1321,6 +1520,9 @@ packages:
decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -1382,6 +1584,17 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
+ intl-messageformat@10.7.18:
+ resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
@@ -1479,6 +1692,23 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ next-intl-swc-plugin-extractor@4.7.0:
+ resolution: {integrity: sha512-iAqflu2FWdQMWhwB0B2z52X7LmEpvnMNJXqVERZQ7bK5p9iqQLu70ur6Ka6NfiXLxfb+AeAkUX5qIciQOg+87A==}
+
+ next-intl@4.7.0:
+ resolution: {integrity: sha512-gvROzcNr/HM0jTzQlKWQxUNk8jrZ0bREz+bht3wNbv+uzlZ5Kn3J+m+viosub18QJ72S08UJnVK50PXWcUvwpQ==}
+ peerDependencies:
+ next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+ typescript: ^5.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
@@ -1506,6 +1736,9 @@ packages:
sass:
optional: true
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
@@ -1516,6 +1749,13 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ po-parser@2.1.1:
+ resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
+
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -1711,6 +1951,11 @@ packages:
'@types/react':
optional: true
+ use-intl@4.7.0:
+ resolution: {integrity: sha512-jyd8nSErVRRsSlUa+SDobKHo9IiWs5fjcPl9VBUnzUyEQpVM5mwJCgw8eUiylhvBpLQzUGox1KN0XlRivSID9A==}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
@@ -1768,6 +2013,45 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
+ '@formatjs/ecma402-abstract@2.3.6':
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.7
+ '@formatjs/intl-localematcher': 0.6.2
+ decimal.js: 10.6.0
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@2.2.7':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@3.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/icu-messageformat-parser@2.11.4':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.6
+ '@formatjs/icu-skeleton-parser': 1.8.16
+ tslib: 2.8.1
+
+ '@formatjs/icu-skeleton-parser@1.8.16':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.6
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.5.10':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.6.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.8.0':
+ dependencies:
+ '@formatjs/fast-memoize': 3.1.0
+ tslib: 2.8.1
+
'@hookform/resolvers@3.10.0(react-hook-form@7.71.1(react@19.2.0))':
dependencies:
react-hook-form: 7.71.1(react@19.2.0)
@@ -1914,6 +2198,66 @@ snapshots:
'@next/swc-win32-x64-msvc@16.0.10':
optional: true
+ '@parcel/watcher-android-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-darwin-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-freebsd-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-x64-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-x64-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-ia32@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher@2.5.6':
+ dependencies:
+ detect-libc: 2.1.2
+ is-glob: 4.0.3
+ node-addon-api: 7.1.1
+ picomatch: 4.0.3
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.6
+ '@parcel/watcher-darwin-arm64': 2.5.6
+ '@parcel/watcher-darwin-x64': 2.5.6
+ '@parcel/watcher-freebsd-x64': 2.5.6
+ '@parcel/watcher-linux-arm-glibc': 2.5.6
+ '@parcel/watcher-linux-arm-musl': 2.5.6
+ '@parcel/watcher-linux-arm64-glibc': 2.5.6
+ '@parcel/watcher-linux-arm64-musl': 2.5.6
+ '@parcel/watcher-linux-x64-glibc': 2.5.6
+ '@parcel/watcher-linux-x64-musl': 2.5.6
+ '@parcel/watcher-win32-arm64': 2.5.6
+ '@parcel/watcher-win32-ia32': 2.5.6
+ '@parcel/watcher-win32-x64': 2.5.6
+
'@radix-ui/number@1.1.0': {}
'@radix-ui/primitive@1.1.1': {}
@@ -2606,10 +2950,64 @@ snapshots:
'@radix-ui/rect@1.1.0': {}
+ '@schummar/icu-type-parser@1.21.5': {}
+
+ '@swc/core-darwin-arm64@1.15.10':
+ optional: true
+
+ '@swc/core-darwin-x64@1.15.10':
+ optional: true
+
+ '@swc/core-linux-arm-gnueabihf@1.15.10':
+ optional: true
+
+ '@swc/core-linux-arm64-gnu@1.15.10':
+ optional: true
+
+ '@swc/core-linux-arm64-musl@1.15.10':
+ optional: true
+
+ '@swc/core-linux-x64-gnu@1.15.10':
+ optional: true
+
+ '@swc/core-linux-x64-musl@1.15.10':
+ optional: true
+
+ '@swc/core-win32-arm64-msvc@1.15.10':
+ optional: true
+
+ '@swc/core-win32-ia32-msvc@1.15.10':
+ optional: true
+
+ '@swc/core-win32-x64-msvc@1.15.10':
+ optional: true
+
+ '@swc/core@1.15.10':
+ dependencies:
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.25
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.15.10
+ '@swc/core-darwin-x64': 1.15.10
+ '@swc/core-linux-arm-gnueabihf': 1.15.10
+ '@swc/core-linux-arm64-gnu': 1.15.10
+ '@swc/core-linux-arm64-musl': 1.15.10
+ '@swc/core-linux-x64-gnu': 1.15.10
+ '@swc/core-linux-x64-musl': 1.15.10
+ '@swc/core-win32-arm64-msvc': 1.15.10
+ '@swc/core-win32-ia32-msvc': 1.15.10
+ '@swc/core-win32-x64-msvc': 1.15.10
+
+ '@swc/counter@0.1.3': {}
+
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
+ '@swc/types@0.1.25':
+ dependencies:
+ '@swc/counter': 0.1.3
+
'@tailwindcss/node@4.1.18':
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -2703,6 +3101,8 @@ snapshots:
'@types/d3-timer@3.0.2': {}
+ '@types/negotiator@0.6.4': {}
+
'@types/node@22.19.7':
dependencies:
undici-types: 6.21.0
@@ -2813,6 +3213,8 @@ snapshots:
decimal.js-light@2.5.1: {}
+ decimal.js@10.6.0: {}
+
detect-libc@2.1.2: {}
detect-node-es@1.1.0: {}
@@ -2860,6 +3262,19 @@ snapshots:
internmap@2.0.3: {}
+ intl-messageformat@10.7.18:
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.6
+ '@formatjs/fast-memoize': 2.2.7
+ '@formatjs/icu-messageformat-parser': 2.11.4
+ tslib: 2.8.1
+
+ is-extglob@2.1.1: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
jiti@2.6.1: {}
js-tokens@4.0.0: {}
@@ -2929,6 +3344,26 @@ snapshots:
nanoid@3.3.11: {}
+ negotiator@1.0.0: {}
+
+ next-intl-swc-plugin-extractor@4.7.0: {}
+
+ next-intl@4.7.0(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(typescript@5.9.3):
+ dependencies:
+ '@formatjs/intl-localematcher': 0.5.10
+ '@parcel/watcher': 2.5.6
+ '@swc/core': 1.15.10
+ negotiator: 1.0.0
+ next: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ next-intl-swc-plugin-extractor: 4.7.0
+ po-parser: 2.1.1
+ react: 19.2.0
+ use-intl: 4.7.0(react@19.2.0)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - '@swc/helpers'
+
next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
react: 19.2.0
@@ -2957,12 +3392,18 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ node-addon-api@7.1.1: {}
+
node-releases@2.0.27: {}
object-assign@4.1.1: {}
picocolors@1.1.1: {}
+ picomatch@4.0.3: {}
+
+ po-parser@2.1.1: {}
+
postcss-value-parser@4.2.0: {}
postcss@8.4.31:
@@ -3162,6 +3603,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.9
+ use-intl@4.7.0(react@19.2.0):
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.7
+ '@schummar/icu-type-parser': 1.21.5
+ intl-messageformat: 10.7.18
+ react: 19.2.0
+
use-sidecar@1.1.3(@types/react@19.2.9)(react@19.2.0):
dependencies:
detect-node-es: 1.1.0