Languages and direction

Mark up a page in more than one language, including one that reads right to left.

20of 22
≈ 9Min read
8Parts

lang is not decoration

<html lang="en"> tells a screen reader which voice and pronunciation rules to use, tells the browser which hyphenation and quotation rules apply, and tells a translation tool what it is looking at. A page of Azerbaijani read aloud with English pronunciation is unintelligible.

Use a BCP 47 tag: en, az, ru, tr, and a region only when it changes something — en-GB against en-US, pt-BR against pt-PT.

Changing language mid-page

lang is inherited, and any element may override it. Mark a quotation, a name or a phrase in another language and it will be pronounced correctly.

one sentence in three languages
<html lang="en">
  …
  <p>The company calls them <span lang="az">gizli qapılar</span> —
     literally <q lang="az">hidden doors</q>.</p>

  <blockquote lang="ru">
    <p>Дверь, которой не видно.</p>
  </blockquote>

Direction

Arabic, Hebrew, Persian and Urdu read right to left. dir="rtl" on <html> flips the whole layout: text alignment, the order of inline elements, the side the scrollbar sits on, even which way list markers face.

  • dir="ltr" — left to right. The default.
  • dir="rtl" — right to left.
  • dir="auto" — the browser decides from the first strong character. Right for user-generated text whose language you do not know.
a right-to-left document
<html lang="ar" dir="rtl">

Writing CSS that flips with it

The moment a layout has to work in both directions, physical properties become a liability. Use the logical ones: they follow the writing direction automatically, and you write the stylesheet once.

one rule, both directions
.callout {
  border-inline-start: 3px solid currentColor;
  padding-inline-start: 1rem;
  margin-block-end: 1.5rem;
  text-align: start;
}
PhysicalLogicalMeans
margin-leftmargin-inline-startThe side the text starts on.
padding-rightpadding-inline-endThe side it ends on.
left: 0inset-inline-start: 0Pinned to the start edge.
text-align: lefttext-align: startAligned to the start edge.
border-topborder-block-startThe block direction — usually the top.
width / heightinline-size / block-sizeAlong and across the text.

Mixed direction in one line

When a right-to-left sentence contains a left-to-right fragment — a user name, a number, a URL — the bidirectional algorithm sometimes puts the punctuation in the wrong place. <bdi> isolates a fragment so it cannot disturb what is around it.

isolating a name
<!-- the username could be in any script -->
<p><bdi>إيان</bdi>: 3 comments</p>

<!-- force a direction for one run -->
<p><bdo dir="ltr">This run is forced left to right.</bdo></p>

<bdi> is the one you will use. <bdo> overrides the algorithm entirely and is a last resort.

Telling search engines about translations

Every version must list all of them, including itself. x-default is the fallback for a language you have not translated.

alternates in the head
<link rel="alternate" hreflang="en" href="https://example.com/en/doors/">
<link rel="alternate" hreflang="az" href="https://example.com/az/qapilar/">
<link rel="alternate" hreflang="ru" href="https://example.com/ru/dveri/">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/doors/">

Text that is not English

  • UTF-8 everywhere. <meta charset="utf-8"> first in the head, and save the file as UTF-8 — a mismatch is what produces ü instead of ü.
  • Never build a sentence by joining fragments in code. Word order differs between languages, and the result is untranslatable.
  • Leave room: German and Russian run 30% longer than English, and a button sized to "Save" will break.
  • Dates, numbers and currency belong to Intl, not to string formatting you wrote.
  • translate="no" on a product name or a code sample stops automatic translation mangling it.
formatting for a locale
new Intl.DateTimeFormat("az", { dateStyle: "long" }).format(new Date());
// "21 sentyabr 2026"

new Intl.NumberFormat("az", { style: "currency", currency: "AZN" }).format(1250);
// "1.250,00 ₼"

Fonts

A font that looks right in Latin may have no Cyrillic or Arabic glyphs at all, and the browser will silently substitute another one — which is why a page can suddenly change typeface in one paragraph. Check the subsets your font ships, and give a fallback stack that covers the scripts you serve.

Practice