Repository navigation
Language Tag Matching
Enbrea.Bcp47 implements the three matching schemes defined by RFC 4647:
- Basic Filtering
- Extended Filtering
- Lookup
Matching is case-insensitive and registry-independent.
The central API is Bcp47Matcher. For a single LanguageRange, equivalent extension methods are available directly on the range.
Basic Filtering matches a basic language range against the beginning of a language tag on subtag boundaries.
For example, the range de matches:
de
de-DE
de-Latn-DE
The range de-DE matches:
de-DE
de-DE-1996
but not:
de-Latn-DE
Use BasicFilter() on a range:
var range = LanguageRangeParser.ParseBasic("de-DE");
var tags = new[]
{
LanguageTag.Parse("de-DE"),
LanguageTag.Parse("de-DE-1996"),
LanguageTag.Parse("de-Latn-DE"),
LanguageTag.Parse("en-US")
};
var matches = range.BasicFilter(tags);The static form is equivalent:
var matches = Bcp47Matcher.BasicFilter(range, tags);Test a single tag with IsBasicMatch():
var range = LanguageRangeParser.ParseBasic("de-DE");
var tag = LanguageTag.Parse("de-DE-1996");
bool matches = range.IsBasicMatch(tag); // trueThe standalone wildcard matches every tag in Basic Filtering:
var range = LanguageRangeParser.ParseBasic("*");
var matches = range.BasicFilter(tags);Extended Filtering accepts both basic and extended ranges and allows wildcard subtags to match across ordinary subtags.
For example:
de-*-DE
can match tags such as:
de-DE
de-Latn-DE
de-Deva-DE
de-DE-1996
de-Latn-DE-1996
de-DE-x-goethe
Use ExtendedFilter():
var range = LanguageRangeParser.Parse("de-*-DE");
var matches = range.ExtendedFilter(tags);Or use the static matcher:
var matches = Bcp47Matcher.ExtendedFilter(range, tags);A single tag can be tested with:
bool matches = range.IsExtendedMatch(tag);Extended Filtering does not skip over a singleton subtag. Singletons introduce extension sequences or private use and therefore form a boundary for the matching algorithm.
For example, de-*-DE does not match:
de-x-DE
because x is a singleton and the matcher cannot skip over it to find DE afterward.
The static matcher also accepts a sequence of ranges:
var ranges = new[]
{
LanguageRangeParser.ParseBasic("de"),
LanguageRangeParser.ParseBasic("en-GB")
};
var matches = Bcp47Matcher.BasicFilter(ranges, tags);Extended Filtering supports the same pattern:
var ranges = new[]
{
LanguageRangeParser.Parse("de-*-DE"),
LanguageRangeParser.Parse("en-*")
};
var matches = Bcp47Matcher.ExtendedFilter(ranges, tags);A language tag is returned at most once even if it matches more than one range. Results preserve the input order of tags.
Lookup selects one best available language tag from a basic language range.
If an exact match is unavailable, the range is progressively truncated from the right.
For example, given the range:
de-DE-1996
Lookup tries successively:
de-DE-1996
de-DE
de
With these available tags:
var tags = new[]
{
LanguageTag.Parse("de"),
LanguageTag.Parse("de-DE"),
LanguageTag.Parse("en-US")
};Lookup returns de-DE:
var range = LanguageRangeParser.ParseBasic("de-DE-1996");
var result = range.Lookup(tags);
Console.WriteLine(result); // de-DEThe static form is:
var result = Bcp47Matcher.Lookup(range, tags);When truncation exposes an extension or private-use singleton, the singleton is removed as well.
For example, the fallback sequence for a range containing private-use subtags does not leave a dangling x singleton.
This behavior is part of the RFC 4647 Lookup algorithm.
Lookup can process several basic ranges in priority order:
var ranges = new[]
{
LanguageRangeParser.ParseBasic("de-CH"),
LanguageRangeParser.ParseBasic("de"),
LanguageRangeParser.ParseBasic("en")
};
var match = Bcp47Matcher.Lookup(ranges, tags);The first range that produces a match wins.
The wildcard range * is skipped during Lookup because it does not identify one specific best language tag. If no concrete range matches, Lookup() returns null and leaves default selection to the caller.
Basic Filtering and Lookup require a basic language range:
var range = LanguageRangeParser.Parse("de-*-DE");
range.BasicFilter(tags); // throws ArgumentException
range.Lookup(tags); // throws ArgumentExceptionExtended Filtering accepts both basic and extended ranges.
See Language Ranges for parsing and identifying range types.