Why Arabic websites break after translation

Learn why correct Arabic copy still breaks in browsers through direction, bidi ordering, physical CSS, font fallback, forms and dynamic layout.

Guide13 min read

Correct Arabic copy can enter the browser and leave as a broken interface. The browser still has to choose a base direction, resolve Arabic and Latin character runs, map start and end positions, select fonts, size boxes, order flex and grid items, and place dynamic content. Translation controls almost none of those steps.

That is why the failures look unrelated. A model number reverses around a hyphen. A chat bubble hugs the wrong side of the thread. A button clips only after the Arabic font loads. A visually mirrored toolbar receives focus in a different order. They share one cause: the interface was built around LTR assumptions that translation did not and could not remove.

The bugs stop looking random when the rendering pipeline is made explicit. Each section below isolates one browser or application mechanism, shows the failure it creates, and identifies the layer that should own the fix.

Translation changes content, not the document model#

Replacing English strings with Arabic does not automatically change the document's language or direction. An Arabic page should normally establish both at the root:

html
<html lang="ar" dir="rtl">

lang identifies the content language. dir establishes base direction. The HTML dir documentation treats them as independent. In the measured Chromium 151 behavior used throughout this page, lang="ar" with no dir still computes to LTR.

Incomplete setup

html
<html lang="ar">
css
html {
  direction: rtl;
}

The CSS can make content render RTL, but it does not create the same semantic direction contract. In Chromium 151, an element whose only direction comes from CSS does not match :dir(rtl). Components that use that selector can disagree with a page that merely looks right-to-left.

Better

html
<html lang="ar" dir="rtl">

Inspect the live root after direct entry, hydration and client-side locale switching. A translation bundle can update visible copy while the root keeps lang="en" or dir="ltr" from the previous route. R001 reports a missing or LTR root direction on an Arabic page. R002 reports a missing or contradictory root language.

Do not compensate by forcing LTR or RTL on broad component wrappers. Direction should inherit from the document, with local changes only for content whose direction genuinely differs. R020 reports a forced-LTR container whose content is mostly Arabic.

Mixed text is reordered by character properties#

Arabic pages contain Latin product names, phone numbers, emails, URLs and IDs. The browser lays the combined line out with the Unicode Bidirectional Algorithm. It does not preserve every substring as one visual unit merely because the application thinks of it as a value.

Arabic and Latin letters are strong directional characters. Digits and punctuation follow different rules. That difference makes two model numbers from the same catalog behave differently after an Arabic label:

  • SKU-4200 remains intact in the measured context because the Latin prefix establishes a strong LTR run.
  • 4200-XL displays as XL-4200 because it starts with digits: the numeric run and the Latin run are placed as separate pieces, right to left.

Problem

html
<p dir="rtl">الموديل 4200-XL</p>

An Arabic reader expects the label to follow RTL reading order while the model number preserves 4200-XL, the form printed on the box and typed into search.

Better when the value direction is unknown

html
<p dir="rtl">الموديل <bdi>4200-XL</bdi></p>

Better when the value is defined as LTR

html
<p dir="rtl">الموديل <span dir="ltr">4200-XL</span></p>

<bdi> isolates the inserted value and derives its base direction from its content. The W3C inline bidi guidance recommends tightly wrapping opposite-direction phrases.

A plain span does not help. It groups DOM nodes but creates no bidi isolation. The correct boundary must carry dir or use an isolating element.

The common wrong fixes alter the data: reverse the string, insert spaces or move punctuation in translation. Those fixes can corrupt copying, searching and another display context. R021 reports unisolated phones, emails and Latin tokens inside Arabic text.

Separators and signs are not directionally equivalent#

Teams often test one value containing digits and conclude that numeric content is safe. The result depends on the surrounding letters and the characters between or beside the digits.

A product page is a good place to see it, because a single listing carries half a dozen kinds of value. Each was measured in Chromium 151 after an Arabic label:

Authored valueMeasured displayWhy it is a useful fixture
2.4.1 (firmware version)2.4.1Full stops keep the digits together
1/2 (quantity)1/2The slash keeps the fraction together
12-14 (size range)14-12The hyphen splits the numbers, which an Arabic reader still reads as 12 to 14
EID-25 (coupon)EID-25A Latin prefix holds the whole code in one LTR run
20% (discount)%20The percent sign moves to the other end after Arabic text
+3 (stock change)3+The plus sign moves too
Apple TV+ (bundle name)+Apple TVTrailing Latin punctuation joins the Arabic side

