Skip to content

Commit 919f0b2

Browse files
ptr727dependabot[bot]github-actions[bot]Copilot
authored
Develop (#78)
Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ptr727 <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 5f39b9a commit 919f0b2

37 files changed

+8650
-953
lines changed

.editorconfig

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ insert_final_newline = true
2222
trim_trailing_whitespace = true
2323

2424
# Markdown files
25-
[*.{md}]
25+
[*.md]
2626
end_of_line = crlf
2727
trim_trailing_whitespace = false
2828

@@ -39,7 +39,6 @@ indent_size = 2
3939
# Json files
4040
[*.json]
4141
end_of_line = crlf
42-
indent_size = 4
4342

4443
# Linux scripts
4544
[*.sh]
@@ -73,7 +72,7 @@ csharp_prefer_simple_using_statement = true
7372
csharp_prefer_static_anonymous_function = true
7473
csharp_prefer_static_local_function = true
7574
csharp_prefer_system_threading_lock = true
76-
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
75+
csharp_preferred_modifier_order = public,private,protected,internal,file,static,abstract,sealed,virtual,override,readonly,unsafe,volatile,async,extern,new,partial:warning
7776
csharp_preserve_single_line_blocks = true
7877
csharp_preserve_single_line_statements = false
7978
csharp_space_after_cast = false
@@ -204,3 +203,9 @@ dotnet_style_qualification_for_method = false
204203
dotnet_style_qualification_for_property = false
205204
dotnet_style_readonly_field = true
206205
dotnet_style_require_accessibility_modifiers = for_non_interface_members
206+
207+
# ReSharper settings
208+
resharper_csharp_trailing_comma_in_multiline_lists = true
209+
resharper_csharp_var_for_built_in_types = false
210+
resharper_csharp_var_when_type_is_apparent = false
211+
resharper_csharp_var_when_type_is_not_apparent = false

.github/copilot-instructions.md

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@
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

AGENTS.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@ For comprehensive coding standards and detailed conventions, refer to [`.github/
3030

3131
### Key Components
3232

33-
**Public API Classes:**
34-
35-
- `LanguageTag`: Main class for working with language tags (parse, build, normalize, validate)
36-
- `LanguageTagBuilder`: Fluent builder for constructing language tags
37-
- `LanguageLookup`: Language code conversion and matching (IETF ↔ ISO)
38-
- `Iso6392Data`: ISO 639-2 language code data
39-
- `Iso6393Data`: ISO 639-3 language code data
40-
- `Rfc5646Data`: RFC 5646 / BCP 47 language subtag registry data
41-
- `ExtensionTag`: Represents extension subtags
42-
- `PrivateUseTag`: Represents private use subtags
33+
**Public API Classes:**
34+
35+
- `LanguageTag`: Main class for working with language tags (parse, build, normalize, validate)
36+
- `LanguageTagBuilder`: Fluent builder for constructing language tags
37+
- `LanguageLookup`: Language code conversion and matching (IETF ↔ ISO)
38+
- `Iso6392Data`: ISO 639-2 language code data (`Create()`, `FromDataAsync()`, `FromJsonAsync()`)
39+
- `Iso6393Data`: ISO 639-3 language code data (`Create()`, `FromDataAsync()`, `FromJsonAsync()`)
40+
- `Rfc5646Data`: RFC 5646 / BCP 47 language subtag registry data (`Create()`, `FromDataAsync()`, `FromJsonAsync()`)
41+
- `ExtensionTag`: Represents extension subtags (sealed record)
42+
- `PrivateUseTag`: Represents private use subtags (sealed record)
43+
- `LogOptions`: Static class for configuring library-wide logging via `ILoggerFactory`
4344

4445
**Internal Classes:**
4546

46-
- `LanguageTagParser`: Internal parser (use `LanguageTag.Parse()` instead)
47+
- `LanguageTagParser`: Internal parser implementation (use `LanguageTag.Parse()` instead)
4748

4849
## Authoritative References
4950

CODESTYLE.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,38 @@ Note: Code snippets are illustrative examples only. Replace namespaces/types to
168168
- C#, XML, YAML, JSON, Windows scripts: CRLF
169169
- Linux scripts (`.sh`): LF
170170

171-
6. **`#region`**: Do not use. Prefer logical file/folder/namespace organization.
172-
7. **Member ordering (StyleCop-like)**: Constants → fields → constructors → properties → indexers → methods → events → operators → finalizers → delegates → nested types
171+
6. **`#region`**: Do not use regions. Prefer logical file/folder/namespace organization.
172+
7. **Member ordering (StyleCop SA1201)**: conststatic readonly → static fields → instance readonly fields → instance fields → constructors → public (events → properties → indexers → methods → operators)non-public in same order → nested types
173173

174174
### Comments and Documentation
175175

176176
1. **XML documentation**
177177
- `<GenerateDocumentationFile>true</GenerateDocumentationFile>`
178178
- Missing XML comments for public APIs are suppressed (`.editorconfig`)
179-
- Single-line summaries
179+
- Must document all public surfaces.
180+
- Single-line summaries, additional details in remarks, document input parameters, returns values, exceptions, and add crefs
180181

181182
```csharp
182183
/// <summary>
183-
/// This property always returns a value < 1.
184+
/// Example of a single line summary.
184185
/// </summary>
186+
/// <remarks>
187+
/// Additional important details about usage.
188+
/// Multiple lines if needed.
189+
/// </remarks>
190+
/// <param name="category">
191+
/// The quote category to request
192+
/// </param>
193+
/// <param name="cancellationToken">
194+
/// A <see cref="System.Threading.CancellationToken"/> that can be used to cancel the request.
195+
/// </param>
196+
/// <returns>
197+
/// A <see cref="string"/> containing the quote text.
198+
/// </returns>
199+
/// <exception cref="System.ArgumentException">
200+
/// Thrown when <paramref name="category"/> is not a supported value.
201+
/// </exception>
202+
public async Task<string> GetQuoteOfTheDayAsync(string category, CancellationToken cancellationToken) {}
185203
```
186204

187205
2. **Code analysis suppressions**
@@ -248,7 +266,7 @@ Note: Code snippets are illustrative examples only. Replace namespaces/types to
248266

249267
### Testing Conventions
250268

251-
1. **Framework**: xUnit with FluentAssertions
269+
1. **Framework**: xUnit with AwesomeAssertions
252270

253271
```csharp
254272
[Fact]

HISTORY.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ C# .NET library for ISO 639-2, ISO 639-3, RFC 5646 / BCP 47 language tags.
55
## Release History
66

77
- Version 1.2:
8-
- Refactored the project to follow standard patterns across other projects.
9-
- IO APIs are now async-only (`LoadDataAsync`, `LoadJsonAsync`, `SaveJsonAsync`, `GenCodeAsync`).
10-
- Added logging support for `ILogger` or `ILoggerFactory` per class instance or statically.
11-
- JSON load/save and codegen now stream directly to/from files, no intermediate text buffers.
12-
8+
- Refactored the project to follow standard patterns used across other projects.
9+
- Added logging support configured through `LogOptions.SetFactory(ILoggerFactory)`.
10+
- ⚠️ IO API's are async only, e.g. `LoadJson()` -> `async FromJsonAsync()`.
11+
- ⚠️ Collection instantiation follows the `From` pattern, e.g. `LoadData()` -> `FromDataAsync()`.
12+
- IO now streams directly to/from code/files without intermediate text buffers.
1313
- Version 1.1:
1414
- .NET 10 and AOT support.
1515
- Refactored public surfaces to minimize internals exposure.

LanguageTags.code-workspace

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
"ANTLR",
1414
"arevela",
1515
"boont",
16+
"chamí",
1617
"CLDR",
1718
"codegen",
1819
"csdevkit",
1920
"datebadge",
2021
"davidanson",
2122
"derbend",
2223
"dotnettools",
24+
"Emberá",
2325
"extlang",
2426
"finalizers",
2527
"gruntfuggly",
@@ -52,6 +54,7 @@
5254
"pyfisch",
5355
"Qaaa",
5456
"Qabx",
57+
"reparsed",
5558
"rspeer",
5659
"Serilog",
5760
"Simoncic",
@@ -99,7 +102,6 @@
99102
"github.vscode-github-actions",
100103
"ms-azuretools.vscode-docker",
101104
"ms-dotnettools.csdevkit",
102-
"sanjulaganepola.github-local-actions",
103105
"yzhang.markdown-all-in-one",
104106
]
105107
}

LanguageTags/Extensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal bool LogAndPropagate(
1111
[CallerMemberName] string function = "unknown"
1212
)
1313
{
14-
LogCatchException(logger, function, exception);
14+
logger.LogCatchException(function, exception);
1515
return false;
1616
}
1717

@@ -20,7 +20,7 @@ internal bool LogAndHandle(
2020
[CallerMemberName] string function = "unknown"
2121
)
2222
{
23-
LogCatchException(logger, function, exception);
23+
logger.LogCatchException(function, exception);
2424
return true;
2525
}
2626
}

0 commit comments

Comments
 (0)