The iOS & Android locale codes reference table
Common locales with their BCP-47 tag, the Apple/iOS language ID (also the .lproj folder name), and the Android resource folder. Where Android supports both an older region folder and a modern script folder, both are shown.
| Language (Region) |
BCP-47 tag |
Apple/iOS code (.lproj) |
Android resource folder |
Notes |
| English (US) |
en-US |
en |
values/ (default) |
Base strings normally live in unqualified values/ |
| English (UK) |
en-GB |
en-GB |
values-en-rGB |
|
| English (Australia) |
en-AU |
en-AU |
values-en-rAU |
|
| English (Canada) |
en-CA |
en-CA |
values-en-rCA |
|
| Spanish (Spain) |
es-ES |
es-ES (or es) |
values-es / values-es-rES |
es alone commonly targets Spain |
| Spanish (Mexico) |
es-MX |
es-MX |
values-es-rMX |
|
| Spanish (Latin America) |
es-419 |
es-419 |
values-b+es+419 |
419 is a UN M.49 region code, needs b+ |
| Portuguese (Brazil) |
pt-BR |
pt-BR |
values-pt-rBR |
|
| Portuguese (Portugal) |
pt-PT |
pt-PT |
values-pt-rPT |
|
| French (France) |
fr-FR |
fr |
values-fr |
|
| French (Canada) |
fr-CA |
fr-CA |
values-fr-rCA |
|
| German (Germany) |
de-DE |
de |
values-de |
|
| Italian (Italy) |
it-IT |
it |
values-it |
|
| Dutch (Netherlands) |
nl-NL |
nl |
values-nl |
|
| Chinese (Simplified) |
zh-Hans |
zh-Hans |
values-b+zh+Hans / values-zh-rCN |
b+ script form needs API 21+; rCN is the legacy fallback |
| Chinese (Traditional) |
zh-Hant |
zh-Hant |
values-b+zh+Hant / values-zh-rTW |
|
| Chinese (Traditional, Hong Kong) |
zh-Hant-HK |
zh-HK |
values-b+zh+Hant+HK / values-zh-rHK |
|
| Japanese (Japan) |
ja-JP |
ja |
values-ja |
|
| Korean (South Korea) |
ko-KR |
ko |
values-ko |
|
| Russian (Russia) |
ru-RU |
ru |
values-ru |
|
| Arabic |
ar |
ar |
values-ar |
Right-to-left |
| Hebrew (Israel) |
he-IL |
he |
values-iw |
Android uses the legacy ISO code iw, not he |
| Hindi (India) |
hi-IN |
hi |
values-hi |
|
| Thai (Thailand) |
th-TH |
th |
values-th |
|
| Vietnamese (Vietnam) |
vi-VN |
vi |
values-vi |
|
| Indonesian (Indonesia) |
id-ID |
id |
values-in |
Android uses the legacy ISO code in, not id |
| Turkish (Turkey) |
tr-TR |
tr |
values-tr |
|
| Polish (Poland) |
pl-PL |
pl |
values-pl |
|
| Ukrainian (Ukraine) |
uk-UA |
uk |
values-uk |
|
| Czech (Czechia) |
cs-CZ |
cs |
values-cs |
|
| Swedish (Sweden) |
sv-SE |
sv |
values-sv |
|
| Danish (Denmark) |
da-DK |
da |
values-da |
|
| Norwegian Bokmål |
nb-NO |
nb |
values-nb |
Some older projects used values-no |
| Finnish (Finland) |
fi-FI |
fi |
values-fi |
|
| Greek (Greece) |
el-GR |
el |
values-el |
|
| Romanian (Romania) |
ro-RO |
ro |
values-ro |
|
| Hungarian (Hungary) |
hu-HU |
hu |
values-hu |
|
When a literal folder isn't in this list, derive it from the rules in the next two sections rather than guessing.
How Apple resolves locales on iOS
Apple identifies each localization with a language ID — a BCP-47 subtag string like en, pt-BR, zh-Hans, or es-419. That same string is the name of the .lproj folder inside your bundle (fr.lproj, pt-BR.lproj, zh-Hant.lproj) and the localization identifier you see in Xcode's project editor and in a String Catalog (.xcstrings) or .xcloc export.
At launch iOS walks the user's ordered list of preferred languages and matches each against your bundle's available localizations, applying a best-match fallback: zh-Hant-HK falls back to zh-Hant, and es-MX falls back to es if you ship a generic Spanish but no Mexican one. A language subtag with a script (zh-Hans, zh-Hant) is distinct from one with only a region (zh-CN); for Chinese you should key on script, because that is what actually changes the characters.
Two practical consequences:
- The region is separate from the UI language. A user can run the system in English while formatting dates and numbers as
en_GB via their region setting. Locale.current reflects the region; your .lproj resolution reflects the language list.
- Ship the least specific tag that covers your content. If one Spanish translation is fine everywhere, ship
es. Add es-MX or es-419 only when the copy genuinely differs, so fallback does the rest.
How Android values-xx and BCP-47 (values-b+) folders work
Android selects strings by matching resource qualifiers on the folder name. The base, unqualified values/ folder is the default and final fallback. Language and region qualifiers follow a strict shape:
values-es # language only (ISO 639)
values-es-rMX # language + region: lowercase 'r' then UPPERCASE region
values-b+zh+Hans # BCP-47 form: 'b+' then '+'-separated subtags
values-b+es+419 # BCP-47 with a UN M.49 numeric region
Rules that trip people up in the values-xx locale folders:
- The region needs the lowercase
r prefix and uppercase region code — values-es-rMX, never values-es-MX or values-es-mx.
- Scripts (needed to separate Simplified from Traditional Chinese) are only expressible through the
b+ BCP-47 notation — values-b+zh+Hans — and that form requires API level 21+. For older devices you fall back to the region approximation values-zh-rCN / values-zh-rTW.
- Android carries a few legacy ISO 639 codes: Hebrew is
iw (not he), Indonesian is in (not id), and Yiddish is ji. The folder must use the legacy code even though your BCP-47 tag uses the modern one.
- Android resolution has no script-blind regional fallback the way iOS chains do; if the exact qualifier set doesn't match, it drops to
values/. Name folders deliberately.
Because the folder name is the code, a single wrong character silently ships the default language. That's the class of error a native-format pipeline is meant to remove.
App Store and Play Store language codes
App Store Connect and Google Play maintain their own localization lists for store metadata — titles, descriptions, keywords, and screenshots. Those store language codes mostly mirror the BCP-47 tags above (App Store uses en-US, es-MX, pt-BR, zh-Hans; Play uses entries like es-419 and pt-PT), but they are a separate surface from your in-app resource folders and are not always one-to-one.
To be explicit about scope: strings.dev localizes the in-app native strings — your .xcloc / .xcstrings and strings.xml / <plurals>. Store metadata and screenshots are a distinct workflow we don't translate; treat any store-listing localization as a separate task you run yourself. Same format in, same format out — the codes above are exactly the identifiers your exported files already carry.
Regional variants matter — and where to choose them
The reference above is the mechanical mapping. Whether you ship es-MX alongside es-ES, or split pt-BR from pt-PT, is a content decision, not a code one — and those variants genuinely diverge in vocabulary, formality, and tone. For per-language guidance on which variants are worth shipping and how the translated copy differs, see the language localization pages.
strings.dev preserves your placeholder syntax across every variant — iOS %@ %lld %1$@ and String Catalog plural variations, Android %s %d %1$s and <plurals> quantity strings — and protects brand phrases, URLs, emails, handles, and proper nouns while translating everything around them.
The free Indie tier covers 1 project and 1 language with unlimited word translations plus app and brand localization. To ship the multi-locale matrix this reference describes — unlimited languages, 2 projects, brand context, and the QA & analytics dashboard that flags failed translations for retry — Indie Plus is $20/mo, or $10/mo billed annually (see pricing).