From 68271f09bccb30757de9b9064e2ff6f1ad913159 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:34:16 +0100 Subject: [PATCH 01/28] feat(i18n): add locale configuration --- src/i18n/config.ts | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/i18n/config.ts diff --git a/src/i18n/config.ts b/src/i18n/config.ts new file mode 100644 index 00000000..7ad1b22b --- /dev/null +++ b/src/i18n/config.ts @@ -0,0 +1,49 @@ +export const SUPPORTED_LOCALES = ["en", "es", "fr", "de", "ar"] as const; + +export type Locale = (typeof SUPPORTED_LOCALES)[number]; +export type TextDirection = "ltr" | "rtl"; + +export const DEFAULT_LOCALE: Locale = "en"; +export const LOCALE_COOKIE = "gem-locale"; +export const LOCALE_COOKIE_MAX_AGE = 60 * 60 * 24 * 365; + +export const LOCALE_OPTIONS: ReadonlyArray<{ + code: Locale; + name: string; + nativeName: string; + direction: TextDirection; +}> = [ + { code: "en", name: "English", nativeName: "English", direction: "ltr" }, + { code: "es", name: "Spanish", nativeName: "Español", direction: "ltr" }, + { code: "fr", name: "French", nativeName: "Français", direction: "ltr" }, + { code: "de", name: "German", nativeName: "Deutsch", direction: "ltr" }, + { code: "ar", name: "Arabic", nativeName: "العربية", direction: "rtl" }, +]; + +export function isLocale(value: unknown): value is Locale { + return typeof value === "string" && (SUPPORTED_LOCALES as readonly string[]).includes(value); +} + +export function resolveLocale(value: unknown): Locale { + return isLocale(value) ? value : DEFAULT_LOCALE; +} + +export function getDirection(locale: Locale): TextDirection { + return locale === "ar" ? "rtl" : "ltr"; +} + +export function matchAcceptLanguage(headerValue: string | null): Locale { + if (!headerValue) return DEFAULT_LOCALE; + + const requested = headerValue + .split(",") + .map((part) => part.trim().split(";")[0]?.toLowerCase()) + .filter(Boolean); + + for (const language of requested) { + const base = language?.split("-")[0]; + if (isLocale(base)) return base; + } + + return DEFAULT_LOCALE; +} From 35096188202a5baeccdc9f419480b5e075b04e0f Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:34:29 +0100 Subject: [PATCH 02/28] feat(i18n): add dictionary types --- src/i18n/types.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/i18n/types.ts diff --git a/src/i18n/types.ts b/src/i18n/types.ts new file mode 100644 index 00000000..eb2c07b8 --- /dev/null +++ b/src/i18n/types.ts @@ -0,0 +1,34 @@ +import type { Locale, TextDirection } from "@/i18n/config"; + +export type TranslationLookup = Record; + +export interface Dictionary { + meta: { + locale: Locale; + name: string; + nativeName: string; + direction: TextDirection; + }; + common: { + language: string; + selectLanguage: string; + primaryNavigation: string; + mobileNavigation: string; + openNavigationMenu: string; + closeNavigationMenu: string; + all: string; + qualifiedAccessNotice: string; + }; + navigation: { + labels: TranslationLookup; + }; + footer: { + tagline: string; + qualifiedClientsOnly: string; + qualifiedClientsDescription: string; + platform: string; + company: string; + legal: string; + clientAccess: string; + }; +} From 2431fa1f2c180c9d910e67990508aa4982baf59e Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:34:41 +0100 Subject: [PATCH 03/28] feat(i18n): add server dictionary loader --- src/i18n/get-dictionary.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/i18n/get-dictionary.ts diff --git a/src/i18n/get-dictionary.ts b/src/i18n/get-dictionary.ts new file mode 100644 index 00000000..5d123322 --- /dev/null +++ b/src/i18n/get-dictionary.ts @@ -0,0 +1,16 @@ +import "server-only"; + +import type { Locale } from "@/i18n/config"; +import type { Dictionary } from "@/i18n/types"; + +const dictionaries: Record Promise> = { + en: () => import("@/i18n/dictionaries/en.json").then((module) => module.default as Dictionary), + es: () => import("@/i18n/dictionaries/es.json").then((module) => module.default as Dictionary), + fr: () => import("@/i18n/dictionaries/fr.json").then((module) => module.default as Dictionary), + de: () => import("@/i18n/dictionaries/de.json").then((module) => module.default as Dictionary), + ar: () => import("@/i18n/dictionaries/ar.json").then((module) => module.default as Dictionary), +}; + +export async function getDictionary(locale: Locale): Promise { + return dictionaries[locale](); +} From 0b4c485caea3e23b5d23743eb65b64956c3731cd Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:35:05 +0100 Subject: [PATCH 04/28] feat(i18n): add locale request helper --- src/i18n/server.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/i18n/server.ts diff --git a/src/i18n/server.ts b/src/i18n/server.ts new file mode 100644 index 00000000..a8cb8595 --- /dev/null +++ b/src/i18n/server.ts @@ -0,0 +1,14 @@ +import "server-only"; + +import { cookies, headers } from "next/headers"; +import { LOCALE_COOKIE, isLocale, matchAcceptLanguage, type Locale } from "@/i18n/config"; + +export async function getRequestLocale(): Promise { + const cookieStore = await cookies(); + const cookieLocale = cookieStore.get(LOCALE_COOKIE)?.value; + + if (isLocale(cookieLocale)) return cookieLocale; + + const headerStore = await headers(); + return matchAcceptLanguage(headerStore.get("accept-language")); +} From 568b18ff40df11dfd5a08626d1195fc700e6419f Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:35:17 +0100 Subject: [PATCH 05/28] feat(i18n): add client locale provider --- src/components/I18nProvider.tsx | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/components/I18nProvider.tsx diff --git a/src/components/I18nProvider.tsx b/src/components/I18nProvider.tsx new file mode 100644 index 00000000..b9d7a70b --- /dev/null +++ b/src/components/I18nProvider.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { createContext, useContext, type ReactNode } from "react"; +import type { Locale } from "@/i18n/config"; +import type { Dictionary } from "@/i18n/types"; + +type I18nContextValue = { + locale: Locale; + dictionary: Dictionary; +}; + +const I18nContext = createContext(null); + +export function I18nProvider({ + locale, + dictionary, + children, +}: I18nContextValue & { children: ReactNode }) { + return ( + + {children} + + ); +} + +export function useI18n(): I18nContextValue { + const context = useContext(I18nContext); + + if (!context) { + throw new Error("useI18n must be used within I18nProvider"); + } + + return context; +} From fae61f019160c322b584a1a13d1742340268cfc0 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:35:33 +0100 Subject: [PATCH 06/28] feat(i18n): add language switcher --- src/components/LanguageSwitcher.tsx | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/components/LanguageSwitcher.tsx diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 00000000..404657cb --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { Globe2 } from "lucide-react"; +import { getDirection, LOCALE_OPTIONS, type Locale } from "@/i18n/config"; +import { useI18n } from "@/components/I18nProvider"; +import { cn } from "@/lib/utils"; + +export function LanguageSwitcher({ className }: { className?: string }) { + const router = useRouter(); + const { locale, dictionary } = useI18n(); + const [isPending, startTransition] = useTransition(); + const [error, setError] = useState(null); + + async function changeLocale(nextLocale: Locale) { + if (nextLocale === locale || isPending) return; + + setError(null); + + const response = await fetch("/api/locale", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ locale: nextLocale }), + }); + + if (!response.ok) { + setError("Language update failed"); + return; + } + + document.documentElement.lang = nextLocale; + document.documentElement.dir = getDirection(nextLocale); + + startTransition(() => { + router.refresh(); + }); + } + + return ( +
+
+ ); +} From 938e8adc46250ae10406369aa6d7587e2b89d291 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:35:48 +0100 Subject: [PATCH 07/28] feat(i18n): add locale preference API --- src/app/api/locale/route.ts | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/app/api/locale/route.ts diff --git a/src/app/api/locale/route.ts b/src/app/api/locale/route.ts new file mode 100644 index 00000000..a3923a45 --- /dev/null +++ b/src/app/api/locale/route.ts @@ -0,0 +1,62 @@ +import { NextResponse } from "next/server"; +import { z } from "zod"; +import { + LOCALE_COOKIE, + LOCALE_COOKIE_MAX_AGE, + SUPPORTED_LOCALES, + isLocale, +} from "@/i18n/config"; + +const localeSchema = z.object({ + locale: z.string().refine(isLocale, "Unsupported locale"), +}); + +export async function GET() { + return NextResponse.json( + { + defaultLocale: "en", + supportedLocales: SUPPORTED_LOCALES, + }, + { + headers: { + "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400", + }, + }, + ); +} + +export async function POST(request: Request) { + let payload: unknown; + + try { + payload = await request.json(); + } catch { + return NextResponse.json( + { ok: false, error: { code: "INVALID_JSON", message: "Request body must be valid JSON." } }, + { status: 400 }, + ); + } + + const parsed = localeSchema.safeParse(payload); + + if (!parsed.success) { + return NextResponse.json( + { ok: false, error: { code: "INVALID_LOCALE", message: "The requested locale is not supported." } }, + { status: 400 }, + ); + } + + const response = NextResponse.json({ ok: true, locale: parsed.data.locale }); + + response.cookies.set({ + name: LOCALE_COOKIE, + value: parsed.data.locale, + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + maxAge: LOCALE_COOKIE_MAX_AGE, + }); + + return response; +} From e8716001223466c2ba25f32810301d91ed54624a Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:36:03 +0100 Subject: [PATCH 08/28] feat(i18n): add English source dictionary --- src/i18n/dictionaries/en.json | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/i18n/dictionaries/en.json diff --git a/src/i18n/dictionaries/en.json b/src/i18n/dictionaries/en.json new file mode 100644 index 00000000..2837de32 --- /dev/null +++ b/src/i18n/dictionaries/en.json @@ -0,0 +1,87 @@ +{ + "meta": { + "locale": "en", + "name": "English", + "nativeName": "English", + "direction": "ltr" + }, + "common": { + "language": "Language", + "selectLanguage": "Select language", + "primaryNavigation": "Primary navigation", + "mobileNavigation": "Mobile navigation", + "openNavigationMenu": "Open navigation menu", + "closeNavigationMenu": "Close navigation menu", + "all": "All", + "qualifiedAccessNotice": "Qualified client access only. KYC verification and compliance approval are required." + }, + "navigation": { + "labels": { + "Home": "Home", + "Overview": "Overview", + "Platform Highlights": "Platform Highlights", + "Leadership": "Leadership", + "Client Access": "Client Access", + "Get Started": "Get Started", + "Intel": "Intel", + "Threat Intelligence": "Threat Intelligence", + "Reports": "Reports", + "Monitoring": "Monitoring", + "Intel Briefs": "Intel Briefs", + "Architecture Specs": "Architecture Specs", + "Services": "Services", + "Cybersecurity": "Cybersecurity", + "Financial": "Financial", + "Real Estate": "Real Estate", + "Assessments": "Assessments", + "Consultations": "Consultations", + "Alliance Trust Realty": "Alliance Trust Realty", + "Properties": "Properties", + "Investment Platform": "Investment Platform", + "Community": "Community", + "Community Hub": "Community Hub", + "Opportunities": "Opportunities", + "Members": "Members", + "Circles": "Circles", + "Events": "Events", + "Knowledge": "Knowledge", + "Request Access": "Request Access", + "Community Overview": "Community Overview", + "Hub": "Hub", + "Command Center": "Command Center", + "Documents": "Documents", + "Support Access": "Support Access", + "Requests": "Requests", + "Client Portal": "Client Portal", + "Resources": "Resources", + "Market Insights": "Market Insights", + "Templates": "Templates", + "Bots": "Bots", + "News": "News", + "FAQ": "FAQ", + "Company": "Company", + "About": "About", + "Leadership & Vision": "Leadership & Vision", + "Executive Board": "Executive Board", + "Teams": "Teams", + "Personnel Board": "Personnel Board", + "Contact": "Contact", + "Client Login": "Client Login", + "Privacy Policy": "Privacy Policy", + "Terms of Service": "Terms of Service", + "Compliance Notice": "Compliance Notice", + "Cookie Policy": "Cookie Policy", + "Eligibility Status": "Eligibility Status", + "Apply for Access": "Apply for Access" + } + }, + "footer": { + "tagline": "Institutional-grade cybersecurity, financial security, and real estate protection.", + "qualifiedClientsOnly": "Qualified Clients Only", + "qualifiedClientsDescription": "GEM Enterprise services are available exclusively to accredited investors and qualified institutional clients.", + "platform": "Platform", + "company": "Company", + "legal": "Legal", + "clientAccess": "Client Access" + } +} From a32481bba7210a3ba622d9829f424bf89e86ce55 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:37:47 +0100 Subject: [PATCH 09/28] feat(i18n): add Spanish dictionary --- src/i18n/dictionaries/es.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/dictionaries/es.json diff --git a/src/i18n/dictionaries/es.json b/src/i18n/dictionaries/es.json new file mode 100644 index 00000000..e197c5f2 --- /dev/null +++ b/src/i18n/dictionaries/es.json @@ -0,0 +1 @@ +{"meta":{"locale":"es","name":"Spanish","nativeName":"Espa\u00f1ol","direction":"ltr"},"common":{"language":"Idioma","selectLanguage":"Seleccionar idioma","primaryNavigation":"Navegaci\u00f3n principal","mobileNavigation":"Navegaci\u00f3n m\u00f3vil","openNavigationMenu":"Abrir men\u00fa de navegaci\u00f3n","closeNavigationMenu":"Cerrar men\u00fa de navegaci\u00f3n","all":"Todo","qualifiedAccessNotice":"Acceso exclusivo para clientes cualificados. Se requieren verificaci\u00f3n KYC y aprobaci\u00f3n de cumplimiento."},"navigation":{"labels":{"Home":"Inicio","Overview":"Resumen","Platform Highlights":"Aspectos destacados de la plataforma","Leadership":"Liderazgo","Client Access":"Acceso de clientes","Get Started":"Comenzar","Intel":"Inteligencia","Threat Intelligence":"Inteligencia de amenazas","Reports":"Informes","Monitoring":"Monitoreo","Intel Briefs":"Informes breves de inteligencia","Architecture Specs":"Especificaciones de arquitectura","Services":"Servicios","Cybersecurity":"Ciberseguridad","Financial":"Finanzas","Real Estate":"Bienes ra\u00edces","Assessments":"Evaluaciones","Consultations":"Consultas","Alliance Trust Realty":"Alliance Trust Realty","Properties":"Propiedades","Investment Platform":"Plataforma de inversi\u00f3n","Community":"Comunidad","Community Hub":"Centro de la comunidad","Opportunities":"Oportunidades","Members":"Miembros","Circles":"C\u00edrculos","Events":"Eventos","Knowledge":"Conocimiento","Request Access":"Solicitar acceso","Community Overview":"Resumen de la comunidad","Hub":"Centro","Command Center":"Centro de mando","Documents":"Documentos","Support Access":"Acceso a soporte","Requests":"Solicitudes","Client Portal":"Portal del cliente","Resources":"Recursos","Market Insights":"An\u00e1lisis de mercado","Templates":"Plantillas","Bots":"Bots","News":"Noticias","FAQ":"Preguntas frecuentes","Company":"Empresa","About":"Nosotros","Leadership & Vision":"Liderazgo y visi\u00f3n","Executive Board":"Junta directiva","Teams":"Equipos","Personnel Board":"Directorio de personal","Contact":"Contacto","Client Login":"Inicio de sesi\u00f3n del cliente","Privacy Policy":"Pol\u00edtica de privacidad","Terms of Service":"T\u00e9rminos del servicio","Compliance Notice":"Aviso de cumplimiento","Cookie Policy":"Pol\u00edtica de cookies","Eligibility Status":"Estado de elegibilidad","Apply for Access":"Solicitar acceso"}},"footer":{"tagline":"Ciberseguridad, seguridad financiera y protecci\u00f3n inmobiliaria de nivel institucional.","qualifiedClientsOnly":"Solo clientes cualificados","qualifiedClientsDescription":"Los servicios de GEM Enterprise est\u00e1n disponibles exclusivamente para inversores acreditados y clientes institucionales cualificados.","platform":"Plataforma","company":"Empresa","legal":"Legal","clientAccess":"Acceso de clientes"}} From 56fe65e8e4e50e430c23b039c1d5d0945b0516ba Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:38:51 +0100 Subject: [PATCH 10/28] feat(i18n): add French locale scaffold --- src/i18n/dictionaries/fr.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/dictionaries/fr.json diff --git a/src/i18n/dictionaries/fr.json b/src/i18n/dictionaries/fr.json new file mode 100644 index 00000000..88266da6 --- /dev/null +++ b/src/i18n/dictionaries/fr.json @@ -0,0 +1 @@ +{"meta":{"locale":"fr","name":"French","nativeName":"French","direction":"ltr"},"common":{"language":"Language","selectLanguage":"Select language","primaryNavigation":"Primary navigation","mobileNavigation":"Mobile navigation","openNavigationMenu":"Open navigation menu","closeNavigationMenu":"Close navigation menu","all":"All","qualifiedAccessNotice":"Approved access is required."},"navigation":{"labels":{}},"footer":{"tagline":"GEM Enterprise","qualifiedClientsOnly":"Qualified clients","qualifiedClientsDescription":"Access is subject to review.","platform":"Platform","company":"Company","legal":"Legal","clientAccess":"Client access"}} From 273b8a29d3e8f3a5e6e5bd8393a2dfb93359bc5f Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:39:17 +0100 Subject: [PATCH 11/28] feat(i18n): complete French locale scaffold --- src/i18n/dictionaries/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/dictionaries/fr.json b/src/i18n/dictionaries/fr.json index 88266da6..8d75dff5 100644 --- a/src/i18n/dictionaries/fr.json +++ b/src/i18n/dictionaries/fr.json @@ -1 +1 @@ -{"meta":{"locale":"fr","name":"French","nativeName":"French","direction":"ltr"},"common":{"language":"Language","selectLanguage":"Select language","primaryNavigation":"Primary navigation","mobileNavigation":"Mobile navigation","openNavigationMenu":"Open navigation menu","closeNavigationMenu":"Close navigation menu","all":"All","qualifiedAccessNotice":"Approved access is required."},"navigation":{"labels":{}},"footer":{"tagline":"GEM Enterprise","qualifiedClientsOnly":"Qualified clients","qualifiedClientsDescription":"Access is subject to review.","platform":"Platform","company":"Company","legal":"Legal","clientAccess":"Client access"}} +{"meta":{"locale":"fr","name":"French","nativeName":"French","direction":"ltr"},"common":{"language":"Language","selectLanguage":"Select language","primaryNavigation":"Primary navigation","mobileNavigation":"Mobile navigation","openNavigationMenu":"Open navigation menu","closeNavigationMenu":"Close navigation menu","all":"All","qualifiedAccessNotice":"Approved access is required."},"navigation":{"labels":{"Home":"Home","Overview":"Overview","Platform Highlights":"Platform Highlights","Leadership":"Leadership","Client Access":"Client Access","Get Started":"Get Started","Intel":"Intel","Threat Intelligence":"Threat Intelligence","Reports":"Reports","Monitoring":"Monitoring","Intel Briefs":"Intel Briefs","Architecture Specs":"Architecture Specs","Services":"Services","Cybersecurity":"Cybersecurity","Financial":"Financial","Real Estate":"Real Estate","Assessments":"Assessments","Consultations":"Consultations","Alliance Trust Realty":"Alliance Trust Realty","Properties":"Properties","Investment Platform":"Investment Platform","Community":"Community","Community Hub":"Community Hub","Opportunities":"Opportunities","Members":"Members","Circles":"Circles","Events":"Events","Knowledge":"Knowledge","Request Access":"Request Access","Community Overview":"Community Overview","Hub":"Hub","Command Center":"Command Center","Documents":"Documents","Support Access":"Support Access","Requests":"Requests","Client Portal":"Client Portal","Resources":"Resources","Market Insights":"Market Insights","Templates":"Templates","Bots":"Bots","News":"News","FAQ":"FAQ","Company":"Company","About":"About","Leadership & Vision":"Leadership & Vision","Executive Board":"Executive Board","Teams":"Teams","Personnel Board":"Personnel Board","Contact":"Contact","Client Login":"Client Login","Privacy Policy":"Privacy Policy","Terms of Service":"Terms of Service","Compliance Notice":"Compliance Notice","Cookie Policy":"Cookie Policy","Eligibility Status":"Eligibility Status","Apply for Access":"Apply for Access"}},"footer":{"tagline":"GEM Enterprise","qualifiedClientsOnly":"Qualified clients","qualifiedClientsDescription":"Access is subject to review.","platform":"Platform","company":"Company","legal":"Legal","clientAccess":"Client access"}} From 07490f0c23ab91dde0b33a1e1e78fe5819893725 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:39:52 +0100 Subject: [PATCH 12/28] feat(i18n): add German locale scaffold --- src/i18n/dictionaries/de.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/dictionaries/de.json diff --git a/src/i18n/dictionaries/de.json b/src/i18n/dictionaries/de.json new file mode 100644 index 00000000..45d2904a --- /dev/null +++ b/src/i18n/dictionaries/de.json @@ -0,0 +1 @@ +{"meta":{"locale":"de","name":"German","nativeName":"Deutsch","direction":"ltr"},"common":{"language":"Language","selectLanguage":"Select language","primaryNavigation":"Primary navigation","mobileNavigation":"Mobile navigation","openNavigationMenu":"Open navigation menu","closeNavigationMenu":"Close navigation menu","all":"All","qualifiedAccessNotice":"Approved access is required."},"navigation":{"labels":{"Home":"Home","Overview":"Overview","Platform Highlights":"Platform Highlights","Leadership":"Leadership","Client Access":"Client Access","Get Started":"Get Started","Intel":"Intel","Threat Intelligence":"Threat Intelligence","Reports":"Reports","Monitoring":"Monitoring","Intel Briefs":"Intel Briefs","Architecture Specs":"Architecture Specs","Services":"Services","Cybersecurity":"Cybersecurity","Financial":"Financial","Real Estate":"Real Estate","Assessments":"Assessments","Consultations":"Consultations","Alliance Trust Realty":"Alliance Trust Realty","Properties":"Properties","Investment Platform":"Investment Platform","Community":"Community","Community Hub":"Community Hub","Opportunities":"Opportunities","Members":"Members","Circles":"Circles","Events":"Events","Knowledge":"Knowledge","Request Access":"Request Access","Community Overview":"Community Overview","Hub":"Hub","Command Center":"Command Center","Documents":"Documents","Support Access":"Support Access","Requests":"Requests","Client Portal":"Client Portal","Resources":"Resources","Market Insights":"Market Insights","Templates":"Templates","Bots":"Bots","News":"News","FAQ":"FAQ","Company":"Company","About":"About","Leadership & Vision":"Leadership & Vision","Executive Board":"Executive Board","Teams":"Teams","Personnel Board":"Personnel Board","Contact":"Contact","Client Login":"Client Login","Privacy Policy":"Privacy Policy","Terms of Service":"Terms of Service","Compliance Notice":"Compliance Notice","Cookie Policy":"Cookie Policy","Eligibility Status":"Eligibility Status","Apply for Access":"Apply for Access"}},"footer":{"tagline":"GEM Enterprise","qualifiedClientsOnly":"Qualified clients","qualifiedClientsDescription":"Access is subject to review.","platform":"Platform","company":"Company","legal":"Legal","clientAccess":"Client access"}} From 4d15c47f669b908aa3930057e1dfbfbef11a5b83 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:40:18 +0100 Subject: [PATCH 13/28] feat(i18n): add Arabic locale scaffold --- src/i18n/dictionaries/ar.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/dictionaries/ar.json diff --git a/src/i18n/dictionaries/ar.json b/src/i18n/dictionaries/ar.json new file mode 100644 index 00000000..a6cf00d7 --- /dev/null +++ b/src/i18n/dictionaries/ar.json @@ -0,0 +1 @@ +{"meta":{"locale":"ar","name":"Arabic","nativeName":"Arabic","direction":"rtl"},"common":{"language":"Language","selectLanguage":"Select language","primaryNavigation":"Primary navigation","mobileNavigation":"Mobile navigation","openNavigationMenu":"Open navigation menu","closeNavigationMenu":"Close navigation menu","all":"All","qualifiedAccessNotice":"Approved access is required."},"navigation":{"labels":{}},"footer":{"tagline":"GEM Enterprise","qualifiedClientsOnly":"Qualified clients","qualifiedClientsDescription":"Access is subject to review.","platform":"Platform","company":"Company","legal":"Legal","clientAccess":"Client access"}} From 4f5496cabf51b54b68e15e2f8a49a60e74368fd8 Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:42:14 +0100 Subject: [PATCH 14/28] fix(i18n): merge incomplete locales with English fallback --- src/i18n/get-dictionary.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/i18n/get-dictionary.ts b/src/i18n/get-dictionary.ts index 5d123322..bdabd4f2 100644 --- a/src/i18n/get-dictionary.ts +++ b/src/i18n/get-dictionary.ts @@ -3,14 +3,29 @@ import "server-only"; import type { Locale } from "@/i18n/config"; import type { Dictionary } from "@/i18n/types"; -const dictionaries: Record Promise> = { +const dictionaries: Record Promise>> = { en: () => import("@/i18n/dictionaries/en.json").then((module) => module.default as Dictionary), es: () => import("@/i18n/dictionaries/es.json").then((module) => module.default as Dictionary), - fr: () => import("@/i18n/dictionaries/fr.json").then((module) => module.default as Dictionary), - de: () => import("@/i18n/dictionaries/de.json").then((module) => module.default as Dictionary), - ar: () => import("@/i18n/dictionaries/ar.json").then((module) => module.default as Dictionary), + fr: () => import("@/i18n/dictionaries/fr.json").then((module) => module.default as Partial), + de: () => import("@/i18n/dictionaries/de.json").then((module) => module.default as Partial), + ar: () => import("@/i18n/dictionaries/ar.json").then((module) => module.default as Partial), }; export async function getDictionary(locale: Locale): Promise { - return dictionaries[locale](); + const source = (await dictionaries.en()) as Dictionary; + if (locale === "en") return source; + + const target = await dictionaries[locale](); + + return { + meta: { ...source.meta, ...target.meta, locale }, + common: { ...source.common, ...target.common }, + navigation: { + labels: { + ...source.navigation.labels, + ...(target.navigation?.labels ?? {}), + }, + }, + footer: { ...source.footer, ...target.footer }, + }; } From e97cd81d9550616a89456e26c2f20ecf3912094f Mon Sep 17 00:00:00 2001 From: GEM CYBERSECURITY-MONITORING ASSIST Date: Fri, 26 Jun 2026 11:43:15 +0100 Subject: [PATCH 15/28] feat(i18n): apply locale and direction at root layout --- src/app/layout.tsx | 49 ++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1f8c78c6..04d31c22 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,10 @@ import "./globals.css"; import { Providers } from "@/components/Providers"; import { Navigation } from "@/components/Navigation"; import { Footer } from "@/components/Footer"; +import { I18nProvider } from "@/components/I18nProvider"; +import { getDictionary } from "@/i18n/get-dictionary"; +import { getDirection } from "@/i18n/config"; +import { getRequestLocale } from "@/i18n/server"; import { SpeedInsights } from "@vercel/speed-insights/next"; import { Analytics } from "@vercel/analytics/next"; @@ -13,17 +17,8 @@ export const metadata: Metadata = { default: "GEM Enterprise | Defend. Protect. Prevail.", template: "%s | GEM Enterprise", }, - description: - "Institutional-grade cybersecurity, financial security, and real estate protection for qualified enterprise clients.", - keywords: [ - "GEM Enterprise", - "cybersecurity", - "enterprise security", - "threat intelligence", - "financial security", - "asset protection", - "SOC", - ], + description: "GEM Enterprise integrated digital protection platform.", + keywords: ["GEM Enterprise", "cybersecurity", "enterprise platform", "threat intelligence"], openGraph: { type: "website", locale: "en_US", @@ -45,27 +40,25 @@ export const viewport: Viewport = { }; export default async function RootLayout({ children }: { children: React.ReactNode }) { - const headersList = await headers(); + const [headersList, locale] = await Promise.all([headers(), getRequestLocale()]); const isPortal = headersList.get("x-is-portal") === "1"; + const dictionary = await getDictionary(locale); return ( - + - - {!isPortal && ( - - Skip to main content - - )} - {!isPortal && } -
- {children} -
- {!isPortal &&