Skip to content

Arabic first

Direction and language, locale files and the t filter, dates and numerals, typography, and the RTL checklist.

Updated 2 Sept 20262 min read
On this page

This page is for anyone designing a theme that sells in an Arabic market. Dukkan storefronts are Arabic first and RTL by default; English is a second layer enabled on request. By the end you know how to build correct direction, translated strings, local numerals and dates, and sound Arabic typography.

Direction and language#

request.locale is ar or en, and request.direction is rtl or ltr. Bind both to the document root and never hardcode a direction in CSS:

layout/theme.liquidLiquid
<html dir="{{ request.direction }}" lang="{{ request.locale }}">

Design RTL first and check LTR second. Use logical properties exclusively (margin-inline-start, padding-inline, inset-inline-end, text-align: start) so the mirrored layout needs no second stylesheet.

Locale files and the t filter#

Every string a visitor sees goes through {{ 'key' | t }} with its value in locales/ar.json (and locales/en.json if you support English). Never write Arabic in templates.

locales-ar.jsonJSON
{
  "nav": {
    "home": "الرئيسية",
    "cart": "السلة"
  },
  "product": {
    "add_to_cart": "أضف إلى السلة",
    "sold_out": "نفد المخزون"
  },
  "footer": {
    "powered_by": "مدعوم من دكان"
  }
}
snippets/buy-box.liquidLiquid
<button type="button" data-dukkan-add-to-cart data-variant-id="{{ variant.id }}">
  {{ 'product.add_to_cart' | t }}
</button>
  • Files are flattened to dotted keys (product.add_to_cart), so nesting is free.
  • The current locale's table is selected at render; a missing key returns the key itself and never throws, so you spot it easily in preview.
  • Merchant text settings carrying localized: true arrive with their English value automatically when the reader visits in English and a translation exists in the settings document; see Settings schema.

Numerals#

  • money values display with Western digits by default, the currency symbol before the amount, and bidi wrapping so a price never flips inside an Arabic line.
  • Declare a checkbox setting with the id arabic_numerals to offer the merchant Eastern Arabic digits: when enabled, the money filters render ٠١٢٣٤٥٦٧٨٩ with Arabic separators, while money_amount stays Western for machine consumers. Stamp the root with the attribute so the browser-side currency converter matches:
layout/theme.liquidLiquid
<html dir="{{ request.direction }}" lang="{{ request.locale }}"{% if theme_settings.arabic_numerals %} data-dukkan-numerals="arabic"{% endif %}>
  • Other numbers (product.sold_count, pagination.total) arrive as raw numbers; display them as is or format them with standard Liquid filters.

Dates#

Platform dates (such as review.created_at) are YYYY-MM-DD. Turn them into readable text in the current locale:

Liquid
{{ review.created_at | date_localized: request.locale }}

In Arabic you get Levantine month names ("٢٧ آب ٢٠٢٦"); in English, plain English formatting. Any value not in the date shape passes through untouched.

Typography#

  • The recommended typeface is IBM Plex Sans Arabic at weights 400, 500 and 600; bundle it as woff2 files under assets/ or rely on the system's Arabic fonts.
  • Line height 1.7 to 1.9 for Arabic text; never use letter-spacing with Arabic, it breaks the joins.
  • Arabic headings read one step smaller than their Latin counterparts at the same weight; tune the scale on Arabic glyphs first.
  • Technical identifiers (tracking code, discount code, phone number) belong in an element with unicode-bidi: plaintext or dir="ltr" so they never flip.

RTL checklist#

  • dir and lang come from request, never hardcoded.
  • No left/right in CSS except for what must not mirror (such as images).
  • Directional icons (arrows, chevrons) mirror in RTL.
  • Prices never flip inside Arabic sentences (use money, never manual formatting).
  • Forms and fields align to the start, with numbers inside them dir="ltr".
  • Every string goes through t; no Arabic text in templates or CSS.
  • The page reads on a 360 px wide phone first.