These outcomes do not show that the browser recognizes dates, versions, prices or codes. The Unicode bidi rules resolve character types in context. Some of the displays are correct for an Arabic reader, and some corrupt the value; the table cannot say which, because only the product knows what each value means. That judgment belongs in the fixture's expected result.

Wrap each value the product defines as LTR in the boundary it requires:

html
<p dir="rtl">
  اشتراك <bdi>Apple TV+</bdi> السنوي
</p>

Script context also matters. The measured rule that changes European numbers after Arabic-type letters applies to Arabic, Persian and Urdu, but not to Hebrew in the same way. A result from one RTL script is not a universal RTL rule.

Create fixtures from the values the product actually owns: model numbers, sizes, coupons, percentages, signed amounts and ranges. Place each one after Arabic text, at the beginning of the line and between two Arabic phrases. The correct test expectation comes from the business value and an Arabic reader's expected sentence, not from whether the output matches English geometry.

One unisolated variable can reorder its neighbors#

Bidi failures are not limited to the variable itself. A strong LTR string can pull nearby digits into its run. An order summary that inserts a product name before a quantity shows it:

html
<p dir="rtl">اشتريت Galaxy S24 2 قطع</p>

In Chromium 151, the quantity 2 joins the product name's LTR run and displays between the Arabic verb and Galaxy S24, so the line appears to name a product called "Galaxy S24 2". The name establishes an LTR run and the number resolves with it.

Better

html
<p dir="rtl">اشتريت <bdi>Galaxy S24</bdi> 2 قطع</p>

This is why wrapping the entire translated sentence in dir="auto" is the wrong abstraction. The sentence has several directional sources. One first-strong decision cannot model all of them. Wrap dynamic names, titles and values individually.

Punctuation can leak in the same way. A status message passed through untranslated shows its final mark at the left end in the measured browser: Upload complete! inside an RTL paragraph displays as !Upload complete. Keep punctuation that belongs to the foreign phrase inside its own LTR boundary and add a matching lang attribute for language processing.

html
<p dir="rtl">
  الحالة: <span lang="en" dir="ltr">Upload complete!</span>
</p>

R024 reports punctuation at the wrong visual end of Arabic text, while R080 reports intentional foreign-language runs with no language markup.

Copy and accessibility checks can miss a visual bidi defect#

Bidi layout reorders glyphs for display without rewriting the underlying character sequence. In Chromium 151, the model number that displays as XL-4200 still copies as 4200-XL, and the accessibility tree exposes the authored order.

That creates a subtle false pass. A QA script copies the model number, verifies the clipboard and declares the value correct. A screen-reader test also reads the source order. A sighted customer still sees a model number that does not match the box.

Use separate assertions:

AssertionWhat it proves
API or DOM valueData was not mutated
Visual review in Arabic contextDisplay order is readable
Copy and selectionInteraction preserves source data
Accessibility reviewProgrammatic content and sequence make sense

No one assertion replaces the others. A screenshot cannot prove copied data, and copied data cannot prove visual order.

When reporting the bug, record both the authored and observed values. Then reduce the case to a plain RTL paragraph. If it still fails, the problem is the text boundary, not flexbox, grid or the API.

Layout engines follow direction, physical CSS does not#

When an element has dir="rtl", direction-aware layout changes automatically. In Chromium 151, the first item of a flex row, grid or table row begins at the right edge. margin-inline-start maps to the right margin.

Physical properties keep their physical meaning. left, margin-left, translateX() and SVG paths do not mirror. A component that mixes the two models becomes internally inconsistent.

A support chat shows the mechanism well, because the customer's own messages sit on the far side of the thread from the agent's:

Problem

css
.bubble--mine {
  margin-left: auto;
  border-bottom-right-radius: 0;
}

In the Arabic thread the customer's bubbles still hug the right edge, which is where the agent's replies now start, and the flattened corner points the tail the wrong way.

Better

css
.bubble--mine {
  margin-inline-start: auto;
  border-end-end-radius: 0;
}

CSS logical properties express relationships relative to writing direction. They are appropriate when the requirement is start or end.

Physical CSS is still correct when the requirement is physical. A map's east marker, a media timeline or a chart axis should not move merely because the language changes. The fix is to model intent, not to replace every left with start mechanically.

Audit margins, padding, offsets, borders, corner radii, floats, shadows, text indentation and transforms. R022, R030, R031, R032 and R033 report common direction-sensitive assumptions.

