The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Declare the document language | <html lang="en-US"> | View examples |
| Mark a language change | <span lang="fr">Crème brûlée</span> | View examples |
| Protect a token from translation | <code translate="no">ServerFault</code> | View examples |
| Link a localized equivalent | <link
rel="alternate"
hreflang="pt-BR"
href="https://example.com/pt-br/guia/"> | View examples |
| Set right-to-left direction | <html lang="ar" dir="rtl"> | View examples |
| Infer direction for unknown text | <p dir="auto">User-provided text</p> | View examples |
| Isolate unknown inline text | <bdi class="username">مريم</bdi> | View examples |
| Show text with an explicit override | <bdo dir="rtl">ABC-123</bdo> | View examples |
| Inspect a locale tag | const locale = new Intl.Locale('zh-Hant-TW') | View examples |
| Check supported locales | Intl.DateTimeFormat.supportedLocalesOf(['pt-BR', 'fr-CA']) | View examples |
| Format a localized date | new Intl.DateTimeFormat('de-DE', { dateStyle: 'long', timeZone: 'UTC' }).format(date) | View examples |
| Format currency | new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(1299.9) | View examples |
| Format a conjunction list | new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }).format(names) | View examples |
| Select a plural category | new Intl.PluralRules('en').select(count) | View examples |
| Segment words | const parts = [...new Intl.Segmenter('ja', { granularity: 'word' }).segment(text)] | View examples |
| Select elements by language | :lang(ar) { font-family: system-ui, sans-serif; } | View examples |
Internationalization is part of the data model, not a final visual polish. Mark the actual human language and base direction in HTML so browsers, assistive technology, spellcheckers, and translation systems receive durable semantics. Keep locale selection separate from language detection, store machine values independently from their presentation, and use the built-in Intl APIs instead of assembling culturally sensitive output by hand.
Step by step
Detailed examples
Declare the language of content, not the intended audience
Put a valid BCP 47 language tag on the html element and override it on the smallest practical ancestor when content changes language. The declaration affects language-aware processing; it is not a routing instruction and cannot be inferred reliably from encoding. Avoid obsolete meta http-equiv Content-Language declarations for describing page text. translate=no is a hint for content that must remain stable, while hreflang identifies an equivalent linked resource and does not replace lang on either page. When visible text and an attribute such as title use different languages, put the visible text in a nested element so each language can be marked accurately.
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/guide/">
<link rel="alternate" hreflang="x-default" href="https://example.com/guide/">
<title>International menu guide</title>
</head>
<body>
<p>The dessert is <span lang="fr">crème brûlée</span>.</p>
<p>Keep the identifier <code translate="no">MenuItem</code> unchanged.</p>
</body>
</html> Set base direction explicitly and independently from language
Language and writing direction are separate facts: a language may use more than one script, and a tag may omit its customary script. Use dir=ltr or dir=rtl when the direction is known. dir=auto is useful at the boundary of genuinely unknown user-generated text, but its first-strong heuristic can guess incorrectly when a sentence begins with a URL, brand, or number. Prefer the HTML dir attribute over styling alone because direction is semantic and must survive when CSS is missing, copied, or transformed. Use CSS logical properties for layout so the interface follows the declared direction without duplicating left- and right-specific rules.
<article lang="ar" dir="rtl">
<h2>تفاصيل الطلب</h2>
<p>رقم الطلب: <bdi>INV-2048</bdi></p>
</article>
<ul aria-label="Recent messages">
<li dir="auto">Hello from Ottawa</li>
<li dir="auto">مرحبا من عمّان</li>
</ul> Isolate embedded strings and reserve overrides for exact transcriptions
A bidirectional string can reorder punctuation and neighboring tokens when inserted into text with the opposite direction. The bdi element creates an isolation boundary for usernames, titles, IDs, and other values whose direction is unknown; it is usually safer than manually inserting Unicode directional controls. bdo with dir=ltr or dir=rtl deliberately overrides character ordering and is appropriate only when the required display does not follow normal Unicode bidi behavior. Do not use bdo as a general RTL container. Keep machine identifiers in their original logical order and test punctuation around dynamic values in both directions.
<p>Reviewers: <bdi>إيمان</bdi>, <bdi>Ada</bdi>, and <bdi>דנה</bdi>.</p>
<p>Printed label: <bdo dir="rtl">ABC-123</bdo></p> Separate locale choice from formatting and capability checks
Intl constructors accept one or more requested locale identifiers and apply the runtime's locale negotiation and fallback rules. Use the user's explicit in-product setting when available; otherwise a server may negotiate from request preferences and the client may offer navigator.languages as a hint. Never assume that a locale identifies a person's location, currency, time zone, measurement system, or legal requirements. Intl.Locale exposes structured subtags, while supportedLocalesOf tells you which requested locales an individual formatter can support without falling back. Persist a stable locale identifier, not already formatted strings.
const available = new Set(['en-US', 'fr-CA', 'pt-BR']);
const savedSetting = 'fr-CA';
const requested = available.has(savedSetting) ? savedSetting : 'en-US';
const [supported] = Intl.DateTimeFormat.supportedLocalesOf([requested]);
const chosen = supported ?? 'en-US';
const locale = new Intl.Locale(chosen);
document.documentElement.lang = locale.toString(); Format dates and numbers from machine values at the presentation boundary
Keep dates as instants or calendar-aware domain values and numbers as numeric values until display time. Specify a timeZone when the output must be deterministic; otherwise DateTimeFormat uses the host's current zone. Name the ISO 4217 currency explicitly because a locale does not select one, and do not parse user-entered numbers by stripping punctuation from formatted output. Construct formatters once per option set in hot paths. formatToParts is preferable when the UI must wrap individual components, because splitting a formatted string assumes punctuation and word order that vary by locale.
const instant = new Date('2026-08-12T15:30:00Z');
const date = new Intl.DateTimeFormat('fr-CA', {
dateStyle: 'long',
timeStyle: 'short',
timeZone: 'America/Toronto'
});
const money = new Intl.NumberFormat('fr-CA', {
style: 'currency',
currency: 'CAD'
});
console.log(date.format(instant));
console.log(money.format(1299.9)); Use locale rules for lists, plurals, and text boundaries
ListFormat handles separators and conjunctions without hard-coded commas. PluralRules returns categories such as one, few, or other; it does not translate a message, so map every category required by the target locale and always provide other. Segmenter finds grapheme, word, or sentence boundaries and avoids corrupting emoji sequences, combining marks, and languages without spaces. Do not treat JavaScript string length, whitespace splitting, or English plural rules as universal text operations. For full messages, use a reviewed localization system that supports variables, grammatical context, and translator-visible source strings.
const names = ['Amina', 'Chloé', '李雷'];
const joined = new Intl.ListFormat('en', { type: 'conjunction' }).format(names);
const count = 3;
const category = new Intl.PluralRules('en').select(count);
const messages = { one: `${count} reviewer: ${joined}`, other: `${count} reviewers: ${joined}` };
console.log(messages[category] ?? messages.other); Test semantics, expansion, bidi, and fallback behavior
Automated checks can catch missing or invalid lang values, untranslated keys, and accidental direction regressions, but visual review with real scripts remains necessary. Test long translations, mixed-direction names, localized digits, narrow viewports, zoom, and fonts that cover the target script. Use :lang() for language-specific typography rather than attribute-prefix selectors, because the pseudo-class follows inherited language. Pseudo-localization is useful for finding concatenated strings and inflexible layouts, but it does not replace review by fluent speakers or assistive-technology testing.
:lang(ar) {
font-family: "Noto Sans Arabic", system-ui, sans-serif;
}
.notice {
border-inline-start: 0.25rem solid currentColor;
padding-inline-start: 1rem;
margin-block: 1rem;
} Local code tester
Try mixed-language and bidirectional content
Compare declared RTL content with isolated user names and a direction-neutral layout.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- WHATWGHTML Standard: Global attributes and bidirectional requirementshtml.spec.whatwg.org
- Ecma InternationalECMAScript Internationalization API Specification402.ecma-international.org
- World Wide Web ConsortiumDeclaring language in HTMLw3.org
- World Wide Web ConsortiumInternationalization Best Practices for Spec Developersw3.org
- MDN Web DocsIntldeveloper.mozilla.org
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



