diff --git a/components/atoms/locale-switcher/index.tsx b/components/atoms/locale-switcher/index.tsx
new file mode 100644
index 0000000..eebe109
--- /dev/null
+++ b/components/atoms/locale-switcher/index.tsx
@@ -0,0 +1,39 @@
+import { useRouter } from 'next/router'
+import { useTranslations } from 'next-intl'
+import { locales, type Locale } from '../../../i18n/messages'
+
+/**
+ * LocaleSwitcher — switches the active UI language while preserving the current
+ * route (pathname + query). Relies on Next.js built-in locale routing, so the
+ * selected locale is reflected in the URL and `useRouter().locale`.
+ */
+export function LocaleSwitcher() {
+ const router = useRouter()
+ const t = useTranslations('LocaleSwitcher')
+ const active = (router.locale ?? router.defaultLocale) as string
+
+ const onChange = (next: Locale) => {
+ if (next === active) return
+ router.push({ pathname: router.pathname, query: router.query }, router.asPath, {
+ locale: next,
+ })
+ }
+
+ return (
+
+ )
+}
diff --git a/components/organisms/navbar/index.tsx b/components/organisms/navbar/index.tsx
index 44aa7d0..0167f0f 100644
--- a/components/organisms/navbar/index.tsx
+++ b/components/organisms/navbar/index.tsx
@@ -1,19 +1,22 @@
import { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
+import { useTranslations } from 'next-intl';
import { ThemeToggle } from '../../atoms/theme-toggle';
+import { LocaleSwitcher } from '../../atoms/locale-switcher';
const NAV_LINKS = [
- { label: 'Home', href: '/' },
- { label: 'Explore', href: '/explore' },
- { label: 'Leaderboard', href: '/leaderboard' },
- { label: 'Dashboard', href: '/dashboard' },
- { label: 'Escrow', href: '/escrow' },
-];
+ { key: 'home', href: '/' },
+ { key: 'explore', href: '/explore' },
+ { key: 'leaderboard', href: '/leaderboard' },
+ { key: 'dashboard', href: '/dashboard' },
+ { key: 'escrow', href: '/escrow' },
+] as const;
export function Navbar() {
const [open, setOpen] = useState(false);
const router = useRouter();
+ const t = useTranslations('Nav');
const toggle = () => setOpen((v) => !v);
const close = () => setOpen(false);
@@ -27,7 +30,7 @@ export function Navbar() {
{/* Desktop links */}
- {NAV_LINKS.map(({ label, href }) => (
+ {NAV_LINKS.map(({ key, href }) => (
-
- {label}
+ {t(key)}
))}
@@ -45,12 +48,13 @@ export function Navbar() {
{/* Right actions */}
+
- Launch App
+ {t('launchApp')}
@@ -59,7 +63,7 @@ export function Navbar() {
className="flex md:hidden flex-col gap-1.5 bg-transparent border-none cursor-pointer p-1"
onClick={toggle}
aria-expanded={open}
- aria-label={open ? 'Close menu' : 'Open menu'}
+ aria-label={open ? t('closeMenu') : t('openMenu')}
>
@@ -70,7 +74,7 @@ export function Navbar() {
{open && (
- {NAV_LINKS.map(({ label, href }) => (
+ {NAV_LINKS.map(({ key, href }) => (
-
- {label}
+ {t(key)}
))}
+
- Launch App
+ {t('launchApp')}
diff --git a/i18n/messages.ts b/i18n/messages.ts
new file mode 100644
index 0000000..acf4646
--- /dev/null
+++ b/i18n/messages.ts
@@ -0,0 +1,21 @@
+import type { AbstractIntlMessages } from 'next-intl'
+import en from '../messages/en.json'
+import es from '../messages/es.json'
+
+/** Locales supported by the app. Keep in sync with `i18n.locales` in next.config.js. */
+export const locales = ['en', 'es'] as const
+export type Locale = (typeof locales)[number]
+
+export const defaultLocale: Locale = 'en'
+
+const messagesByLocale: Record = { en, es }
+
+/** Type guard for an incoming (possibly undefined) locale string. */
+export function isLocale(value: string | undefined): value is Locale {
+ return value !== undefined && (locales as readonly string[]).includes(value)
+}
+
+/** Resolve the message catalog for a locale, falling back to the default. */
+export function getMessages(locale: string | undefined): AbstractIntlMessages {
+ return messagesByLocale[isLocale(locale) ? locale : defaultLocale]
+}
diff --git a/messages/en.json b/messages/en.json
new file mode 100644
index 0000000..7f457ef
--- /dev/null
+++ b/messages/en.json
@@ -0,0 +1,17 @@
+{
+ "Nav": {
+ "home": "Home",
+ "explore": "Explore",
+ "leaderboard": "Leaderboard",
+ "dashboard": "Dashboard",
+ "escrow": "Escrow",
+ "launchApp": "Launch App",
+ "openMenu": "Open menu",
+ "closeMenu": "Close menu"
+ },
+ "LocaleSwitcher": {
+ "label": "Language",
+ "en": "English",
+ "es": "Español"
+ }
+}
diff --git a/messages/es.json b/messages/es.json
new file mode 100644
index 0000000..b0c32c6
--- /dev/null
+++ b/messages/es.json
@@ -0,0 +1,17 @@
+{
+ "Nav": {
+ "home": "Inicio",
+ "explore": "Explorar",
+ "leaderboard": "Clasificación",
+ "dashboard": "Panel",
+ "escrow": "Depósito en garantía",
+ "launchApp": "Abrir app",
+ "openMenu": "Abrir menú",
+ "closeMenu": "Cerrar menú"
+ },
+ "LocaleSwitcher": {
+ "label": "Idioma",
+ "en": "English",
+ "es": "Español"
+ }
+}
diff --git a/next.config.js b/next.config.js
index 3dd7ef1..54b36ce 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,4 +1,10 @@
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
+ // Built-in locale routing (Pages Router). Prefixes non-default locales, e.g.
+ // `/es/explore`, and exposes the active locale via `useRouter().locale`.
+ i18n: {
+ locales: ['en', 'es'],
+ defaultLocale: 'en',
+ },
};
diff --git a/package-lock.json b/package-lock.json
index 5df8a9d..83ab38d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"humanize-duration": "^3.27.3",
"moment": "^2.29.4",
"next": "^13.4.4",
+ "next-intl": "^4.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.78.0",
@@ -204,6 +205,36 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@formatjs/fast-memoize": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.6.tgz",
+ "integrity": "sha512-H5aexk1Le7T9TPmscacZ+1pR6CTa2n1wq+HDVGXhH8TzUlQQpeXzZs91dRtmFHrbeNbjPFPfQujUqm7MHgVoXQ==",
+ "license": "MIT"
+ },
+ "node_modules/@formatjs/icu-messageformat-parser": {
+ "version": "3.5.11",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.11.tgz",
+ "integrity": "sha512-NVsuNsc2dUVG9+4HBJ/srScxtA/18LqGgwtop/tuN/OIBjVl6QA+0KhfZQddDD9sEh2LeVjLFPGVU3ixa3blcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/icu-skeleton-parser": "2.1.10"
+ }
+ },
+ "node_modules/@formatjs/icu-skeleton-parser": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.10.tgz",
+ "integrity": "sha512-XuSva+8ZGawk8VnD5VD6UeH8KarQ/Z022zgjHDoHmlNiAewstXuuzXc0Hk5pGFSdG+nNw5bfJKXqj1ZXHn9yUA==",
+ "license": "MIT"
+ },
+ "node_modules/@formatjs/intl-localematcher": {
+ "version": "0.8.10",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.10.tgz",
+ "integrity": "sha512-P/IC3qws3jH+1fEs+o0RIFgXKRaQlFehjS5W0FPAqdo6hgzawLl+eD0q0JjheQ3XtoOe5n8WSYfX06KQZI/QJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "3.1.6"
+ }
+ },
"node_modules/@hookform/resolvers": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz",
@@ -477,6 +508,313 @@
"node": ">= 8"
}
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
+ "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.3",
+ "is-glob": "^4.0.3",
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "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"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+ "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+ "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+ "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+ "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+ "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+ "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+ "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+ "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+ "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+ "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+ "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+ "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+ "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@pkgr/utils": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
@@ -712,6 +1050,12 @@
"integrity": "sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==",
"dev": true
},
+ "node_modules/@schummar/icu-type-parser": {
+ "version": "1.21.5",
+ "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
+ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
+ "license": "MIT"
+ },
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
@@ -723,6 +1067,204 @@
"resolved": "https://registry.npmjs.org/@stellar/freighter-api/-/freighter-api-1.6.0.tgz",
"integrity": "sha512-Z6CRY+3+whzMspda6PiHEc4Rs9tdQkHLin9FopdnHhij/FEEAK+IiuF8Ki2ROcQweXdazb8N237aSim10s+Zgw=="
},
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz",
+ "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz",
+ "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz",
+ "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz",
+ "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz",
+ "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-ppc64-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz",
+ "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-s390x-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz",
+ "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz",
+ "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz",
+ "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz",
+ "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz",
+ "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz",
+ "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
@@ -731,6 +1273,15 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@swc/types": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz",
+ "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3"
+ }
+ },
"node_modules/@types/humanize-duration": {
"version": "3.27.1",
"resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.1.tgz",
@@ -1713,6 +2264,15 @@
"node": ">=6"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -2890,6 +3450,21 @@
"resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.29.0.tgz",
"integrity": "sha512-G5wZGwYTLaQAmYqhfK91aw3xt6wNbJW1RnWDh4qP1PvF4T/jnkjx2RVhG5kzB2PGsYGTn+oSDBQp+dMdILLxcg=="
},
+ "node_modules/icu-minify": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.13.0.tgz",
+ "integrity": "sha512-SIFMeUHZJjzS5RvIGvybKvWoHjDm9cGVEs2EpJ8PmywOdJLWyblPm7TdPLLoUtkJtwQD7iGhl2WMptZ+N0on+w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/icu-messageformat-parser": "^3.4.0"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -2972,6 +3547,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/intl-messageformat": {
+ "version": "11.2.8",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.8.tgz",
+ "integrity": "sha512-l323RCl3qJDVQ8U9j74ut/hVMdg3VPsOHpVMDvFfz9qiq4dPO5ooVYFNVUzzrpgG39a+RLzcXyJb8VFgIU+tUA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@formatjs/fast-memoize": "3.1.6",
+ "@formatjs/icu-messageformat-parser": "3.5.11"
+ }
+ },
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -3097,7 +3682,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3106,7 +3690,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -3608,6 +4191,15 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "13.4.13",
"resolved": "https://registry.npmjs.org/next/-/next-13.4.13.tgz",
@@ -3654,6 +4246,94 @@
}
}
},
+ "node_modules/next-intl": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.13.0.tgz",
+ "integrity": "sha512-OvNq2v5XLx4EkQOsAhVE9g+6zdb83XHusADCXXtIW4LILYnjEVaeINdr1lkVWKSjzwNUiMSlH5N4K0OQTRiv6A==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/intl-localematcher": "^0.8.1",
+ "@parcel/watcher": "^2.4.1",
+ "@swc/core": "^1.15.2",
+ "icu-minify": "^4.13.0",
+ "negotiator": "^1.0.0",
+ "next-intl-swc-plugin-extractor": "^4.13.0",
+ "po-parser": "^2.1.1",
+ "use-intl": "^4.13.0"
+ },
+ "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"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-intl-swc-plugin-extractor": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.13.0.tgz",
+ "integrity": "sha512-6S/fJI0KXvLCL8nhBo9P8eGaJPzmwJBTCzX0NaUIj0VyU8U89d//T+vjMLdNIXl5MlLaYH7B9MbAjb8Mvu+tqQ==",
+ "license": "MIT"
+ },
+ "node_modules/next-intl/node_modules/@swc/core": {
+ "version": "1.15.43",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz",
+ "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "@swc/types": "^0.1.27"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.15.43",
+ "@swc/core-darwin-x64": "1.15.43",
+ "@swc/core-linux-arm-gnueabihf": "1.15.43",
+ "@swc/core-linux-arm64-gnu": "1.15.43",
+ "@swc/core-linux-arm64-musl": "1.15.43",
+ "@swc/core-linux-ppc64-gnu": "1.15.43",
+ "@swc/core-linux-s390x-gnu": "1.15.43",
+ "@swc/core-linux-x64-gnu": "1.15.43",
+ "@swc/core-linux-x64-musl": "1.15.43",
+ "@swc/core-win32-arm64-msvc": "1.15.43",
+ "@swc/core-win32-ia32-msvc": "1.15.43",
+ "@swc/core-win32-x64-msvc": "1.15.43"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.17"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-intl/node_modules/@swc/helpers": {
+ "version": "0.5.23",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
+ "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
@@ -3687,6 +4367,12 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "license": "MIT"
+ },
"node_modules/node-gyp-build": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
@@ -4052,6 +4738,12 @@
"node": ">= 6"
}
},
+ "node_modules/po-parser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
+ "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
+ "license": "MIT"
+ },
"node_modules/postcss": {
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
@@ -5194,9 +5886,10 @@
}
},
"node_modules/tslib": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
- "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
},
"node_modules/tweetnacl": {
"version": "1.0.3",
@@ -5394,6 +6087,27 @@
}
}
},
+ "node_modules/use-intl": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.13.0.tgz",
+ "integrity": "sha512-fAFDrWaASxlhXOipcOyb5VDD+YONqj6+8O8EcG/J7RBoOUF3A8YahRWLN+mBxYMrlMQB8N6Voqk5X+YC+HSL0A==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "^3.1.0",
+ "@schummar/icu-type-parser": "1.21.5",
+ "icu-minify": "^4.13.0",
+ "intl-messageformat": "^11.1.0"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
+ }
+ },
"node_modules/use-sidecar": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
diff --git a/package.json b/package.json
index e8e2743..0831a83 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"humanize-duration": "^3.27.3",
"moment": "^2.29.4",
"next": "^13.4.4",
+ "next-intl": "^4.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.78.0",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index bb5417f..b843273 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,8 +1,11 @@
import type { AppProps } from 'next/app'
import { createContext, useContext } from 'react'
+import { useRouter } from 'next/router'
+import { NextIntlClientProvider } from 'next-intl'
import '../styles/globals.css'
import { useToast } from '../hooks/useToast'
import { ToastContainer } from '../components/atoms/toast'
+import { defaultLocale, getMessages } from '../i18n/messages'
// Create a context for global toast access
interface ToastContextValue {
@@ -24,12 +27,20 @@ export function useGlobalToast() {
function MyApp({ Component, pageProps }: AppProps) {
const { toasts, dismiss, success, error, warning, info } = useToast()
+ const { locale } = useRouter()
+ const activeLocale = locale ?? defaultLocale
return (
-
-
-
-
+
+
+
+
+
+
)
}