diff --git a/src/ClaudeDo.Localization/LocaleStore.cs b/src/ClaudeDo.Localization/LocaleStore.cs new file mode 100644 index 0000000..c752b79 --- /dev/null +++ b/src/ClaudeDo.Localization/LocaleStore.cs @@ -0,0 +1,31 @@ +namespace ClaudeDo.Localization; + +public sealed class LocaleStore +{ + private readonly Dictionary _byCode; + + private LocaleStore(Dictionary byCode) => _byCode = byCode; + + public IReadOnlyList Available => _byCode.Values.ToList(); + + public bool TryGet(string code, out LocaleFile? file) => _byCode.TryGetValue(code, out file); + + public static LocaleStore Load(string folder) + { + var byCode = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (Directory.Exists(folder)) + { + foreach (var path in Directory.EnumerateFiles(folder, "*.json")) + { + try + { + var file = LocaleJson.Parse(File.ReadAllText(path)); + if (!string.IsNullOrWhiteSpace(file.Code)) + byCode[file.Code] = file; + } + catch { /* skip malformed locale files */ } + } + } + return new LocaleStore(byCode); + } +} diff --git a/tests/ClaudeDo.Localization.Tests/LocaleStoreTests.cs b/tests/ClaudeDo.Localization.Tests/LocaleStoreTests.cs new file mode 100644 index 0000000..1c7d8cb --- /dev/null +++ b/tests/ClaudeDo.Localization.Tests/LocaleStoreTests.cs @@ -0,0 +1,38 @@ +using ClaudeDo.Localization; + +namespace ClaudeDo.Localization.Tests; + +public class LocaleStoreTests +{ + private static string WriteTempLocales(params (string file, string json)[] files) + { + var dir = Path.Combine(Path.GetTempPath(), "loc_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + foreach (var (file, json) in files) + File.WriteAllText(Path.Combine(dir, file), json); + return dir; + } + + [Fact] + public void Load_discovers_all_json_files() + { + var dir = WriteTempLocales( + ("en.json", """{ "metadata": { "code": "en", "name": "English" }, "a": { "b": "x" } }"""), + ("de.json", """{ "metadata": { "code": "de", "name": "Deutsch" }, "a": { "b": "y" } }""")); + + var store = LocaleStore.Load(dir); + + Assert.Equal(2, store.Available.Count); + Assert.True(store.TryGet("en", out var en)); + Assert.Equal("x", en!.Strings["a.b"]); + Assert.True(store.TryGet("de", out var de)); + Assert.Equal("Deutsch", de!.Name); + } + + [Fact] + public void Load_returns_empty_when_folder_missing() + { + var store = LocaleStore.Load(Path.Combine(Path.GetTempPath(), "does_not_exist_" + Guid.NewGuid())); + Assert.Empty(store.Available); + } +}