refactor: centralize locale constants to prevent breaking changes
Created src/lib/i18n/locales.ts as single source of truth for: - SUPPORTED_LOCALES array - LOCALE_COOKIE name - DEFAULT_LOCALE - LOCALE_CONFIG (labels, flags, Saleor locale mapping) - Helper functions (isValidLocale, getSaleorLocale, getLocaleFromPath) Updated all files to use centralized constants: - middleware.ts - Header.tsx - ProductCard.tsx - sitemap.ts - root layout and locale layout - routing.ts Benefits: - Adding new locale only requires updating ONE file (locales.ts) - No more hardcoded locale lists scattered across codebase - Cookie name defined in one place - Type-safe locale validation
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
|
import { SUPPORTED_LOCALES, DEFAULT_LOCALE, LOCALE_COOKIE, getLocaleFromPath } from "@/lib/i18n/locales";
|
||||||
|
|
||||||
const LOCALE_COOKIE = "NEXT_LOCALE";
|
const OLD_SERBIAN_PATHS = ["products", "about", "contact", "checkout"];
|
||||||
|
|
||||||
export default function middleware(request: NextRequest) {
|
export default function middleware(request: NextRequest) {
|
||||||
const pathname = request.nextUrl.pathname;
|
const pathname = request.nextUrl.pathname;
|
||||||
@@ -9,9 +10,9 @@ export default function middleware(request: NextRequest) {
|
|||||||
const acceptLanguage = request.headers.get("accept-language") || "";
|
const acceptLanguage = request.headers.get("accept-language") || "";
|
||||||
|
|
||||||
if (pathname === "/" || pathname === "") {
|
if (pathname === "/" || pathname === "") {
|
||||||
let locale = "sr";
|
let locale = DEFAULT_LOCALE;
|
||||||
|
|
||||||
if (cookieLocale && ["sr", "en", "de", "fr"].includes(cookieLocale)) {
|
if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale as typeof SUPPORTED_LOCALES[number])) {
|
||||||
locale = cookieLocale;
|
locale = cookieLocale;
|
||||||
} else if (acceptLanguage.includes("en")) {
|
} else if (acceptLanguage.includes("en")) {
|
||||||
locale = "en";
|
locale = "en";
|
||||||
@@ -22,15 +23,14 @@ export default function middleware(request: NextRequest) {
|
|||||||
return NextResponse.redirect(url, 301);
|
return NextResponse.redirect(url, 301);
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldSerbianPaths = ["products", "about", "contact", "checkout"];
|
const isOldSerbianPath = OLD_SERBIAN_PATHS.some(
|
||||||
const isOldSerbianPath = oldSerbianPaths.some(
|
|
||||||
(path) => pathname === `/${path}` || pathname.startsWith(`/${path}/`)
|
(path) => pathname === `/${path}` || pathname.startsWith(`/${path}/`)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isOldSerbianPath) {
|
if (isOldSerbianPath) {
|
||||||
let locale = "sr";
|
let locale = DEFAULT_LOCALE;
|
||||||
|
|
||||||
if (cookieLocale && ["sr", "en", "de", "fr"].includes(cookieLocale)) {
|
if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale as typeof SUPPORTED_LOCALES[number])) {
|
||||||
locale = cookieLocale;
|
locale = cookieLocale;
|
||||||
} else if (acceptLanguage.includes("en")) {
|
} else if (acceptLanguage.includes("en")) {
|
||||||
locale = "en";
|
locale = "en";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { NextIntlClientProvider } from "next-intl";
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
import { getMessages, setRequestLocale } from "next-intl/server";
|
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||||
import { routing } from "@/i18n/routing";
|
import { SUPPORTED_LOCALES } from "@/lib/i18n/locales";
|
||||||
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return routing.locales.map((locale) => ({ locale }));
|
return SUPPORTED_LOCALES.map((locale) => ({ locale }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
@@ -18,7 +18,7 @@ export async function generateMetadata({
|
|||||||
const localePrefix = locale === "sr" ? "" : `/${locale}`;
|
const localePrefix = locale === "sr" ? "" : `/${locale}`;
|
||||||
|
|
||||||
const languages: Record<string, string> = {};
|
const languages: Record<string, string> = {};
|
||||||
for (const loc of routing.locales) {
|
for (const loc of SUPPORTED_LOCALES) {
|
||||||
const prefix = loc === "sr" ? "" : `/${loc}`;
|
const prefix = loc === "sr" ? "" : `/${loc}`;
|
||||||
languages[loc] = `${baseUrl}${prefix}`;
|
languages[loc] = `${baseUrl}${prefix}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import type { Metadata, Viewport } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import ErrorBoundary from "@/components/providers/ErrorBoundary";
|
import ErrorBoundary from "@/components/providers/ErrorBoundary";
|
||||||
|
import { SUPPORTED_LOCALES } from "@/lib/i18n/locales";
|
||||||
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||||
|
|
||||||
@@ -13,12 +14,9 @@ export const metadata: Metadata = {
|
|||||||
robots: "index, follow",
|
robots: "index, follow",
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: baseUrl,
|
canonical: baseUrl,
|
||||||
languages: {
|
languages: Object.fromEntries(
|
||||||
sr: baseUrl,
|
SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? baseUrl : `${baseUrl}/${locale}`])
|
||||||
en: `${baseUrl}/en`,
|
),
|
||||||
de: `${baseUrl}/de`,
|
|
||||||
fr: `${baseUrl}/fr`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MetadataRoute } from "next";
|
import { MetadataRoute } from "next";
|
||||||
import { getProducts } from "@/lib/saleor";
|
import { getProducts } from "@/lib/saleor";
|
||||||
|
import { SUPPORTED_LOCALES, type Locale } from "@/lib/i18n/locales";
|
||||||
|
|
||||||
const LOCALES = ["sr", "en", "de", "fr"] as const;
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||||
|
|
||||||
interface SitemapEntry {
|
interface SitemapEntry {
|
||||||
@@ -29,12 +29,9 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
changeFrequency: "daily",
|
changeFrequency: "daily",
|
||||||
priority: 1,
|
priority: 1,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: {
|
languages: Object.fromEntries(
|
||||||
sr: `${baseUrl}`,
|
SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? baseUrl : `${baseUrl}/${locale}`])
|
||||||
en: `${baseUrl}/en`,
|
),
|
||||||
de: `${baseUrl}/de`,
|
|
||||||
fr: `${baseUrl}/fr`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -43,12 +40,9 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
changeFrequency: "daily",
|
changeFrequency: "daily",
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: {
|
languages: Object.fromEntries(
|
||||||
sr: `${baseUrl}/products`,
|
SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? `${baseUrl}/products` : `${baseUrl}/${locale}/products`])
|
||||||
en: `${baseUrl}/en/products`,
|
),
|
||||||
de: `${baseUrl}/de/products`,
|
|
||||||
fr: `${baseUrl}/fr/products`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -57,12 +51,9 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: {
|
languages: Object.fromEntries(
|
||||||
sr: `${baseUrl}/about`,
|
SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? `${baseUrl}/about` : `${baseUrl}/${locale}/about`])
|
||||||
en: `${baseUrl}/en/about`,
|
),
|
||||||
de: `${baseUrl}/de/about`,
|
|
||||||
fr: `${baseUrl}/fr/about`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -71,12 +62,9 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: {
|
languages: Object.fromEntries(
|
||||||
sr: `${baseUrl}/contact`,
|
SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? `${baseUrl}/contact` : `${baseUrl}/${locale}/contact`])
|
||||||
en: `${baseUrl}/en/contact`,
|
),
|
||||||
de: `${baseUrl}/de/contact`,
|
|
||||||
fr: `${baseUrl}/fr/contact`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -85,12 +73,9 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: {
|
languages: Object.fromEntries(
|
||||||
sr: `${baseUrl}/checkout`,
|
SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? `${baseUrl}/checkout` : `${baseUrl}/${locale}/checkout`])
|
||||||
en: `${baseUrl}/en/checkout`,
|
),
|
||||||
de: `${baseUrl}/de/checkout`,
|
|
||||||
fr: `${baseUrl}/fr/checkout`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -98,7 +83,13 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
const productUrls: SitemapEntry[] = [];
|
const productUrls: SitemapEntry[] = [];
|
||||||
|
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
for (const locale of LOCALES) {
|
const hreflangs: Record<string, string> = {};
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
|
const path = locale === "sr" ? `/products/${product.slug}` : `/${locale}/products/${product.slug}`;
|
||||||
|
hreflangs[locale] = `${baseUrl}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
const localePrefix = locale === "sr" ? "" : `/${locale}`;
|
const localePrefix = locale === "sr" ? "" : `/${locale}`;
|
||||||
productUrls.push({
|
productUrls.push({
|
||||||
url: `${baseUrl}${localePrefix}/products/${product.slug}`,
|
url: `${baseUrl}${localePrefix}/products/${product.slug}`,
|
||||||
@@ -106,12 +97,7 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
|
|||||||
changeFrequency: "weekly",
|
changeFrequency: "weekly",
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: {
|
languages: hreflangs,
|
||||||
sr: `${baseUrl}/products/${product.slug}`,
|
|
||||||
en: `${baseUrl}/en/products/${product.slug}`,
|
|
||||||
de: `${baseUrl}/de/products/${product.slug}`,
|
|
||||||
fr: `${baseUrl}/fr/products/${product.slug}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import { useTranslations } from "next-intl";
|
|||||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||||
import { User, ShoppingBag, Menu, X, Globe } from "lucide-react";
|
import { User, ShoppingBag, Menu, X, Globe } from "lucide-react";
|
||||||
import CartDrawer from "@/components/cart/CartDrawer";
|
import CartDrawer from "@/components/cart/CartDrawer";
|
||||||
|
import { SUPPORTED_LOCALES, LOCALE_COOKIE, LOCALE_CONFIG, isValidLocale } from "@/lib/i18n/locales";
|
||||||
const LOCALES = [
|
import type { Locale } from "@/lib/i18n/locales";
|
||||||
{ code: "sr", label: "Srpski", flag: "🇷🇸" },
|
|
||||||
{ code: "en", label: "English", flag: "🇬🇧" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
locale?: string;
|
locale?: string;
|
||||||
@@ -29,9 +26,7 @@ export default function Header({ locale = "sr" }: HeaderProps) {
|
|||||||
const { getLineCount, toggleCart, initCheckout } = useSaleorCheckoutStore();
|
const { getLineCount, toggleCart, initCheckout } = useSaleorCheckoutStore();
|
||||||
|
|
||||||
const itemCount = getLineCount();
|
const itemCount = getLineCount();
|
||||||
const localePath = `/${locale}`;
|
const currentLocale = isValidLocale(locale) ? LOCALE_CONFIG[locale] : LOCALE_CONFIG.sr;
|
||||||
|
|
||||||
const currentLocale = LOCALES.find((l) => l.code === locale) || LOCALES[0];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -48,7 +43,11 @@ export default function Header({ locale = "sr" }: HeaderProps) {
|
|||||||
setLangDropdownOpen(false);
|
setLangDropdownOpen(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
|
if (!isValidLocale(newLocale)) {
|
||||||
|
setLangDropdownOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.cookie = `${LOCALE_COOKIE}=${newLocale}; path=/; max-age=31536000`;
|
||||||
const pathWithoutLocale = pathname.replace(/^\/(sr|en|de|fr)/, "") || "/";
|
const pathWithoutLocale = pathname.replace(/^\/(sr|en|de|fr)/, "") || "/";
|
||||||
const newPath = `/${newLocale}${pathWithoutLocale}`;
|
const newPath = `/${newLocale}${pathWithoutLocale}`;
|
||||||
window.location.replace(newPath);
|
window.location.replace(newPath);
|
||||||
@@ -115,7 +114,7 @@ export default function Header({ locale = "sr" }: HeaderProps) {
|
|||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<Link href={localePath || "/"} className="flex-shrink-0 lg:absolute lg:left-1/2 lg:-translate-x-1/2">
|
<Link href={`/${locale}`} className="flex-shrink-0 lg:absolute lg:left-1/2 lg:-translate-x-1/2">
|
||||||
<Image
|
<Image
|
||||||
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
||||||
alt="ManoonOils"
|
alt="ManoonOils"
|
||||||
@@ -144,16 +143,16 @@ export default function Header({ locale = "sr" }: HeaderProps) {
|
|||||||
exit={{ opacity: 0, y: -10 }}
|
exit={{ opacity: 0, y: -10 }}
|
||||||
className="absolute right-0 top-full mt-1 bg-white border border-[#e5e5e5] shadow-lg rounded-md overflow-hidden z-50"
|
className="absolute right-0 top-full mt-1 bg-white border border-[#e5e5e5] shadow-lg rounded-md overflow-hidden z-50"
|
||||||
>
|
>
|
||||||
{LOCALES.map((loc) => (
|
{SUPPORTED_LOCALES.map((loc) => (
|
||||||
<button
|
<button
|
||||||
key={loc.code}
|
key={loc}
|
||||||
onClick={() => switchLocale(loc.code)}
|
onClick={() => switchLocale(loc)}
|
||||||
className={`flex items-center gap-2 px-4 py-2 text-sm hover:bg-black/5 transition-colors w-full text-left ${
|
className={`flex items-center gap-2 px-4 py-2 text-sm hover:bg-black/5 transition-colors w-full text-left ${
|
||||||
loc.code === locale ? "bg-black/5 font-medium" : ""
|
loc === locale ? "bg-black/5 font-medium" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{loc.flag}</span>
|
<span>{LOCALE_CONFIG[loc].flag}</span>
|
||||||
<span>{loc.label}</span>
|
<span>{LOCALE_CONFIG[loc].label}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -195,7 +194,7 @@ export default function Header({ locale = "sr" }: HeaderProps) {
|
|||||||
>
|
>
|
||||||
<div className="container h-full flex flex-col">
|
<div className="container h-full flex flex-col">
|
||||||
<div className="flex items-center justify-between h-[72px]">
|
<div className="flex items-center justify-between h-[72px]">
|
||||||
<Link href={localePath || "/"} onClick={() => setMobileMenuOpen(false)}>
|
<Link href={`/${locale}`} onClick={() => setMobileMenuOpen(false)}>
|
||||||
<Image
|
<Image
|
||||||
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
||||||
alt="ManoonOils"
|
alt="ManoonOils"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Link from "next/link";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import type { Product } from "@/types/saleor";
|
import type { Product } from "@/types/saleor";
|
||||||
import { getProductPrice, getProductImage, getLocalizedProduct } from "@/lib/saleor";
|
import { getProductPrice, getProductImage, getLocalizedProduct } from "@/lib/saleor";
|
||||||
|
import { isValidLocale, getSaleorLocale } from "@/lib/i18n/locales";
|
||||||
|
|
||||||
interface ProductCardProps {
|
interface ProductCardProps {
|
||||||
product: Product;
|
product: Product;
|
||||||
@@ -13,13 +14,13 @@ interface ProductCardProps {
|
|||||||
locale?: string;
|
locale?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProductCard({ product, index = 0, locale = "SR" }: ProductCardProps) {
|
export default function ProductCard({ product, index = 0, locale = "sr" }: ProductCardProps) {
|
||||||
const t = useTranslations("ProductCard");
|
const t = useTranslations("ProductCard");
|
||||||
const image = getProductImage(product);
|
const image = getProductImage(product);
|
||||||
const price = getProductPrice(product);
|
const price = getProductPrice(product);
|
||||||
const localized = getLocalizedProduct(product, locale);
|
const saleorLocale = isValidLocale(locale) ? getSaleorLocale(locale) : "SR";
|
||||||
|
const localized = getLocalizedProduct(product, saleorLocale);
|
||||||
const isAvailable = product.variants?.[0]?.quantityAvailable > 0;
|
const isAvailable = product.variants?.[0]?.quantityAvailable > 0;
|
||||||
const urlLocale = locale === "SR" ? "sr" : "en";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -28,7 +29,7 @@ export default function ProductCard({ product, index = 0, locale = "SR" }: Produ
|
|||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||||
>
|
>
|
||||||
<Link href={`/${urlLocale}/products/${localized.slug}`} className="group block">
|
<Link href={`/${locale}/products/${localized.slug}`} className="group block">
|
||||||
<div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden mb-4">
|
<div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden mb-4">
|
||||||
{image ? (
|
{image ? (
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineRouting } from "next-intl/routing";
|
import { defineRouting } from "next-intl/routing";
|
||||||
|
import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from "@/lib/i18n/locales";
|
||||||
|
|
||||||
export const routing = defineRouting({
|
export const routing = defineRouting({
|
||||||
locales: ["sr", "en", "de", "fr"],
|
locales: SUPPORTED_LOCALES,
|
||||||
defaultLocale: "sr",
|
defaultLocale: DEFAULT_LOCALE,
|
||||||
localePrefix: "as-needed",
|
localePrefix: "as-needed",
|
||||||
});
|
});
|
||||||
26
src/lib/i18n/locales.ts
Normal file
26
src/lib/i18n/locales.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
export const SUPPORTED_LOCALES = ["sr", "en", "de", "fr"] as const;
|
||||||
|
export type Locale = (typeof SUPPORTED_LOCALES)[number];
|
||||||
|
|
||||||
|
export const DEFAULT_LOCALE: Locale = "sr";
|
||||||
|
|
||||||
|
export const LOCALE_COOKIE = "NEXT_LOCALE";
|
||||||
|
|
||||||
|
export const LOCALE_CONFIG: Record<Locale, { label: string; flag: string; saleorLocale: string }> = {
|
||||||
|
sr: { label: "Srpski", flag: "🇷🇸", saleorLocale: "SR" },
|
||||||
|
en: { label: "English", flag: "🇬🇧", saleorLocale: "EN" },
|
||||||
|
de: { label: "Deutsch", flag: "🇩🇪", saleorLocale: "EN" },
|
||||||
|
fr: { label: "Français", flag: "🇫🇷", saleorLocale: "EN" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isValidLocale(locale: string): locale is Locale {
|
||||||
|
return SUPPORTED_LOCALES.includes(locale as Locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSaleorLocale(locale: Locale): string {
|
||||||
|
return LOCALE_CONFIG[locale].saleorLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocaleFromPath(pathname: string): string {
|
||||||
|
const match = pathname.match(/^\/(sr|en|de|fr)/);
|
||||||
|
return match ? match[1] : DEFAULT_LOCALE;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user