using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using BaseGames.Localization;
namespace BaseGames.Editor.Localization
{
///
/// 本地化 JSON 表的编辑器侧磁盘读写门面(唯一入口)。
///
/// 所有编辑器工具(表格编辑器、CSV 工具、审计模块、各 Inspector)读写本地化文件
/// 都必须经此类,统一:路径()、格式
/// ()、Addressable 注册、编辑器缓存刷新。
///
/// 这样运行时加载与编辑器写盘永不脱节——杜绝"编辑器看着正常、Play 加载不到"的隐藏 bug。
///
public static class LocalizationFileIO
{
/// 所有受支持语言(枚举顺序)。
public static readonly Language[] AllLanguages =
(Language[])Enum.GetValues(typeof(Language));
// ── 读 ───────────────────────────────────────────────────────────────
///
/// 读取指定语言 + 表的 key→value 字典(返回可安全修改的副本,不存在时返回空字典)。
/// 内部走 复用其静态缓存。
///
public static Dictionary Read(Language language, string table)
{
var src = LocalizationManager.GetEditorTable(language, table);
return src == null
? new Dictionary(StringComparer.Ordinal)
: new Dictionary(src, StringComparer.Ordinal);
}
/// 该表的 JSON 文件是否已存在于磁盘。
public static bool TableExists(Language language, string table)
=> AssetDatabase.LoadAssetAtPath(LocalizationPaths.AssetPath(language, table)) != null;
/// 扫描数据根目录,返回磁盘上实际存在子目录的语言。
public static List DiscoverLanguages()
{
var result = new List();
if (!AssetDatabase.IsValidFolder(LocalizationPaths.DataRoot)) return result;
foreach (var langFolder in AssetDatabase.GetSubFolders(LocalizationPaths.DataRoot))
{
string langName = Path.GetFileName(langFolder);
if (Enum.TryParse(langName, out var lang))
result.Add(lang);
}
return result;
}
// ── 写 ───────────────────────────────────────────────────────────────
///
/// 将字典写回指定语言 + 表的 JSON 文件(正确的 {entries:[…]} 格式 + 正确目录)。
/// 新文件会自动注册到 Addressables(地址 ),
/// 写盘后清除编辑器预览缓存。
///
/// 目标语言。
/// 目标表名( 常量)。
/// key→value 字典。
/// 是否确保 Addressable 注册(默认 true)。
public static void Write(Language language, string table,
IReadOnlyDictionary dict, bool registerAddressable = true)
{
string assetPath = LocalizationPaths.AssetPath(language, table);
EditorScaffoldUtils.EnsureFolder(LocalizationPaths.LanguageFolder(language));
// JSON 文件不写 BOM(Addressables/TextAsset 解析期望纯 UTF-8)。
string json = LocalizationSerializer.Serialize(dict, sortKeys: true);
string fullPath = ToFullPath(assetPath);
File.WriteAllText(fullPath, json, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
if (registerAddressable)
EnsureAddressable(language, table, assetPath);
LocalizationManager.ClearEditorPreviewCache();
}
// ── Ping ───────────────────────────────────────────────────────────────
/// 在 Project 窗口中定位指定语言 + 表的 JSON 文件。
public static TextAsset Ping(Language language, string table)
{
var asset = AssetDatabase.LoadAssetAtPath(LocalizationPaths.AssetPath(language, table));
if (asset != null) EditorScaffoldUtils.PingAndSelect(asset);
return asset;
}
/// 在 Project 窗口中定位指定表的任一存在语言文件(优先简体中文 → 英文 → 其余)。
public static TextAsset PingAny(string table)
{
foreach (var lang in OrderedForPing())
{
var asset = AssetDatabase.LoadAssetAtPath(LocalizationPaths.AssetPath(lang, table));
if (asset != null) { EditorScaffoldUtils.PingAndSelect(asset); return asset; }
}
Debug.LogWarning($"[LocalizationFileIO] 未找到本地化表文件:{LocalizationPaths.DataRoot}/…/{table}.json");
return null;
}
// ── 内部 ───────────────────────────────────────────────────────────────
private static IEnumerable OrderedForPing()
{
yield return Language.ChineseSimplified;
yield return Language.English;
foreach (var lang in AllLanguages)
if (lang != Language.ChineseSimplified && lang != Language.English)
yield return lang;
}
///
/// 确保该 JSON 文件已注册为对应 Addressable 地址。
/// 复用 的 FindEntry-or-Create 幂等范式。
///
private static void EnsureAddressable(Language language, string table, string assetPath)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
{
Debug.LogWarning(
$"[LocalizationFileIO] Addressable Settings 未初始化,未能注册 " +
$"{LocalizationPaths.Address(language, table)}。运行时可能加载不到该表。");
return;
}
string guid = AssetDatabase.AssetPathToGUID(assetPath);
if (string.IsNullOrEmpty(guid)) return;
string address = LocalizationPaths.Address(language, table);
var entry = settings.FindAssetEntry(guid)
?? settings.CreateOrMoveEntry(guid, settings.DefaultGroup, false, false);
// 已注册且地址正确则无需改动(避免无意义的 SetDirty)。
if (entry.address == address) return;
entry.address = address;
EditorUtility.SetDirty(settings);
AssetDatabase.SaveAssets();
}
private static string ToFullPath(string assetPath)
{
string projectRoot = Path.GetDirectoryName(Application.dataPath)!;
return Path.Combine(projectRoot, assetPath.Replace('/', Path.DirectorySeparatorChar));
}
}
}