RTL and Persian localization¶
rtl-and-persian-localization
Localize a storefront for Persian (fa_IR) and right-to-left layout: logical CSS, digits, Yeh and Kaf, ZWNJ, currency display, and Gregorian storage. Use when building or reviewing Persian, Farsi, or RTL commerce UI.
Version 0.3.2 · MIT · Source · Rule
Install this skill
Copy the skill and its rule into your project:
The skill goes to .agents/skills/ and the rule to .agents/rules/. See Command line for other directories.
Use this skill for a Persian storefront or any right-to-left commerce UI. Search matching uses the marketplace search skill. The normalizer in scripts/normalize-persian.mjs is for display and for search text. Letter and digit rules are in the Persian text notes.
Procedure¶
- Set the document language and direction. For Persian,
lang="fa"anddir="rtl". In WordPress, thefa_IRlocale is RTL andis_rtl()is the check to use in PHP. - Lay out with CSS logical properties. Do not mirror the whole page with a plugin that flips
leftandrightin existing files. - Store catalog text as Unicode with Persian Yeh and Kaf and with ZWNJ where the word needs it. Store dates as Gregorian timestamps. Store money as a decimal amount plus an ISO currency code.
- Format digits for display only. APIs, SKUs, payment amounts, and HTML attributes stay ASCII.
- Translate user-facing strings with the plugin text domain. Do not hard-code Persian in a PHP string that should go through gettext, and do not hard-code English in a template that Persian shoppers see.
Direction¶
- Prefer
margin-inline-start,padding-inline,inset-inline-start,border-inline-end, andtext-align: start. - Use flex and grid.
flex-direction: rowfollows the document direction. Userow-reverseonly when the order is intentionally opposite to the language. - Mirror directional icons such as back and forward. Do not mirror logos, payment marks, product photos, or numbers.
- Form focus order follows the visual order. Test with the keyboard, not only with a screenshot.
- WooCommerce emails that render the same template need the same direction. A storefront
dir="rtl"does not change an email that is a separate HTML document.
Persian text¶
- Locale code:
fa_IR. - Fold Arabic Yeh
ي(U+064A) to Persian Yehی(U+06CC) and Arabic Kafك(U+0643) to Persian Kafک(U+06A9) when you normalize input. Shoppers and old content use both. - Keep ZWNJ (U+200C) in stored and displayed words (
میروم). Search normalization may remove it so the two spellings match. Display normalization must not. - Persian digits
۰۱۲۳۴۵۶۷۸۹are a display format. Also accept Arabic-Indic digits٠١٢٣٤٥٦٧٨٩on input and normalize them. - Use a font that covers Arabic script. A Latin-only stack renders Persian as missing glyphs.
Run the script:
node scripts/normalize-persian.mjs --file title.txt
node scripts/normalize-persian.mjs --search "كالاي ۱۲"
--search prints the match form. The default prints the display form and keeps ZWNJ. --persian-digits converts ASCII digits to Persian digits for display.
Money, dates, addresses¶
- WooCommerce currency for the Iranian rial is the ISO code
IRR. Format the amount with the store's decimal settings (wc_get_price_decimals()), not with a hard-coded exponent. - Toman is a display and pricing decision: 1 toman equals 10 rials. If the shop charges in toman, say that in the currency label and store a consistent unit. Do not divide amounts by 10 only in the theme while checkout charges rials.
- WordPress and WooCommerce store order dates in Gregorian, with a site timezone and a GMT field. A Jalali calendar is a presentation layer. Do not write Jalali strings into order date columns.
- Iranian addresses need country
IR, province, city, street, and a 10-digit postal code. Do not require a state from a country list that has no Iranian provinces unless those provinces are actually configured. Add them through WooCommerce's country locale and state list rather than a one-off checkout hack. - Phone numbers: accept a local mobile number without a North American mask. Store a normalized form you can dial, and display the local form.
WordPress and WooCommerce¶
- Put every new string in gettext with the plugin text domain. Load private translations on
init. - Switch field order and labels with
woocommerce_get_country_localeandwoocommerce_localisation_address_formatsforIR, not by editing core templates first. bodygets thertlclass from WordPress when the locale is RTL. Theme CSS should key offdiror.rtland still prefer logical properties so the same stylesheet serves both directions.- Prices in templates go through
wc_price(). That helper uses the store currency settings. Do not concatenate a hard-codedریالor$in front of a raw number.
Gotchas¶
direction: rtlon a single inner box, while the document is LTR, traps punctuation and mixed English SKUs. Set direction on the document, and on emails, not on random cards.leftandrightin copied CSS survive an RTL locale and pin totals to the wrong side.- Replacing every digit in the HTML document breaks CSS, JSON, and payment signatures. Convert digits in text nodes you control.
- Sorting Persian with a binary code-point order is not alphabetical. Use a Persian collation when the list is a dictionary. Do not use it for SKUs.
- Translating a string that includes the price ("Pay 19.00") needs a placeholder, not concatenation:
sprintf( __( 'Pay %s', 'example' ), $formatted ).
Verification¶
fa_IRrenders the shopper-facing checkout RTL, with the total and the pay control aligned to the inline end.- A title that contains Arabic Yeh and Persian digits round-trips through display normalization without losing ZWNJ.
- Search normalization makes
كالاandکالاmatch. The stored title is unchanged. - An order date stored by WooCommerce remains Gregorian. Any Jalali label is display-only.
- A test payment uses the same major units the shopper saw. Toman and rial are not mixed inside one order.
Evaluation¶
Worked examples and the checks they must satisfy are in evals/tasks.json.
References¶
Persian text handling¶
Use this note when you change strings, search tokens, or formatted amounts. The functions in scripts/normalize-persian.mjs implement the folds below.
Letters¶
| Input | Code point | Fold to |
|---|---|---|
Arabic Yeh ي |
U+064A | Persian Yeh ی U+06CC |
Persian Yeh ی |
U+06CC | unchanged |
Arabic Kaf ك |
U+0643 | Persian Kaf ک U+06A9 |
Persian Kaf ک |
U+06A9 | unchanged |
Apply this fold on input and before search. Display the folded Persian letters.
ZWNJ (U+200C) is part of Persian orthography. Display and storage keep it. Search text removes it so میروم and میروم match. Do not replace ZWNJ with a normal space. That splits one word into two.
Arabic Teh Marbuta ة is not a Persian letter. Do not fold it to Heh unless you have a specific migration from Arabic copy. Leave it and show it.
Digits¶
| Glyphs | Code points | Use |
|---|---|---|
0-9 |
U+0030–U+0039 | APIs, SKUs, JSON, payment minor units, HTML attributes |
۰-۹ |
U+06F0–U+06F9 | Optional Persian display |
٠-٩ |
U+0660–U+0669 | Accept on input, then fold to ASCII or to Persian digits |
toAsciiDigits maps both Persian and Arabic-Indic digits. toPersianDigits maps ASCII to Persian. Do not run toPersianDigits on a whole HTML or JSON document.
Normalization entry points¶
normalizePersianDisplay(text)folds Yeh and Kaf, keeps ZWNJ, and leaves digits unless you pass{ digits: "persian" }or{ digits: "ascii" }.normalizePersianSearch(text)folds letters, removes ZWNJ, converts digits to ASCII, lowercases Latin letters, and collapses whitespace.
Latin lowercasing is for mixed catalog titles. Persian letters have no case.
Currency display¶
ISO 4217 code in WooCommerce: IRR for the Iranian rial.
If the merchant prices in toman, pick one stored unit and label it. A theme-only division by 10 will not match the amount sent to the payment provider. There is no separate ISO code to assume for toman in this skill. Do not invent a code in payment requests.
Format through wc_price() so the decimal separator and currency symbol come from WooCommerce settings.
Dates¶
Store date_created_gmt as WooCommerce provides it. Format for fa_IR with wp_date() for Gregorian output in the locale. A Jalali formatter, if the shop uses one, reads that timestamp and must not write back a Jalali string.
Addresses¶
Postal codes in Iran are 10 digits. Validate the length after converting digits to ASCII. Province names should come from the configured state list for IR so shipping zones and the address form agree.
Evaluation¶
Fixtures from evals/tasks.json. See evaluation fixtures for how the checks work.
document-direction¶
Render the Persian storefront in the fa_IR locale with right-to-left document direction and logical CSS.
Must include: fa_IR, dir="rtl"
Must not include: margin-left
Worked example
Set the document for Persian.
Use the fa_IR locale so is_rtl() is true. Space the total with margin-inline-start, not a physical left margin.
rial-display¶
Show an Iranian rial price through WooCommerce formatting without converting to toman only in the theme.
Must include: IRR, wc_price
Must not include: / 10