# UI microcopy quality (fa + en message catalogs)

## Current state

All UI copy lives in two flat-namespace catalogs, client/messages/fa.json and client/messages/en.json (~1,582 lines each, 28 namespaces, full key parity — no missing namespaces or keys on either side). Coverage is unusually complete for a young product: every namespace ships loading/empty/error strings (e.g. booking.list_error, payouts.history_empty_body, tickets.thread_empty_body), confirm-dialog bodies, toasts, and field-level hints. The Persian is clearly hand-written, not machine-translated: it consistently uses the formal شما register with polite imperatives (کنید), uses Persian digits in literals (۲۴ ساعت, ۷ تا ۱۰ روز کاری), and gets culturally sensitive register right — خانم/آقا for caregiver gender in search/booking vs the clinical مرد/زن for patient gender in onboarding. The English catalog is idiomatic and often better than the Persian ("Home care you can trust", "the detail a nurse needs to find the door", "Queue clear — nothing to review").

The trust-critical copy — the product's core — is mostly strong: payment.escrow_notice explains escrow in plain language, payouts.explainer_point_1–3 explain the weekly batch / 72-hour dispute window / BNPL-fee-never-deducted invariant to nurses, refunds uses calming status language ("در راه" / "On its way", failed → "نیازمند بررسی" / "Needs attention"), and verification failure reasons (reason_shared_sim, reason_blurry_scan) tell the nurse exactly what to fix. The weaknesses are not tone but craft: inconsistent Persian orthography (hamza, ZWNJ, even the brand name itself is spelled two ways), two genuine grammar bugs that read as nonsense ("ورود بازی", "هشدار بازی"), one broken help-text sentence, a typo on a trust chip, missing ICU plural/zero handling on the fa side where en has it, one wrong-direction arrow baked into an en string, and policy numbers (24h/72h/7–10 days) hardcoded into copy that the admin config panel can change. Namespaces most needing a copy pass, in order: booking (largest at 183 keys, contains the grammar bug and arrow bug), verification (heaviest تایید/تأیید mixing), payment/auth/common (brand spelling split), bnpl (jargon), search (hardcoded city suggestions, missing plurals), and admin (second grammar bug).

## Problems (16)

- **[high]** `client/messages/fa.json` — The brand name itself is spelled two different ways: 'بالین یار' (space) in 5 keys vs 'بالین‌یار' (ZWNJ) in 17 keys. Users see one spelling on the login screen and another on the payment/escrow/refund screens — for a trust-first brand, an unstable brand mark in the money copy is the worst place to be inconsistent.
  - evidence: common.brand + auth.customer_title/select_role_title + verification.start_body use 'بالین یار'; payment.row_commission, payment.escrow_notice, refunds.admin_approval_explainer, payouts.balance_owed_label and 13 more use 'بالین‌یار'
- **[high]** `client/messages/fa.json` — Grammar bug that reads as nonsense: 'ورود بازی برای ثبت خروج وجود ندارد' — the indefinite ی attached to باز makes it read as 'there is no game-entrance'. Nurse-facing EVV error. Should be e.g. 'ورودِ ثبت‌شده‌ای برای خروج وجود ندارد؛ ابتدا ورود را ثبت کنید.' The same bug appears in admin.alert_empty: 'هشدار بازی وجود ندارد' ('no game alert') — better: 'هشداری برای رسیدگی نیست.'
  - evidence: line 542 booking.evv_no_open_check_in and line 1360 admin.alert_empty
- **[high]** `client/messages/fa.json` — Broken sentence in customer-facing address help text: 'جزئیاتی که پرستار برای یافتن در نیاز دارد' — a word-for-word translation of the en 'find the door' where 'در' (door) collides with the preposition 'در', so it reads as 'details the nurse needs for finding in'. Should be e.g. 'هر جزئیاتی که پرستار برای پیدا کردن منزل شما لازم دارد.'
  - evidence: line 228 address.line_hint
