Every official language, in JSON: 113 listed, five shipped so far
Build / Desktop, server and tools (push) Successful in 33s

The language list comes from CLDR (official in a UN member state, plus the
six UN languages), with autonyms and text direction supplied for the 21
that ICU does not know. Strings move from resx to one JSON file per
language, looked up by code, so a language unknown to ICU or MSBuild still
loads; tools/strings.cs checks keys and placeholders in CI. Arabic, Hindi
and Chinese drafts prove right-to-left and system-font fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-15 01:18:13 +02:00
1 parent 7589121846
commit 8eaf9f36fc
17 files changed
+1072 -99

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
var root = FindRoot();
var folder = Path.Combine(root, "src", "MeWeb.App", "Strings");
var options = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
var placeholder = new Regex(@"\{\d+\}");
if (args.Length >= 1 && args[0] == "check") return Check();
if (args.Length == 3 && args[0] == "set") return Set(args[1], args[2]);
Console.Error.WriteLine("usage: dotnet run tools/strings.cs -- check | set <Key> <values.json> (values.json maps a language code to its text)");
return 2;
int Check()
{
var english = Read("en");
var problems = 0;
foreach (var file in Directory.GetFiles(folder, "*.json").OrderBy(f => f, StringComparer.Ordinal))
{
var code = Path.GetFileNameWithoutExtension(file);
if (code == "en") continue;
var values = Read(code);
var missing = english.Keys.Where(k => !values.ContainsKey(k)).ToList();
var extra = values.Keys.Where(k => !english.ContainsKey(k)).ToList();
var mismatched = english.Keys.Where(values.ContainsKey)
.Where(k => !placeholder.Matches(english[k]).Select(m => m.Value).Order().SequenceEqual(placeholder.Matches(values[k]).Select(m => m.Value).Order())).ToList();
if (missing.Count + extra.Count + mismatched.Count == 0) continue;
problems++;
Console.WriteLine($"{code}: missing [{string.Join(", ", missing)}] extra [{string.Join(", ", extra)}] placeholders [{string.Join(", ", mismatched)}]");
}
Console.WriteLine(problems == 0 ? $"strings: OK ({english.Count} keys)" : $"strings: {problems} language(s) to fix");
return problems == 0 ? 0 : 1;
}
int Set(string key, string valuesPath)
{
var values = JsonNode.Parse(File.ReadAllText(valuesPath)).AsObject();
if (values["en"] is null) throw new InvalidOperationException("values.json must carry at least \"en\"");
foreach (var (code, value) in values)
{
var strings = Read(code);
strings[key] = (string)value;
Write(code, strings);
}
Console.WriteLine($"{key}: set in {values.Count} language(s)");
return 0;
}
SortedDictionary<string, string> Read(string code)
{
var path = Path.Combine(folder, $"{code}.json");
if (!File.Exists(path)) return new SortedDictionary<string, string>(StringComparer.Ordinal);
var node = JsonNode.Parse(File.ReadAllText(path)).AsObject();
return new SortedDictionary<string, string>(node.ToDictionary(p => p.Key, p => (string)p.Value), StringComparer.Ordinal);
}
void Write(string code, SortedDictionary<string, string> strings)
{
var node = new JsonObject();
foreach (var (key, value) in strings) node[key] = value;
File.WriteAllText(Path.Combine(folder, $"{code}.json"), node.ToJsonString(options) + "\n");
}
static string FindRoot()
{
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "MeWeb.slnx"))) dir = dir.Parent;
return dir?.FullName ?? throw new InvalidOperationException("run from inside the repository (MeWeb.slnx not found)");
}