A feature is ready for the world when it works in any language and follows each reader's conventions without changes to its code. This page follows Microsoft's internationalisation checklist: each section says what differs between markets, how to write code that gets it right, and the Globalisation check code that marks the habit that gets it wrong.
Most of it comes down to one rule: let the reader's locale decide. The browser's
Intl APIs, your framework's formatter and the data behind
Culture know each market's conventions; code
that hard-codes one market's does not.
Text and sentences
Every text a person reads belongs in the source catalogue, under a key, and is
rendered through the project's translation function. That includes labels,
headings, buttons, messages, errors shown to people, placeholders, alt text
and ARIA labels. Codes: hardcoded_text.
One sentence is one message. Word order differs between languages, so a
sentence assembled from parts cannot be translated. Pass values as placeholders
and counts as plural forms. Codes: concatenated_text.
// Not world-ready: English word order and English plurals
'You have ' + count + ' new ' + (count === 1 ? 'message' : 'messages');
// World-ready: one ICU message
t('inbox.new', { count });
// "inbox.new": "{count, plural, one {You have # new message} other {You have # new messages}}"Languages need different plural forms: English has two, Arabic six, Japanese one. Write the source message with ICU plurals and the engine adds the forms each language needs.
Numbers
Markets write the same number differently: 1,234.5 in the United States,
1.234,5 in Germany, 1 234,5 in Sweden and France, and with other digits in
some Arabic markets. Format with the reader's locale. Codes: locale_number.
new Intl.NumberFormat(locale).format(1234.5);
new Intl.NumberFormat(locale, { style: 'percent' }).format(0.25);Read a number a person typed with the same locale, or use a number input,
whose value the browser normalises. parseFloat('1,5') is 1, not one and a
half. Codes: locale_parsing.
Dates, times and time zones
Date order, separators, month names, the 12- or 24-hour clock and the calendar
all differ. Format with the reader's locale and never with a fixed pattern such
as MM/dd/yyyy. Codes: locale_date.
new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short', timeZone }).format(when);- Store instants in UTC, as ISO 8601 with an offset, and format them in the reader's time zone. Compute "today" and day boundaries in that time zone, not the server's.
- Weeks start on different days: Sunday in the United States, Monday in
most of Europe, Saturday in parts of the Middle East. Weekends differ too.
Take both from the locale (
new Intl.Locale(locale).getWeekInfo()where available, or your date library's locale data), never a constant. Codes:calendar_names. - Weekday and month names come from the locale
(
Intl.DateTimeFormat(locale, { weekday: 'long' })), not a list in the code. Codes:calendar_names. - Calendars. Saudi Arabia, Israel and Japan use the Gregorian calendar alongside the Umm al-Qura, Hebrew and Japanese calendars; Thailand and Iran default to the Buddhist and Persian calendars. Culture shows which a market uses.
Money
Format amounts with the currency and the reader's locale, never a symbol next
to a number. The same amount reads $1,234.50, 1.234,50 $ or US$1,234.50
depending on the reader, and many markets share a symbol. Codes:
hardcoded_currency.
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(1234.5);The currency is a business decision, not a formatting one: changing the reader's locale must not convert prices. Culture records the currency each market pays in.
Sorting, comparing and case
Each language sorts its own way: Swedish puts å, ä and ö after z, German sorts
ä with a, and Chinese sorts by pinyin or by strokes. Sort text people read with the
reader's locale. Codes: ordinal_sort.
names.sort(new Intl.Collator(locale).compare);Case mapping depends on the language too: in Turkish, the capital of i is İ.
Use toLocaleUpperCase(locale) for text people read. Codes: case_mapping.
Lists
"Anna, Ben and Chen" is written differently in each language. Join lists with
the locale. Codes: joined_list.
new Intl.ListFormat(locale, { style: 'long', type: 'conjunction' }).format(names);Names, addresses, postal codes and phone numbers
- Names. Many markets write the family name first, and many people have one
name, or more than two. Take a person's name in one field unless a market
needs its parts. Accept every script: Åsa, José, 王 and Мария are all names.
Codes:
latin_only_input. - Addresses. Fields, their order and what each is called differ: a state and ZIP code in the United States, a prefecture in Japan, no postal code at all in some markets. Culture shows each market's address as its post writes it. Don't require fields a market does not use.
- Postal codes. Keep them as text and validate per country, or not at all.
A five-digit pattern refuses most of the world. Codes:
postal_code_format. - Phone numbers. Store them in E.164 form (
+46 8 123 456 78), format and validate them with a library that knows each country's numbering plan, such as libphonenumber. A pattern like(555) 555-1234refuses every number outside North America. Codes:phone_number_format.
Units and paper
Most of the world measures in kilometres, kilograms and degrees Celsius; the
United States uses miles, pounds and degrees Fahrenheit; the United Kingdom
mixes both. Take the unit from the reader's region and format it with the
locale. Codes: fixed_unit.
new Intl.NumberFormat(locale, { style: 'unit', unit: 'kilometer' }).format(12);Printed and PDF output follows the market's paper: Letter in North America, A4
almost everywhere else. Codes: paper_size.
Input methods and fonts
People type Chinese, Japanese and Korean through an input method editor: they
press Enter to choose a word, not to submit. A handler that submits on Enter
must ignore it while a word is composed. Codes: ime_composition.
onKeyDown={event => {
if (event.key === 'Enter' && !event.nativeEvent.isComposing) submit();
}}Choose fonts that cover every script you ship, or fall back to system fonts that do; Arabic, Chinese, Hebrew, Japanese, Korean and Thai each need their own. Leave room in the line height for scripts with tall marks, don't rely on italics (Chinese and Japanese have none), and never put text in images, which cannot be translated.
Right to left
Arabic and Hebrew are written right to left, and the whole layout mirrors with them: navigation, alignment, icons that point somewhere, and the order of columns.
- Set
dir="rtl"on the root element for those languages, andlangfor every language. - Use logical properties:
margin-inline-startinstead ofmargin-left,padding-inline-end,inset-inline-start,text-align: start,float: inline-start, and the logical utility classes of your CSS framework (ms-4,pe-2,text-start). Codes:physical_direction,unmirrored_layout. - Mirror icons that show direction, such as arrows and chevrons, but not others, such as a clock or a play button.
- Numbers, code, URLs and Latin-script names stay left to right inside
right-to-left text. Isolate text from people or data in
<bdi>so it doesn't reorder its surroundings.
Room to grow
Translations are often longer than English, and short labels grow the most: a
German "OK" can become a whole word, and a ten-character label can double. Give
text room: let it wrap, let buttons and tabs grow with their labels, and avoid
fixed widths and heights on text. Don't truncate labels people need in order
to act. Codes: clipped_text.
The pseudo translation lengthens every string by these amounts, so you see the problem before a translator does:
| Source length | Added |
|---|---|
| Up to 10 characters | 100% |
| 11 to 20 | 80% |
| 21 to 30 | 60% |
| 31 to 50 | 40% |
| 51 to 70 | 31% |
| Over 70 | 30% |
Chinese, Japanese and Korean are usually shorter but need taller lines.
A checklist for every change
- Text people read comes from the catalogue, and each sentence is one message.
- Numbers, dates, times, money, units and lists go through the reader's locale.
- Sorting and case mapping use the reader's locale.
- Names, addresses, postal codes and phone numbers accept every market.
- Enter does not submit while a word is composed.
- The layout uses logical properties and leaves room for longer text.