A Roslyn incremental source generator that reads JSON translation files (the format vue-i18n uses) and generates strongly typed C# for them. Texts are checked at compile time and selected from the current culture at run time.
Language: a typed member per key, for exampleLanguage.menu.saveorLanguage.greeting(name).LanguageKeys:const stringkey paths, for example to send keys to a frontend.LanguageStrings: lookups, listings and formatting by key at run time.LocalizedLanguage: the same members asLanguagefor an explicit culture.- Culture matching through parent cultures and language aliases (
nb-NOusesno.json), with per-key fallback to a fallback language. - vue-i18n message syntax: placeholders, plural forms, linked messages and literals, plus .NET format specifiers such as
{date:d}. - Optional
IStringLocalizerimplementation when the project referencesMicrosoft.Extensions.Localization. - Build diagnostics for invalid files, missing or extra keys and placeholder mismatches.
- Generated code is shaped so that hot reload applies translation edits.
dotnet add package BccCode.I18N.SourceGenAdd one JSON file per language, named after the language (en.json, no.json):
{
"greeting": "Hello, {name}!",
"menu": { "save": "Save" },
"files": "no files | one file | {count} files"
}Mark the files and choose the fallback language and namespace in the project file:
<PropertyGroup>
<FallbackLanguage>en</FallbackLanguage>
<GeneratedNamespace>MyApp.Localization</GeneratedNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="en.json" I18NTranslation="true" />
<AdditionalFiles Include="no.json" I18NTranslation="true" />
</ItemGroup>Build once, then use the generated classes:
using MyApp.Localization;
string save = Language.menu.save; // "Save", or "Lagre" for a Norwegian UI culture
string greeting = Language.greeting("Kari"); // "Hello, Kari!"
string files = Language.files(3); // "3 files"
string key = LanguageKeys.menu.save; // "menu.save"
string byKey = LanguageStrings.GetString(key); // "Save"The fallback language defaults to no when FallbackLanguage is not set. Without GeneratedNamespace, the classes are generated in the project's RootNamespace.
- Tutorial: your first translated app
- How-to guides: aliases, explicit cultures, formatting, plurals, vue-i18n, IStringLocalizer, diagnostics, hot reload, ProjectReference
- Reference: MSBuild properties, generated API, message syntax, diagnostics
- Explanation: culture matching and fallback, plural rules, hot reload, vue-i18n, no runtime dependency
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-localCI runs the same steps (.github/workflows/test.yml) on every pull request and push to main.
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.