diff --git a/src/hooks/marriage/use-profile-main.ts b/src/hooks/marriage/use-profile-main.ts
index 09f1e9c..1fd0a58 100644
--- a/src/hooks/marriage/use-profile-main.ts
+++ b/src/hooks/marriage/use-profile-main.ts
@@ -14,6 +14,8 @@ import {
getScopedMarriageProfile,
saveScopedMarriageProfile,
} from "@/lib/user-scoped-storage";
+import { getClientCookie } from "@/lib/cookies";
+import { isLocale } from "@/translations/config";
import type { QueryOptions } from "./options";
import { marriageQueryKeys } from "./query-keys";
import type { MarriageProfileResponse } from "./types";
@@ -57,11 +59,31 @@ export function getInitialMarriageProfile(
}
export async function getMarriageProfile() {
- console.log("🌐 [Profile API] Sending GET /api/marriage/profile/main/ ...");
+ const pathSegment =
+ typeof window !== "undefined"
+ ? window.location.pathname.split("/")[1]
+ : undefined;
+ const docLang =
+ typeof document !== "undefined" ? document.documentElement.lang : undefined;
+ const cookieLanguage =
+ getClientCookie("HABIB_LANGUAGE") ??
+ getClientCookie("habib_language") ??
+ undefined;
+ const lang = isLocale(pathSegment)
+ ? pathSegment
+ : isLocale(cookieLanguage)
+ ? cookieLanguage
+ : isLocale(docLang)
+ ? docLang
+ : undefined;
+
+ const url = lang
+ ? `/api/marriage/profile/main/?lang=${encodeURIComponent(lang)}`
+ : "/api/marriage/profile/main/";
+
+ console.log(`🌐 [Profile API] Sending GET ${url} ...`);
try {
- const { data: rawData } = await http.get(
- "/api/marriage/profile/main/",
- );
+ const { data: rawData } = await http.get(url);
const data = normalizeMarriageProfile(rawData) || rawData;
diff --git a/src/lib/marriage-field-formatter.test.ts b/src/lib/marriage-field-formatter.test.ts
new file mode 100644
index 0000000..fd394b1
--- /dev/null
+++ b/src/lib/marriage-field-formatter.test.ts
@@ -0,0 +1,65 @@
+import { describe, expect, it } from "vitest";
+import { formatFieldLabel, titleFromKey } from "./marriage-field-formatter";
+import { dictionaries } from "@/translations/dictionaries";
+
+describe("marriage-field-formatter: formatFieldLabel", () => {
+ it("directly respects dynamic Arabic labels returned from backend DB without hardcoded JSON dependency", () => {
+ const field = {
+ key: "personal_identity.place_of_birth",
+ label: "مكان الميلاد (الدولة والمدينة)",
+ value: { city: "Mashhad", country: "Iran" },
+ };
+
+ // When viewing with Arabic dictionary, must directly display backend's Arabic title
+ const labelAr = formatFieldLabel(field, dictionaries.ar as Record);
+ expect(labelAr).toBe("مكان الميلاد (الدولة والمدينة)");
+
+ // Must NOT be overridden with English "Place Of Birth"
+ expect(labelAr).not.toBe("Place Of Birth");
+ });
+
+ it("directly respects dynamic Arabic labels for place_of_upbringing", () => {
+ const field = {
+ key: "personal_identity.place_of_upbringing",
+ label: "مكان النشأة والترعرع (الدولة والمدينة)",
+ value: { city: "Mashhad", country: "Iran" },
+ };
+
+ const labelAr = formatFieldLabel(field, dictionaries.ar as Record);
+ expect(labelAr).toBe("مكان النشأة والترعرع (الدولة والمدينة)");
+ expect(labelAr).not.toBe("Place Of Upbringing");
+ });
+
+ it("directly respects dynamically updated DB question text", () => {
+ const field = {
+ key: "personal_identity.place_of_birth",
+ label: "في أي دولة ومدينة ولدت؟",
+ value: "...",
+ };
+
+ const labelAr = formatFieldLabel(field, dictionaries.ar as Record);
+ expect(labelAr).toBe("في أي دولة ومدينة ولدت؟");
+ });
+
+ it("converts Persian/Arabic labels to English title when dictionary is English", () => {
+ const field = {
+ key: "personal_identity.place_of_birth",
+ label: "مكان الميلاد (الدولة والمدينة)",
+ value: { city: "Mashhad", country: "Iran" },
+ };
+
+ const labelEn = formatFieldLabel(field, dictionaries.en as Record);
+ expect(labelEn).toBe("Place Of Birth");
+ });
+
+ it("falls back to canonical / titleFromKey when backend label is missing", () => {
+ const field = {
+ key: "personal_identity.place_of_birth",
+ label: "",
+ value: "...",
+ };
+
+ const labelAr = formatFieldLabel(field, dictionaries.ar as Record);
+ expect(labelAr).toBe("مكان الميلاد");
+ });
+});
diff --git a/src/lib/marriage-field-formatter.ts b/src/lib/marriage-field-formatter.ts
index 7d2ec6d..2ce0ee6 100644
--- a/src/lib/marriage-field-formatter.ts
+++ b/src/lib/marriage-field-formatter.ts
@@ -104,50 +104,168 @@ export function titleFromKey(key: string): string {
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}
+function isEnglishDictionary(dictionary?: Record): boolean {
+ if (!dictionary) return false;
+ return (
+ dictionary === (dictionaries.en as Record) ||
+ (dictionary["Yes"] === "Yes" &&
+ dictionary["No"] === "No" &&
+ dictionary["Cancel"] === "Cancel")
+ );
+}
+
+export const FIELD_KEY_TO_CANONICAL_EN: Record = {
+ "personal_identity.date_of_birth": ["Date of Birth"],
+ date_of_birth: ["Date of Birth"],
+ "personal_identity.place_of_birth": [
+ "Place of Birth (Country and City)",
+ "Place of Birth",
+ "Birthplace",
+ ],
+ place_of_birth: [
+ "Place of Birth (Country and City)",
+ "Place of Birth",
+ "Birthplace",
+ ],
+ birthplace: ["Birthplace", "Place of Birth"],
+ "personal_identity.place_of_upbringing": [
+ "Place of Upbringing / Where You Grew Up (Country and City)",
+ "Place of Upbringing",
+ "City of Upbringing / Growing Up",
+ ],
+ place_of_upbringing: [
+ "Place of Upbringing / Where You Grew Up (Country and City)",
+ "Place of Upbringing",
+ "City of Upbringing / Growing Up",
+ ],
+ "personal_identity.current_nationality_citizenship": [
+ "Current Nationality / Citizenship",
+ "Current Nationality / Citizenship (or Countries of Citizenship)",
+ "Nationality",
+ "Citizenship",
+ ],
+ current_nationality_citizenship: [
+ "Current Nationality / Citizenship",
+ "Current Nationality / Citizenship (or Countries of Citizenship)",
+ "Nationality",
+ "Citizenship",
+ ],
+ "personal_identity.ethnicity_family_origin_race": [
+ "Ethnicity / Family Origin / Race",
+ "Ethnicity",
+ ],
+ ethnicity_family_origin_race: [
+ "Ethnicity / Family Origin / Race",
+ "Ethnicity",
+ ],
+ "personal_identity.mother_tongue": [
+ "Mother Tongue",
+ "Native Language",
+ "Language",
+ ],
+ mother_tongue: [
+ "Mother Tongue",
+ "Native Language",
+ "Language",
+ ],
+ "personal_identity.religion": [
+ "What is your religion?",
+ "Religion",
+ ],
+ religion: [
+ "What is your religion?",
+ "Religion",
+ ],
+ "personal_identity.islamic_madhhab": [
+ "What is your branch / school of thought in Islam?",
+ "Islamic Madhhab",
+ "School of Thought",
+ ],
+ islamic_madhhab: [
+ "What is your branch / school of thought in Islam?",
+ "Islamic Madhhab",
+ "School of Thought",
+ ],
+ "contact_residence.current_residence_location": [
+ "Current Place of Residence (Country, City / State)",
+ "Current Place of Residence",
+ "Current Residence",
+ ],
+ current_residence_location: [
+ "Current Place of Residence (Country, City / State)",
+ "Current Place of Residence",
+ "Current Residence",
+ ],
+};
+
export function formatFieldLabel(
field: MarriageField,
dictionary?: Record,
): string {
const englishTitle = titleFromKey(field.key);
const rawLabel = (field.label || "").trim();
+ const isEnglish = isEnglishDictionary(dictionary);
+
+ // If rawLabel is provided from the backend (the dynamic source of truth)
+ if (rawLabel) {
+ // 1. Direct dictionary match for field.label (e.g. static UI overrides)
+ if (dictionary?.[rawLabel]) {
+ return dictionary[rawLabel];
+ }
+
+ // 2. If rawLabel is in Persian reverse map, lookup translation in dictionary
+ const enKeyFromFa = persianToEnglishMap[rawLabel];
+ if (enKeyFromFa) {
+ if (dictionary?.[enKeyFromFa]) {
+ return dictionary[enKeyFromFa];
+ }
+ if (isEnglish) {
+ return enKeyFromFa;
+ }
+ }
- // 1. Direct dictionary match for field.label
- if (rawLabel && dictionary?.[rawLabel]) {
- return dictionary[rawLabel];
+ // 3. If active dictionary is English and rawLabel contains non-Latin (Persian/Arabic) characters, fallback to englishTitle
+ const hasPersianOrArabic = /[-ۿ]/.test(rawLabel);
+ if (hasPersianOrArabic && isEnglish) {
+ return englishTitle || rawLabel;
+ }
+
+ // 4. In all non-English locales (Arabic, Persian, etc.), trust the backend's dynamic label directly!
+ return rawLabel;
}
- // 2. If rawLabel is in Persian, convert it to its canonical English key via reverse map
- const enKeyFromFa = rawLabel ? persianToEnglishMap[rawLabel] : null;
- if (enKeyFromFa) {
- if (dictionary?.[enKeyFromFa]) {
- return dictionary[enKeyFromFa];
+ // If rawLabel is missing, synthesize from canonical keys or key name
+ const canonicalCandidates =
+ FIELD_KEY_TO_CANONICAL_EN[field.key] ||
+ FIELD_KEY_TO_CANONICAL_EN[field.key.split(".").pop() || ""] ||
+ [];
+ for (const candidate of canonicalCandidates) {
+ if (dictionary?.[candidate]) {
+ return dictionary[candidate];
}
- return enKeyFromFa;
}
- // 3. Direct match for English title derived from key
if (dictionary?.[englishTitle]) {
return dictionary[englishTitle];
}
- // 4. Normalized match for key or label (e.g. ignoring punctuation/spaces like apostrophes and parentheses)
- const normKey = (field.key || "").split(".").pop()?.toLowerCase().replace(/[^a-z0-9]/g, "") || "";
+ const normKey =
+ (field.key || "")
+ .split(".")
+ .pop()
+ ?.toLowerCase()
+ .replace(/[^a-z0-9]/g, "") || "";
const matchedCanonicalKey = normalizedEnglishKeys[normKey];
if (matchedCanonicalKey) {
if (dictionary?.[matchedCanonicalKey]) {
return dictionary[matchedCanonicalKey];
}
- return matchedCanonicalKey;
- }
-
- // 5. If rawLabel contains Persian/Arabic characters and active dictionary is English, use englishTitle
- const hasPersian = /[-ۿ]/.test(rawLabel);
- if (hasPersian && dictionary) {
- return englishTitle || rawLabel;
+ if (isEnglish) {
+ return matchedCanonicalKey;
+ }
}
- // 6. Fallback
- return rawLabel || englishTitle;
+ return englishTitle || field.key;
}
export function formatOptionValue(