Your Arabic site looks fine because you're checking the wrong thing
Bilingual failures cluster in three places, and only one of them is visible to a reviewer who reads a single language. Here's how to find the other two.
Last updated
A review in one language finds one of the three failures
Open the Arabic site, scroll it, decide whether it looks right. That review has one instrument, a pair of eyes reading one language, and it reports what those eyes register: the navigation on the right, the text running the correct way, the headings in Arabic. A site can fail in ways it never touches and still come back clean.
Bilingual sites fail in three places, and they sort by who can see them.
Direction is the visible one. All of it shows up in a screenshot, which is the one thing an eyeball review is equipped to find. The seam is the second: the points where Latin content sits inside Arabic content, or the reverse. A phone number, a product code, a brand name, an English error message on an Arabic form. Those only read as wrong to someone fluent in both languages who is paying attention to the order of characters. The third class is declaration, the machine-readable statement of what language this page is in and which other versions of it exist. Nothing about it appears on screen for a reviewer to look at. Its readers are a crawler, a screen reader and the browser’s own typography.
They also sort by build order, which is what makes the ranking hold. A site whose English version was designed and whose Arabic version was fitted to it afterwards comes out strongest in the class you can see and weakest in the two you can’t, because that is the order the work got reviewed in. That build order is what a business produces when it works in English and sells into a market that reads, buys and regulates in Arabic. In the UAE that regulating half sits in consumer law. The pattern below is really a pattern about which version was the afterthought.
So stop auditing the Arabic page. Audit the three classes, each with the reader who can actually detect it. That reordering is the whole method.
Direction belongs in the HTML
Put dir="rtl" on the <html> element of the Arabic version, next to
lang="ar". MDN’s reasoning for preferring the attribute over the CSS
direction property is worth internalising: directionality is a property of the
content, not of its presentation, so the attribute keeps the text readable on a
browser where the stylesheet failed to load. Set direction in CSS alone and the
stylesheet becomes the only thing holding the page the right way round. Arabic
letters carry strong directionality of their own, so they still run right to
left without it. What goes is the base direction of the paragraph: text aligns
left, punctuation lands at the wrong end of a sentence, and any run mixed with
Latin is ordered against the wrong baseline.
Underneath that, stop writing physical directions. CSS logical properties map to
the correct physical side from the document’s direction: margin-inline-start
resolves left in an LTR context and right in an RTL one, and padding-inline,
border-inline-end and inset-inline-start
follow the same rule. They’ve been safe across browsers without fallbacks since 2021. Build the layout in logical properties once and the RTL version is the
same layout, not a second one.
The alternative is an override sheet full of [dir="rtl"] rules undoing the
left-and-right decisions made everywhere else. That sheet is a second site you
maintain in parallel, and it starts drifting the first time a component ships
without it.
Then decide deliberately what mirrors. Icons describing movement through the interface flip: back and forward arrows, next-step chevrons, progress fills, carousel controls, indent and outdent. Material Design’s bidirectionality guidance covers the exceptions, and its reasoning is the part worth carrying. A media playback control holds its direction because the triangle points the way the media runs, which has nothing to do with reading order; mirroring it draws a rewind button. Clock faces and circular refresh arrows hold theirs on the same logic. So does anything depicting a physical object, since a keyboard looks the same everywhere in the world. Numerals are governed elsewhere: the bidirectional algorithm gives them their own internal order inside an Arabic sentence, whichever way the page runs.
One trap gets past a code review by looking correct: flex-direction: row-reverse inside an RTL container reverses an order that RTL already
reversed. The row comes out in the original left-to-right sequence, which is
what the LTR layout asked for. If a row-reverse exists in your
codebase for visual reasons, it is wrong in one of your two languages right now.
The seam: Latin text sitting inside Arabic sentences
This is the class a one-language review cannot reach, and it produces a bilingual bug that changes what a string says instead of how it looks.
Browsers resolve mixed-direction text with the Unicode bidirectional algorithm.
The part that matters in practice: an element with dir="auto" takes its base
direction from the first character with strong directionality, and everything
else arranges around that decision. So an Arabic paragraph containing a phone
number, an order reference or a Latin brand name is a negotiation the browser
settles with a rule that knows nothing about your content.
A Kuwaiti number stored
as 965 2221 6656 renders as 6656 2221 965, because each group of digits
keeps its own order while the groups themselves get laid out right to left. A
trailing question mark lands at the wrong end of a sentence. An order code comes
apart at its hyphen and reassembles in a different sequence. A display glitch is
what that looks like. It isn’t one: a reversed order number is a different order
number, and a customer reading it back to your support team is reading back
something that doesn’t exist.
Three fixes, and they apply in different places.
Anything whose direction you don’t control at build time wants dir="auto".
MDN is explicit that this is what it’s for: data with unknown directionality,
like user input or content from an external source. Product names off a supplier
feed, customer-submitted reviews, search queries echoed back on a results page.
Wrap embedded runs of the other language in <bdi>. The element works out its
own direction from its content, as though dir="auto" were set on it, and it
isolates that content so it can’t renegotiate the direction of the sentence
around it. A Latin brand name inside an Arabic product title belongs in one.
Where you can’t reach the markup, unicode-bidi: plaintext is the CSS
equivalent: it reads the base direction off the first strong character and seals
the run off from its surroundings. unicode-bidi: isolate alone does the second
half and takes its direction from the direction property, so it detects
nothing.
Then there are the form fields. Email, telephone, URL, password and anything
taking a reference code all need dir="ltr" set on them explicitly, whatever
the page direction says. The label stays in Arabic and right-aligned. The field
contents run left to right, because that is the direction the data is in. Leave
those to inherit the page and a customer types an email address, watches the
cursor do something strange, and retypes it twice.
Who actually reads your lang attribute
Not Google. Google’s own multi-regional documentation says it doesn’t use
code-level language information such as lang attributes or the URL, and that
it determines the language of a page from its visible content.
That has a consequence when a translation budget runs out: the navigation, the footer and the buttons get translated while the body copy stays English. Google warns about exactly that pattern. If the language verdict comes from visible content, a page of Arabic chrome wrapped around English prose is an English page with some decoration. Whatever you declared about it is beside the point.
So lang is not an SEO tag. It earns its place because of who does read it.
WCAG 3.1.1 Language of Page is Level A. Its sibling, 3.1.2 Language of
Parts, is Level AA, and requires that the human language of each passage or
phrase can be programmatically determined, with four exceptions: proper names,
technical terms, words of indeterminate language, and words or phrases that have
become part of the vernacular of the immediately surrounding text. Screen readers
use that to switch pronunciation rules. Browsers use it to select a
language-appropriate font and to apply the right hyphenation, line-breaking and
quotation marks.
3.1.2 is the one to check first, because what it governs is the switched
passage, and switched passages are what bilingual sites are made of. An Arabic
testimonial on an English page. An English legal clause
inside Arabic terms. A bilingual product spec table. Mark every one of them with
lang on the containing element. If your site targets WCAG 2.1 AA, and
Bracketworks builds to that bar, this isn’t optional polish.
Hreflang is reciprocal or it is ignored
Hreflang is the other half of the declaration class: lang states what this
page is, hreflang states which other versions exist. Google’s rule for it is
unusually blunt for a search documentation page. Each language version must list
itself as well as all other versions, and if two pages don’t both point to each
other, the tags will be ignored. Not downweighted. Ignored.
Which turns the build-order asymmetry into a null operation. The English page carries a full set of annotations, because it is the one the build was designed around. The Arabic version, fitted afterwards against a template that was never part of that design, points at nothing. Inspect the English source and the annotations look correct. They do nothing.
The syntax rules that bite after that one:
Only ISO 639-1 language codes and ISO 3166-1 alpha-2 region codes are supported,
and you can’t specify a country code on its own. The first code is always the
language. ar targets Arabic speakers anywhere; ar-AE targets Arabic speakers
in the UAE. Pick based on whether the content genuinely differs by country. Run
one Arabic version for the whole Gulf and ar is the honest declaration; a
country code is a claim you’ll have to keep making good on.
Add x-default for the version that serves anyone the other annotations don’t
match. And keep every URL in the set reachable. Google doesn’t document what
becomes of a set containing a URL that redirects, 404s or sits behind a
robots.txt disallow, so treat it as the reciprocity rule extended one step: a
page nobody can fetch cannot carry a return link, and the pair it belonged to
breaks.
Keep the mechanism’s limit in view too. Annotations are a signal about which version to show which searcher, weighed against everything else Google knows. They don’t assert what language a page is in; the visible content settles that, which is the same rule reaching hreflang from a different angle. So hreflang can’t rescue a half-translated page, and the effort is better spent finishing the translation. This is also the point where bilingual work stops being a language problem and becomes the same crawl-and-canonical discipline as everything else, which is a broader set of foundations worth getting right together.
The switcher that drops you on the homepage
The switcher is where the declaration class meets an actual person, and Google gives three instructions about it that are easy to break. Don’t automatically redirect users from one language version to another. Don’t use IP analysis to adapt content, because the location read is unreliable. Don’t use cookies or browser settings to change the language a single URL serves. Do add crawlable links to the other language versions so a person can choose.
One reason sits behind all three. Every version needs a stable URL returning the
same content to everybody, including a crawler whose request carries no language
preference at all. Googlebot sends no Accept-Language header, so a page
deciding what to serve from one is deciding on a blank.
Then there’s the part costing you conversions instead of impressions. A switcher should map the current URL to its counterpart. A reader four levels deep in your product catalogue who clicks the Arabic link and lands on the Arabic homepage has been handed a chore instead of a translation, and that chore is what reading the other version costs. On a bilingual site it is charged at every crossing, and a reader who checks a specification in one language and the return policy in the other crosses more than once in a session. The same journey runs straight into the checkout, where the Arabic path needs its own review cycle.
Build a test the site can actually fail
Looking at the Arabic page passes by construction. Replace it with five checks that have a defined failure state.
Render a page carrying a Latin brand name, a phone number and a reference code inside an Arabic paragraph, then read the character order out loud. The Arabic form wants submitting with a deliberately invalid email, so the validation message appears and you can confirm it is in Arabic and on the correct side of the field. Language switching gets tested from the deepest page you have. Return tags need a crawl of both versions, not an inspection of the English source and an assumption about the other one. And a screen reader run over a passage that changes language mid-page tells you in seconds whether the pronunciation changes with it.
Two more things to build in.
Budget vertical space, not horizontal. The widely repeated figure that Arabic runs around a quarter longer than English doesn’t appear in W3C’s own work on text size in translation. What that work does say is that Arabic words are built from compact, pattern-based roots and resist abbreviation, and that it lists Arabic among the scripts needing more height than Latin, most of all in Nastaliq faces. So the component that breaks is the one with a fixed height or a tight line clamp, and the fix is generous line-height with no clamping on Arabic text. Arabic webfonts carry their own cost, and it lands on your loading metrics instead of your layout, which puts it in the speed diagnosis.
And if you sell to consumers in the UAE, move the Arabic transactional path to the front of the queue. The UAE government’s own guidance on the federal consumer protection law states that the invoice must be in Arabic, with any other language added at the supplier’s discretion, and that e-commerce businesses must give consumers information in Arabic about the product or service. That takes a half-finished Arabic checkout out of the backlog of conversion improvements and puts it somewhere with a harder deadline.
The declaration class is machine work: crawl both versions and compare what each one declares. Our Site Audit runs that findability pass in a few minutes. The seam is the part you have to read yourself, in both languages, knowing what to look for. We’d spend an hour there before spending a week anywhere else.
Standing offer
The hard part is deciding what to do first
Bring us a URL, an audit report, or a proposal you're not sure about. We'll tell you which problems deserve budget this quarter, and you'll leave with an order of operations you can hand to whoever does the work, whether that's us or not.
Book twenty minutes