feat(i18n): add ClaudeDo.Localization project with nested-JSON locale parser
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
<Project Path="src/ClaudeDo.Worker/ClaudeDo.Worker.csproj" />
|
<Project Path="src/ClaudeDo.Worker/ClaudeDo.Worker.csproj" />
|
||||||
<Project Path="src/ClaudeDo.Installer/ClaudeDo.Installer.csproj" />
|
<Project Path="src/ClaudeDo.Installer/ClaudeDo.Installer.csproj" />
|
||||||
<Project Path="src/ClaudeDo.Releases/ClaudeDo.Releases.csproj" />
|
<Project Path="src/ClaudeDo.Releases/ClaudeDo.Releases.csproj" />
|
||||||
|
<Project Path="src/ClaudeDo.Localization/ClaudeDo.Localization.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tests/">
|
<Folder Name="/tests/">
|
||||||
<Project Path="tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj" />
|
<Project Path="tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj" />
|
||||||
@@ -13,5 +14,6 @@
|
|||||||
<Project Path="tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj" />
|
<Project Path="tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj" />
|
||||||
<Project Path="tests/ClaudeDo.Installer.Tests/ClaudeDo.Installer.Tests.csproj" />
|
<Project Path="tests/ClaudeDo.Installer.Tests/ClaudeDo.Installer.Tests.csproj" />
|
||||||
<Project Path="tests/ClaudeDo.Releases.Tests/ClaudeDo.Releases.Tests.csproj" />
|
<Project Path="tests/ClaudeDo.Releases.Tests/ClaudeDo.Releases.Tests.csproj" />
|
||||||
|
<Project Path="tests/ClaudeDo.Localization.Tests/ClaudeDo.Localization.Tests.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
10
src/ClaudeDo.Localization/ClaudeDo.Localization.csproj
Normal file
10
src/ClaudeDo.Localization/ClaudeDo.Localization.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="ClaudeDo.Localization.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
15
src/ClaudeDo.Localization/LocaleFile.cs
Normal file
15
src/ClaudeDo.Localization/LocaleFile.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace ClaudeDo.Localization;
|
||||||
|
|
||||||
|
public sealed class LocaleFile
|
||||||
|
{
|
||||||
|
public LocaleFile(string code, string name, IReadOnlyDictionary<string, string> strings)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
Name = name;
|
||||||
|
Strings = strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Code { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public IReadOnlyDictionary<string, string> Strings { get; }
|
||||||
|
}
|
||||||
46
src/ClaudeDo.Localization/LocaleJson.cs
Normal file
46
src/ClaudeDo.Localization/LocaleJson.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Localization;
|
||||||
|
|
||||||
|
public static class LocaleJson
|
||||||
|
{
|
||||||
|
public static LocaleFile Parse(string json)
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
|
||||||
|
var code = "";
|
||||||
|
var name = "";
|
||||||
|
if (root.TryGetProperty("metadata", out var meta) && meta.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
if (meta.TryGetProperty("code", out var c)) code = c.GetString() ?? "";
|
||||||
|
if (meta.TryGetProperty("name", out var n)) name = n.GetString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var strings = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||||
|
foreach (var prop in root.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (prop.NameEquals("metadata")) continue;
|
||||||
|
Flatten(prop.Name, prop.Value, strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LocaleFile(code, name, strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Flatten(string prefix, JsonElement el, IDictionary<string, string> into)
|
||||||
|
{
|
||||||
|
switch (el.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.Object:
|
||||||
|
foreach (var p in el.EnumerateObject())
|
||||||
|
Flatten($"{prefix}.{p.Name}", p.Value, into);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.String:
|
||||||
|
into[prefix] = el.GetString() ?? "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
into[prefix] = el.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../../src/ClaudeDo.Localization/ClaudeDo.Localization.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
38
tests/ClaudeDo.Localization.Tests/LocaleJsonTests.cs
Normal file
38
tests/ClaudeDo.Localization.Tests/LocaleJsonTests.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using ClaudeDo.Localization;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Localization.Tests;
|
||||||
|
|
||||||
|
public class LocaleJsonTests
|
||||||
|
{
|
||||||
|
private const string Sample = """
|
||||||
|
{
|
||||||
|
"metadata": { "code": "en", "name": "English" },
|
||||||
|
"settings": { "save": "Save", "general": { "model": "Model" } },
|
||||||
|
"tasks": { "addPlaceholder": "Add a task" }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_reads_metadata()
|
||||||
|
{
|
||||||
|
var f = LocaleJson.Parse(Sample);
|
||||||
|
Assert.Equal("en", f.Code);
|
||||||
|
Assert.Equal("English", f.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_flattens_nested_keys_to_dot_paths()
|
||||||
|
{
|
||||||
|
var f = LocaleJson.Parse(Sample);
|
||||||
|
Assert.Equal("Save", f.Strings["settings.save"]);
|
||||||
|
Assert.Equal("Model", f.Strings["settings.general.model"]);
|
||||||
|
Assert.Equal("Add a task", f.Strings["tasks.addPlaceholder"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_excludes_metadata_from_strings()
|
||||||
|
{
|
||||||
|
var f = LocaleJson.Parse(Sample);
|
||||||
|
Assert.False(f.Strings.ContainsKey("metadata.code"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user