Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@
"themes/theme-clone/scripts/theme_audit.py"
]
},
{
"slug": "themes/rtl-localization",
"name": "RTL localization",
"description": "Localize a file-based Fluid theme for an RTL-language market (Arabic, Hebrew, Persian, Urdu). Always mirrors the layout via dir + CSS logical properties on the shared theme and fixes directional sections in place; asks the user whether the market also needs different content before creating any variant template. No section copies.",
"category": "themes",
"icon": "align-right",
"path": "themes/rtl-localization/SKILL.md",
"updated_at": "2026-07-31T00:00:00Z",
"references": [
"themes/rtl-localization/references/direction-map.md",
"themes/rtl-localization/references/special-sections.md",
"themes/rtl-localization/references/fluid.md"
]
},
{
"slug": "themes/theme-source-inventory",
"name": "Theme Source Inventory",
Expand Down
126 changes: 126 additions & 0 deletions themes/rtl-localization/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
name: RTL localization
description: Use when localizing a file-based Fluid theme for an RTL-language market — Arabic, Hebrew, Persian/Farsi, Urdu, Pashto, Sindhi, Uyghur, Sorani Kurdish, Yiddish. Symptoms: onboarding a company selling into Saudi Arabia / UAE / Israel / Iran, text or layout stays left-aligned in an RTL locale, nav/carousels point the wrong way. The goal is always to localize the LAYOUT to the market; whether content also differs is asked, not assumed. Not for vertical/CJK top-to-bottom text.
icon: align-right
category: themes
---

# RTL localization

Company: {{company.name}}

Localize a **file-based** Fluid theme so an RTL-language market reads correctly right-to-left.
The invariant goal is **the layout is localized to the market** — mirrored via `dir` + CSS
logical properties on the *shared* theme, not by rebuilding or copying anything. Whether the
market also gets *different content* is a question for the user, not a default.

**Core principles:**
- **Always localize the layout; ask about content.** Direction localization runs every time.
Different content only happens if the user asks for it in Phase 0.
- **Mirror on the shared theme.** `dir` + logical CSS in `layouts/theme.liquid` and shared
`sections/*` — this localizes every page at once.
- **Never copy sections to flip them.** Duplicating the PDP's sections into RTL copies is
thousands of lines that immediately drift. Mirroring is CSS/`dir`, not duplication.

RTL **horizontal** only (Arabic-family). Vertical/CJK top-to-bottom is out of scope.
Surface: **file-based themes only** (`product/default/index.liquid` + `sections/*`). If the
company's theme is API-managed instead, STOP and say so — the mechanism differs and guessing
leaves an orphan template.

## When to Use

- A company is opening into an RTL-language market and its file-based storefront reads LTR.
- Text stays left-aligned, nav/logo sit on the wrong side, or carousels point the wrong way.

**Do NOT use for:** vertical CJK text, a non-RTL language, API-managed themes, or pure copy
translation (that's the `translating-theme-languages` skill).

## The Workflow

Work one phase at a time. **Stop for a review checkpoint after each phase.**

```dot
digraph rtl {
"0 Clarify with user" [shape=box];
"1 Detect direction" [shape=box];
"Is target language RTL?" [shape=diamond];
"STOP — not RTL" [shape=box];
"2 Localize layout (shared dir + logical CSS)" [shape=box];
"3 Fix special sections (shared)" [shape=box];
"Different content wanted?" [shape=diamond];
"4 Variant template (reuse sections)" [shape=box];
"5 Push + verify" [shape=box];

"0 Clarify with user" -> "1 Detect direction";
"1 Detect direction" -> "Is target language RTL?";
"Is target language RTL?" -> "STOP — not RTL" [label="no"];
"Is target language RTL?" -> "2 Localize layout (shared dir + logical CSS)" [label="yes"];
"2 Localize layout (shared dir + logical CSS)" -> "3 Fix special sections (shared)";
"3 Fix special sections (shared)" -> "Different content wanted?";
"Different content wanted?" -> "5 Push + verify" [label="no — mirror only"];
"Different content wanted?" -> "4 Variant template (reuse sections)" [label="yes"];
"4 Variant template (reuse sections)" -> "5 Push + verify";
}
```

### Phase 0 — Clarify with the user (interactive flow)
Do not infer. Ask:
1. **Which market/language?** (Confirm; Phase 1 verifies it's RTL.)
2. **Same content mirrored, or different content for this market?** Make clear the layout gets
localized either way — this only decides whether content also changes. Default is
same-content-mirrored.
3. If **different content**: what specifically differs, and confirm they accept that a variant
template is a separate copy that won't auto-inherit future edits to the default.
4. Confirm the theme is **file-based** and locate the theme directory.

### Phase 1 — Detect direction
Resolve the market's language → direction via `references/direction-map.md`. Key on **language**,
not country. Not in the RTL set → **STOP**.

### Phase 2 — Localize the layout (always)
Edit the **shared** theme, not a copy. See `references/fluid.md`.
1. In `layouts/theme.liquid`, set `dir` on `<html>` from the active locale
(`dir="rtl"` when `localization.language` is RTL, else `ltr`). Drive it from the locale —
never hardcode.
2. Convert physical CSS to logical properties in the theme's shared CSS so the layout mirrors:

| Physical (LTR-only) | Logical (direction-aware) |
|---|---|
| `margin-left` / `margin-right` | `margin-inline-start` / `margin-inline-end` |
| `padding-left` / `padding-right` | `padding-inline-start` / `padding-inline-end` |
| `left:` / `right:` | `inset-inline-start` / `inset-inline-end` |
| `text-align: left` / `right` | `text-align: start` / `end` |
| `border-left` / `border-right` | `border-inline-start` / `border-inline-end` |
| `float: left` / `right` | `float: inline-start` / `inline-end` |

### Phase 3 — Fix special sections (in place, no copies)
Hand-fix the shared components logical CSS can't flip using `references/special-sections.md`
(carousels, directional icons, image↔text splits, breadcrumbs, hardcoded positions) with
`[dir=rtl]` overrides on the existing sections. Keep numbers, prices, and Latin embeds LTR (bidi).

### Phase 4 — Variant template (ONLY if the user chose different content)
Skip entirely for mirror-only. If different content was requested, create a variant template
folder (e.g. `product/<variant>/index.liquid`) that **reuses the shared sections** with a
different order/settings, adding at most a single market section. Do not fork the section files.
See `references/fluid.md`.

### Phase 5 — Push + verify
Push the theme, then load the storefront in the RTL locale and check: text right-aligned,
nav/logo mirrored, icons/carousels point the correct way, spacing mirrored, and numbers/prices
stay LTR (bidi). Screenshot before/after.

## Common Mistakes

- **Copying sections to flip them** — mirroring is `dir` + logical CSS on shared sections, not
duplication. Copies drift immediately.
- **Editing a copy instead of the shared theme for mirroring** — Phase 2 is shared-theme work.
- **Assuming different content** — always ask in Phase 0; default is same-content-mirrored.
- **Wrong surface** — this is file-based only; STOP on API-managed themes to avoid an orphan.
- **Hardcoding `dir="rtl"`** instead of driving it from the locale.
- **Forgetting bidi** — numbers/prices/URLs must stay LTR inside an RTL layout.

## References

- `references/direction-map.md` — language → direction lookup (the RTL set).
- `references/special-sections.md` — the exception-component checklist and fixes.
- `references/fluid.md` — file-based theme specifics: where `dir`/CSS go, `localization.language`, variant templates, and pushing.
35 changes: 35 additions & 0 deletions themes/rtl-localization/references/direction-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Language → Text Direction Map

Key text direction on the **language**, not the country. An Arabic storefront reads
right-to-left whether the market is Saudi Arabia, the UAE, or Egypt.

## Right-to-left (horizontal) — apply RTL treatment

| Language | ISO 639-1 / code | Primary markets |
|---|---|---|
| Arabic | `ar` | Saudi Arabia, UAE, Egypt, Qatar, Kuwait, Iraq, Jordan, Morocco, … |
| Hebrew | `he` (`iw`) | Israel |
| Persian / Farsi | `fa` | Iran |
| Urdu | `ur` | Pakistan, India |
| Pashto | `ps` | Afghanistan |
| Sindhi | `sd` | Pakistan |
| Uyghur | `ug` | — |
| Kurdish (Sorani) | `ckb` | Iraq, Iran |
| Yiddish | `yi` | — |

Any language **not** in this table → **left-to-right**. Default to LTR when unknown.

## Explicitly out of scope

- **Vertical / CJK top-to-bottom** (traditional Japanese `ja`, Chinese `zh`, Korean `ko`,
Mongolian). These read **left-to-right horizontally on the modern web** — they are NOT RTL.
Do not apply RTL treatment to them. True vertical typesetting (`writing-mode: vertical-rl`)
is a separate, much larger effort and is not covered by this skill.

## Detection notes

- Normalize locale variants before lookup: strip region suffix (`ar-SA` → `ar`), and map
legacy codes (`iw` → `he`, `jp` → `ja`).
- If a company supports multiple languages, direction is per **active locale**, not a single
company-wide flag — an RTL company may still serve an English (LTR) page.
- The direction test in code is simply: `RTL_LANGS.include?(locale_language_code)`.
63 changes: 63 additions & 0 deletions themes/rtl-localization/references/fluid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Fluid File-Based Theme Appendix

This skill operates on a **file-based** Fluid theme (the `product/default/index.liquid` +
`sections/*` layout), not the API-managed template API. If the company's theme is API-managed,
STOP — creating a file-based artifact against an API theme leaves an orphan.

## Theme layout

```
layouts/theme.liquid ← the <html> shell; where `dir` goes
sections/<name>/index.liquid ← shared, reusable sections (with {% schema %})
product/default/index.liquid ← a page template: composes sections via {% section %}
product/<variant>/index.liquid← a slug/variant template (only for different structure)
```

A page template composes shared sections and carries a `{% schema %}` with `sections` + `order`.
It holds no page copy and does not recreate section blocks. Every RTL-locale visitor renders the
same templates — so mirroring belongs in the shared layout/CSS, applied once.

## Phase 2 — where direction localization goes

- **`dir` attribute:** in `layouts/theme.liquid`, on the `<html>` tag, driven by the locale:
```liquid
<html lang="{{ localization.language.iso_code }}" dir="{% if localization.language.rtl %}rtl{% else %}ltr{% endif %}">
```
If the theme exposes no `rtl` flag, resolve it by testing `localization.language.iso` against
the RTL set in `references/direction-map.md` (a small `{% case %}`/`{% if contains %}` check).
`localization.language`, `localization.country`, and `localization.available_countries` are
available in every template.
- **Logical CSS:** convert physical properties to logical ones in the theme's shared CSS so the
whole layout mirrors under `dir="rtl"`. Do this in the shared stylesheets/section CSS — not in
per-market copies.

## Phase 3 — special sections

Fix directional components in the **existing shared** `sections/*` with `[dir=rtl]` overrides
(see `references/special-sections.md`). Never fork a section into an RTL twin.

## Phase 4 — variant template (only for different content)

Repo rule: *"Only create a slug/variant template when the source truly uses a different
structure."* Mirroring is never a reason to create one. When the user has asked for different
content:
- Create `product/<variant>/index.liquid` (or the relevant resource) that **reuses the shared
sections** with a different `order`/settings, plus at most one new market section.
- Give every section instance a unique `id`; keep blocks in the section presets, not the template.

## Phase 5 — push + verify

Push the theme with the project's normal mechanism (e.g. `fluid theme push`, or the Mist theme
tooling). Then load the storefront in the target locale/region and run the Phase 5 checks. Take
before/after screenshots.

## Country routing & SEO — what to know

- Pure mirroring needs **no** per-country routing: `dir` is locale-driven, so any RTL-locale
visitor gets the mirrored layout automatically once the shared theme is pushed.
- A *different-content* variant that must be served to a specific country relies on region
routing (a region rule keyed on country ISO). That is an API/config concern outside the theme
files — confirm the mechanism with the user before wiring it, to avoid an orphan mapping.
- **SEO limitation (don't overpromise):** there is no exposed way to emit per-country `hreflang`
alternates or path-based localized URLs; `hreflang` is per-language via a `?lang=<iso>` param.
Flag deeper per-country indexing as a backend follow-up.
37 changes: 37 additions & 0 deletions themes/rtl-localization/references/special-sections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Special Sections — the exceptions logical CSS can't auto-mirror

Logical properties (Phase 2) mirror the box model — margins, padding, alignment, insets.
They do **not** flip content that encodes direction in markup, JS, transforms, or fixed
geometry. Walk this checklist against the target, and for each one either fix it or flag it
for review.

## Checklist

| Component | What breaks in RTL | Fix |
|---|---|---|
| **Carousels / sliders** | Slides advance the wrong way; prev/next arrows point wrong; scroll/translate math assumes LTR. | Reverse slide order or the transform sign in RTL; swap prev/next; if using `scroll-snap`, verify it honors `dir`. |
| **Image ↔ text splits** | "Image left, text right" should become "image right, text left". Often driven by `flex-direction: row` + physical order. | Rely on logical order; if hardcoded, add an RTL rule to reverse `flex-direction` or the DOM order. |
| **Step / progress indicators** | Steps 1→2→3 must run right→left; connector lines point wrong. | Reverse visual order and connector direction under `[dir=rtl]`. |
| **Directional icons** | Arrows, chevrons, back/forward, "next", carets stay pointing LTR. | Mirror with `[dir=rtl] .icon { transform: scaleX(-1); }` for glyphs that encode direction. **Do NOT mirror** non-directional icons (logos, checkmarks, stars, brand marks). |
| **Breadcrumbs** | Separator direction (`Home › Shop`) points the wrong way; order stays LTR. | Reverse order and flip/replace the separator under RTL. |
| **Hardcoded position** | Elements placed with literal `left:`/`right:`, absolute badges, drawer/slide-in panels, tooltips. | Convert to `inset-inline-*`; verify off-canvas panels slide from the correct edge. |
| **Transforms / animations** | `translateX(...)`, slide-in keyframes move the wrong direction. | Negate the X translation under RTL, or express motion in logical terms. |
| **Background position / sprites** | `background-position: left` and sprite offsets don't follow `dir`. | Add RTL overrides where the asset is directional. |
| **Shadows / gradients** | Directional `box-shadow`/`linear-gradient(to right, …)` may look off. | Judgment call — mirror only if the direction is meaningful. |

## Bidi (mixed-direction content)

Even in an RTL layout, these stay **left-to-right** and must not reverse:
- Numbers, prices, phone numbers, dates
- Code, URLs, email addresses
- Latin-script brand names and embeds

Wrap or isolate them (`dir="ltr"`, `unicode-bidi: isolate`, or `<bdi>`) if they leak.

## How to work this phase

1. Grep the theme/component CSS and JS for the LTR tells: `left`, `right`, `translateX`,
`flex-direction: row`, `text-align: left`, `float`, `scaleX`, arrow/chevron icon names.
2. For each hit, decide: already handled by logical CSS? needs an `[dir=rtl]` override?
or is it a non-directional element to leave alone?
3. Fix the directional ones; list anything ambiguous for the review checkpoint.