Reversing rows fixes the screenshot and breaks the model#

flex-direction: row-reverse is often added to make an English component look RTL. The declaration reverses the flex axis. It does not set document direction, text behavior or language.

In an RTL container, the default row already begins at the right. Adding row-reverse reverses it again. In Chromium 151, the items then lay out left to right in DOM order, and Tab moves left to right.

Visual reordering also risks disconnecting presentation from source, speech and focus order. MDN warns that row-reverse can create a visual and DOM-order mismatch.

Pagination is where this usually shows. Start with semantic direction and logical DOM order:

html
<nav dir="rtl" class="pager" aria-label="الصفحات">
  <a href="?page=1">السابق</a>
  <a href="?page=1">1</a>
  <a href="?page=2" aria-current="page">2</a>
  <a href="?page=3">3</a>
  <a href="?page=3">التالي</a>
</nav>
css
.pager {
  display: flex;
  gap: 0.5rem;
}

Page 1 and "السابق" (previous) sit at the right, where an Arabic reader starts, with no reversal needed. Then use Tab through the component. Focus order does not need to trace every pixel mechanically, but it must preserve the task and agree with relationships the screen communicates. R081 reports row-reverse used to imitate RTL.

Do not repair the mismatch with positive tabindex. That creates a second order that becomes difficult to maintain when controls are responsive or conditional.

Tables and grids can be reversed twice#

The same misunderstanding affects data layouts. The page becomes RTL, then code manually reverses columns because the first column used to sit on the left.

Under RTL, table and grid flow can already place the first item at the right edge. Reversing the data array or applying CSS order again may restore LTR appearance, separate headers from cells or make focus jump.

Keep DOM order aligned by meaning. Set direction on the table or containing layout. Give individual code, email or numeric values their own direction boundary rather than forcing the full table LTR. Test header associations, sorting, row actions, horizontal scrolling and keyboard focus. A table is not correct simply because its first column appears on the right; common RTL bugs works through a full example.

Geometry has a new edge and a new origin#

Two browser mechanisms change coordinates rather than content, and both break code that was only ever run left to right.

The first is overflow. In Chromium 151, content that extends past the left edge of an RTL page adds horizontal scrollable width, while content past the right edge does not; an LTR page behaves the other way round. A closed drawer or a physically positioned badge that was harmlessly off-screen in English can give the Arabic page a sideways scroll, and a test habit of inspecting the right edge never finds it. Do not hide the symptom with overflow-x: hidden on the root; how to fix horizontal overflow in RTL walks through finding the box that escapes. R040, R041, R042 and R043 report overflow, overlap, clipping and fixed-width containers exceeded by Arabic content.

The second is scroll position. In an RTL scroll container, scrollLeft starts at zero and becomes negative toward the end. A product carousel whose "next" button adds a positive offset does nothing at the start in the measured browser, and moves backward from anywhere else. Model the controls as previous and next, convert to scroll coordinates in one helper, and assert which item becomes visible rather than a raw number.

Font fallback changes geometry after translation#

A declared font can support English and omit Arabic. The browser then falls through to another family only for Arabic glyphs. Translation is correct, but width, baseline, line height and wrapping no longer match the component's assumptions.

Inspect the rendered font after web fonts load. R050 reports measured fallback or a declared stack that names no Arabic-capable family. Test the intended font, a slow-load state and a realistic failure state.

Arabic is a connected script. Positive letter spacing inherited from an uppercase Latin label can separate letters and damage the word shape. Uppercase has no equivalent Arabic transformation, and synthetic italics may not fit the approved Arabic type system. R051, R054 and R055 report those inherited styles.

The wrong repair is shortening the translation or reducing the Arabic font until it fits. Use an Arabic-capable stack, remove script-inappropriate display styles and allow content-driven dimensions. Test long headings, buttons and multiline errors at narrow widths and zoom. R052 and R053 cover cramped line height and undersized body text.

Input direction can change while the user types#

Forms expose the difference between page direction and data direction. Arabic names, addresses and prose normally use RTL. Phones, emails, URLs and many codes remain LTR.

dir="auto" looks like a universal solution but has a visible empty-state edge case. In Chromium 151, an empty <input dir="auto"> resolves LTR, so an Arabic placeholder appears at the left edge on an RTL page. Typing an Arabic letter changes the field to RTL and the alignment jumps. Latin letters or digits keep it LTR.

