# Convert a locale code into every format

> Convert locale codes between BCP 47, POSIX, ICU, Java, Android, Apple and gettext, one or fifty at once. Live date and number preview, hreflang errors flagged.

Free in-browser tool (no upload): https://www.mauriziofonte.it/tools/locale-converter

**pt-BR**, **pt_BR.UTF-8**, **values-pt-rBR**, **pt-BR.lproj**: one Brazilian Portuguese, four spellings, one per platform. Paste a code or a whole list and get every correct form, a check that the code is real and a **live preview of how that locale writes dates, numbers and currency**. Runs in your browser.

## How to use the converter

1. **Paste one or more codes** - Any spelling works: pt-BR, pt_BR.UTF-8, values-b+sr+Latn, zh_Hant_TW, es-419. Put one per line to convert many at once, or paste a full Accept-Language header.
2. **Read the breakdown** - Each code is split into language, script and region with their English names, plus the canonical form and the maximized one (zh-TW becomes zh-Hant-TW).
3. **Copy the form you need** - One row per platform: BCP 47 for HTML and JavaScript, POSIX and gettext for Linux and PHP, ICU and Java for backends, the Android resource qualifier, the Apple .lproj folder and the hreflang value.
4. **Check preview and warnings** - The preview shows date, time, numbers, currency, percentages and plural categories as your browser formats them for that locale. Warnings flag malformed codes, deprecated codes and values Google rejects in hreflang.
5. **Export** - A list of codes becomes a full table you can copy or download as CSV or JSON, ready for a config file.

## Why one locale has so many spellings

A **locale** is the combination of language, optional script and region that software uses to pick translations and to format dates, numbers and money. Every ecosystem writes it differently. The web and JavaScript use **BCP 47** tags with hyphens (*pt-BR*). Linux and C libraries use the **POSIX** form with an underscore and an encoding (*pt_BR.UTF-8*). Java and ICU use underscores without the encoding. Android wants a prefix and an r before the region (*values-pt-rBR*) and switches to a different syntax altogether as soon as a script is involved (*values-b+sr+Latn*). Apple names folders *pt-BR.lproj*.

On a single platform that is fine. When the same list of languages has to land in your HTML, your backend, your Android app and your iOS app, hand conversion produces errors that raise no error at all: the translation simply does not load and the user sees English.

Most online converters rebuild three forms and stop there. Here every code is first **validated with the browser's Intl API**, the same engine your JavaScript will rely on, then decomposed and rebuilt for each platform, hard cases included: scripts, variants, numeric region codes, deprecated codes. The preview tells you straight away whether you picked the right locale: if decimals come out with a dot instead of a comma, the code is wrong, not your formatter.

## Each format, platform by platform

**BCP 47 (RFC 5646).** The web standard: lowercase language, title-case script, uppercase region, joined by hyphens. *sr-Latn-RS* is Serbian in Latin script, in Serbia. It goes in the HTML *lang* attribute, JavaScript's Intl APIs, HTTP headers and nearly every modern library.

**POSIX and gettext.** The form of the LANG and LC_ALL variables: *sr_RS.UTF-8@latin*. Language and region joined by an underscore, the encoding after a dot and the script as a modifier after the at sign. It is also the folder name for gettext translations in PHP, WordPress and countless open source projects.

**ICU and Java.** ICU writes *sr_Latn_RS*. Java accepts the same idea through Locale.forLanguageTag("sr-Latn-RS") and prints sr_RS_#Latn from toString. Going through forLanguageTag and toLanguageTag avoids most surprises.

**Android.** Resource folders use *values-pt-rBR* for language plus region. Scripts or three-letter codes require the BCP 47 syntax with the b+ prefix: *values-b+sr+Latn+RS*. Put a script in the old form and the folder is silently ignored.

**Apple.** iOS and macOS .lproj folders use Apple's language identifiers: *pt-BR.lproj*, *zh-Hans.lproj*. For Chinese, Apple folders name the script (Hans, Hant), not the region.

**hreflang.** Google expects an ISO 639-1 language code optionally followed by an ISO 3166-1 alpha-2 region. A valid BCP 47 tag can still be invalid there, and the converter says so.

## The usual mistakes, and how they are flagged

**en-UK instead of en-GB.** The ISO code for the United Kingdom is GB, not UK. Browser Intl APIs quietly rewrite en-UK to en-GB, which is why the mistake survives testing. Wherever the code is compared as plain text (an hreflang value, an Android or iOS folder, a backend config file), en-UK matches nothing. You get the correct form and a warning.

**Underscores in HTML.** *lang="en_US"* is not a valid tag: the standard requires a hyphen. Browsers treat the page as an unknown language, which affects hyphenation, text to speech and spell checking.

**Deprecated codes.** Hebrew, Indonesian and Yiddish used to be *iw*, *in* and *ji*, later replaced by *he*, *id* and *yi*. Older Java versions kept returning the old forms, and many databases still store them. Plain Norwegian *no* should usually be *nb* (Bokmål) or *nn* (Nynorsk).

**Chinese by region instead of script.** *zh_TW* and *zh_CN* name a region, but what matters is the script: Traditional (*zh-Hant*) or Simplified (*zh-Hans*). The converter always shows the full form with the script.

**Numeric regions in hreflang.** *es-419* (Latin American Spanish) is a perfectly good BCP 47 tag. In hreflang, though, Google accepts only two-letter region codes. Use *es*, or one tag per country.

**Letter case.** *EN-us* or *pt-br* work almost everywhere because tags are case-insensitive, yet string comparisons in code can still fail. The canonical form shown at the top is the one to store.

## Glossary

- **Locale**: The combination of language, script and region software uses to translate and format.
- **BCP 47**: The IETF standard for language tags used on the web (RFC 5646 plus the subtag registry).
- **Script subtag**: Four letters naming the writing system: Latn (Latin), Cyrl (Cyrillic), Hans and Hant (Simplified and Traditional Chinese).
- **POSIX locale**: The form used by Linux and C libraries: language_REGION.encoding@modifier, for example en_US.UTF-8.
- **CLDR**: Unicode's Common Locale Data Repository: the data that defines how each language writes dates, numbers and plurals.
- **Maximized tag**: The tag completed with its most likely subtags: zh-TW becomes zh-Hant-TW, sr becomes sr-Cyrl-RS.
- **Accept-Language**: The HTTP header in which the browser lists preferred languages, each with a weight (q=0.9).
- **hreflang**: The attribute that tells Google which language versions of a page exist.

## Frequently asked questions

### What is the difference between pt-BR and pt_BR?

They mean the same thing, Brazilian Portuguese, in two conventions. pt-BR is the BCP 47 form used by the web and JavaScript; pt_BR is the POSIX, Java and ICU form. The converter gives you both, plus the Android and Apple ones.

### Which form goes in the HTML lang attribute?

Always BCP 47 with a hyphen: lang="en" or lang="en-US". Underscores are invalid in HTML and make the page an unknown language.

### How do I name an Android values folder with a script?

Use the b+ syntax: values-b+sr+Latn for Serbian in Latin script, values-b+zh+Hant+TW for Traditional Chinese in Taiwan. The r form (values-sr-rRS) cannot carry a script.

### Is en-UK a valid code?

No, even though it is everywhere. The United Kingdom is GB in ISO, so the right tag is en-GB. Browsers fix it silently when it goes through Intl, but in string comparisons, app folders and hreflang en-UK stays wrong.

### Can I use es-419 in hreflang?

No. es-419 is a valid BCP 47 tag for Latin American Spanish, but Google only accepts two-letter country codes in hreflang. Use es as the generic language or one tag per country, such as es-MX or es-AR.

### What is the maximized form?

The tag completed with its most likely implied parts according to CLDR data. zh-TW becomes zh-Hant-TW because Taiwan writes Traditional Chinese. It shows what a short code actually means.

### Why does the preview format dates differently from what I expected?

The preview comes from your browser, which uses the CLDR data bundled with its version. Different browsers and systems can differ slightly, for example in where the currency symbol goes. Check the preview before you ship.

### Can it parse an Accept-Language header?

Yes. Paste it as is, for example en-US,en;q=0.9,it;q=0.7: the converter splits it into languages, keeps the order given by the weights and converts each one.

### Does it handle dozens of codes at once?

Yes. Paste one code per line and you get a table with every form, ready to copy or download as CSV or JSON. Handy when the language list has to match across web, backend and apps.

### Are the codes I paste sent anywhere?

No. Validation, conversion and preview use your browser's Intl APIs and run on your device. Nothing is sent to a server.

## Sources used

- [RFC 5646, Tags for Identifying Languages](https://www.rfc-editor.org/rfc/rfc5646): BCP 47 tag syntax
- [IANA Language Subtag Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry): official subtags and deprecated codes
- [Unicode CLDR](https://cldr.unicode.org/): formatting data and likely subtags
- [Android, language and locale resolution](https://developer.android.com/guide/topics/resources/multilingual-support): values qualifiers and the b+ syntax
- [Google Search Central, localized versions](https://developers.google.com/search/docs/specialty/international/localized-versions): codes accepted in hreflang
- [ECMAScript Internationalization API (ECMA-402)](https://tc39.es/ecma402/): Intl.Locale and Intl.getCanonicalLocales

---
Tool by Maurizio Fonte, senior backend engineer. All tools: https://www.mauriziofonte.it/tools/
