2828 - Converts to JSON and generates C# code files
2929 - Target framework: .NET 10.0
3030
31- - **LanguageTagsTests** (`LanguageTagsTests/LanguageTagsTests.csproj`)
32- - xUnit test suite with 211+ comprehensive tests
33- - Uses AwesomeAssertions for test assertions
34- - 100% coverage of all public APIs
35- - Target framework: .NET 10.0
31+ - **LanguageTagsTests** (`LanguageTagsTests/LanguageTagsTests.csproj`)
32+ - xUnit test suite with comprehensive coverage
33+ - Uses AwesomeAssertions for test assertions
34+ - Target framework: .NET 10.0
3635
3736### Key Directories
3837
@@ -56,12 +55,9 @@ The main public API for working with language tags:
5655
5756**Static Factory Methods:**
5857- `Parse(string tag)`: Parse a language tag string, returns null on failure
59- - `Parse(string tag, Options? options)`: Parse with per-call logging options
6058- `TryParse(string tag, out LanguageTag? result)`: Safe parsing with out parameter
61- - `TryParse(string tag, out LanguageTag? result, Options? options)`: Safe parsing with per-call logging options
6259- `ParseOrDefault(string tag, LanguageTag? defaultTag = null)`: Parse with fallback to "und"
6360- `ParseAndNormalize(string tag)`: Parse and normalize in one step
64- - `ParseAndNormalize(string tag, Options? options)`: Parse and normalize with per-call logging options
6561- `CreateBuilder()`: Create a fluent builder instance
6662- `FromLanguage(string language)`: Factory for simple language tags
6763- `FromLanguageRegion(string language, string region)`: Factory for language+region tags
@@ -77,10 +73,9 @@ The main public API for working with language tags:
7773- `PrivateUse`: PrivateUseTag object
7874- `IsValid`: Property to check if tag is valid
7975
80- **Instance Methods:**
81- - `Validate()`: Verify tag correctness
82- - `Normalize()`: Return normalized copy of tag
83- - `Normalize(Options? options)`: Return normalized copy with per-call logging options
76+ **Instance Methods:**
77+ - `Validate()`: Verify structural correctness
78+ - `Normalize()`: Return normalized copy of tag (does not validate)
8479- `ToString()`: String representation
8580- `Equals()`: Equality comparison (case-insensitive)
8681- `GetHashCode()`: Hash code for collections
@@ -107,8 +102,7 @@ Fluent builder for constructing language tags:
107102- `PrivateUseAdd(string value)`: Add private use tag
108103- `PrivateUseAddRange(IEnumerable<string> values)`: Add multiple private use tags
109104- `Build()`: Return constructed tag (no validation)
110- - `Normalize()`: Return normalized tag (with validation)
111- - `Normalize(Options? options)`: Return normalized tag with per-call logging options
105+ - `Normalize()`: Return normalized tag (no validation)
112106
113107### LanguageTagParser Class (LanguageTagParser.cs)
114108
@@ -138,56 +132,77 @@ Provides language code conversion and matching:
138132- `GetIsoFromIetf(string languageTag)`: Convert IETF to ISO format
139133- `IsMatch(string prefix, string languageTag)`: Prefix matching for content selection
140134
141- **Construction:**
142- - `new LanguageLookup(Options? options = null)`: Optional per-instance logging
135+ ### LogOptions Class (LogOptions.cs)
136+
137+ Static class for configuring global logging for the entire library:
138+
139+ **Properties:**
140+ - `LoggerFactory`: Gets or sets the global logger factory for creating category loggers
141+
142+ **Methods:**
143+ - `SetFactory(ILoggerFactory loggerFactory)`: Configure the library to use a logger factory
144+ - `TrySetFactory(ILoggerFactory loggerFactory)`: Set factory only if none is configured
145+
146+ **Logger Resolution Priority:**
147+ 1. `LoggerFactory` property (when not `NullLoggerFactory`)
148+ 2. `NullLogger.Instance` (default fallback)
149+
150+ **Important Notes:**
151+ - Loggers are created and cached at time of use by each class instance
152+ - Changes to `LoggerFactory` after a logger is created do not affect existing cached loggers
153+ - Only new logger requests use updated configuration
143154
144155### Data Models
145156
146- #### Iso6392Data.cs
147- - ISO 639-2 language codes (3-letter bibliographic/terminologic codes)
148- - **Public Methods:**
149- - `Create()`: Load embedded data
150- - `LoadDataAsync (string fileName)`: Load from file
151- - `LoadJsonAsync (string fileName)`: Load from JSON
152- - `Find(string? languageTag, bool includeDescription)`: Find record by tag
153- - `Find (string? languageTag, bool includeDescription, Options? options)`: Find record by tag with logging options
157+ #### Iso6392Data.cs
158+ - ISO 639-2 language codes (3-letter bibliographic/terminologic codes)
159+ - **Public Methods:**
160+ - `Create()`: Load embedded data
161+ - `FromDataAsync (string fileName)`: Load from file
162+ - `FromJsonAsync (string fileName)`: Load from JSON
163+ - `Find(string? languageTag, bool includeDescription)`: Find record by tag
164+ - **Internal Methods:** `SaveJsonAsync (string fileName)`, `SaveCodeAsync(string fileName)`
154165- **Record Properties:** `Part2B`, `Part2T`, `Part1`, `RefName`
155166
156- #### Iso6393Data.cs
157- - ISO 639-3 language codes (comprehensive language codes)
158- - **Public Methods:**
159- - `Create()`: Load embedded data
160- - `LoadDataAsync (string fileName)`: Load from file
161- - `LoadJsonAsync (string fileName)`: Load from JSON
162- - `Find(string? languageTag, bool includeDescription)`: Find record by tag
163- - `Find (string? languageTag, bool includeDescription, Options? options)`: Find record by tag with logging options
167+ #### Iso6393Data.cs
168+ - ISO 639-3 language codes (comprehensive language codes)
169+ - **Public Methods:**
170+ - `Create()`: Load embedded data
171+ - `FromDataAsync (string fileName)`: Load from file
172+ - `FromJsonAsync (string fileName)`: Load from JSON
173+ - `Find(string? languageTag, bool includeDescription)`: Find record by tag
174+ - **Internal Methods:** `SaveJsonAsync (string fileName)`, `SaveCodeAsync(string fileName)`
164175- **Record Properties:** `Id`, `Part2B`, `Part2T`, `Part1`, `Scope`, `LanguageType`, `RefName`, `Comment`
165176
166- #### Rfc5646Data.cs
167- - RFC 5646 / BCP 47 language subtag registry
168- - **Public Methods:**
169- - `Create()`: Load embedded data
170- - `LoadDataAsync (string fileName)`: Load from file
171- - `LoadJsonAsync (string fileName)`: Load from JSON
172- - `Find(string? languageTag, bool includeDescription)`: Find record by tag
173- - `Find(string? languageTag, bool includeDescription, Options? options)`: Find record by tag with logging options
174- - **Properties :** `FileDate `, `RecordList`
175- - **Record Properties:** `Type`, `Tag`, `SubTag`, `Description` (ImmutableArray), `Added`, `SuppressScript`, `Scope`, `MacroLanguage`, `Deprecated`, `Comments` (ImmutableArray), `Prefix` (ImmutableArray), `PreferredValue`, `TagAny`
177+ #### Rfc5646Data.cs
178+ - RFC 5646 / BCP 47 language subtag registry
179+ - **Public Methods:**
180+ - `Create()`: Load embedded data
181+ - `FromDataAsync (string fileName)`: Load from file
182+ - `FromJsonAsync (string fileName)`: Load from JSON
183+ - `Find(string? languageTag, bool includeDescription)`: Find record by tag
184+ - **Properties:** `FileDate`, `RecordList`
185+ - **Internal Methods :** `SaveJsonAsync(string fileName) `, `SaveCodeAsync(string fileName)`
186+ - **Record Properties:** `Type`, `Tag`, `SubTag`, `Description` (ImmutableArray), `Added`, `SuppressScript`, `Scope`, `MacroLanguage`, `Deprecated`, `Comments` (ImmutableArray), `Prefix` (ImmutableArray), `PreferredValue`, `TagValue`
176187- **Enums:**
177188 - `RecordType`: None, Language, ExtLanguage, Script, Variant, Grandfathered, Region, Redundant
178189 - `RecordScope`: None, MacroLanguage, Collection, Special, PrivateUse
179190
180191#### Supporting Classes
181192
182- **ExtensionTag:**
193+ **ExtensionTag (sealed record) :**
183194- `Prefix`: Single-character extension prefix (char)
184195- `Tags`: ImmutableArray of extension values
185196- `ToString()`: Format as "prefix-tag1-tag2"
197+ - `Normalize()`: Returns normalized copy with sorted, lowercase tags
198+ - `Equals()`: Case-insensitive equality comparison
186199
187- **PrivateUseTag:**
200+ **PrivateUseTag (sealed record) :**
188201- `Prefix`: Constant 'x'
189202- `Tags`: ImmutableArray of private use values
190203- `ToString()`: Format as "x-tag1-tag2"
204+ - `Normalize()`: Returns normalized copy with sorted, lowercase tags
205+ - `Equals()`: Case-insensitive equality comparison
191206
192207### Language Tag Structure
193208
@@ -273,24 +288,24 @@ LanguageTag tag = LanguageTag.ParseOrDefault(input); // Falls back to "und"
273288
274289## Recent API Changes
275290
276- ### Changed (Breaking)
291+ ### Changed (Breaking)
277292- `LanguageTagParser` is now internal (use `LanguageTag.Parse()` instead)
278293- Properties changed from `IList<string>` to `ImmutableArray<string>`:
279294 - `VariantList` → `Variants`
280295 - `ExtensionList` → `Extensions`
281296 - `TagList` → `Tags`
282- - Data file APIs are async-only: `LoadDataAsync()`/`LoadJsonAsync()`; sync versions removed
297+ - Data file APIs are async-only and use static creators: `FromDataAsync()`/`FromJsonAsync()`
298+ - Logging configuration now uses `ILoggerFactory` only; `ILogger` support was removed from `LogOptions`
283299- Tag construction requires use of factory methods or builder (constructors are internal)
284300
285301### Added (Non-Breaking)
286302- `LanguageTag.ParseOrDefault()`: Safe parsing with fallback
287303- `LanguageTag.ParseAndNormalize()`: Combined parse and normalize
288- - `LanguageTag.ParseAndNormalize(string, Options?)`: Combined parse and normalize with logging options
289304- `LanguageTag.IsValid`: Property for validation
290305- `LanguageTag.FromLanguage()`, `FromLanguageRegion()`, `FromLanguageScriptRegion()`: Factory methods
291306- `IEquatable<LanguageTag>` implementation with operators
292- - Options-aware logging for parsing/normalization and lookup (`Options` + `LogOptions`)
293- - `LanguageLookup` supports optional logging via primary constructor
307+ - `LogOptions` static class for global logging configuration with `ILoggerFactory`
308+ - `ExtensionTag` and `PrivateUseTag` are now sealed records with `Normalize()` and case-insensitive `Equals()` methods
294309- Comprehensive XML documentation for all public APIs
295310
296311## Future Improvements
@@ -300,7 +315,6 @@ Consider these areas for enhancement:
300315- Implement comprehensive subtag content validation against registry data
301316- Add more language lookup and validation features
302317- Improve error messages and diagnostics
303- - Consider making `ExtensionTag` and `PrivateUseTag` immutable records
304318
305319## Contributing
306320
0 commit comments