The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
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 tagconst locale = new Intl.Locale('zh-Hant-TW')View examples
Check supported localesIntl.DateTimeFormat.supportedLocalesOf(['pt-BR', 'fr-CA'])View examples
Format a localized datenew Intl.DateTimeFormat('de-DE', { dateStyle: 'long', timeZone: 'UTC' }).format(date)View examples
Format currencynew Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(1299.9)View examples
Format a conjunction listnew Intl.ListFormat('en', { style: 'long', type: 'conjunction' }).format(names)View examples
Select a plural categorynew Intl.PluralRules('en').select(count)View examples
Segment wordsconst 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

01

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.

Mark a multilingual passage and localized alternatives
<!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>
Back to quick reference ↑
02

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.

Render known RTL content and unknown messages safely
<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>
Back to quick reference ↑
03

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.

Protect surrounding punctuation around user names
<p>Reviewers: <bdi>إيمان</bdi>, <bdi>Ada</bdi>, and <bdi>דנה</bdi>.</p>

<p>Printed label: <bdo dir="rtl">ABC-123</bdo></p>
Back to quick reference ↑
04

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.

Validate an explicit application locale
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();
Back to quick reference ↑
05

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.

Format a fixed instant and monetary amount
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));
Back to quick reference ↑
06

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.

Build a small pluralized list without punctuation assumptions
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);
Back to quick reference ↑
07

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.

Apply language-aware typography and direction-neutral spacing
: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;
}
Back to quick reference ↑

Local code tester

Try mixed-language and bidirectional content

Compare declared RTL content with isolated user names and a direction-neutral layout.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. WHATWGHTML Standard: Global attributes and bidirectional requirementshtml.spec.whatwg.org
  2. Ecma InternationalECMAScript Internationalization API Specification402.ecma-international.org
  3. World Wide Web ConsortiumDeclaring language in HTMLw3.org
  4. World Wide Web ConsortiumInternationalization Best Practices for Spec Developersw3.org
  5. 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.

Share feedback