Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 64 additions & 50 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
- Converts to JSON and generates C# code files
- Target framework: .NET 10.0

- **LanguageTagsTests** (`LanguageTagsTests/LanguageTagsTests.csproj`)
- xUnit test suite with comprehensive coverage
- Uses AwesomeAssertions for test assertions
- Target framework: .NET 10.0
- **LanguageTagsTests** (`LanguageTagsTests/LanguageTagsTests.csproj`)
- xUnit test suite with comprehensive coverage
- Uses AwesomeAssertions for test assertions
- Target framework: .NET 10.0

### Key Directories

Expand All @@ -47,6 +47,20 @@
- `build-release-task.yml`, `build-library-task.yml`: Build tasks
- `get-version-task.yml`, `build-datebadge-task.yml`: Version and badge generation

### Project Configuration

- **Directory.Build.props**: Common MSBuild properties shared across all projects
(`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, `AnalysisMode`,
`EnableNETAnalyzers`, `ArtifactsPath`, `IsPackable`, `ManagePackageVersionsCentrally`)
live here at the solution root. Only add a property to a `.csproj` when it is
specific to that project or requires an explicit override of the shared default.

- **Directory.Packages.props**: All NuGet package versions are centralised here via
`PackageVersion` items. Individual `.csproj` files use `PackageReference Include="..."`
with no `Version` attribute. Asset metadata (`PrivateAssets`, `IncludeAssets`) stays
in the `.csproj` `PackageReference` element. Use `VersionOverride` only when a project
genuinely requires a different version from the central default.

## Core Components

### LanguageTag Class (LanguageTag.cs)
Expand All @@ -73,9 +87,9 @@ The main public API for working with language tags:
- `PrivateUse`: PrivateUseTag object
- `IsValid`: Property to check if tag is valid

**Instance Methods:**
- `Validate()`: Verify structural correctness
- `Normalize()`: Return normalized copy of tag (does not validate)
**Instance Methods:**
- `Validate()`: Verify structural correctness
- `Normalize()`: Return normalized copy of tag (does not validate)
- `ToString()`: String representation
- `Equals()`: Equality comparison (case-insensitive)
- `GetHashCode()`: Hash code for collections
Expand All @@ -102,7 +116,7 @@ Fluent builder for constructing language tags:
- `PrivateUseAdd(string value)`: Add private use tag
- `PrivateUseAddRange(IEnumerable<string> values)`: Add multiple private use tags
- `Build()`: Return constructed tag (no validation)
- `Normalize()`: Return normalized tag (no validation)
- `Normalize()`: Return normalized tag (no validation)

### LanguageTagParser Class (LanguageTagParser.cs)

Expand Down Expand Up @@ -136,54 +150,54 @@ Provides language code conversion and matching:

Static class for configuring global logging for the entire library:

**Properties:**
- `LoggerFactory`: Gets or sets the global logger factory for creating category loggers

**Methods:**
- `SetFactory(ILoggerFactory loggerFactory)`: Configure the library to use a logger factory
- `TrySetFactory(ILoggerFactory loggerFactory)`: Set factory only if none is configured

**Logger Resolution Priority:**
1. `LoggerFactory` property (when not `NullLoggerFactory`)
2. `NullLogger.Instance` (default fallback)
**Properties:**
- `LoggerFactory`: Gets or sets the global logger factory for creating category loggers
**Methods:**
- `SetFactory(ILoggerFactory loggerFactory)`: Configure the library to use a logger factory
- `TrySetFactory(ILoggerFactory loggerFactory)`: Set factory only if none is configured
**Logger Resolution Priority:**
1. `LoggerFactory` property (when not `NullLoggerFactory`)
2. `NullLogger.Instance` (default fallback)

**Important Notes:**
- Loggers are created and cached at time of use by each class instance
- Changes to `LoggerFactory` after a logger is created do not affect existing cached loggers
- Only new logger requests use updated configuration
- Changes to `LoggerFactory` after a logger is created do not affect existing cached loggers
- Only new logger requests use updated configuration

### Data Models

#### Iso6392Data.cs
- ISO 639-2 language codes (3-letter bibliographic/terminologic codes)
- **Public Methods:**
- `Create()`: Load embedded data
- `FromDataAsync(string fileName)`: Load from file
- `FromJsonAsync(string fileName)`: Load from JSON
- `Find(string? languageTag, bool includeDescription)`: Find record by tag
- **Internal Methods:** `SaveJsonAsync(string fileName)`, `SaveCodeAsync(string fileName)`
#### Iso6392Data.cs
- ISO 639-2 language codes (3-letter bibliographic/terminologic codes)
- **Public Methods:**
- `Create()`: Load embedded data
- `FromDataAsync(string fileName)`: Load from file
- `FromJsonAsync(string fileName)`: Load from JSON
- `Find(string? languageTag, bool includeDescription)`: Find record by tag
- **Internal Methods:** `SaveJsonAsync(string fileName)`, `SaveCodeAsync(string fileName)`
- **Record Properties:** `Part2B`, `Part2T`, `Part1`, `RefName`

#### Iso6393Data.cs
- ISO 639-3 language codes (comprehensive language codes)
- **Public Methods:**
- `Create()`: Load embedded data
- `FromDataAsync(string fileName)`: Load from file
- `FromJsonAsync(string fileName)`: Load from JSON
- `Find(string? languageTag, bool includeDescription)`: Find record by tag
- **Internal Methods:** `SaveJsonAsync(string fileName)`, `SaveCodeAsync(string fileName)`
#### Iso6393Data.cs
- ISO 639-3 language codes (comprehensive language codes)
- **Public Methods:**
- `Create()`: Load embedded data
- `FromDataAsync(string fileName)`: Load from file
- `FromJsonAsync(string fileName)`: Load from JSON
- `Find(string? languageTag, bool includeDescription)`: Find record by tag
- **Internal Methods:** `SaveJsonAsync(string fileName)`, `SaveCodeAsync(string fileName)`
- **Record Properties:** `Id`, `Part2B`, `Part2T`, `Part1`, `Scope`, `LanguageType`, `RefName`, `Comment`

#### Rfc5646Data.cs
- RFC 5646 / BCP 47 language subtag registry
- **Public Methods:**
- `Create()`: Load embedded data
- `FromDataAsync(string fileName)`: Load from file
- `FromJsonAsync(string fileName)`: Load from JSON
- `Find(string? languageTag, bool includeDescription)`: Find record by tag
- **Properties:** `FileDate`, `RecordList`
- **Internal Methods:** `SaveJsonAsync(string fileName)`, `SaveCodeAsync(string fileName)`
- **Record Properties:** `Type`, `Tag`, `SubTag`, `Description` (ImmutableArray), `Added`, `SuppressScript`, `Scope`, `MacroLanguage`, `Deprecated`, `Comments` (ImmutableArray), `Prefix` (ImmutableArray), `PreferredValue`, `TagValue`
#### Rfc5646Data.cs
- RFC 5646 / BCP 47 language subtag registry
- **Public Methods:**
- `Create()`: Load embedded data
- `FromDataAsync(string fileName)`: Load from file
- `FromJsonAsync(string fileName)`: Load from JSON
- `Find(string? languageTag, bool includeDescription)`: Find record by tag
- **Properties:** `FileDate`, `RecordList`
- **Internal Methods:** `SaveJsonAsync(string fileName)`, `SaveCodeAsync(string fileName)`
- **Record Properties:** `Type`, `Tag`, `SubTag`, `Description` (ImmutableArray), `Added`, `SuppressScript`, `Scope`, `MacroLanguage`, `Deprecated`, `Comments` (ImmutableArray), `Prefix` (ImmutableArray), `PreferredValue`, `TagValue`
- **Enums:**
- `RecordType`: None, Language, ExtLanguage, Script, Variant, Grandfathered, Region, Redundant
- `RecordScope`: None, MacroLanguage, Collection, Special, PrivateUse
Expand Down Expand Up @@ -288,14 +302,14 @@ LanguageTag tag = LanguageTag.ParseOrDefault(input); // Falls back to "und"

## Recent API Changes

### Changed (Breaking)
### Changed (Breaking)
- `LanguageTagParser` is now internal (use `LanguageTag.Parse()` instead)
- Properties changed from `IList<string>` to `ImmutableArray<string>`:
- `VariantList` → `Variants`
- `ExtensionList` → `Extensions`
- `TagList` → `Tags`
- Data file APIs are async-only and use static creators: `FromDataAsync()`/`FromJsonAsync()`
- Logging configuration now uses `ILoggerFactory` only; `ILogger` support was removed from `LogOptions`
- Data file APIs are async-only and use static creators: `FromDataAsync()`/`FromJsonAsync()`
- Logging configuration now uses `ILoggerFactory` only; `ILogger` support was removed from `LogOptions`
- Tag construction requires use of factory methods or builder (constructors are internal)

### Added (Non-Breaking)
Expand All @@ -304,7 +318,7 @@ LanguageTag tag = LanguageTag.ParseOrDefault(input); // Falls back to "und"
- `LanguageTag.IsValid`: Property for validation
- `LanguageTag.FromLanguage()`, `FromLanguageRegion()`, `FromLanguageScriptRegion()`: Factory methods
- `IEquatable<LanguageTag>` implementation with operators
- `LogOptions` static class for global logging configuration with `ILoggerFactory`
- `LogOptions` static class for global logging configuration with `ILoggerFactory`
- `ExtensionTag` and `PrivateUseTag` are now sealed records with `Normalize()` and case-insensitive `Equals()` methods
- Comprehensive XML documentation for all public APIs

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build-library-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ jobs:
- name: Build library project step
run: |
dotnet build ${{ env.PROJECT_FILE }} \
--output ${{ runner.temp }}/publish \
-property:OutputPath=${{ runner.temp }}/publish/ \
-property:PackageOutputPath=${{ runner.temp }}/publish/ \
--configuration ${{ env.IS_MAIN_BRANCH == 'true' && 'Release' || 'Debug' }} \
-property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \
-property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} \
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/run-periodic-codegen-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ on:
- cron: '0 2 * * MON'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Workflow always checks out and targets main/codegen
group: codegen-main
cancel-in-progress: true

jobs:
Expand Down
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": ".Net Build",
"program": "${workspaceFolder}/LanguageTagsCreate/bin/Debug/net10.0/LanguageTagsCreate.dll",
"program": "${workspaceFolder}/.artifacts/bin/LanguageTagsCreate/debug/LanguageTagsCreate.dll",
"args": [
"--codepath",
"${workspaceFolder}",
],
"cwd": "${workspaceFolder}/LanguageTagsCreate/bin/Debug/net10.0",
"cwd": "${workspaceFolder}/.artifacts/bin/LanguageTagsCreate/debug",
"console": "internalConsole",
Comment on lines 8 to 15
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preLaunchTask runs .Net Build, which (per .vscode/tasks.json) just executes dotnet build with default output paths. This launch config now points program/cwd at /.artifacts/..., so debugging will fail unless the build is also configured to emit binaries there. Either update the build task/MSBuild props to output into .artifacts (e.g., enable the SDK artifacts output feature / set OutputPath) or revert program/cwd to the standard bin/Debug/<tfm> location.

Copilot uses AI. Check for mistakes.
"stopAtEntry": false
}
Expand Down
31 changes: 20 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,28 @@ For comprehensive coding standards and detailed conventions, refer to [`.github/
- xUnit test suite with comprehensive test coverage
- Uses AwesomeAssertions for test assertions

### Project Configuration

- Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, etc.)
live in `Directory.Build.props` at the solution root. Do not duplicate these in individual `.csproj`
files — only add a property to a `.csproj` when it is project-specific or overrides the shared default.
- All NuGet package versions are centralised in `Directory.Packages.props`. `PackageReference` elements
in `.csproj` files must not include a `Version` attribute. Asset metadata (`PrivateAssets`,
`IncludeAssets`) stays in the `.csproj` `PackageReference` element.

### Key Components

**Public API Classes:**

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

**Internal Classes:**

Expand Down
14 changes: 14 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AnalysisLevel>latest-all</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArtifactsPath is defined here, but nothing enables the SDK's artifacts output layout, so local builds will still emit to bin//obj/ by default. Since .vscode/launch.json was updated to reference /.artifacts/bin/..., consider explicitly enabling artifacts output (or setting OutputPath/BaseOutputPath consistently) so the .artifacts path actually exists after a normal dotnet build.

Suggested change
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseArtifactsOutput>true</UseArtifactsOutput>

Copilot uses AI. Check for mistakes.
<ArtifactsPath>$(MSBuildThisFileDirectory).artifacts</ArtifactsPath>
<IsPackable>false</IsPackable>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
18 changes: 18 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project>
<ItemGroup>
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.201" />
<PackageVersion Include="Serilog" Version="4.3.1" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.5" />
<PackageVersion Include="xunit.analyzers" Version="1.27.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
</ItemGroup>
</Project>
Loading
Loading