#nullable enable using System.Collections; using System.Collections.Frozen; using Dapper; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Data; using Microsoft.EntityFrameworkCore; using System; using System.Runtime.InteropServices; using Newtonsoft.Json.Linq; using static System.Net.Mime.MediaTypeNames; using YamlDotNet.Core.Tokens; namespace BTCPayServer.Services { public partial class Translations : IEnumerable> { public record Diff(string Key) { public record Deleted(string Key, string OldValue) : Diff(Key); public record Added(string Key, string Value) : Diff(Key); public record Modified(string Key, string NewValue, string OldValue) : Diff(Key); } public static bool TryCreateFromText(string text, [MaybeNullWhen(false)] out Translations translations) { translations = null; try { translations = CreateFromText(text); return true; } catch { return false; } } public static Translations CreateFromJson(string text) { text = (text ?? "{}"); var translations = new List<(string key, string? value)>(); foreach (var prop in JObject.Parse(text).Properties()) { var v = prop.Value.Value(); if (string.IsNullOrEmpty(v)) translations.Add((prop.Name, prop.Name)); else translations.Add((prop.Name, v)); } return new Translations(translations .Select(t => KeyValuePair.Create(t.key, t.value))); } public static Translations CreateFromText(string text) { text = (text ?? "").Replace("\r\n", "\n"); var translations = new List<(string key, string? value)>(); foreach (var line in text.Split("\n", StringSplitOptions.RemoveEmptyEntries)) { var splitted = line.Split("=>", StringSplitOptions.RemoveEmptyEntries); if (splitted is [var key, var value]) { translations.Add((key, value)); } else if (splitted is [var key2]) { translations.Add((key2, key2)); } } return new Translations(translations .Select(t => KeyValuePair.Create(t.key, t.value))); } public Translations(IEnumerable> records) : this (records, null) { } public Translations(IEnumerable> records, Translations? fallback) { Dictionary thisRecords = new Dictionary(); foreach (var r in records) { var v = r.Value?.Trim(); if (string.IsNullOrEmpty(v)) continue; thisRecords.TryAdd(r.Key.Trim(), v); } if (fallback is not null) { foreach (var r in fallback.Records) { thisRecords.TryAdd(r.Key, r.Value); } } Records = thisRecords.ToFrozenDictionary(); } public readonly FrozenDictionary Records; public string? this[string? key] => key is null ? null : Records.TryGetValue(key, out var v) ? v : null; public Diff[] CalculateDiff(Translations translations) { List diff = new List(translations.Records.Count + 10); foreach (var kv in translations) { if (Records.TryGetValue(kv.Key, out var oldValue)) { if (oldValue != kv.Value) diff.Add(new Diff.Modified(kv.Key, kv.Value, oldValue)); } else { diff.Add(new Diff.Added(kv.Key, kv.Value)); } } foreach (var kv in this) { if (!translations.Records.ContainsKey(kv.Key)) diff.Add(new Diff.Deleted(kv.Key, kv.Value)); } return diff.ToArray(); } public Translations WithFallback(Translations? fallback) { return new Translations(this!, fallback); } public IEnumerator> GetEnumerator() { return Records.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public string ToJsonFormat() { JObject obj = new JObject(); foreach (var record in Records) { obj.Add(record.Key, record.Value); } return obj.ToString(Newtonsoft.Json.Formatting.Indented); } public string ToTextFormat() { return string.Join('\n', Records.OrderBy(r => r.Key).Select(r => $"{r.Key} => {r.Value}").ToArray()); } } }