BccCode.I18N.SourceGen
0.2.0
See the version list below for details.
dotnet add package BccCode.I18N.SourceGen --version 0.2.0
NuGet\Install-Package BccCode.I18N.SourceGen -Version 0.2.0
<PackageReference Include="BccCode.I18N.SourceGen" Version="0.2.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="BccCode.I18N.SourceGen" Version="0.2.0" />
<PackageReference Include="BccCode.I18N.SourceGen"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add BccCode.I18N.SourceGen --version 0.2.0
#r "nuget: BccCode.I18N.SourceGen, 0.2.0"
#:package BccCode.I18N.SourceGen@0.2.0
#addin nuget:?package=BccCode.I18N.SourceGen&version=0.2.0
#tool nuget:?package=BccCode.I18N.SourceGen&version=0.2.0
BccCode.I18N.SourceGen
A Roslyn incremental source generator that reads JSON translation files (additional files) and generates strongly-typed C# classes for internationalization (i18n).
You must build this project to see the result (generated code) in the IDE.
How It Works
Add your translation files as AdditionalFiles in your .csproj. Each file is one language, named by its file name (e.g. en.json, no.json). The fallback language is English (en) unless FallbackLanguage says otherwise (see below).
The JSON files can contain nested objects (mapped to nested static classes) and string values (mapped to properties or constants). Strings support:
- Interpolation:
{paramName}placeholders become method parameters (e.g."Hello {name}"→Language.hello(object name)) - Pluralization: pipe-separated variants
"no items | one item | {count} items"— the first parameter becomesint count, and the correct variant is selected at runtime- A key is pluralized when any language uses pipe-separated variants. Languages that use a single form for that key return it for every count. When the fallback language has no plural variants for the key, the count parameter is always named
count(use{count}in the other languages).
- A key is pluralized when any language uses pipe-separated variants. Languages that use a single form for that key return it for every count. When the fallback language has no plural variants for the key, the count parameter is always named
Generated Classes
The generator produces three generated classes from the same JSON translation pipeline:
Language (I18NLanguageGenerator)
Generated file: Language.g.cs
A strongly-typed static class that mirrors the structure of your JSON files. Each key becomes a static string property or method that returns the correct translation for CultureInfo.CurrentUICulture at runtime, falling back to English (en) if the current culture is not available.
Example — given en.json:
{
"message": { "hello": "hello world" },
"plural": { "car": "car | cars" }
}
Usage:
string greeting = Language.message.hello; // plain string
string cars = Language.plural.car(3); // pluralized: "cars"
LanguageKeys (I18NLanguageKeyGenerator)
Generated file: LanguageKeys.g.cs
A static class of const string keys that mirror the JSON structure. Useful for returning keys to the frontend in an API response.
Example:
string key = LanguageKeys.message.hello; // == "message.hello"
LanguageStrings (I18NLanguageDictGenerator)
Generated file: LanguageStrings.g.cs
A static class that looks up translations by dot-separated key path for the two-letter ISO language code of CultureInfo.CurrentUICulture, using the fallback language when there is no file for that language. The lookups are generated as switch expressions, so hot reload can apply changes to translation files without restarting the app. Exposes two lookup methods:
string value = LanguageStrings.GetString("message.hello"); // returns key if not found
string? value = LanguageStrings.GetStringOrNull("message.hello"); // returns null if not found
How To?
How to add translation files
In your .csproj, include JSON files as AdditionalFiles:
<ItemGroup>
<AdditionalFiles Include="no.json" I18NTranslation="true" />
<AdditionalFiles Include="en.json" I18NTranslation="true" />
</ItemGroup>
Mark translation files with I18NTranslation="true". When at least one AdditionalFiles item is marked, only marked .json files are read as translations. Other JSON files added as AdditionalFiles (for example configuration for other analyzers) are ignored.
If no file is marked, every .json file in AdditionalFiles is treated as a translation file, as in earlier versions. Marking your files is recommended so that unrelated JSON files are not parsed as translations.
The NuGet package registers the I18NTranslation metadata automatically. If you use a ProjectReference and import build/BccCode.I18N.SourceGen.props yourself, that import makes the metadata visible to the generator.
How to configure the fallback language
By default the fallback language is English (en). You can change this by setting FallbackLanguage in your .csproj:
<PropertyGroup>
<FallbackLanguage>no</FallbackLanguage>
</PropertyGroup>
The value must match the filename of one of your translation JSON files (without the .json extension). All three generated classes — Language, LanguageKeys, and LanguageStrings — will use this language as the default when no matching translation is found for CultureInfo.CurrentUICulture.
How to configure the generated namespace
The generated classes default to the consuming project's RootNamespace. You can override that with GeneratedNamespace:
<PropertyGroup>
<GeneratedNamespace>MyApp.Localization</GeneratedNamespace>
</PropertyGroup>
With that configuration, the generator emits MyApp.Localization.Language, MyApp.Localization.LanguageKeys, and MyApp.Localization.LanguageStrings instead of generating them in the global namespace.
Note for project references: When referencing the generator as a
ProjectReference(rather than via NuGet), import the props file manually soFallbackLanguageandGeneratedNamespaceare visible to the analyzer:<Import Project="..\BccCode.I18N.SourceGen\build\BccCode.I18N.SourceGen.props" />When consumed via NuGet, this import happens automatically.
Packaging and Integration Testing
The NuGet package is built as an analyzer package:
BccCode.I18N.SourceGen.dllis packed underanalyzers/dotnet/cs- normal
lib/output is disabled so consumers do not reference the generator assembly directly build/BccCode.I18N.SourceGen.propsis included soFallbackLanguageremains compiler-visible for package consumers
The repository contains two integration test projects under BccCode.I18N.SourceGen/BccCode.I18N.SourceGen:
BccCode.I18N.SourceGen.IntegrationTeststests consumption through an analyzer-styleProjectReferenceBccCode.I18N.SourceGen.NuGetIntegrationTeststests the packed.nupkgthroughPackageReference
Use the following commands from the nested repository root BccCode.I18N.SourceGen/BccCode.I18N.SourceGen:
dotnet test .\BccCode.I18N.SourceGen.IntegrationTests\BccCode.I18N.SourceGen.IntegrationTests.csproj
dotnet pack .\BccCode.I18N.SourceGen\BccCode.I18N.SourceGen.csproj -c Release -p:PackageVersion=0.0.0-local -o .\artifacts
Remove-Item .\packages -Recurse -Force -ErrorAction SilentlyContinue
dotnet restore .\BccCode.I18N.SourceGen.NuGetIntegrationTests -p:BccCodeI18NSourceGenPackageVersion=0.0.0-local --packages .\packages
dotnet test .\BccCode.I18N.SourceGen.NuGetIntegrationTests -c Release --no-restore -p:BccCodeI18NSourceGenPackageVersion=0.0.0-local
CI runs the same steps (.github/workflows/test.yml) on every pull request and push to main.
Releasing
Push a tag vX.Y.Z (or a prerelease such as vX.Y.Z-beta.1) on a commit that is on main. .github/workflows/publish-nuget.yml then runs all tests, packs with that version, tests the packed package, pushes it to nuget.org via trusted publishing and creates a GitHub release with generated notes and the .nupkg attached.
This mirrors the source-generator publishing flow described by Andrew Lock: first verify compiler integration through an analyzer-style project reference, then verify the actual packed analyzer from a local NuGet source without polluting the machine-wide NuGet cache.
Clearing the local packages folder before restore is important when reusing the same package version locally, otherwise NuGet can keep an older packed analyzer and stale build props.
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.