- **[high]** `client/messages/fa.json` — Typo on a trust-status chip: bank verification success chip reads 'تاییدشد' (missing final ه) instead of 'تأییدشده'. This is the chip a nurse stares at while waiting for IBAN ownership verification — a typo exactly where the product is asserting correctness.
  - evidence: line 180 bank.status_verified_chip: "تاییدشد"
- **[high]** `client/messages/fa.json` — The word تأیید (confirm/verify) — the single most frequent word in a verification product — is written without hamza ('تایید', 38 occurrences) and with hamza ('تأیید', 25 occurrences) with no pattern, often inside the same namespace (verification.status_passed 'تاییدشده' vs verification.identity_title 'تأیید هویت'; booking uses no-hamza, admin uses hamza). Same-status labels also diverge across namespaces (verification.status_failed 'رد شد' vs admin.step_failed 'ناموفق'). Pick one orthography (recommend the hamza form) and one status vocabulary.
  - evidence: 38 no-hamza vs 25 hamza occurrences (grep count); e.g. bank.status_verified_title vs admin.ver_pass
- **[medium]** `client/messages/en.json` — Wrong-direction arrow baked into an English string: booking.continue_payment is 'Continue to payment ←' (left arrow in LTR English) while the sibling key payment.cta_pay is 'Continue to payment →'. The fa file mirrors arrows manually per-string — directional glyphs inside translatable copy is exactly how this bug happens; arrows belong in the component as mirrored icons.
  - evidence: en.json line 445 booking.continue_payment: "Continue to payment ←" vs line 577 payment.cta_pay: "Continue to payment →"
- **[medium]** `client/messages/fa.json` — fa lacks ICU plural/zero handling where en has it, so Persian users get degenerate strings: search.cta_view_results is 'مشاهده {count} پرستار' → the primary search CTA can render 'مشاهده ۰ پرستار'; search.results_count, search.reviews_count and booking.session_count have the same gap. en handles =0 ('no nurses') — though en's own =0 case produces the odd button label 'View no nurses', so both sides need a proper zero-state ('پرستاری یافت نشد' / 'No nurses found').
  - evidence: fa line 350 cta_view_results, 352 results_count, 364 reviews_count, 515 session_count vs en ICU-plural equivalents
- **[medium]** `client/messages/fa.json` — BNPL trust copy is written from the platform's perspective with banking jargon: bnpl.ownership_note tells the customer 'ریسک نکول مشتری کاملاً با اوست' ('the customer's default risk is entirely the provider's') — 'نکول' is credit-desk vocabulary, and framing the reader as 'the customer' in third person is cold and confusing. Rewrite reader-first: 'قسط‌ها را مستقیماً به {provider} می‌پردازید؛ بالین‌یار مبلغ کامل را همان ابتدا دریافت می‌کند و پرستار شما تحت تأثیر قرار نمی‌گیرد.'
  - evidence: line 857 bnpl.ownership_note
- **[medium]** `client/messages/fa.json` — Policy numbers are hardcoded into trust-critical copy while the admin config panel (cfg_group_deadlines, cfg_group_cancellation) can change them: the 72-hour dispute window (payouts.explainer_point_2), the 24-hour cancellation tiers (refunds.lead_gt_24h/lead_lt_24h), and the 7–10 business-day refund ETA (refunds.eta_business_days). One config edit silently makes the UI copy lie — these should be interpolated ({hours}, {days}) from server-served config.
  - evidence: fa lines 958, 789–790, 850; same hardcodes in en.json
- **[medium]** `client/messages/fa.json` — Search empty-state suggests hardcoded cities regardless of where the user searched: 'شهر نزدیک دیگری مانند مشهد، اصفهان یا شیراز را امتحان کنید' — Mashhad, Isfahan and Shiraz are ~900km apart and are nonsense advice for a Tehran user (the launch market). Replace with a location-neutral suggestion or interpolate actual nearby covered cities.
  - evidence: line 361 search.empty_suggest_city (en line 361 identical pattern)
