using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using LocalizationTool.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace LocalizationTool.Services
{
///
/// 语言包仓库(JSON 格式,人类可读,与主程序 TeamAAS 共用同一份 .json 文件)。
///
public static class PackRepository
{
/// 加载目录下所有语言包;zh-CN 识别为源(IsSource)。
public static List Load(string dir)
{
var packs = new List();
foreach (var file in Directory.GetFiles(dir, "*.json"))
{
try
{
var pack = LanguagePackIO.LoadJson(file);
var code = pack.LanguageCode;
packs.Add(new PackFile
{
FilePath = file,
LanguageCode = code,
LanguageName = pack.LanguageName ?? code,
Version = pack.Version,
Author = pack.Author,
IsSource = code == "zh-CN",
Translations = pack.Translations ?? new Dictionary(),
});
}
catch { /* 解析失败的文件跳过 */ }
}
return packs.OrderBy(p => p.LanguageCode).ToList();
}
///
/// 构建全部行:Key = 应翻译的中文源文集合。
/// 来源= 所有语言 translations 键的并集 ∪ zh-CN 分节结构的叶子中文值(捕获新属性)。
///
public static List BuildRows(List packs, out List targetLangs)
{
var source = packs.FirstOrDefault(p => p.IsSource);
targetLangs = packs.Where(p => !p.IsSource).ToList();
var keys = new HashSet(StringComparer.Ordinal);
foreach (var p in packs)
foreach (var k in p.Translations.Keys)
keys.Add(k);
if (source != null)
foreach (var cv in EnumerateChineseValues(source))
keys.Add(cv);
// 排序:优先按 zh-CN 源的枚举顺序——新增词条自然排在末尾,不再"随机"插入
List orderedKeys;
if (source != null)
{
orderedKeys = new List();
var seen = new HashSet(StringComparer.Ordinal);
foreach (var k in source.Translations.Keys)
if (keys.Contains(k) && seen.Add(k))
orderedKeys.Add(k);
// 仅存在于其他语言包的 key 追加在后(字母序)
foreach (var k in keys.OrderBy(k => k, StringComparer.Ordinal))
if (seen.Add(k))
orderedKeys.Add(k);
}
else
{
orderedKeys = keys.OrderBy(k => k, StringComparer.Ordinal).ToList();
}
var rows = new List();
int index = 1;
foreach (var key in orderedKeys)
{
var row = new RowVm { Index = index++, Key = key };
foreach (var lang in targetLangs)
{
lang.Translations.TryGetValue(key, out var val);
row.Cells[lang.LanguageCode] = new CellVm { LangCode = lang.LanguageCode, Value = val ?? "" };
}
rows.Add(row);
}
return rows;
}
/// 保存语言包为 .json(人类可读)。
public static void Save(PackFile pack)
{
LanguagePackIO.SaveJson(new LanguagePack
{
LanguageCode = pack.LanguageCode,
LanguageName = pack.LanguageName,
Version = string.IsNullOrWhiteSpace(pack.Version) ? "1.0.0" : pack.Version,
Author = pack.Author ?? "TeamAAS",
Translations = pack.Translations
}, pack.FilePath);
}
/// 新增语言:以 zh-CN 中文源为 Key 集生成空模板(值留空)。
public static bool CreateFromTemplate(string langCode, string langName, List packs, string dir, out PackFile result)
{
result = null;
if (string.IsNullOrWhiteSpace(langCode)) return false;
var path = Path.Combine(dir, langCode + ".json");
if (File.Exists(path)) return false;
var source = packs.FirstOrDefault(p => p.IsSource);
var pack = new PackFile
{
FilePath = path,
LanguageCode = langCode,
LanguageName = langName,
Version = "1.0.0",
Author = "TeamAAS",
IsSource = false,
};
foreach (var kv in EnumerateKeysFromSource(packs, source))
pack.Translations[kv] = "";
Save(pack);
result = pack;
return true;
}
/// 由各语言 translations 键并集 + zh 源值生成"应翻译 Key 集"(供模板/新增语言用)。
private static IEnumerable EnumerateKeysFromSource(List packs, PackFile source)
{
var keys = new HashSet(StringComparer.Ordinal);
foreach (var p in packs)
foreach (var k in p.Translations.Keys)
keys.Add(k);
if (source != null)
foreach (var cv in EnumerateChineseValues(source))
keys.Add(cv);
return keys;
}
/// zh-CN 分节结构的所有叶子中文值(properties/plugins/pluginCategories/enums/uiTexts)。
private static IEnumerable EnumerateChineseValues(PackFile source)
{
var result = new List();
try
{
var root = JObject.Parse(File.ReadAllText(source.FilePath, Encoding.UTF8));
foreach (var leaf in EnumerateLeaves(root))
if (leaf.Value is JValue jv && jv.Value is string s && !string.IsNullOrWhiteSpace(s))
result.Add(s);
}
catch { }
return result;
}
/// 校验:JSON 可解析、UTF-8、含 languageCode。
public static List Validate(string dir)
{
var errors = new List();
foreach (var file in Directory.GetFiles(dir, "*.json"))
{
var bytes = HandleBom(File.ReadAllBytes(file));
try { new UTF8Encoding(false, true).GetString(bytes); }
catch (Exception) { errors.Add($"{Path.GetFileName(file)}: 不是合法 UTF-8"); }
try
{
var root = JObject.Parse(Encoding.UTF8.GetString(bytes));
if (root["languageCode"] == null)
errors.Add($"{Path.GetFileName(file)}: 缺少 languageCode");
}
catch (Exception) { errors.Add($"{Path.GetFileName(file)}: JSON 解析失败"); }
}
return errors;
}
private static byte[] HandleBom(byte[] bytes)
{
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
return bytes.Skip(3).ToArray();
return bytes;
}
// ---------- 通用叶子枚举(供 zh-CN 分节结构取值) ----------
public class Leaf { public string Section; public string Path; public JToken Value; public List ValuePath; }
public static IEnumerable EnumerateLeaves(JObject root)
{
var list = new List();
WalkSection(root, "properties", list, 4);
WalkSection(root, "plugins", list, 3);
WalkSection(root, "pluginCategories", list, 2);
WalkSection(root, "enums", list, 3);
WalkSection(root, "uiTexts", list, 2);
return list;
}
private static void WalkSection(JObject root, string section, List list, int depth)
{
var seg = root[section];
if (seg == null) return;
Collect(seg, new List { section }, section, depth, list);
}
private static void Collect(JToken token, List prefix, string section, int depth, List list)
{
if (token is JObject obj)
{
foreach (var prop in obj.Properties())
{
var next = new List(prefix) { prop.Name };
if (next.Count == depth && prop.Value is JValue v)
list.Add(new Leaf { Section = section, Path = string.Join(".", next), Value = v, ValuePath = next });
else
Collect(prop.Value, next, section, depth, list);
}
}
}
}
}