-
Notifications
You must be signed in to change notification settings - Fork 0
Bilingual and RTL
One page, two languages, one section order. Translatable fields hold a map keyed by locale inside the same block tree:
{
"id": "b_8f3a",
"type": "hero",
"attributes": {
"heading": { "en": "Welcome", "ar": "أهلا بك" },
"align": "center"
}
}Not two trees, not two pages. The structure is shared and only the text differs.
The accepted cost: Arabic cannot have a different section order from English. For a marketing site that is the right trade, and it stops the two languages drifting into different pages that nobody notices have diverged.
'locales' => [
'en' => ['label' => 'English', 'dir' => 'ltr'],
'ar' => ['label' => 'العربية', 'dir' => 'rtl'],
],The first locale is the default and lives at /{slug}. Every other locale lives at
/{locale}/{slug}. The label is what the editor's language switcher shows, and dir drives
dir="rtl" on the html element plus the Arabic font swap.
Reorder the array. That is the whole change:
'locales' => [
'ar' => ['label' => 'العربية', 'dir' => 'rtl'],
'en' => ['label' => 'English', 'dir' => 'ltr'],
],Now Arabic lives at /{slug} and English at /en/{slug}:
| Locale | URL |
|---|---|
ar (first) |
/man-nahnu |
en |
/en/about |
Everything that reads "the default locale" follows the order, so this one edit also means the editor opens on Arabic, a missing translation falls back to Arabic, page settings show the Arabic slug without a prefix, and the canonical URL of the Arabic page is the unprefixed one. There is nothing else to change and no data to migrate: block trees are keyed by locale code, and the codes did not move.
/about is now at /en/about, and /about 404s unless an Arabic page
claims that slug. The slugs themselves never changed, so the redirect that normally covers
a rename never fires. Reorder before launch, or be ready to add redirects by hand.
Nothing here is Arabic-specific. Put fr first and French becomes the default; add it
anywhere else and you get /fr/{slug}, a third tab in page settings and a third language in
the editor.
A block declares it:
public static function translatable(): array
{
return ['heading', 'items'];
}Everything not listed is shared across every locale. A background colour, an alignment, an image, a link target: one value, both languages.
A translatable repeater holds the whole list per locale, so Arabic can have a different number of FAQ items than English.
A missing value falls back to the default locale rather than rendering a hole. A half-translated page reads as untranslated rather than broken, which is the better failure for something a client is part-way through.
Each locale has its own slug, edited in its own tab. With the shipped config, where English is first:
| Locale | URL |
|---|---|
en (first, so no prefix) |
/about |
ar |
/ar/about-ar |
Slugs live in their own table with a unique index on (locale, slug), because a JSON map
cannot carry a unique index and two pages sharing a slug in one locale is a real bug.
Slugs are per locale, so the Arabic URL can be a transliteration, an Arabic-script slug, or
the same string as English. Nested slugs work: a page at services/web-design resolves, and
its Arabic counterpart can be khadamat/tasmim-web.
<html lang="ar" dir="rtl">-
hreflanglinks between every locale, so neither competes with itself - Per-locale meta title, description, canonical and share image
- The Arabic font stack, swapped in by a
[dir="rtl"]rule
hreflang="x-default" is not emitted yet.
Arabic is mirrored by dir="rtl" and CSS logical properties, not by different markup.
Use logical properties, never physical ones:
| Instead of | Write |
|---|---|
ml-4 |
ms-4 |
mr-4 |
me-4 |
pl-6 |
ps-6 |
pr-6 |
pe-6 |
text-left |
text-start |
text-right |
text-end |
border-l |
border-s |
rounded-l-lg |
rounded-s-lg |
left-0 |
start-0 |
Every shipped block follows this, and there is not a single physical direction utility in any of them.
Things that still need thought in RTL:
-
Arrows and chevrons pointing at "next" need mirroring.
rtl:-scale-x-100on the icon. - Logos and photographs must not be mirrored. Only directional glyphs flip.
- Numbers and code stay LTR inside RTL text, which browsers handle if you leave them alone.
Check both directions in the editor's language switcher before shipping. The preview renders RTL exactly as the public page will.
The toolbar has a language switcher. Switching changes which locale's values the settings form edits and which locale the preview renders, including direction. Nothing is lost switching back and forth, because both locales live in the same tree and only the view of it changes.
The page settings screen has a tab per locale for slug and SEO.
- Add it to
atelier.locales. - Every page now has an empty slug for it. Open each page and fill it in, or generate one from the title by saving with the field empty.
- Translate the content. Until then, the new locale renders the default locale's text.
- Check the sitemap: the new URLs appear as soon as they have slugs, and each locale can be marked noindex on its own while you translate.
Building
Running a site
Reference