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/layout.tsx b/app/[locale]/contact/layout.tsx
similarity index 100%
rename from app/contact/layout.tsx
rename to app/[locale]/contact/layout.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/documents/layout.tsx b/app/[locale]/documents/layout.tsx
similarity index 100%
rename from app/documents/layout.tsx
rename to app/[locale]/documents/layout.tsx
diff --git a/app/documents/page.tsx b/app/[locale]/documents/page.tsx
similarity index 100%
rename from app/documents/page.tsx
rename to app/[locale]/documents/page.tsx
diff --git a/app/fraud/layout.tsx b/app/[locale]/fraud/layout.tsx
similarity index 100%
rename from app/fraud/layout.tsx
rename to app/[locale]/fraud/layout.tsx
diff --git a/app/fraud/page.tsx b/app/[locale]/fraud/page.tsx
similarity index 100%
rename from app/fraud/page.tsx
rename to app/[locale]/fraud/page.tsx
diff --git a/app/inspection/layout.tsx b/app/[locale]/inspection/layout.tsx
similarity index 100%
rename from app/inspection/layout.tsx
rename to app/[locale]/inspection/layout.tsx
diff --git a/app/inspection/page.tsx b/app/[locale]/inspection/page.tsx
similarity index 100%
rename from app/inspection/page.tsx
rename to app/[locale]/inspection/page.tsx
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
new file mode 100644
index 0000000..429a259
--- /dev/null
+++ b/app/[locale]/layout.tsx
@@ -0,0 +1,77 @@
+import type { Metadata } from "next";
+import "./globals.css";
+import { Lora, Figtree } from "next/font/google";
+import { Nav } from "@/components/layout/Nav";
+import { Footer } from "@/components/layout/Footer";
+// import { CarCursor } from "@/components/ui/CarCursor";
+
+const lora = Lora({
+ subsets: ["latin"],
+ variable: "--font-heading",
+ weight: ["400", "500", "600", "700"],
+ style: ["normal", "italic"],
+ display: "swap",
+});
+
+const figtree = Figtree({
+ subsets: ["latin"],
+ variable: "--font-body",
+ weight: ["400", "500", "600", "700", "800", "900"],
+ display: "swap",
+});
+
+export const metadata: Metadata = {
+ metadataBase: new URL(
+ process.env.NEXT_PUBLIC_SITE_URL ?? "https://getvindicated.org"
+ ),
+ title: {
+ default: "VINdicated | Educate. Empower. Vindicate.",
+ template: "%s | VINdicated",
+ },
+ description:
+ "VINdicated is a nonprofit built on the belief that car knowledge should be public knowledge. We break down the systems that allow consumer discrimination to thrive.",
+ openGraph: {
+ type: "website",
+ siteName: "VINdicated",
+ title: {
+ default: "VINdicated | Educate. Empower. Vindicate.",
+ template: "%s | VINdicated",
+ },
+ description:
+ "VINdicated is a nonprofit built on the belief that car knowledge should be public knowledge. We break down the systems that allow consumer discrimination to thrive.",
+ url: "/",
+ images: [
+ {
+ url: "/illus-woman-dealership.png",
+ alt: "VINdicated",
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: {
+ default: "VINdicated | Educate. Empower. Vindicate.",
+ template: "%s | VINdicated",
+ },
+ description:
+ "VINdicated is a nonprofit built on the belief that car knowledge should be public knowledge. We break down the systems that allow consumer discrimination to thrive.",
+ images: ["/illus-woman-dealership.png"],
+ },
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {/* */}
+
+ {children}
+
+
+
+ );
+}
diff --git a/app/page.tsx b/app/[locale]/page.tsx
similarity index 100%
rename from app/page.tsx
rename to app/[locale]/page.tsx
diff --git a/app/research/page.tsx b/app/[locale]/research/page.tsx
similarity index 100%
rename from app/research/page.tsx
rename to app/[locale]/research/page.tsx
diff --git a/app/rights/page.tsx b/app/[locale]/rights/page.tsx
similarity index 100%
rename from app/rights/page.tsx
rename to app/[locale]/rights/page.tsx
diff --git a/app/team/page.tsx b/app/[locale]/team/page.tsx
similarity index 100%
rename from app/team/page.tsx
rename to app/[locale]/team/page.tsx
diff --git a/app/volunteer/page.tsx b/app/[locale]/volunteer/page.tsx
similarity index 100%
rename from app/volunteer/page.tsx
rename to app/[locale]/volunteer/page.tsx
diff --git a/app/volunteer/page.txt b/app/[locale]/volunteer/page.txt
similarity index 100%
rename from app/volunteer/page.txt
rename to app/[locale]/volunteer/page.txt
diff --git a/app/layout.tsx b/app/layout.tsx
index 429a259..cd4a053 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,77 +1,7 @@
-import type { Metadata } from "next";
-import "./globals.css";
-import { Lora, Figtree } from "next/font/google";
-import { Nav } from "@/components/layout/Nav";
-import { Footer } from "@/components/layout/Footer";
-// import { CarCursor } from "@/components/ui/CarCursor";
-
-const lora = Lora({
- subsets: ["latin"],
- variable: "--font-heading",
- weight: ["400", "500", "600", "700"],
- style: ["normal", "italic"],
- display: "swap",
-});
-
-const figtree = Figtree({
- subsets: ["latin"],
- variable: "--font-body",
- weight: ["400", "500", "600", "700", "800", "900"],
- display: "swap",
-});
-
-export const metadata: Metadata = {
- metadataBase: new URL(
- process.env.NEXT_PUBLIC_SITE_URL ?? "https://getvindicated.org"
- ),
- title: {
- default: "VINdicated | Educate. Empower. Vindicate.",
- template: "%s | VINdicated",
- },
- description:
- "VINdicated is a nonprofit built on the belief that car knowledge should be public knowledge. We break down the systems that allow consumer discrimination to thrive.",
- openGraph: {
- type: "website",
- siteName: "VINdicated",
- title: {
- default: "VINdicated | Educate. Empower. Vindicate.",
- template: "%s | VINdicated",
- },
- description:
- "VINdicated is a nonprofit built on the belief that car knowledge should be public knowledge. We break down the systems that allow consumer discrimination to thrive.",
- url: "/",
- images: [
- {
- url: "/illus-woman-dealership.png",
- alt: "VINdicated",
- },
- ],
- },
- twitter: {
- card: "summary_large_image",
- title: {
- default: "VINdicated | Educate. Empower. Vindicate.",
- template: "%s | VINdicated",
- },
- description:
- "VINdicated is a nonprofit built on the belief that car knowledge should be public knowledge. We break down the systems that allow consumer discrimination to thrive.",
- images: ["/illus-woman-dealership.png"],
- },
-};
-
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
- return (
-
-
- {/* */}
-
- {children}
-
-
-
- );
+ return children;
}
diff --git a/components/layout/LanguageSwitcher.tsx b/components/layout/LanguageSwitcher.tsx
new file mode 100644
index 0000000..96ea8fc
--- /dev/null
+++ b/components/layout/LanguageSwitcher.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import { useState, useRef, useEffect } from "react";
+import { usePathname, useRouter } from "next/navigation";
+import { locales, localeLabels, type Locale } from "@/lib/i18n/config";
+
+export function LanguageSwitcher({ currentLocale }: { currentLocale: Locale }) {
+ const [open, setOpen] = useState(false);
+ const ref = useRef(null);
+ const pathname = usePathname();
+ const router = useRouter();
+
+ useEffect(() => {
+ function onClickOutside(e: MouseEvent) {
+ if (ref.current && !ref.current.contains(e.target as Node)) {
+ setOpen(false);
+ }
+ }
+ document.addEventListener("mousedown", onClickOutside);
+ return () => document.removeEventListener("mousedown", onClickOutside);
+ }, []);
+
+ function switchTo(locale: Locale) {
+ const segments = pathname.split("/");
+ segments[1] = locale;
+ router.push(segments.join("/"));
+ setOpen(false);
+ }
+
+ return (
+
+
+
+ {open && (
+
+ {locales.map((locale) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/lib/i18n/config.ts b/lib/i18n/config.ts
new file mode 100644
index 0000000..1ed48fc
--- /dev/null
+++ b/lib/i18n/config.ts
@@ -0,0 +1,50 @@
+// lib/i18n/config.ts
+export type Locale =
+ | "en"
+ | "es"
+ | "zh-Hans"
+ | "tl"
+ | "vi"
+ | "ko"
+ | "fa"
+ | "hy"
+ | "hi"
+ | "ar-EG";
+
+export const locales: Locale[] = [
+ "en",
+ "es",
+ "zh-Hans",
+ "tl",
+ "vi",
+ "ko",
+ "fa",
+ "hy",
+ "hi",
+ "ar-EG"
+];
+
+export const defaultLocale: Locale = "en";
+
+export const localeLabels: Record = {
+ en: "English",
+ es: "Español",
+ "zh-Hans": "简体中文",
+ tl: "Tagalog",
+ vi: "Tiếng Việt",
+ ko: "한국어",
+ fa: "فارسی",
+ hy: "Հայերեն",
+ hi: "हिन्दी",
+ "ar-EG": "العربية المصرية",
+};
+
+export const rtlLocales: Locale[] = ["fa", "ar-EG"];
+
+export function isRtl(locale: Locale): boolean {
+ return rtlLocales.includes(locale);
+}
+
+export function isValidLocale(value: string): value is Locale {
+ return locales.includes(value as Locale);
+}
diff --git a/lib/i18n/dictionaries/ar-EG.json b/lib/i18n/dictionaries/ar-EG.json
new file mode 100644
index 0000000..bd2069a
--- /dev/null
+++ b/lib/i18n/dictionaries/ar-EG.json
@@ -0,0 +1,18 @@
+{
+ "dir": "rtl",
+ "nav": {
+ "home": "الرئيسية",
+ "about": "عننا",
+ "inspection": "دليل الفحص",
+ "fraud": "الوقاية من النصب",
+ "documents": "شرح المستندات",
+ "research": "أبحاث",
+ "contact": "تواصل معنا"
+ },
+ "footer": {
+ "tagline": "نتعلّم. نتمكّن. ننصف حقنا.",
+ "subtagline": "تثقيف مجاني عن السيارات للستات والمهاجرين وأول مرة بيشتروا عربية.",
+ "copyright": "© 2026 VINdicated. جميع الحقوق محفوظة. أسستها رنا درويش.",
+ "mission": "منظمة غير هادفة للربح ملتزمة بالمساواة بين الجنسين في تجارة السيارات."
+ }
+}
diff --git a/lib/i18n/dictionaries/en.json b/lib/i18n/dictionaries/en.json
new file mode 100644
index 0000000..d101a8a
--- /dev/null
+++ b/lib/i18n/dictionaries/en.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "Home",
+ "about": "About Us",
+ "inspection": "Inspection Guide",
+ "fraud": "Fraud Prevention",
+ "documents": "Documents Decoded",
+ "research": "Research",
+ "contact": "Contact"
+ },
+ "footer": {
+ "tagline": "Educate. Empower. Vindicate.",
+ "subtagline": "Free automotive education for women, immigrants, and first-time buyers.",
+ "copyright": "© 2026 VINdicated. All rights reserved. Founded by Rana Darwich.",
+ "mission": "A nonprofit committed to gender equity in automotive commerce."
+ }
+}
diff --git a/lib/i18n/dictionaries/es.json b/lib/i18n/dictionaries/es.json
new file mode 100644
index 0000000..eb3bcd6
--- /dev/null
+++ b/lib/i18n/dictionaries/es.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "Inicio",
+ "about": "Sobre Nosotros",
+ "inspection": "Guía de Inspección",
+ "fraud": "Prevención de Fraude",
+ "documents": "Documentos Descifrados",
+ "research": "Investigación",
+ "contact": "Contacto"
+ },
+ "footer": {
+ "tagline": "Educar. Empoderar. Reivindicar.",
+ "subtagline": "Educación automotriz gratuita para mujeres, inmigrantes y compradores primerizos.",
+ "copyright": "© 2026 VINdicated. Todos los derechos reservados. Fundado por Rana Darwich.",
+ "mission": "Una organización sin fines de lucro comprometida con la equidad de género en el comercio automotriz."
+ }
+}
diff --git a/lib/i18n/dictionaries/fa.json b/lib/i18n/dictionaries/fa.json
new file mode 100644
index 0000000..054c44f
--- /dev/null
+++ b/lib/i18n/dictionaries/fa.json
@@ -0,0 +1,18 @@
+{
+ "dir": "rtl",
+ "nav": {
+ "home": "خانه",
+ "about": "درباره ما",
+ "inspection": "راهنمای بازرسی",
+ "fraud": "پیشگیری از کلاهبرداری",
+ "documents": "رمزگشایی اسناد",
+ "research": "پژوهش",
+ "contact": "تماس با ما"
+ },
+ "footer": {
+ "tagline": "آموزش. توانمندسازی. احقاق حق.",
+ "subtagline": "آموزش رایگان خودرو برای زنان، مهاجران و خریداران بار اول.",
+ "copyright": "© ۲۰۲۶ VINdicated. تمامی حقوق محفوظ است. بنیانگذار: رعنا درویش.",
+ "mission": "یک سازمان غیرانتفاعی متعهد به برابری جنسیتی در تجارت خودرو."
+ }
+}
diff --git a/lib/i18n/dictionaries/hi.json b/lib/i18n/dictionaries/hi.json
new file mode 100644
index 0000000..573f945
--- /dev/null
+++ b/lib/i18n/dictionaries/hi.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "होम",
+ "about": "हमारे बारे में",
+ "inspection": "निरीक्षण गाइड",
+ "fraud": "धोखाधड़ी से बचाव",
+ "documents": "दस्तावेज़ों की व्याख्या",
+ "research": "शोध",
+ "contact": "संपर्क करें"
+ },
+ "footer": {
+ "tagline": "शिक्षित करें। सशक्त बनाएं। न्याय दिलाएं।",
+ "subtagline": "महिलाओं, प्रवासियों और पहली बार कार खरीदने वालों के लिए मुफ़्त ऑटोमोटिव शिक्षा।",
+ "copyright": "© 2026 VINdicated. सर्वाधिकार सुरक्षित। संस्थापक: राणा दरविश।",
+ "mission": "ऑटोमोटिव व्यापार में लैंगिक समानता के लिए प्रतिबद्ध एक गैर-लाभकारी संस्था।"
+ }
+}
diff --git a/lib/i18n/dictionaries/hy.json b/lib/i18n/dictionaries/hy.json
new file mode 100644
index 0000000..5d3cfd2
--- /dev/null
+++ b/lib/i18n/dictionaries/hy.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "Գլխավոր",
+ "about": "Մեր Մասին",
+ "inspection": "Ստուգման Ուղեցույց",
+ "fraud": "Խարդախության Կանխարգելում",
+ "documents": "Փաստաթղթերի Վերծանում",
+ "research": "Հետազոտություն",
+ "contact": "Կապ"
+ },
+ "footer": {
+ "tagline": "Կրթել. Հզորացնել. Պաշտպանել.",
+ "subtagline": "Անվճար ավտոմեքենայի կրթություն կանանց, միգրանտների և առաջին անգամ գնորդների համար։",
+ "copyright": "© 2026 VINdicated: Բոլոր իրավունքները պաշտպանված են: Հիմնադրվել է Ռանա Դարվիշի կողմից:",
+ "mission": "Ոչ առևտրային կազմակերպություն, նվիրված ավտոառևտրում գենդերային հավասարությանը:"
+ }
+}
diff --git a/lib/i18n/dictionaries/ko.json b/lib/i18n/dictionaries/ko.json
new file mode 100644
index 0000000..747459a
--- /dev/null
+++ b/lib/i18n/dictionaries/ko.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "홈",
+ "about": "소개",
+ "inspection": "검사 가이드",
+ "fraud": "사기 예방",
+ "documents": "문서 해설",
+ "research": "연구",
+ "contact": "문의하기"
+ },
+ "footer": {
+ "tagline": "교육. 역량 강화. 정당한 권리 수호.",
+ "subtagline": "여성, 이민자, 그리고 첫 자동차 구매자를 위한 무료 자동차 교육입니다.",
+ "copyright": "© 2026 VINdicated. 모든 권리 보유. Rana Darwich 설립.",
+ "mission": "자동차 상거래의 성평등을 위해 힘쓰는 비영리 단체입니다."
+ }
+}
diff --git a/lib/i18n/dictionaries/tl.json b/lib/i18n/dictionaries/tl.json
new file mode 100644
index 0000000..07c367f
--- /dev/null
+++ b/lib/i18n/dictionaries/tl.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "Home",
+ "about": "Tungkol Sa Amin",
+ "inspection": "Gabay sa Inspeksyon",
+ "fraud": "Pag-iwas sa Panloloko",
+ "documents": "Mga Dokumentong Ipinaliwanag",
+ "research": "Pananaliksik",
+ "contact": "Makipag-ugnayan"
+ },
+ "footer": {
+ "tagline": "Turuan. Bigyang-kapangyarihan. Ipagtanggol.",
+ "subtagline": "Libreng edukasyon tungkol sa sasakyan para sa mga kababaihan, imigrante, at mga unang beses na bumibili.",
+ "copyright": "© 2026 VINdicated. Nakalaan ang lahat ng karapatan. Itinatag ni Rana Darwich.",
+ "mission": "Isang nonprofit na nakatuon sa pagkakapantay-pantay ng kasarian sa kalakalang pang-awto."
+ }
+}
diff --git a/lib/i18n/dictionaries/vi.json b/lib/i18n/dictionaries/vi.json
new file mode 100644
index 0000000..28e89f4
--- /dev/null
+++ b/lib/i18n/dictionaries/vi.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "Trang Chủ",
+ "about": "Về Chúng Tôi",
+ "inspection": "Hướng Dẫn Kiểm Tra",
+ "fraud": "Phòng Chống Gian Lận",
+ "documents": "Giải Mã Tài Liệu",
+ "research": "Nghiên Cứu",
+ "contact": "Liên Hệ"
+ },
+ "footer": {
+ "tagline": "Giáo Dục. Trao Quyền. Minh Oan.",
+ "subtagline": "Giáo dục miễn phí về ô tô dành cho phụ nữ, người nhập cư và người mua xe lần đầu.",
+ "copyright": "© 2026 VINdicated. Bảo lưu mọi quyền. Sáng lập bởi Rana Darwich.",
+ "mission": "Một tổ chức phi lợi nhuận cam kết vì bình đẳng giới trong thương mại ô tô."
+ }
+}
diff --git a/lib/i18n/dictionaries/zh-Hans.json b/lib/i18n/dictionaries/zh-Hans.json
new file mode 100644
index 0000000..2ec3dee
--- /dev/null
+++ b/lib/i18n/dictionaries/zh-Hans.json
@@ -0,0 +1,17 @@
+{
+ "nav": {
+ "home": "首页",
+ "about": "关于我们",
+ "inspection": "检验指南",
+ "fraud": "防范欺诈",
+ "documents": "文件解读",
+ "research": "研究",
+ "contact": "联系我们"
+ },
+ "footer": {
+ "tagline": "教育。赋能。维权。",
+ "subtagline": "为女性、移民和首次购车者提供免费的汽车知识教育。",
+ "copyright": "© 2026 VINdicated。保留所有权利。由 Rana Darwich 创立。",
+ "mission": "一家致力于促进汽车商务性别平等的非营利组织。"
+ }
+}
diff --git a/lib/i18n/get-dictionary.ts b/lib/i18n/get-dictionary.ts
new file mode 100644
index 0000000..4291bd3
--- /dev/null
+++ b/lib/i18n/get-dictionary.ts
@@ -0,0 +1,21 @@
+// lib/i18n/get-dictionary.ts
+import "server-only";
+import type { Locale } from "./config";
+
+const dictionaries = {
+ en: () => import("./dictionaries/en.json").then((m) => m.default),
+ es: () => import("./dictionaries/es.json").then((m) => m.default),
+ "zh-Hans": () => import("./dictionaries/zh-Hans.json").then((m) => m.default),
+ tl: () => import("./dictionaries/tl.json").then((m) => m.default),
+ vi: () => import("./dictionaries/vi.json").then((m) => m.default),
+ ko: () => import("./dictionaries/ko.json").then((m) => m.default),
+ fa: () => import("./dictionaries/fa.json").then((m) => m.default),
+ hy: () => import("./dictionaries/hy.json").then((m) => m.default),
+ hi: () => import("./dictionaries/hi.json").then((m) => m.default),
+ "ar-EG": () => import("./dictionaries/ar-EG.json").then((m) => m.default),
+} as const;
+
+export async function getDictionary(locale: Locale) {
+ const loader = dictionaries[locale] ?? dictionaries.en;
+ return loader();
+}
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..a708402
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,42 @@
+// middleware.ts
+import { NextRequest, NextResponse } from "next/server";
+import { locales, defaultLocale, isValidLocale } from "@/lib/i18n/config";
+
+function detectLocale(request: NextRequest): string {
+ const acceptLanguage = request.headers.get("accept-language");
+ if (!acceptLanguage) return defaultLocale;
+
+ const preferred = acceptLanguage.split(",").map((part) => part.split(";")[0].trim());
+
+ for (const lang of preferred) {
+ if (isValidLocale(lang)) return lang;
+ const base = lang.split("-")[0];
+ const match = locales.find((l) => l.split("-")[0] === base);
+ if (match) return match;
+ }
+
+ return defaultLocale;
+}
+
+export function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+
+ if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
+ return NextResponse.next();
+ }
+
+ const segments = pathname.split("/");
+ const maybeLocale = segments[1];
+
+ if (isValidLocale(maybeLocale)) {
+ return NextResponse.next();
+ }
+
+ const locale = detectLocale(request);
+ const newUrl = new URL(`/${locale}${pathname}`, request.url);
+ return NextResponse.redirect(newUrl);
+}
+
+export const config = {
+ matcher: ["/((?!_next|api|.*\\..*).*)"],
+};