- **[medium]** `client/messages/fa.json` — 'احراز هویت' is overloaded: it names the entire 7-step verification pipeline (nav.verification, verification.title) AND one specific step inside it (step_identity_kyc 'احراز هویت (ثبت احوال)', admin.step_identity_kyc 'احراز هویت'). A nurse who completed the KYC step but sees 'احراز هویت' still incomplete in nav gets contradictory signals. Name the pipeline differently, e.g. 'تأیید صلاحیت'.
  - evidence: fa lines 13, 658 vs 676 and 1470
- **[medium]** `client/messages/fa.json` — Domain-term drift: 'مددجو' (care recipient) appears exactly once as a parenthetical — booking.patient_label 'بیمار (مددجو)' — while every other surface says 'بیمار'. Either adopt مددجو consistently (it is the softer, industry-standard term for home care) or drop the one-off. Similarly 'جستجو' (9 keys) vs 'جست‌وجو' (2 keys) are mixed.
  - evidence: line 398 booking.patient_label; جست‌وجو in coverage.empty_warning and booking.missing_nurse_body only
- **[low]** `client/messages/fa.json` — The EVV acronym is exposed to nurses untranslated and never explained: 'ثبت ورود (EVV)', 'ویزیت‌های امروز… با EVV ثبت کنید'. First occurrence should introduce it — 'ثبت حضور الکترونیکی (EVV)' — then short-form thereafter; a Latin acronym as the only name for a core nurse workflow is alienating in a fa-default product.
  - evidence: lines 530–533 booking.evv_* keys; admin.cfg_group_evv does gloss it as 'ثبت حضور (EVV)'
- **[low]** `client/messages/en.json` — en catalog mixes British and American conventions: 'licence' (auth.nurse_subtitle) against American 'center' (21 occurrences: 'Partner centers', nav.partner_home 'Center'); admin namespace also uses curly apostrophes ('don’t', 'Couldn’t') where the rest of the file uses straight ones. Pick American throughout.
  - evidence: en line 628 'Nursing Council licence' vs nav.partners 'Partner centers'; en line 1199 'don’t'
- **[low]** `client/messages/fa.json` — Register slips into bureaucratic officialese in a few money strings: refunds.confirm_restate ends 'کسر می‌گردد' (archaic می‌گردد) while the rest of the catalog uses 'می‌شود'; tickets.thread_empty_body is a comma splice ('هنوز پیامی نیست، هماهنگی را شروع کنید.'). Small, but the cancellation-confirm sentence is a high-anxiety moment where stiff prose reads as fine print.
  - evidence: line 814 refunds.confirm_restate; line 1141 tickets.thread_empty_body
- **[low]** `client/messages/fa.json` — The four app shells are named with four different metaphors: 'اپلیکیشن خانواده' (app), 'نمای پرستار' (view), 'کنسول مدیریت' (console), 'پرتال همکار' (portal). Harmless individually but signals no naming system; pick one pattern per audience.
  - evidence: lines 57–61 shell namespace

## Opportunities (7)

