Repository navigation
Building Language Tags
LanguageTagBuilder provides a fluent API for constructing BCP 47 language tags from individual components.
The builder validates each component according to its own RFC 5646 grammar and delegates final structural validation and normalization to Bcp47Parser when Build() is called.
A simple language-region tag can be constructed as follows:
var tag = new LanguageTagBuilder()
.Language("de")
.Region("DE")
.Build();
Console.WriteLine(tag); // de-DEScript and region can be combined:
var tag = new LanguageTagBuilder()
.Language("zh")
.Script("Hans")
.Region("CN")
.Build();
Console.WriteLine(tag); // zh-Hans-CNThe builder methods return the same builder instance, so calls can be chained.
Set the primary language using Language():
var builder = new LanguageTagBuilder()
.Language("en");A primary language subtag must contain between 2 and 8 ASCII letters.
The builder validates the component immediately. A value that belongs to another component grammar is not silently reinterpreted later:
new LanguageTagBuilder()
.Language("i"); // throws ArgumentExceptionAdd an extlang using Extlang():
var tag = new LanguageTagBuilder()
.Language("zh")
.Extlang("cmn")
.Build();An extlang subtag must contain exactly three ASCII letters.
Final structural validation still applies when Build() is called. In particular, the library rejects multiple extlang subtags as structurally invalid.
Set the script with Script():
var tag = new LanguageTagBuilder()
.Language("zh")
.Script("Hans")
.Build();A script subtag must contain exactly four ASCII letters.
This is rejected immediately:
new LanguageTagBuilder()
.Language("de")
.Script("US");US has region syntax, not script syntax.
Set the region with Region():
var tag = new LanguageTagBuilder()
.Language("de")
.Region("DE")
.Build();A region is either:
- two ASCII letters, or
- three ASCII digits
For example:
var tag = new LanguageTagBuilder()
.Language("es")
.Region("419")
.Build();A value such as 1901 is rejected by Region() because it has variant syntax instead.
Add variants with Variant():
var tag = new LanguageTagBuilder()
.Language("sl")
.Variant("rozaj")
.Variant("biske")
.Build();
Console.WriteLine(tag); // sl-rozaj-biskeVariants are appended in the order in which they are added.
Duplicate variants are detected by the final structural validation:
var builder = new LanguageTagBuilder()
.Language("sl")
.Variant("rozaj")
.Variant("ROZAJ");
builder.Build(); // throws FormatExceptionAdd an extension sequence using Extension():
var tag = new LanguageTagBuilder()
.Language("en")
.Extension('u', "ca", "gregory")
.Build();
Console.WriteLine(tag); // en-u-ca-gregoryThe singleton must be an ASCII alphanumeric character other than x or X. The x singleton is reserved for private use.
Each extension subtag must contain between 2 and 8 ASCII alphanumeric characters.
Extension sequences are normalized into deterministic singleton order by the parser:
var tag = new LanguageTagBuilder()
.Language("en")
.Extension('b', "bbb")
.Extension('a', "aaa")
.Build();
Console.WriteLine(tag); // en-a-aaa-b-bbbDuplicate extension singletons are rejected by Build().
Private-use subtags can follow an ordinary language tag:
var tag = new LanguageTagBuilder()
.Language("de")
.Region("DE")
.PrivateUse("company", "internal")
.Build();
Console.WriteLine(tag); // de-DE-x-company-internalA private-use subtag contains between 1 and 8 ASCII alphanumeric characters.
A builder without a primary language can construct a private-use-only tag:
var tag = new LanguageTagBuilder()
.PrivateUse("company", "internal")
.Build();
Console.WriteLine(tag); // x-company-internal
Console.WriteLine(tag.IsPrivateUseOnly); // trueWithout a primary language, no extlang, script, region, variant, or extension components may be present.
For example, this is invalid:
var builder = new LanguageTagBuilder()
.Script("Latn")
.PrivateUse("example");
builder.Build(); // throws InvalidOperationExceptionUse LanguageTagBuilder.From() to create a builder from an existing parsed tag:
var original = LanguageTag.Parse("de-DE");
var modified = LanguageTagBuilder
.From(original)
.Region("AT")
.Build();
Console.WriteLine(modified); // de-ATAll decomposable components are copied, including extlangs, variants, extensions, and private-use subtags.
Private-use-only tags can also be copied:
var original = LanguageTag.Parse("x-company-internal");
var copy = LanguageTagBuilder.From(original).Build();Grandfathered tags cannot be decomposed into builder components, so From() rejects them:
var tag = LanguageTag.Parse("i-klingon");
LanguageTagBuilder.From(tag); // throws ArgumentExceptionLanguageTagBuilder validates RFC 5646 component grammar and final registry-independent structure. It does not check whether a component is registered in IANA.
For example, building and registry validation are separate operations:
var tag = new LanguageTagBuilder()
.Language("de")
.Script("Abcd")
.Build();If registry membership matters, install Enbrea.Bcp47.Iana and validate the result afterward:
using Enbrea.Bcp47.Iana;
bool valid = tag.IsValidAgainstIanaRegistry();See IANA Validation.