LocalizationTemplateGenerator.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text.Json;
  8. using TeamAAS.Localization;
  9. namespace TeamAAS.Tools
  10. {
  11. /// <summary>
  12. /// 多语言翻译模板生成器 - 自动扫描插件并生成翻译模板
  13. /// </summary>
  14. public class LocalizationTemplateGenerator
  15. {
  16. /// <summary>
  17. /// 扫描程序集中的所有插件模型并生成翻译模板
  18. /// </summary>
  19. /// <param name="assemblies">要扫描的程序集列表</param>
  20. /// <param name="outputPath">输出模板文件路径</param>
  21. /// <param name="languageCode">目标语言代码(如 "en-US")</param>
  22. public static void GenerateTemplate(Assembly[] assemblies, string outputPath, string languageCode = "en-US")
  23. {
  24. var pack = new LanguageResourcePack
  25. {
  26. LanguageCode = languageCode,
  27. LanguageName = GetLanguageName(languageCode),
  28. Version = "1.0.0",
  29. Author = "Auto Generated"
  30. };
  31. // 扫描所有插件模型
  32. foreach (var assembly in assemblies)
  33. {
  34. ScanAssembly(assembly, pack);
  35. }
  36. // 序列化为 JSON
  37. var options = new JsonSerializerOptions
  38. {
  39. WriteIndented = true,
  40. Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
  41. };
  42. var json = JsonSerializer.Serialize(pack, options);
  43. File.WriteAllText(outputPath, json);
  44. Console.WriteLine($"翻译模板已生成: {outputPath}");
  45. Console.WriteLine($" - 类型数: {pack.Properties.Count}");
  46. Console.WriteLine($" - 属性数: {pack.Properties.Sum(p => p.Value.Count)}");
  47. Console.WriteLine($" - 插件数: {pack.Plugins.Count}");
  48. }
  49. /// <summary>
  50. /// 扫描单个程序集
  51. /// </summary>
  52. private static void ScanAssembly(Assembly assembly, LanguageResourcePack pack)
  53. {
  54. try
  55. {
  56. var types = assembly.GetTypes();
  57. foreach (var type in types)
  58. {
  59. // 扫描插件模型(继承自 BasePluginModel)
  60. if (IsPluginModel(type))
  61. {
  62. ScanPluginModel(type, pack);
  63. }
  64. // 扫描插件类(带 PluginAttribute)
  65. if (IsPlugin(type))
  66. {
  67. ScanPlugin(type, pack);
  68. }
  69. // 扫描枚举类型
  70. if (type.IsEnum)
  71. {
  72. ScanEnum(type, pack);
  73. }
  74. }
  75. }
  76. catch (Exception ex)
  77. {
  78. Console.WriteLine($"警告: 扫描程序集 {assembly.FullName} 时出错: {ex.Message}");
  79. }
  80. }
  81. /// <summary>
  82. /// 扫描插件模型类型
  83. /// </summary>
  84. private static void ScanPluginModel(Type type, LanguageResourcePack pack)
  85. {
  86. var typeName = type.Name;
  87. var properties = new Dictionary<string, PropertyTranslation>();
  88. foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
  89. {
  90. // 跳过标记为 Browsable(false) 的属性
  91. var browsable = prop.GetCustomAttribute<BrowsableAttribute>();
  92. if (browsable != null && !browsable.Browsable)
  93. continue;
  94. var displayNameAttr = prop.GetCustomAttribute<DisplayNameAttribute>();
  95. var descriptionAttr = prop.GetCustomAttribute<DescriptionAttribute>();
  96. var categoryAttr = prop.GetCustomAttribute<CategoryAttribute>();
  97. properties[prop.Name] = new PropertyTranslation
  98. {
  99. DisplayName = displayNameAttr?.DisplayName ?? $"[TODO: {prop.Name}]",
  100. Description = descriptionAttr?.Description ?? $"[TODO: Description for {prop.Name}]",
  101. Category = categoryAttr?.Category ?? "Miscellaneous"
  102. };
  103. }
  104. if (properties.Count > 0)
  105. {
  106. pack.Properties[typeName] = properties;
  107. }
  108. }
  109. /// <summary>
  110. /// 扫描插件类
  111. /// </summary>
  112. private static void ScanPlugin(Type type, LanguageResourcePack pack)
  113. {
  114. var pluginAttr = type.GetCustomAttributes(false)
  115. .FirstOrDefault(a => a.GetType().Name == "PluginAttribute");
  116. if (pluginAttr != null)
  117. {
  118. var displayNameProp = pluginAttr.GetType().GetProperty("DisplayName");
  119. var descriptionProp = pluginAttr.GetType().GetProperty("Description");
  120. var categoryProp = pluginAttr.GetType().GetProperty("Category");
  121. var displayName = displayNameProp?.GetValue(pluginAttr)?.ToString();
  122. var description = descriptionProp?.GetValue(pluginAttr)?.ToString();
  123. var category = categoryProp?.GetValue(pluginAttr)?.ToString();
  124. if (!string.IsNullOrWhiteSpace(displayName))
  125. {
  126. pack.Plugins[type.Name] = new PluginTranslation
  127. {
  128. DisplayName = displayName,
  129. Description = description ?? $"[TODO: Description for {type.Name}]",
  130. Category = category?.ToString() ?? "Others"
  131. };
  132. }
  133. }
  134. }
  135. /// <summary>
  136. /// 扫描枚举类型
  137. /// </summary>
  138. private static void ScanEnum(Type type, LanguageResourcePack pack)
  139. {
  140. var enumValues = new Dictionary<string, string>();
  141. foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
  142. {
  143. var displayNameAttr = field.GetCustomAttribute<DisplayNameAttribute>();
  144. var displayName = displayNameAttr?.DisplayName ?? field.Name;
  145. enumValues[field.Name] = $"[TODO: {displayName}]";
  146. }
  147. if (enumValues.Count > 0)
  148. {
  149. pack.Enums[type.Name] = enumValues;
  150. }
  151. }
  152. /// <summary>
  153. /// 判断是否为插件模型类型
  154. /// </summary>
  155. private static bool IsPluginModel(Type type)
  156. {
  157. if (type.IsAbstract || type.IsInterface)
  158. return false;
  159. var baseType = type.BaseType;
  160. while (baseType != null)
  161. {
  162. if (baseType.Name == "BasePluginModel")
  163. return true;
  164. baseType = baseType.BaseType;
  165. }
  166. return false;
  167. }
  168. /// <summary>
  169. /// 判断是否为插件类
  170. /// </summary>
  171. private static bool IsPlugin(Type type)
  172. {
  173. return type.GetCustomAttributes(false)
  174. .Any(a => a.GetType().Name == "PluginAttribute");
  175. }
  176. /// <summary>
  177. /// 获取语言显示名称
  178. /// </summary>
  179. private static string GetLanguageName(string languageCode)
  180. {
  181. return languageCode switch
  182. {
  183. "zh-CN" => "简体中文",
  184. "zh-TW" => "繁体中文",
  185. "en-US" => "English",
  186. "en-GB" => "English (UK)",
  187. "ja-JP" => "日本語",
  188. "ko-KR" => "한국어",
  189. "de-DE" => "Deutsch",
  190. "fr-FR" => "Français",
  191. "es-ES" => "Español",
  192. "ru-RU" => "Русский",
  193. "it-IT" => "Italiano",
  194. "pt-BR" => "Português (Brasil)",
  195. "ar-SA" => "العربية",
  196. "th-TH" => "ไทย",
  197. "vi-VN" => "Tiếng Việt",
  198. _ => languageCode
  199. };
  200. }
  201. /// <summary>
  202. /// 批量生成多个语言的模板
  203. /// </summary>
  204. public static void GenerateMultipleTemplates(Assembly[] assemblies, string outputDirectory, params string[] languageCodes)
  205. {
  206. if (!Directory.Exists(outputDirectory))
  207. Directory.CreateDirectory(outputDirectory);
  208. foreach (var langCode in languageCodes)
  209. {
  210. var outputPath = Path.Combine(outputDirectory, $"{langCode}.json");
  211. GenerateTemplate(assemblies, outputPath, langCode);
  212. }
  213. }
  214. /// <summary>
  215. /// 使用示例
  216. /// </summary>
  217. public static void Example()
  218. {
  219. // 示例 1: 生成单个语言模板
  220. var assemblies = new[]
  221. {
  222. Assembly.Load("TeamAAS.Plugins.Standard"),
  223. Assembly.Load("TeamAAS.Plugins.Vpp"),
  224. Assembly.Load("TeamAAS.Plugins.Feeder")
  225. };
  226. GenerateTemplate(
  227. assemblies,
  228. @"D:\Program\TeamAAS\Localization\template-en-US.json",
  229. "en-US"
  230. );
  231. // 示例 2: 批量生成多个语言模板
  232. GenerateMultipleTemplates(
  233. assemblies,
  234. @"D:\Program\TeamAAS\Localization\templates",
  235. "en-US", "ja-JP", "ko-KR", "de-DE", "fr-FR"
  236. );
  237. Console.WriteLine("模板生成完成!");
  238. Console.WriteLine("请编辑生成的模板文件,将 [TODO: ...] 标记替换为实际翻译。");
  239. }
  240. }
  241. }