- **Check in a Persian style guide + terminology glossary and lint the catalogs against it** (impact: high, effort: small) — One page in the repo (e.g. client/messages/STYLE.md) fixing: brand = 'بالین‌یار' (ZWNJ), hamza form 'تأیید', 'جست‌وجو' or 'جستجو' (pick one), ZWNJ rules for می‌/ها, the domain glossary (پرستار، مددجو vs بیمار، ویزیت، رزرو، نوبت، شبا), and one status vocabulary shared by nurse-facing and admin-facing keys. Then a 30-line node script in CI that greps fa.json for the banned variants (the space-brand, no-hamza تایید, 'بازی' word-boundary traps). This converts today's 60+ scattered orthography findings into a one-time fix that can never regress.
- **Serve policy numbers into copy instead of hardcoding them** (impact: high, effort: medium) — The dispute-window hours, cancellation tiers/percentages, and refund ETA days already live in server config (admin cfg_ keys exist to edit them). Change the message keys to take parameters ('پس از بسته‌شدن پنجرهٔ {hours} ساعته اعتراض…') and feed them from the same endpoint that serves fees. This keeps the product's most legally-sensitive copy permanently truthful and unlocks per-tier cancellation copy on the C-side cancel screen.
- **Add fa plural/zero variants + Persian digit formatting policy** (impact: medium, effort: small) — Give every {count} key an ICU form with a designed =0 case (fa: 'پرستاری یافت نشد' instead of '۰ پرستار'; fix en's 'View no nurses' button while there). Decide once whether interpolated numbers render as Persian digits ({count, number} with fa locale) — currently literals use Persian digits but interpolations will render Latin, so a card can read '۷۲ ساعت' next to '3 visits'.
- **Trust-moments copy pass: write the reassurance where the money moves** (impact: high, effort: small) — The catalogs explain escrow and payouts well, but three anxiety peaks still have thin copy: (1) the OTP screen says nothing about why a family should trust the platform (auth.customer_subtitle is just 'sign in with your mobile'); (2) the checkout escrow notice is one sentence with no link to how disputes work; (3) the nurse 'accept request' screen never states the payout amount protection ('پرداخت خانواده نزد بالین‌یار امانت می‌ماند'). Add one warm trust line per moment — this is copy, not UI work, and it is the cheapest trust lever the product has.
- **Introduce EVV in Persian once, then abbreviate** (impact: medium, effort: small) — Add a first-run explainer key ('ثبت حضور الکترونیکی (EVV) — ورود و خروج شما موقعیت‌سنجی می‌شود تا ویزیت بدون اختلاف تأیید شود') shown on the nurse's first visit day, and keep the short chips thereafter. Turns an alienating acronym into a selling point (EVV is why the nurse gets paid without arguments).
- **Move directional arrows out of strings into mirrored icon components** (impact: low, effort: small) — Five keys embed ←/→ literally and one (en booking.continue_payment) already points the wrong way. Replace with an end-icon in the button component that auto-mirrors with dir; also fixes admin.cfg_history_change ('{old} ← {new}') which relies on translators hand-mirroring.
- **Disambiguate the verification pipeline name and rename status vocabulary once** (impact: medium, effort: medium) — Rename the pipeline (nav + page titles) to 'تأیید صلاحیت' while the KYC step keeps 'احراز هویت'; simultaneously unify the failed/rejected status words (رد شد vs ناموفق vs ردشده) into one nurse-facing and one admin-facing set. Do it as a single sweep because the terms cross-reference each other.

## Keep (do not regress)

- payment.escrow_notice — plain-language escrow explanation at checkout ('مبلغ به‌صورت امانی نزد بالین‌یار می‌ماند و پس از پایان ویزیت آزاد می‌شود'); exactly the right sentence at the right moment
- payouts.explainer_point_1–3 — the three-bullet 'how payouts work' copy, especially point 3 guaranteeing the nurse the BNPL provider fee is never deducted from her; this is model trust writing
- Calming money-status vocabulary: refund steps 'ثبت‌شده → در راه → انجام‌شده' and failed states softened to 'نیازمند بررسی'/'Needs attention' with 'you don't need to do anything else' reassurance (refunds.failed_body, payouts.failure_hint)
- Two-stage disclosure copy is precise on both sides: booking.notes_hint tells the family exactly what the nurse sees pre-acceptance, and booking.disclosure_note tells the nurse exactly what unlocks after accepting
- Culturally-tuned gender copy: خانم/آقا (polite) for caregiver preference vs مرد/زن (clinical) for patient gender, plus search.gender_hint explaining same-gender preference for bodily care without awkwardness
- Verification failure reasons are specific and actionable (reason_shared_sim tells the nurse the SIM isn't in her name and what to do; reason_blurry_scan asks for a sharper copy) — no generic 'verification failed' anywhere
- Consistent formal شما register across the entire fa catalog with warm touches where appropriate ('به‌روز هستید' empty notifications state, 'طولی نمی‌کشد' during BNPL settling)
- The en catalog is genuinely idiomatic hand-written English, not a translation dump — 'Queue clear — nothing to review', 'What stood out', 'Home care you can trust'
- Every namespace ships complete loading/empty/error/confirm/toast strings — the string coverage for states is already better than most mature products
- Admin confirm-dialog bodies state consequences honestly ('Money moves to nurses. This is protected by an idempotency key — a double-click can't pay a booking twice.') — keep this operational candor
