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