People searching BCP 47 language tags, language tag validation, locale identifier, BCP 47 locale, language region code, script subtag, zh-Hant vs zh-Hans, or how to choose the right locale code for localization are usually trying to solve a routing problem that masquerades as a translation problem. A target can be perfectly translated and still fail if the system labels it with the wrong language identifier, falls back to the wrong regional variant, applies the wrong plural rules, chooses the wrong script, or stores pt-BR content under pt-PT.
Current IETF BCP 47 identifies languages with structured tags, while Unicode CLDR and LDML use compatible language and locale identifiers for software internationalization. Current Unicode guidance shows familiar forms such as en, fr-CA, and zh-Hant, and also supports extensions for locale behavior. The search language around language subtag, script, region, variant, Unicode locale extension, canonical locale ID, and language matching points to one dominant reader job: use the most specific identifier the product actually knows, canonicalize it consistently, and validate every localization handoff so language data reaches the correct users.
This article has one dominant job: build a BCP 47 and locale-identifier validation workflow that prevents translated content from being routed to the wrong language, script, region, fallback chain, plural system, date/number formatter, or search index. It does not replace locale fallback, which decides what users see when a preferred target is unavailable. It does not replace target-language script QA, which checks the content itself. This page owns the identifier layer: the tags attached to locales and the matching rules that software uses to interpret them.
Quick answer
A reliable language-tag workflow is:
- use BCP 47/Unicode-style language identifiers rather than homemade locale strings;
- store the base language only when that is genuinely all the product knows;
- add a script subtag when script distinction matters, such as
zh-Hansorzh-Hant; - add a region when language behavior, market, regulation, spelling, terminology, or formatting is region-specific;
- canonicalize deprecated or alternate forms through a trusted locale library;
- reject or quarantine malformed tags before resource import;
- validate that TMS, repository, runtime, analytics, fallback, and formatting layers agree on the same identifier mapping;
- test likely-subtag and fallback behavior rather than guessing what
zh,sr, orazmeans in a specific product; - keep display names separate from machine identifiers;
- version locale-routing changes because one tag edit can reroute thousands of strings.
The central rule is:
a locale tag is not decoration; it is routing metadata that can change which language rules and translations the product applies.
What a BCP 47 tag represents
A simple tag can be:
en
Language: English.
A more specific tag:
en-GB
Language: English.
Region: United Kingdom.
Another:
zh-Hant
Language: Chinese.
Script: Traditional Han.
The tag can carry exactly the distinctions the product needs.
Why not use names such as “English_UK”?
Homemade identifiers create interoperability problems.
Examples:
English_UK;ENGLISH-UK;en_UK;eng-GB.
One service accepts one.
Another expects en-GB.
A third silently maps to en.
Standard identifiers reduce translation between systems.
Language, script, and region are different dimensions
Language
en, fr, zh, sr.
Script
Latn, Cyrl, Hans, Hant.
Region
US, GB, CA, SG.
Do not use a region when what you really mean is a script.
Do not use a script when the distinction is truly market-specific terminology.
Tags should reflect the semantic difference the product needs.
Worked example 1: Chinese script
A product supports:
- Simplified Chinese;
- Traditional Chinese.
Weak identifiers:
zh-CNfor all Simplified;zh-TWfor all Traditional.
That may work for specific markets.
But script and region are not identical concepts.
A user in Singapore may read Simplified Chinese.
A user in Hong Kong may use Traditional Chinese with regional differences.
A more deliberate model can use:
zh-Hans;zh-Hant;- or region-specific forms such as
zh-Hans-SG,zh-Hant-HKwhere the product truly needs them.
The right tag depends on the product’s localization distinctions.
Worked example 2: Serbian scripts
Serbian can be written in:
- Cyrillic;
- Latin.
A product that stores only:
sr
may not be able to distinguish the intended script resource.
Tags such as:
sr-Cyrl;sr-Latn
make the script distinction explicit.
Step 1: identify the actual product distinction
Before creating locales, ask:
What must differ?
Possibilities:
- language;
- script;
- spelling;
- terminology;
- legal text;
- date format;
- currency;
- marketing content.
Then choose a tag that represents that distinction.
Do not create regional locales merely because the marketing team can name many countries.
Every locale creates:
- translation work;
- review work;
- fallback logic;
- release testing.
Use specificity where it has product value.
Step 2: distinguish language from market
A market can contain several languages.
Singapore:
- English;
- Chinese;
- Malay;
- Tamil.
A language can span several markets.
English:
- US;
- GB;
- Singapore;
- Australia;
- Canada.
Do not use one field called locale to mean an unclear mixture of:
- market;
- language;
- currency.
Separate dimensions in the product model when needed.
Step 3: keep display names separate
Machine identifier:
de-DE
Display name:
Deutsch (Deutschland)
Do not use the user-facing name as the system key.
Display names can:
- translate;
- change style;
- use native/endonym form.
The machine identifier should remain stable.
Step 4: canonicalize input
Users or integrations may supply tags in different casing:
EN-us
en-US
BCP 47 tag matching is not about preserving arbitrary case style.
A locale library can canonicalize to conventional casing.
Store one canonical representation.
This reduces duplicate locale records.
Step 5: validate syntax
A valid-looking locale field can contain:
en_US
Some frameworks use underscores internally.
BCP 47 uses hyphen-separated subtags.
Know which layer expects which syntax.
If the API contract says BCP 47: reject or convert underscore forms at the boundary.
Do not let mixed syntax flow through the system.
Step 6: validate registered subtags
A tag can be syntactically well formed and still contain an invalid or unknown subtag.
Use:
- IANA language-subtag registry;
- Unicode/CLDR locale libraries.
Do not maintain your own list by memory.
Standards evolve.
Deprecated language codes
Some codes are deprecated or have preferred replacements.
A locale library can canonicalize or warn.
Do not silently rewrite without recording the mapping if historical data depends on the old identifier.
A migration should preserve identity.
Step 7: understand macrolanguage and naming complexity
Human language names do not always map neatly to one code.
Unicode’s guidance on choosing language identifiers explicitly notes cases where ordinary language names can be ambiguous or differ from registry names.
When a market requests:
Punjabi
ask:
- which linguistic variety?
- which script?
- which market?
Do not infer solely from an English label.
Step 8: use script when it matters
Script is useful for:
- Chinese Simplified/Traditional;
- Serbian Latin/Cyrillic;
- Azerbaijani script distinctions;
- Kurdish script distinctions;
- other multi-script languages.
A script subtag should describe writing system, not font.
Latn means Latin script.
It does not mean a particular typeface.
Step 9: use region when regional language differs materially
Examples:
en-USvsen-GB;pt-BRvspt-PT;fr-FRvsfr-CA.
Differences can include:
- spelling;
- terminology;
- legal language;
- date and number conventions;
- style.
If the product promises a regional locale, translations should reflect it.
Step 10: avoid fake region specificity
Do not label a generic translation:
fr-CA
merely because Canadian users receive it.
If the text is actually generic French from France, the identifier should not create false confidence.
Either:
- localize it properly;
- use a generic French resource;
- use a documented fallback.
Metadata should tell the truth.
Step 11: likely subtags are useful but not absolute product truth
Unicode CLDR provides likely-subtag data to infer common script/region information.
For example, a library can expand an underspecified locale into a likely fuller form.
This is useful for:
- formatting;
- matching;
- defaults.
It does not mean the inferred locale is what your customer contract promised.
Product policy can override likely defaults.
Step 12: minimize tags carefully
Locale libraries can sometimes remove subtags that can be inferred without losing likely identity.
This can be useful for canonical storage.
But do not minimize away a distinction the product intentionally uses.
Example: if sr-Latn is a separate product locale, keep the script explicit even if a tool has another default assumption.
Step 13: fallback matching is not identity
If fr-CA falls back to fr-FR, they are still different locales.
Do not merge them in the database just because fallback exists.
Identity: what locale the content belongs to.
Fallback: what to show when content is missing.
Keep these layers separate.
Step 14: locale matching should be deliberate
Suppose a TMX contains:
en
Destination expects:
en-US.
Should it match?
Possible answers:
- yes, for generic source;
- no, for strict regulatory content.
This is a trust/mapping policy.
The locale tag provides evidence.
It does not make the decision automatically.
Step 15: BCP 47 extensions
BCP 47 supports extensions.
Unicode maintains the u extension for locale behavior.
Example concepts can include:
- collation;
- numbering system;
- calendar.
A locale identifier can therefore carry behavior beyond language/region.
Do not store these extensions unless the application actually uses them.
Specificity without behavior is just complexity.
Example: collation extension
A locale can request a particular collation order.
That is useful when the same language supports:
- dictionary order;
- phonebook order;
- numeric sorting.
The sort behavior belongs in locale configuration, not translation strings.
Step 16: private-use subtags
BCP 47 includes private-use mechanisms.
They can support internal distinctions that standards do not define.
Use cautiously.
Private tags reduce interoperability because external systems do not know their semantics.
Document them.
Prefer registered or Unicode-standard mechanisms when available.
Step 17: variants
Some language forms use variant subtags.
Do not invent variant codes casually.
Check registry and library support.
A custom product edition may be better represented as:
- content variant metadata;
- feature flag;
rather than pretending it is a language variant.
Step 18: locale identifiers and plural rules
Plural rules depend on locale/language data.
If a target resource is mislabeled:
pl content as en,
runtime plural selection can be wrong even if the translation text itself is perfect.
Tag validation therefore affects grammar.
Step 19: locale identifiers and number formatting
A number such as:
1,234.56
can display differently under another locale.
The translation string may contain only a placeholder.
The locale tag tells the formatter how to render the number.
Wrong tag = wrong numeric convention.
Step 20: locale identifiers and date formatting
Same principle.
A wrong region can alter:
- date order;
- month names;
- calendar defaults.
The translator may never see the formatted value in the TMS.
Locale routing is part of localization quality.
Step 21: locale identifiers and currency
Currency should usually come from business data, not simply from locale.
But locale influences:
- symbol placement;
- decimal style;
- spacing.
Do not infer transaction currency solely from language tag.
Language/locale and business market are related but distinct.
Step 22: locale identifiers and collation
Sorting depends on locale.
If a list is tagged: sv vs en, letters such as Å/Ä/Ö can sort differently.
Wrong locale can make an otherwise translated product feel foreign.
Locale-aware collation should use the intended identifier.
Step 23: locale identifiers and fonts
A language/script tag can help the application choose:
- shaping engine;
- font fallback;
- glyph coverage.
Do not rely on language tags alone for font selection, but they are useful routing metadata.
Wrong script tag can trigger the wrong font stack.
Step 24: locale identifiers and line breaking
Line-breaking rules can vary by script/language.
A correct tag gives text engines better context.
This matters in:
- Thai;
- Japanese;
- Chinese;
- Khmer;
- other scripts with language-sensitive breaking.
Step 25: locale identifiers and spellcheck
A spellchecker loaded under: en-US
may flag valid British forms: colour, organise.
If the target is en-GB, the resource and QA locale should match.
QA depends on correct routing.
Step 26: TMS locale and runtime locale must map explicitly
A TMS may call a locale:
Portuguese (Brazil)
and export code:
pt-BR.
The runtime may use:
pt_BR internally.
Create a mapping table.
Do not let each integration guess.
A locale mapping table
| Product/TMS | Canonical BCP 47 | Runtime code |
|---|---|---|
| English US | en-US | en_US |
| Portuguese Brazil | pt-BR | pt_BR |
| Chinese Traditional | zh-Hant | zh-Hant |
The canonical column becomes the shared reference.
Step 27: validate repository paths
Locale directories often encode identifiers:
locales/en-US/
locales/fr-CA/
A typo creates a new directory rather than an obvious error.
CI should compare directory names against the supported-locale manifest.
Reject unknown tags.
Step 28: validate filenames
Files:
messages.en-US.json
messages.fr-FR.json
should use the same canonical tag set.
Do not permit:
fr_FR;FR-fr;french;
unless the build adapter deliberately maps them.
Consistency reduces routing defects.
Step 29: supported-locale manifest
Keep one authoritative list:
- locale tag;
- display name;
- fallback;
- script;
- product tier;
- status.
Every service should consume or derive from it.
Avoid five independent locale lists across:
- TMS;
- app;
- website;
- analytics;
- support portal.
Drift is inevitable otherwise.
Step 30: test tag round trips
A locale can travel:
TMS → API → repository → build → runtime → analytics.
At every boundary, assert that:
fr-CA
remains the intended locale.
If one layer reduces it to:
fr,
you may lose regional routing.
Worked example 3: locale collapsed during export
TMS stores:
pt-BR and pt-PT.
A legacy exporter emits both as:
pt.
Now the repository has a collision.
One target overwrites the other.
No linguistic review can repair this after the fact.
Locale-code validation must happen before merge.
Step 31: analytics tags
If analytics records:
locale=fr
while product supports:
- fr-FR;
- fr-CA,
the team cannot measure fallback or quality by region.
Analytics should preserve the level of specificity needed for product decisions.
Privacy policy still applies.
Step 32: support and bug reports
A user reports:
French translation is wrong.
Which French?
A bug report should include canonical locale:
fr-CA.
This shortens diagnosis.
Display the locale in debug/about screens if appropriate.
Step 33: A/B tests and locale
An experiment may run only in:
en-US.
If matching uses base en, it may accidentally include:
- en-GB;
- en-AU.
Locale matching semantics matter beyond translation.
Use exact or fallback-aware matching deliberately.
Step 34: server content negotiation
HTTP uses language negotiation mechanisms such as Accept-Language.
User preferences can contain ordered language ranges.
Do not simply take the first raw tag and look for an exact file.
Use a standards-aware matching strategy.
The application may need:
- exact match;
- script-aware match;
- regional fallback;
- base-language fallback.
Step 35: user preference versus device locale
A device locale may be:
en-SG.
The user chooses app language:
zh-Hans.
Respect the explicit app choice.
Locale source priority should be documented.
Do not overwrite user choice with inferred market.
Step 36: account locale versus content locale
A user may prefer UI in English while reading content in Japanese.
One locale field cannot always represent both.
Separate:
- interface locale;
- content language;
- market/region
when product requirements need it.
Step 37: database design
Store canonical tags in one field.
Avoid:
- language name free text;
- separate region field without validation;
- inconsistent case.
Add a constraint or validation library at write time.
Garbage locale data spreads quickly.
Step 38: migrations
Changing locale ID:
iw → he
or a custom tag to a canonical tag can affect:
- URLs;
- resource paths;
- user preferences;
- TM/TB mappings;
- analytics history.
Treat it as a migration.
Do not rename production locale identifiers casually.
Step 39: aliases
During migration, accept old tag as alias.
Canonicalize to the new tag internally.
Record:
- old;
- canonical.
Eventually remove the alias when clients have migrated.
This preserves compatibility.
Step 40: version the locale manifest
A locale configuration is production data.
Version changes:
- new locale;
- retired locale;
- fallback change;
- tag migration.
Review them like code.
One line can reroute a market.
Failure mode 1: homemade locale codes
Result: integrations disagree.
Repair: BCP 47/Unicode identifiers.
Failure mode 2: region used as script
Result: Chinese or Serbian routing becomes inaccurate.
Repair: script subtag.
Failure mode 3: generic locale labeled as regional
Result: false quality promise.
Repair: truthful identity/fallback.
Failure mode 4: tag lowercased or reformatted inconsistently
Result: duplicate locale records.
Repair: canonicalization.
Failure mode 5: exporter collapses regional variants
Result: one locale overwrites another.
Repair: round-trip tag validation.
Failure mode 6: unknown tag silently accepted
Result: resource never loads.
Repair: supported-locale manifest.
Failure mode 7: language tag used as currency market
Result: wrong business behavior.
Repair: separate dimensions.
Failure mode 8: analytics truncates region
Result: quality data loses specificity.
Repair: preserve canonical tag.
Failure mode 9: migration renames tag without aliases
Result: old clients/preferences break.
Repair: controlled migration.
Failure mode 10: fallback and identity conflated
Result: fr-CA content treated as fr-FR permanently.
Repair: separate requested locale and served locale.
A language-tag validation checklist
Syntax:
- BCP 47 shape valid?
- registered language?
- script valid?
- region valid?
- extensions understood?
Product:
- locale distinction real?
- display name separate?
- fallback explicit?
- plural/formatting data available?
- script/font behavior correct?
Integration:
- TMS mapping?
- repo path?
- runtime mapping?
- analytics mapping?
- support debug value?
Migration:
- aliases?
- user preferences?
- URLs?
- history?
A supported-locale manifest example
en-US | English (United States) | script=Latn | fallback=en | active
fr-CA | Français (Canada) | script=Latn | fallback=fr-FR | active
zh-Hans-SG | 简体中文(新加坡) | script=Hans | fallback=zh-Hans | active
The exact file format is unimportant.
The single source of truth is important.
A 30-minute locale audit
10 minutes
Export every locale code from:
- TMS;
- repo;
- runtime.
10 minutes
Canonicalize and diff.
10 minutes
Check:
- missing;
- extra;
- collapsed;
- aliases.
This simple audit often reveals hidden routing debt.
Search-intent transfer
People searching:
- “BCP 47 language tags”
- “locale identifier”
- “language region code”
- “script subtag”
- “zh-Hant vs zh-Hans”
need one operational answer:
use canonical standards-based language identifiers and validate them across every localization boundary so content, formatting, fallback, and QA route to the intended locale.
That is the job this URL owns.
Transfer to documentation portals
A documentation site may use paths:
