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.
<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.
<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.
.callout {
border-inline-start: 3px solid currentColor;
padding-inline-start: 1rem;
margin-block-end: 1.5rem;
text-align: start;
}
| Physical | Logical | Means |
|---|---|---|
margin-left | margin-inline-start | The side the text starts on. |
padding-right | padding-inline-end | The side it ends on. |
left: 0 | inset-inline-start: 0 | Pinned to the start edge. |
text-align: left | text-align: start | Aligned to the start edge. |
border-top | border-block-start | The block direction — usually the top. |
width / height | inline-size / block-size | Along 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.
<!-- 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.
<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.
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.