Table of content
A locale is a language combined with the regional conventions software uses to show text, dates, times, numbers, prices and sorted lists to a group of people. It’s identified by a code such as pt-BR (Portuguese, Brazil) or fr-CA (French, Canada). Brazilian and European Portuguese share a language, but pt-BR and pt-PT are different locales.
Every platform writes that code its own way. The same Brazilian Portuguese locale is pt-BR on the web, pt_BR in Java’s toString(), pt_BR.UTF-8 on a Linux server and values-pt-rBR in an Android project. Our advice: store the hyphen form everywhere you control, and convert only where a platform insists on its own.
What does a locale control?
A locale decides how the same data reads to each user: the date order, the time format, the decimal and thousands separators, where the currency sign goes, which day starts the week, how many plural forms a sentence needs and how words sort. Translation changes the words. The locale changes everything around them.
We ran one date (5 March 2026, 14:30 UTC), one number (1234567.891) and one price (1,234.50 euros) through the JavaScript Intl API in eight locales. The formatting rules come from the Unicode Common Locale Data Repository (CLDR 48, Node.js 26.11).
Locale | Date | Time | Week starts |
|---|---|---|---|
en-US | 3/5/26 | 2:30 PM | Sunday |
en-GB | 05/03/2026 | 14:30 | Monday |
de-DE | 05.03.26 | 14:30 | Monday |
fr-FR | 05/03/2026 | 14:30 | Monday |
pt-BR | 05/03/2026 | 14:30 | Sunday |
ja-JP | 2026/03/05 | 14:30 | Sunday |
ar-EG | ٥/٣/٢٠٢٦ | ٢:٣٠ م | Saturday |
hi-IN | 5/3/26 | 2:30 pm | Sunday |
The number, the price and the plural forms came out like this:
Locale | Number | Price in euros | Plural forms |
|---|---|---|---|
en-US | 1,234,567.891 | €1,234.50 | one, other |
en-GB | 1,234,567.891 | €1,234.50 | one, other |
de-DE | 1.234.567,891 | 1.234,50 € | one, other |
fr-FR | 1 234 567,891 | 1 234,50 € | one, many, other |
pt-BR | 1.234.567,891 | € 1.234,50 | one, many, other |
ja-JP | 1,234,567.891 | €1,234.50 | other |
ar-EG | ١٬٢٣٤٬٥٦٧٫٨٩١ | ١٬٢٣٤٫٥٠ € | zero, one, two, few, many, other |
hi-IN | 12,34,567.891 | €1,234.50 | one, other |
A few rows are worth a second look. In en-US, 3/5/26 is March 5, while en-GB writes the same day as 05/03/2026, so a date format hard-coded into a template is wrong for one of them. In hi-IN, the number is grouped 12,34,567: three digits on the right, then pairs. And ar-EG writes Arabic-Indic digits and has six plural forms to English’s two, so a pluralization rule can’t be copied from one language to the next.
Sorting changes too. Sorted for German (de-DE), the letters z, ä, a, o and ö come out as a ä o ö z. Sorted for Swedish (sv-SE), the same letters come out as a o z ä ö.
What’s the difference between a language and a locale?
A language code names a language: pt is Portuguese, zh is Chinese. A locale adds a region, and sometimes a script. es-419 is Spanish for Latin America and the Caribbean, where 419 is a United Nations region code instead of a country. zh-Hans and zh-Hant are Chinese in simplified and traditional characters.
A person’s language and formatting locale can also differ: someone can read your app in English and still want dates and prices the Austrian way. If you have users like that, store their language and their formatting locale as two settings. For the language codes themselves, see our list of ISO 639 language codes.
How is a locale code written?
On the web and in most modern libraries, a locale code is a BCP 47 language tag (RFC 5646): short parts joined by hyphens.
Part | Standard | Example |
|---|---|---|
Language | ISO 639, 2 or 3 letters | pt, es, sr |
Script (optional) | ISO 15924, 4 letters | Latn, Hant |
Region (optional) | ISO 3166-1 (2 letters) or UN M.49 (3 digits) | BR, RS, 419 |
Unicode extension (optional) | -u- keywords from CLDR | ar-u-nu-latn |
The parts aren’t case-sensitive, but the convention is a lowercase language, a title-case script and an uppercase region: sr-Latn-RS is Serbian in Latin script for Serbia. The -u- extension carries preferences the region doesn’t settle: ca for the calendar, nu for the digits, fw for the first day of the week and hc for a 12- or 24-hour clock. ar-u-nu-latn, for example, is Arabic shown with the digits 0 to 9.
Is en_US the same as en-US?
Yes. It’s the same locale, written for different systems. BCP 47 uses a hyphen. Unix systems, Java’s older string form and Apple’s locale IDs use an underscore, and the Unicode CLDR treats the two separators as equivalent while preferring the hyphen. Here’s how Brazilian Portuguese is written in six places a product team ships to:
Where | How pt-BR is written |
|---|---|
Web: HTML lang, JavaScript Intl | pt-BR (lang=“pt-BR”, new Intl.Locale(“pt-BR”)) |
Unicode CLDR and ICU | CLDR prefers pt-BR and accepts pt_BR; ICU’s own form is pt_BR |
Linux and other POSIX systems | pt_BR.UTF-8 (language_territory.codeset) |
Java | toString() gives pt_BR, toLanguageTag() gives pt-BR |
Android resources | values-pt-rBR, or values-b+pt+BR on Android 7.0 and later |
Apple platforms | locale ID pt_BR, language ID pt-BR, which also names the pt-BR.lproj folder |
Two things here cause mistakes. Java’s toString() isn’t a BCP 47 tag, and Oracle’s own documentation says to rely on toLanguageTag() for a string that represents the complete locale. And on a Linux server, the C locale (also called POSIX) is the standard, portable locale, and it isn’t your users’ locale. A server that formats dates or sorts text should set the locale it needs instead of relying on its environment.
How should a team manage locales?
The hyphen form, BCP 47, belongs in your translation files, APIs, database and translation management system. Underscores and Android’s r prefix appear only where a platform asks for them: a Linux server, a Java call, an Android resource folder. Skip that rule and you’ll find pt_BR, pt-br and br-portuguese side by side for the same strings.
Write the fallback order down in configuration (pt-BR, then pt, then your source language) instead of letting each library guess. Watch the script when you do: zh-Hant falling back to plain zh may show simplified characters to a reader of traditional Chinese. Our guide to ISO and BCP 47 codes in software has a full fallback example.
LingoHub is a translation management system that syncs with GitHub, GitLab, Bitbucket, Azure DevOps and AWS CodeCommit, so the codes in your Android, iOS and web resource files stay the same ones your translators and reviewers see.
LingoHub is a translation management system that integrates with GitHub, GitLab, Bitbucket, Azure DevOps, and AWS CodeCommit, keeping your Android, iOS, and web resource files in sync with your translation workflows.
Ready to simplify software localization? Start your free 14-day trial or book a demo to see how LingoHub fits into your development workflow.
Related articles
Language codes: ISO 639 lists and the country-code mix-ups
Every ISO 639-1 and ISO 639-2 language code, the difference between them, and the country codes, like se and kr, that belong to another language.
ISO language codes in software: ISO 639 and BCP 47
Learn how to implement ISO 639 and BCP 47 language tags in web apps, APIs, repositories and localization workflows without common locale mistakes.
Pluralization (p11n) - the many of plurals
Let's refresh our memory on pluralization, one of the most challenging aspects of learning and localization of a new language. Read detailed about in our blog