Skip to content

Commit c82e6ba

Browse files
committed
Add JSON localization NuGet packages with release workflow integration
- Add LocalizationManager.JsonLocalization runtime package (net7.0/8.0/9.0) - Add LocalizationManager.JsonLocalization.Generator source generator package - Add 136 unit tests for JSON localization - Add 4 sample projects demonstrating usage - Integrate NuGet publishing into release.sh and release.yml workflow - Sync package versions with main LRM version (0.6.24)
1 parent 6bd0654 commit c82e6ba

55 files changed

Lines changed: 4357 additions & 21 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,49 @@ jobs:
7070
./build.sh
7171
echo "✓ Built all platforms"
7272
73+
# ═══════════════════════════════════════════════════════════════
74+
# NuGet Packages
75+
# ═══════════════════════════════════════════════════════════════
76+
77+
- name: Build and push NuGet packages
78+
env:
79+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
80+
run: |
81+
VERSION=${{ steps.version.outputs.version }}
82+
83+
# Extract release notes for this version from CHANGELOG.md
84+
# Get content between "## [X.Y.Z]" and the next "## [" or end
85+
RELEASE_NOTES=$(sed -n "/^## \[$VERSION\]/,/^## \[/p" CHANGELOG.md | sed '1d;$d' | head -50)
86+
87+
if [ -z "$RELEASE_NOTES" ]; then
88+
RELEASE_NOTES="See https://github.com/nprotopapas/LocalizationManager/releases/tag/v$VERSION"
89+
fi
90+
91+
# Pack NuGet packages with release notes
92+
dotnet pack LocalizationManager.JsonLocalization/LocalizationManager.JsonLocalization.csproj \
93+
--configuration Release \
94+
--output ./nupkg \
95+
-p:PackageReleaseNotes="$RELEASE_NOTES"
96+
97+
dotnet pack LocalizationManager.JsonLocalization.Generator/LocalizationManager.JsonLocalization.Generator.csproj \
98+
--configuration Release \
99+
--output ./nupkg \
100+
-p:PackageReleaseNotes="$RELEASE_NOTES"
101+
102+
echo "Generated NuGet packages:"
103+
ls -la ./nupkg/*.nupkg
104+
105+
# Push to NuGet.org
106+
for pkg in ./nupkg/*.nupkg; do
107+
echo "Pushing $pkg..."
108+
dotnet nuget push "$pkg" \
109+
--api-key "$NUGET_API_KEY" \
110+
--source https://api.nuget.org/v3/index.json \
111+
--skip-duplicate
112+
done
113+
114+
echo "✓ NuGet packages published"
115+
73116
# ═══════════════════════════════════════════════════════════════
74117
# VS Code Extension - Platform-Specific Builds
75118
# ═══════════════════════════════════════════════════════════════

JSON_BACKEND_IMPLEMENTATION.md

Lines changed: 148 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# JSON Backend Implementation Guide
22

3-
> **Status:** Phase 5.4 Complete - Backup Services Refactored for Multi-Backend Support
3+
> **Status:** Phase 6 In Progress - NuGet Package for JSON Localization
44
> **Target:** Full multi-backend support (RESX + JSON) across CLI/TUI/Web/API/VS Code Extension
55
> **Plus:** NuGet package for consuming JSON localization in .NET apps
66
@@ -2091,40 +2091,168 @@ public override int Execute(CommandContext context, ViewCommandSettings settings
20912091

20922092
---
20932093

2094-
## Phase 6: NuGet Package
2094+
## Phase 6: NuGet Package for JSON Localization
20952095

2096-
> **Goal:** Create standalone NuGet package for consuming JSON localization
2096+
> **Goal:** Create NuGet packages allowing .NET applications to consume JSON localization files managed by LRM
2097+
> **Status:** ✅ Core Implementation Complete - Sample projects working
20972098
20982099
### Progress
20992100

2100-
- [ ] 6.1 Create project structure
2101-
- [ ] 6.2 Implement Localizer class
2102-
- [ ] 6.3 Implement IStringLocalizer
2103-
- [ ] 6.4 Implement EmbeddedResourceLoader
2104-
- [ ] 6.5 Create source generator project
2105-
- [ ] 6.6 Implement ResourcesGenerator
2106-
- [ ] 6.7 Add pluralization rules
2101+
- [x] 6.1 Create project structure (two packages) ✅
2102+
- [x] 6.2 Extract & adapt core code from existing backend ✅
2103+
- [x] 6.3 Implement standalone JsonLocalizer class ✅
2104+
- [x] 6.4 Implement IStringLocalizer integration ✅
2105+
- [x] 6.5 Implement DI extensions (AddJsonLocalization) ✅
2106+
- [x] 6.6 Implement EmbeddedResourceLoader ✅
2107+
- [x] 6.7 Implement FileSystemResourceLoader ✅
2108+
- [x] 6.8 Create source generator project ✅
2109+
- [x] 6.9 Implement ResourcesGenerator ✅
2110+
- [x] 6.10 Create sample projects ✅
2111+
- ConsoleApp.Standalone (file system loading)
2112+
- ConsoleApp.Embedded (embedded resources)
2113+
- WebApp.AspNetCore (IStringLocalizer integration)
2114+
- ConsoleApp.SourceGenerator (strongly-typed access)
2115+
- [x] 6.11 Write unit tests (136 tests for JsonLocalization package)
2116+
- [ ] 6.12 Publish NuGet packages
2117+
2118+
---
2119+
2120+
### Package Architecture
2121+
2122+
**Two NuGet Packages:**
2123+
```
2124+
LocalizationManager.JsonLocalization (Runtime library)
2125+
LocalizationManager.JsonLocalization.Generator (Source generator)
2126+
```
2127+
2128+
**Target Frameworks:**
2129+
```xml
2130+
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
2131+
```
21072132

21082133
---
21092134

21102135
### Project Structure
21112136

21122137
```
21132138
/LocalizationManager.JsonLocalization/
2114-
LocalizationManager.JsonLocalization.csproj
2115-
Localizer.cs
2116-
JsonStringLocalizer.cs
2117-
JsonStringLocalizerFactory.cs
2118-
EmbeddedResourceLoader.cs
2119-
CldrPluralRuleProvider.cs
2120-
ServiceCollectionExtensions.cs
2139+
├── LocalizationManager.JsonLocalization.csproj
2140+
├── JsonLocalizer.cs # Standalone API
2141+
├── JsonStringLocalizer.cs # IStringLocalizer impl
2142+
├── JsonStringLocalizerFactory.cs # IStringLocalizerFactory impl
2143+
├── JsonLocalizationOptions.cs # Configuration options
2144+
├── ServiceCollectionExtensions.cs # AddJsonLocalization()
2145+
├── IResourceLoader.cs # Loader interface
2146+
├── EmbeddedResourceLoader.cs # Load from embedded resources
2147+
├── FileSystemResourceLoader.cs # Load from file system
2148+
├── Core/ # Extracted from main project
2149+
│ ├── Models/
2150+
│ │ ├── ResourceEntry.cs
2151+
│ │ ├── ResourceFile.cs
2152+
│ │ └── LanguageInfo.cs
2153+
│ ├── JsonResourceReader.cs # Adapted from existing
2154+
│ ├── JsonFormatConfiguration.cs
2155+
│ └── PluralResolver.cs # Runtime plural selection
21212156
21222157
/LocalizationManager.JsonLocalization.Generator/
2123-
LocalizationManager.JsonLocalization.Generator.csproj
2124-
ResourcesGenerator.cs
2158+
├── LocalizationManager.JsonLocalization.Generator.csproj
2159+
├── ResourcesGenerator.cs # Source generator
2160+
├── JsonKeyParser.cs # Parse JSON to extract keys
2161+
├── build/
2162+
│ └── LocalizationManager.JsonLocalization.Generator.targets
2163+
```
2164+
2165+
---
2166+
2167+
### Key Design Decisions
2168+
2169+
1. **Reuse Existing Code** (~1800 LOC from existing JSON backend)
2170+
- `JsonResourceReader.cs` - Already has pluralization & format detection
2171+
- `JsonFormatDetector.cs` - Auto-detects standard vs i18next
2172+
- Model classes - Zero dependencies
2173+
2174+
2. **Standalone API (No DI Required)**
2175+
```csharp
2176+
var localizer = new JsonLocalizer("./Resources", "strings");
2177+
var greeting = localizer["Hello"];
2178+
var plural = localizer.Plural("Items", count);
2179+
```
2180+
2181+
3. **ASP.NET Core Integration**
2182+
```csharp
2183+
services.AddJsonLocalization(options => {
2184+
options.ResourcesPath = "Resources";
2185+
options.UseEmbeddedResources = true;
2186+
});
2187+
```
2188+
2189+
4. **Both Resource Loading Strategies**
2190+
- File system: Deploy JSON files alongside application
2191+
- Embedded: Compile JSON into assembly
2192+
2193+
---
2194+
2195+
### Embedded Resources Strategy
2196+
2197+
**Option 1: Runtime Package Only (Manual Setup)**
2198+
```xml
2199+
<ItemGroup>
2200+
<EmbeddedResource Include="Resources\**\*.json" />
2201+
</ItemGroup>
21252202
```
2203+
```csharp
2204+
var localizer = new JsonLocalizer(
2205+
assembly: typeof(Program).Assembly,
2206+
resourceNamespace: "MyApp.Resources",
2207+
baseName: "strings");
2208+
```
2209+
2210+
**Option 2: Source Generator Package (Auto-Embed)**
2211+
```xml
2212+
<ItemGroup>
2213+
<AdditionalFiles Include="Resources\**\*.json" />
2214+
</ItemGroup>
2215+
```
2216+
```csharp
2217+
// Generated Resources class handles everything
2218+
Console.WriteLine(Resources.Welcome);
2219+
```
2220+
2221+
---
2222+
2223+
### Sample Projects
2224+
2225+
Create in `/samples/` folder:
2226+
2227+
| Project | Purpose |
2228+
|---------|---------|
2229+
| `ConsoleApp.Standalone` | Standalone API without DI, file system loading |
2230+
| `ConsoleApp.Embedded` | Embedded resources in DLL |
2231+
| `WebApp.AspNetCore` | ASP.NET Core with IStringLocalizer integration |
2232+
| `WebApp.SourceGenerator` | Strongly-typed access via source generator |
2233+
| `BlazorApp.Wasm` | Blazor WebAssembly example |
2234+
2235+
---
2236+
2237+
### Dependencies
2238+
2239+
**Runtime Package:**
2240+
- `Microsoft.Extensions.Localization.Abstractions` (optional)
2241+
- `Microsoft.Extensions.DependencyInjection.Abstractions` (optional)
2242+
- No mandatory external dependencies (uses System.Text.Json)
2243+
2244+
**Generator Package:**
2245+
- `Microsoft.CodeAnalysis.CSharp` 4.x
2246+
- `Microsoft.CodeAnalysis.Analyzers`
2247+
2248+
---
2249+
2250+
### Testing Strategy
21262251

2127-
*(Detailed implementation deferred - see original plan)*
2252+
1. Unit tests for JsonLocalizer (standalone)
2253+
2. Integration tests for IStringLocalizer with WebApplicationFactory
2254+
3. Generator tests using CSharpGeneratorDriver
2255+
4. Pluralization tests for multiple cultures
21282256

21292257
---
21302258

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>latest</LangVersion>
8+
9+
<!-- Source Generator settings -->
10+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
11+
<IsRoslynComponent>true</IsRoslynComponent>
12+
13+
<!-- NuGet Package Metadata -->
14+
<PackageId>LocalizationManager.JsonLocalization.Generator</PackageId>
15+
<Version>0.6.24</Version>
16+
<Authors>Nikolaos Protopapas</Authors>
17+
<Company>LocalizationManager</Company>
18+
<Product>LocalizationManager.JsonLocalization.Generator</Product>
19+
<Description>Source generator for strongly-typed access to JSON localization resources. Generates compile-time checked resource accessors from JSON localization files.</Description>
20+
<Copyright>Copyright (c) 2025 Nikolaos Protopapas</Copyright>
21+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
22+
<PackageProjectUrl>https://github.com/nprotopapas/LocalizationManager</PackageProjectUrl>
23+
<RepositoryUrl>https://github.com/nprotopapas/LocalizationManager</RepositoryUrl>
24+
<RepositoryType>git</RepositoryType>
25+
<PackageTags>localization;i18n;json;source-generator;roslyn;codegen</PackageTags>
26+
<PackageReadmeFile>README.md</PackageReadmeFile>
27+
28+
<!-- Build settings -->
29+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
30+
<NoWarn>$(NoWarn);CS1591</NoWarn>
31+
32+
<!-- Package as analyzer -->
33+
<IncludeBuildOutput>false</IncludeBuildOutput>
34+
<DevelopmentDependency>true</DevelopmentDependency>
35+
</PropertyGroup>
36+
37+
<ItemGroup>
38+
<None Include="README.md" Pack="true" PackagePath="\" />
39+
<None Include="build\LocalizationManager.JsonLocalization.Generator.targets" Pack="true" PackagePath="build\" />
40+
</ItemGroup>
41+
42+
<ItemGroup>
43+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
44+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
45+
<PackageReference Include="System.Text.Json" Version="8.0.5" PrivateAssets="all" GeneratePathProperty="true" />
46+
</ItemGroup>
47+
48+
<!-- Package the generator as an analyzer -->
49+
<ItemGroup>
50+
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
51+
</ItemGroup>
52+
53+
</Project>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# LocalizationManager.JsonLocalization.Generator
2+
3+
Source generator for strongly-typed access to JSON localization resources.
4+
5+
## Features
6+
7+
- **Compile-time Validation** - Invalid keys caught at build time
8+
- **IntelliSense Support** - Full autocomplete for resource keys
9+
- **Nested Keys** - Supports hierarchical key organization
10+
- **Auto-Embedding** - Automatically embeds JSON files in assembly
11+
12+
## Quick Start
13+
14+
1. Add package reference:
15+
```xml
16+
<PackageReference Include="LocalizationManager.JsonLocalization.Generator" Version="*" />
17+
```
18+
19+
2. Add JSON files as AdditionalFiles:
20+
```xml
21+
<ItemGroup>
22+
<AdditionalFiles Include="Resources\**\*.json" />
23+
</ItemGroup>
24+
```
25+
26+
3. Use generated resources:
27+
```csharp
28+
using MyApp.Resources;
29+
30+
Console.WriteLine(Resources.Welcome);
31+
Console.WriteLine(Resources.Errors.NotFound);
32+
Console.WriteLine(Resources.Items(count: 5)); // Pluralization
33+
```
34+
35+
## Generated Code Example
36+
37+
From `strings.json`:
38+
```json
39+
{
40+
"Welcome": "Welcome!",
41+
"Errors": {
42+
"NotFound": "Not found",
43+
"AccessDenied": "Access denied"
44+
},
45+
"Items": {
46+
"_plural": true,
47+
"one": "{0} item",
48+
"other": "{0} items"
49+
}
50+
}
51+
```
52+
53+
Generated:
54+
```csharp
55+
public static partial class Resources
56+
{
57+
public static string Welcome => Localizer["Welcome"];
58+
59+
public static class Errors
60+
{
61+
public static string NotFound => Localizer["Errors.NotFound"];
62+
public static string AccessDenied => Localizer["Errors.AccessDenied"];
63+
}
64+
65+
public static string Items(int count) => Localizer.Plural("Items", count);
66+
}
67+
```
68+
69+
## License
70+
71+
MIT License - Copyright (c) 2025 Nikolaos Protopapas

0 commit comments

Comments
 (0)