Use explicit RTL when the field expects Arabic content, explicit LTR for known LTR data, and auto for genuinely unknown-direction user content. A delivery address form needs both in one fieldset:

html
<fieldset lang="ar" dir="rtl">
  <label for="street">العنوان</label>
  <input id="street" name="street" dir="rtl" autocomplete="street-address">

  <label for="postcode">الرمز البريدي</label>
  <input id="postcode" name="postcode" inputmode="numeric" dir="ltr" autocomplete="postal-code">
</fieldset>

In Chromium 151, type="tel" is LTR by default. Email, URL, number, search and text controls inherit page direction unless they set one, which is why the postcode above states its direction instead of relying on its type. R023 reports Arabic text inputs rendered LTR, while R070 reports phone, email and URL inputs rendered RTL.

Test empty, placeholder, focused and populated states. Type, paste, select, move the caret and delete around punctuation. A still screenshot cannot prove editing behavior.

Locale formatting happens after translation too#

Translated labels do not configure digit systems, calendars, date patterns or currency. Runtime formatters and locale data decide those outputs.

In Chromium 151, measured defaults for ar-SA and ar-EG used Arabic-Indic digits, while ar and ar-AE used Western digits; all four used the Gregorian calendar. Those defaults can change with locale data. If the result is a business requirement, set it explicitly. An Egyptian store that has decided on Western digits, against its locale's default, would say so in code:

js
const total = new Intl.NumberFormat("ar-EG", {
  style: "currency",
  currency: "EGP",
  numberingSystem: "latn",
});

The options record one product's decision, not a universal Egyptian convention.

Test dates above day 12, decimals, thousands, negative amounts, zero and percentages. Check UI, email, export and receipt because different services may call different formatters. Keep formatter output intact when inserting it into Arabic messages; splitting symbols and units into translated fragments can reintroduce bidi ordering problems.

R060, R061, R062, R063 and R064 report common date, digit, currency, percent or unit, and punctuation inconsistencies.

Dynamic rendering combines failures that isolated reviews miss#

Translation tools show strings outside the complete runtime. Component previews often use short copy, successful API states and a font that is already loaded. Production combines them differently.

A payment failure can arrive after layout settles, add a second line, trigger font fallback and include an unisolated model number. Each subsystem may pass alone. The final state overflows because the component has a fixed height and a physically positioned retry action.

Test state transitions rather than static screens:

  • loading to loaded;
  • empty to populated;
  • valid to invalid;
  • request to server failure;
  • signed out to signed in;
  • font fallback to loaded font;
  • desktop width to responsive wrap point;
  • Arabic to another locale without full reload.

Record computed direction, rendered font and bounding boxes before and after the transition. The change that introduces the failure usually identifies the owner.

Do not ask the translator to shorten accurate copy until the dynamic state fits. Fix fixed dimensions, missing localization paths, bidi boundaries or font coverage in the layer that created the constraint.

Debug from the symptom to the owning mechanism#

Use reduction instead of global RTL overrides:

SymptomFirst reduction
Whole route behaves LTRInspect live root lang, dir and computed direction
One value reordersPut it in a plain RTL paragraph and add isolation
Quantity merges into a product nameIsolate each inserted string individually
Chat bubble or badge on the wrong sideLook for left, right and physical margins in the computed styles
Tab order disagrees with the layoutSearch the component for row-reverse and order
Arabic page scrolls sideways, English does notSomething crosses the left edge; measure its box
Next button does nothing on the first slidePrint scrollLeft before and after the click
Empty field jumps sides on the first keystrokeCheck for dir="auto" on an input that expects Arabic
Label clips only after the web font arrivesCompare widths under the fallback and the loaded font
Price looks wrongTest formatter output alone, then inside the Arabic sentence

Fix shared primitives first. A single spacing token, scrolling helper, field wrapper or formatter can create the same failure on many routes. Add the regression at that layer with real Arabic content, both directions and the state that exposed it.

Each mechanism on this page maps to Ritla checks cited beside it, and a free scan of an Arabic route reports which of them fire on the rendered page. Use a finding to reach the smallest reproducible mechanism, not to justify another global patch.

For the fixes, as opposed to the causes, common RTL bugs works through each failure with a repair and a test. If the mechanism is still unclear, what translation QA misses covers the handoff where most of these start.

Checks in this guide

Show every check in this guideShow fewer

See what your Arabic pages are hiding

Paste a URL. Ritla renders the page on desktop and mobile, runs every check, and shows the top issues with screenshot evidence.