Internationalization and Localization: Orientation, Then Right-to-Left Text
Get oriented on the pieces of internationalization (i18n) and localization (l10n) — locales, translations, pluralization, timezones, and number/currency/date formatting — and go deeper specifically on right-to-left (RTL) text handling, which the locale-formatting-focused Intl Extension lesson doesn't cover.
Internationalization (i18n) is designing an application so it *can* be adapted to different languages and regions; localization (l10n) is actually doing that adaptation for a specific locale. PHP's intl extension — covered in full in the 'Intl Extension' lesson — is the primary tool for the formatting side of this: locale-aware number, currency, and date formatting, and pluralization rules via MessageFormatter. This lesson gives a quick map of that territory and then focuses on something the Intl lesson doesn't: what changes in your HTML and CSS when the locale you're localizing for reads right-to-left, like Arabic or Hebrew.
The i18n/l10n pieces, briefly
A locale identifier (like en_US, ar_EG, or fr_CA) bundles language, region, and often script into one tag that formatting functions key off of. Translation strings are typically stored per-locale (gettext .po/.mo files, or JSON/array-based catalogs) and looked up by key at render time. Pluralization is more than an if/else on count — many languages have more plural categories than English's singular/plural split, which is why MessageFormatter's ICU plural syntax exists rather than hand-rolled count-checking. Timezones need explicit handling (DateTimeZone, storing UTC and converting for display) since a date without a timezone is ambiguous. Number, currency, and date formatting all vary by locale in ways beyond translation — decimal separators, currency symbol placement, and date component order (MM/DD/YYYY vs DD/MM/YYYY vs YYYY年MM月DD日) all differ.
All of that — NumberFormatter, IntlDateFormatter, Locale, and ICU-based pluralization with MessageFormatter — is where the 'Intl Extension' lesson goes deep, with runnable examples for each. This lesson doesn't repeat that; it assumes you'll pair the two.
• Locale identifiers and Locale class: see Intl Extension. • NumberFormatter for locale-aware numbers/currency: see Intl Extension. • IntlDateFormatter for locale-aware date/time formatting: see Intl Extension. • MessageFormatter and ICU plural rules for pluralization: see Intl Extension.
Right-to-left (RTL) text: the piece formatting alone doesn't solve
Arabic, Hebrew, Persian, and Urdu are written right-to-left, and getting the *values* formatted correctly for those locales (which NumberFormatter/IntlDateFormatter handle) is only half the job — the page layout itself has to flip direction too, and that's an HTML/CSS concern, not something intl touches. The base-level fix is the dir attribute: <html dir="rtl" lang="ar"> tells the browser to lay out block and inline content right-to-left by default — text alignment, list bullet position, and the default reading order of inline elements all flip automatically from that one attribute, before any CSS is written.
For finer control, CSS logical properties replace physical directional properties so styles automatically adapt to the text direction instead of needing an RTL-specific override stylesheet: margin-inline-start/margin-inline-end instead of margin-left/margin-right, padding-inline-start/padding-inline-end instead of padding-left/padding-right, border-inline-start instead of border-left, and text-align: start/end instead of text-align: left/right. Written with logical properties, a single stylesheet correctly mirrors itself for both dir="ltr" and dir="rtl" documents with zero direction-specific CSS.
Bidi (bidirectional) text needs extra care when RTL and LTR content mix on the same line — a Latin product code or English brand name embedded in an Arabic sentence, for instance. The Unicode Bidirectional Algorithm handles most of this automatically based on each character's directionality, but ambiguous cases (numbers, punctuation at a direction boundary) can render in a confusing order; the <bdi> HTML element isolates a span of text so its directionality doesn't leak into surrounding content, and the CSS unicode-bidi property plus explicit Unicode control characters (like U+2066 LRI / U+2067 RLI isolates) give finer-grained control for cases where automatic bidi resolution picks the wrong order.
• <html dir="rtl" lang="ar"> flips base layout direction for the whole document. • CSS logical properties (margin-inline-start, text-align: start, etc.) avoid needing a separate RTL stylesheet. • <bdi> isolates embedded text (e.g. a Latin name inside an Arabic sentence) so its direction doesn't disrupt surrounding text. • PHP-side: intl's Locale/NumberFormatter/IntlDateFormatter already produce correctly-ordered output for RTL locales — the dir attribute and logical CSS are what make the surrounding page match.
Code Examples
Locale::getPrimaryLanguage() (from the intl extension) extracts the base language subtag, which is checked against the small set of RTL script languages so the outgoing HTML's dir attribute matches the active locale automatically. Expected result: dir=rtl lang=ar
$dir = isRtlLocale($activeLocale) ? 'rtl' : 'ltr';
<?php
declare(strict_types=1);
function isRtlLocale(string $locale): bool
{
// Base language subtag is what determines script direction here.
$lang = \Locale::getPrimaryLanguage($locale) ?? $locale;
return in_array($lang, ['ar', 'he', 'fa', 'ur'], true);
}
$activeLocale = 'ar_EG';
$dir = isRtlLocale($activeLocale) ? 'rtl' : 'ltr';
$lang = \Locale::getPrimaryLanguage($activeLocale);
// In the view template:
// <html dir="<?= $dir ?>" lang="<?= $lang ?>">
echo "dir={$dir} lang={$lang}\n";Why RTL support is a launch requirement, not a nice-to-have
Any product targeting Arabic-, Hebrew-, Persian-, or Urdu-speaking users treats RTL layout as a functional requirement rather than a stretch goal — a site that gets locale-aware number/date formatting right via intl but ships an LTR-only layout still reads as broken to those users, because paragraph direction, navigation order, and icon placement (back arrows, in particular) all need to mirror. Building with dir on <html> and logical CSS properties from the start avoids a costly retrofit later.
/* rtl.css */ .card { margin-left: 16px; text-align: left; }
/* shared.css, no separate RTL file needed */ .card { margin-inline-start: 16px; text-align: start; }
Physical properties (margin-left, text-align: left) require a whole parallel RTL stylesheet that mirrors every rule by hand and drifts out of sync over time. Logical properties (margin-inline-start, text-align: start) resolve relative to the element's direction, so one stylesheet serves both LTR and RTL documents correctly.
What is the primary purpose of CSS logical properties like margin-inline-start over physical properties like margin-left in an internationalized site? A. They perform better than physical properties B. They are required by the intl PHP extension C. They resolve relative to text direction, so one stylesheet works correctly for both LTR and RTL layouts without a separate mirrored stylesheet D. They only work inside <bdi> elements
C. They resolve relative to text direction, so one stylesheet works correctly for both LTR and RTL layouts without a separate mirrored stylesheet — Logical properties like margin-inline-start, padding-inline-end, and text-align: start resolve relative to the element's writing direction, so the same CSS automatically mirrors correctly for dir="rtl" documents instead of needing a hand-maintained RTL override stylesheet.
Which HTML attribute, set on <html>, is the base-level switch for right-to-left page layout? A. lang B. dir C. charset D. translate
B. dir — The dir attribute (e.g. dir="rtl") tells the browser to lay out block and inline content right-to-left by default; lang identifies the language but does not by itself change layout direction.
Extend the isRtlLocale() example to also return the correct HTML lang tag with region (e.g. "ar-EG" using a hyphen, per BCP 47, rather than intl's underscore-based "ar_EG"), and rewrite a small CSS snippet using margin-left/padding-right/text-align:left into logical-property equivalents. Hint: str_replace('_', '-', $locale) converts an intl-style locale identifier to the hyphenated form HTML's lang attribute expects.
Try it yourself — there's no single right answer here; compare your result against this lesson's examples.