Files
baya-monorepo/client/messages/STYLE.md
T
2026-07-19 21:31:59 +03:30

7.6 KiB
Raw Blame History

Persian (fa) copy style guide

One page, binding for messages/fa.json. scripts/check-copy.mjs (npm run lint:copy, part of npm run check) enforces the banned-variant rules below so these decisions cannot silently regress. This file does not repeat en.json conventions beyond what's noted in §7 — the English catalog is hand-written and reviewed for idiom, not linted.

1. Brand name

«بالین‌یار» — ZWNJ () between بالین and یار, always. Never a plain space («بالین یار»). The brand name appears in money/trust copy (login, escrow, refunds) as often as anywhere else — an unstable brand mark there is the worst place to be inconsistent.

2. تأیید — hamza, always

Write تأیید (with hamza) and its derived forms — تأییدشده, تأییدیه, تأیید کردن — every time, never تایید/تاییدشده/تاییدیه (hamza-less). This is the single most frequent word in a verification product; one spelling, no exceptions, in every namespace (booking, payment, auth, verification, admin, payouts, bnpl, refunds, legal — all of them).

3. جستجو — one form

Standard form: جستجو (no ZWNJ, one word). Not «جست‌وجو» / «جست و جو». Applies to the noun and any compound (در جستجو, نتایج جستجو).

4. ZWNJ (نیم‌فاصله) rules

Use ZWNJ () — never a plain space or no separator — in:

  • می‌ + verb stem: می‌شود، می‌کند، می‌پردازید، می‌ماند (never میشود/می شود).
  • Plural ها: مراقب‌ها-style compounds keep the ZWNJ before ها when the base ends in a consonant that would otherwise misread (شب‌ها not شبها); a plain plural on a word already ending in a vowel/silent-h takes the ZWNJ too (بچه‌ها).
  • Compound past-participle adjectives: تأییدشده، لغوشده، ردشده، منتشرشده، پرداخت‌شده — the doer/state compound is one ZWNJ-joined word, not two spaced words («تایید شده») and not fused with no separator.
  • Brand name itself (§1) is the other load-bearing ZWNJ case.

5. Punctuation & quotes

  • Persian text uses «...» guillemets for quoted terms/labels in prose (as this document does), and Persian «،» / «؛» for commas/semicolons inside translated sentences where the surrounding punctuation is itself Persian prose (most UI strings use plain Latin ,/; today for simplicity in short labels — don't retrofit existing short strings, but prefer «،»/«؛» in new multi-clause sentences).
  • English (en.json) uses straight apostrophes (don't, couldn't) throughout — never curly (, ). One admin-namespace holdout (curly don't/couldn't) is fixed by this phase; don't reintroduce curly quotes when editing English copy.

6. Domain glossary

  • بیمار — the care recipient, used consistently everywhere except one parenthetical. Do not adopt «مددجو» — it appeared exactly once (booking.patient_label) and has been dropped in favor of the 99%-majority «بیمار».
  • پرستار — the caregiver, always (never «مراقب» as a noun for the person — «مراقب» only survives as an adjective/role qualifier, e.g. booking.gender_label "جنسیت مراقب" meaning "the caregiver's gender").
  • رزرو — a confirmed, paid booking. درخواست رزرو — a pre-payment request. Never conflate the two; a booking_request is never called «رزرو» before it converts.
  • ویزیت — one scheduled visit/session within a booking.
  • شبا — IBAN, always («شماره شبا» for the field label, «شبا» alone elsewhere).

7. Shell naming system

One metaphor per audience class, not four:

  • End-user shells (family, nurse — the apps people book/work through day to day) → «اپلیکیشن»: «اپلیکیشن خانواده», «اپلیکیشن پرستار».
  • Back-office shells (staff consoles — admin, partner-center) → «کنسول»: «کنسول مدیریت», «کنسول همکار».
  • Never «نما» (view) or «پرتال» (portal) for a whole shell name — those read as one-off inconsistent metaphors. (booking.evv_nurse_view "نمای پرستار" is a different thing — a chip labeling whose perspective a shared booking-detail screen is rendered from, not a shell name; it correctly keeps «نما» in that narrower sense.)

8. Verification pipeline vs. the identity step

«تأیید صلاحیت» names the whole 7-step nurse trust pipeline (nav entry, the verification hub's title, its start/progress/approved states, the admin queue). «احراز هویت» stays the name of the one KYC step inside it (national-ID + civil-registry + liveness selfie) — both on the nurse side (verification.step_identity_kyc) and the admin side (admin.step_identity_kyc). A nurse who passed the KYC step but still sees a pipeline titled «احراز هویت» incomplete in the nav used to read as a contradiction; they no longer share a name.

9. Status vocabulary — one nurse-facing form, one admin-facing form

For "this step/item was rejected/failed" style states that appear on both a nurse-facing screen and an admin-facing screen for the same underlying concept (a verification step's outcome):

  • Nurse-facing: «رد شد» (verification.status_failed) — a short declarative sentence-style status, matching the register of its sibling status_passed ("تأییدشده")/status_in_review ("در حال بررسی").
  • Admin-facing: «ردشده» (admin.step_failed, admin.agg_rejected, admin.rstatus_rejected, admin.mstatus_rejected) — the compound-adjective state form, matching the admin namespace's own step_passed/agg_approved/center_state_verified ("تأییدشده") pattern.

This does not extend to unrelated money-failure vocabulary (payouts.pstatus_failed, refunds.rstatus_failed, admin.batch_status_failed all legitimately use «ناموفق» — a transfer/payment failing is a different concept from a document being rejected, and conflating them would blur a real distinction).

10. Digits policy

Persian digits (۰۱۲۳۴۵۶۷۸۹) everywhere on /fa — both hard-coded literals ("۲۴ ساعت") and interpolated numbers. For an interpolated {count}/{hours}/… inside an ICU message, use the ICU number sub-format ({count, number}) or a plain # inside a plural block — next-intl formats both through the active locale (fa → Persian digits) automatically. When a raw number is interpolated at a call site instead of through ICU (e.g. built into a larger string in code, not a message placeholder), route it through formatNumber (@/utils) — never template a raw JS number directly into Persian text.

11. Register

Formal شما throughout, with polite imperatives (کنید) for actions and instructions. Already consistent across the whole catalog — this codifies it so a future addition can't drift into informal تو/imperative stems (نکن, برو).

12. Policy numbers

Legally/financially sensitive numbers that the admin config panel can change (the dispute-window hours, cancellation lead-time hours, refund ETA days) are never hard-coded into a message string. The message key takes a parameter ({hours}, {minDays}/{maxDays}) and the call site interpolates from client/src/constants/policy.ts (single-sourced, REQ-065 tracks the eventual public config-read that replaces the constants file). A config edit must never again silently make the UI copy lie.