Browse Source
refactor: move marriage field formatting logic to library and add configurable SSR timeouts with debug logging
master
refactor: move marriage field formatting logic to library and add configurable SSR timeouts with debug logging
master
3 changed files with 165 additions and 172 deletions
-
93src/app/new-match/new-match-client.tsx
-
93src/app/new-match/profile/page.tsx
-
151src/lib/marriage-field-formatter.ts
@ -0,0 +1,151 @@ |
|||||
|
import type { |
||||
|
MarriageField, |
||||
|
MarriageFieldValue, |
||||
|
MarriagePhoneFieldValue, |
||||
|
} from "@/hooks/marriage/types"; |
||||
|
import { dictionaries } from "@/translations/dictionaries"; |
||||
|
|
||||
|
const persianToEnglishMap: Record<string, string> = {}; |
||||
|
for (const [enKey, faVal] of Object.entries(dictionaries.fa)) { |
||||
|
if (typeof faVal === "string") { |
||||
|
persianToEnglishMap[faVal.trim()] = enKey; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const normalizedEnglishKeys: Record<string, string> = {}; |
||||
|
for (const enKey of Object.keys(dictionaries.en)) { |
||||
|
const norm = enKey.toLowerCase().replace(/[^a-z0-9]/g, ""); |
||||
|
if (norm) { |
||||
|
normalizedEnglishKeys[norm] = enKey; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function isMarriagePhoneFieldValue( |
||||
|
value: unknown, |
||||
|
): value is MarriagePhoneFieldValue { |
||||
|
if (!value || typeof value !== "object") { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const phoneValue = value as Partial<MarriagePhoneFieldValue>; |
||||
|
|
||||
|
return ( |
||||
|
typeof phoneValue.countryCode === "string" && |
||||
|
typeof phoneValue.phoneNumber === "string" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function formatFieldValue(value: MarriageFieldValue): string | null { |
||||
|
if (value === null || value === "") { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (isMarriagePhoneFieldValue(value)) { |
||||
|
return `+${value.countryCode}${value.phoneNumber}`; |
||||
|
} |
||||
|
|
||||
|
if (typeof value === "boolean") { |
||||
|
return value ? "Yes" : "No"; |
||||
|
} |
||||
|
|
||||
|
return String(value); |
||||
|
} |
||||
|
|
||||
|
export function titleFromKey(key: string): string { |
||||
|
const leafKey = key.includes(".") ? key.split(".").pop()! : key; |
||||
|
return leafKey |
||||
|
.replace(/^q\d+[_-]?/i, "") |
||||
|
.replace(/[_-]+/g, " ") |
||||
|
.replace(/\s+/g, " ") |
||||
|
.trim() |
||||
|
.replace(/\b\w/g, (letter) => letter.toUpperCase()); |
||||
|
} |
||||
|
|
||||
|
export function formatFieldLabel( |
||||
|
field: MarriageField, |
||||
|
dictionary?: Record<string, string>, |
||||
|
): string { |
||||
|
const englishTitle = titleFromKey(field.key); |
||||
|
const rawLabel = (field.label || "").trim(); |
||||
|
|
||||
|
// 1. Direct dictionary match for field.label
|
||||
|
if (rawLabel && dictionary?.[rawLabel]) { |
||||
|
return dictionary[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]; |
||||
|
} |
||||
|
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 matchedCanonicalKey = normalizedEnglishKeys[normKey]; |
||||
|
if (matchedCanonicalKey) { |
||||
|
if (dictionary?.[matchedCanonicalKey]) { |
||||
|
return dictionary[matchedCanonicalKey]; |
||||
|
} |
||||
|
return matchedCanonicalKey; |
||||
|
} |
||||
|
|
||||
|
// 5. Fallback
|
||||
|
return rawLabel || englishTitle; |
||||
|
} |
||||
|
|
||||
|
export function formatOptionValue( |
||||
|
value: MarriageFieldValue, |
||||
|
dictionary?: Record<string, string>, |
||||
|
): string | null { |
||||
|
if (Array.isArray(value)) { |
||||
|
const formattedItems = value |
||||
|
.map((item) => formatOptionValue(item as MarriageFieldValue, dictionary)) |
||||
|
.filter(Boolean); |
||||
|
return formattedItems.length ? formattedItems.join(", ") : null; |
||||
|
} |
||||
|
|
||||
|
const base = formatFieldValue(value); |
||||
|
if (!base) return null; |
||||
|
|
||||
|
const trimmed = base.trim(); |
||||
|
|
||||
|
// 1. Direct dictionary match
|
||||
|
if (dictionary?.[trimmed]) return dictionary[trimmed]; |
||||
|
|
||||
|
// 2. Reverse map if base is in Persian
|
||||
|
const enKeyFromFa = persianToEnglishMap[trimmed]; |
||||
|
if (enKeyFromFa) { |
||||
|
if (dictionary?.[enKeyFromFa]) { |
||||
|
return dictionary[enKeyFromFa]; |
||||
|
} |
||||
|
return enKeyFromFa; |
||||
|
} |
||||
|
|
||||
|
// 3. Try replacing underscores with spaces: "single;_never_married" -> "single; never married"
|
||||
|
const withSpaces = trimmed.replace(/_/g, " ").trim(); |
||||
|
if (dictionary?.[withSpaces]) return dictionary[withSpaces]; |
||||
|
|
||||
|
// 4. Try capitalized first letter
|
||||
|
const capitalized = withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1); |
||||
|
if (dictionary?.[capitalized]) return dictionary[capitalized]; |
||||
|
|
||||
|
// 5. Normalized match for option value
|
||||
|
const normVal = withSpaces.toLowerCase().replace(/[^a-z0-9]/g, ""); |
||||
|
const matchedCanonicalKey = normalizedEnglishKeys[normVal]; |
||||
|
if (matchedCanonicalKey) { |
||||
|
if (dictionary?.[matchedCanonicalKey]) { |
||||
|
return dictionary[matchedCanonicalKey]; |
||||
|
} |
||||
|
return matchedCanonicalKey; |
||||
|
} |
||||
|
|
||||
|
return withSpaces; |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue