Localizely logo
  • Home
  • Getting started
  • How it works
  • Pricing
  • FAQ
Sign up

I18n questions · Software localization

Should I use en_US or en-US? 

Use en-US. The hyphen is the separator defined by BCP 47 (RFC 5646), and that is the format expected by HTML lang, hreflang, the Accept-Language and Content-Language headers, the JavaScript Intl API, Apple language IDs (en-GB.lproj) and Flutter's Locale.toLanguageTag().

The underscore comes from a different world. POSIX locale names have the form language[_territory][.codeset][@modifier], for example en_US.UTF-8, and that convention carried over into tooling built on C locales: gettext names catalogs after ll_CC locale names such as pt_BR, Java's Locale.toString() prints en_US, and Android resource folders use their own variant, values-en-rUS. Apple keeps both: a language ID is en-GB (hyphen) while a locale ID for formatting conventions is en_GB (underscore).

So each form is correct in its own context. The practical rule: store and transmit BCP 47 tags everywhere you control (database columns, API payloads, config files, file names you choose yourself) and convert at the platform boundary. In Java the conversion is built in:

Locale locale = Locale.forLanguageTag("en-US");
locale.toString();      // "en_US"  (debugging and legacy format)
locale.toLanguageTag(); // "en-US"  (BCP 47, for interchange)

The Java docs describe toString() as a debugging and backward-compatibility format and point to toLanguageTag() for interchange. Flutter has the same pair: Locale.toString() gives en_US and toLanguageTag() gives en-US.

Casing is a convention rather than a rule: language subtags lowercase (en), script subtags title case (Hant), region subtags uppercase (US). RFC 5646 states that tags are case-insensitive, so en-us means the same as en-US, but file systems, string-keyed lookups and other developers often are case-sensitive. Normalize once (Locale.forLanguageTag does this for you) and write tags the conventional way.

Pitfalls:

  • Comparing tags with plain string equality without normalizing case or separator, so en_us never matches en-US.
  • Sending en_US in Accept-Language or hreflang; it is not a well-formed BCP 47 tag, so strict parsers reject or ignore it.
  • Mixing both forms in file names (messages_en_US.properties next to app-en-US.json) and then writing the conversion in three places instead of one.

Published Oct 9, 2026 · Markdown version

Related software localization questions:

Localization workflow:

Learn how a localization workflow works
Localizely logo
About
Free tools
Boring stuff
  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Compare
Newsletter
Copyrights 2026 © Localizely