Files
MeWeb/tools/strings.cs
T
thepraandClaude Opus 5.5 8eaf9f36fc
Build / Desktop, server and tools (push) Successful in 33s
Every official language, in JSON: 113 listed, five shipped so far
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
2026-10-15 01:18:13 +02:00

72 lines
3.1 KiB
C#

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)");
}