BaiDuTranslationService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /*
  2. *━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  3. * 文件名称:BaiDuTranslationService
  4. * 创建时间:2025年03月25日 星期二 20:54:04
  5. * 创 建 者:莫闻啼
  6. *━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  7. * 功能描述:
  8. * 调用百度翻译Api接口在线翻译,在DeBug模式下生成前端i18n Ts翻译key value,需要先维护对应目录下的zh-CN.ts,对比对应语言包下不存在的key,将value进行翻译并新增到对应语言包文件中
  9. *
  10. *━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  11. */
  12. using System.Security.Cryptography;
  13. namespace Admin.NET.Core;
  14. /// <summary>
  15. /// 百度翻译
  16. /// </summary>
  17. [ApiDescriptionSettings("Extend", Module = "Extend", Order = 200)]
  18. public class BaiDuTranslationService : IDynamicApiController, ITransient
  19. {
  20. // http远程请求
  21. private readonly IHttpRemoteService _httpRemoteService;
  22. /// <summary>
  23. /// 百度翻译appId
  24. /// </summary>
  25. private static readonly string _appId = "xxxxxxxxxxx";
  26. /// <summary>
  27. /// 百度翻译appKey
  28. /// </summary>
  29. private static readonly string _appKey = "xxxxxxxxxxx";
  30. /// <summary>
  31. /// 百度翻译api地址
  32. /// </summary>
  33. private static readonly string _baseUrl = "https://fanyi-api.baidu.com/api/trans/vip/translate?";
  34. // 语言映射字典
  35. private static readonly Dictionary<string, string> langMap = new Dictionary<string, string>
  36. {
  37. ["en"] = "en", ["de"] = "de", ["fi"] = "fin",
  38. ["es"] = "spa", ["fr"] = "fra", ["it"] = "it",
  39. ["ja"] = "jp", ["ko"] = "kor", ["no"] = "nor",
  40. ["pl"] = "pl", ["pt"] = "pt", ["ru"] = "ru",
  41. ["th"] = "th", ["id"] = "id", ["ms"] = "may",
  42. ["vi"] = "vie", ["zh-HK"] = "yue", ["zh-TW"] = "cht"
  43. };
  44. /// <summary>
  45. /// 初始化一个<see cref="BaiDuTranslationService"/>类型的新实例.
  46. /// </summary>
  47. /// <param name="httpRemoteService"></param>
  48. public BaiDuTranslationService(IHttpRemoteService httpRemoteService)
  49. {
  50. _httpRemoteService = httpRemoteService;
  51. }
  52. /// <summary>
  53. /// 百度在线翻译
  54. /// </summary>
  55. /// <param name="from">翻译源语种</param>
  56. /// <param name="to">翻译目标语种</param>
  57. /// <param name="content">文本内容</param>
  58. ///<remarks>
  59. ///源语种和目标语种支持:
  60. ///zh:简体中文
  61. ///cht:繁體中文(台灣)
  62. ///yue:繁體中文(香港)
  63. ///en:英语
  64. ///de:德语
  65. ///spa:西班牙语
  66. ///fin:芬兰语
  67. ///fra:法语
  68. ///it:意大利语
  69. ///jp:日语
  70. ///kor:韩语
  71. ///nor:挪威语
  72. ///pl:波兰语
  73. ///pt:葡萄牙语
  74. ///ru:俄语
  75. ///th:泰语
  76. ///id:印度尼西亚语
  77. ///may:马来西亚
  78. ///vie:越南语
  79. ///
  80. ///更多语种请查看:https://api.fanyi.baidu.com/doc/21
  81. /// </remarks>
  82. /// <returns>翻译后的文本内容</returns>
  83. [DisplayName("百度在线翻译")]
  84. [HttpGet]
  85. public async Task<BaiDuTranslationResult> Translation([FromQuery] [Required] string from, [FromQuery] [Required] string to, [FromQuery] [Required] string content)
  86. {
  87. // 标准版API授权只能翻译基础18语言,201种需要企业尊享版支持见百度api 文档
  88. Random rd = new Random();
  89. string salt = rd.Next(100000).ToString();
  90. // 改成您的密钥
  91. string secretKey = _appKey;
  92. string sign = EncryptString(_appId + content + salt + secretKey);
  93. string url = $"{_baseUrl}q={HttpUtility.UrlEncode(content)}&from={from}&to={to}&appid={_appId}&salt={salt}&sign={sign}";
  94. var res = await _httpRemoteService.GetAsAsync<BaiDuTranslationResult>(url);
  95. if (!res.error_code.Equals("0"))
  96. {
  97. throw Oops.Bah($"翻译失败,错误码:{res.error_code},错误信息:{res.error_msg}");
  98. }
  99. return res;
  100. }
  101. #if DEBUG
  102. /// <summary>
  103. /// 生成前端页面i18n文件
  104. /// </summary>
  105. [DisplayName("生成前端页面i18n文件")]
  106. [HttpPost]
  107. public async Task GeneratePageI18nFile()
  108. {
  109. try
  110. {
  111. // 获取基础路径
  112. var i18nPath = AppContext.BaseDirectory;
  113. for (int i = 0; i < 6; i++)
  114. {
  115. i18nPath = Directory.GetParent(i18nPath).FullName;
  116. }
  117. i18nPath = Path.Combine(i18nPath, "Web", "src", "i18n", "pages", "systemMenu");
  118. // 读取基础语言文件
  119. var dic = await ReadBaseLanguageFile(i18nPath);
  120. if (dic.Count == 0)
  121. {
  122. throw Oops.Bah("未查询到属性定义,不能生成");
  123. }
  124. // 并行处理所有语言文件
  125. var files = Directory.GetFiles(i18nPath, "*.ts").Where(f => !f.EndsWith("zh-CN.ts")).ToList();
  126. foreach (var file in files)
  127. {
  128. var langCode = Path.GetFileNameWithoutExtension(file);
  129. var langDic = await ReadLanguageFile(file);
  130. // 查询出没有生成的键值对
  131. // Linq查询
  132. // var notGen = dic.Where(kv => !langDic.ContainsKey(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
  133. // 转换为 HashSet 提升性能
  134. var langDicKey = new HashSet<string>(langDic.Keys);
  135. var notGen = dic.Where(kv => !langDicKey.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
  136. // 没有未生成的跳出
  137. if (notGen.Count == 0)
  138. {
  139. Console.WriteLine($"{langCode,-6} 语言包:{langDic.Count}/共:{dic.Count} 已全部生成,无需再次生成");
  140. continue;
  141. }
  142. var str = string.Empty;
  143. Console.WriteLine($"{langCode,-6}开始生成语言包,未生成:{notGen.Count}/已生成:{langDic.Count}/共{dic.Count}");
  144. foreach (var gen in notGen)
  145. {
  146. try
  147. {
  148. if (!langMap.TryGetValue(langCode, out var targetLang))
  149. {
  150. continue;
  151. }
  152. var result = await Translation("zh", targetLang, $"{gen.Value}");
  153. if (result.error_code.Equals("0"))
  154. {
  155. LogTranslationProgress(gen.Key, gen.Value, result.trans_result[0].Dst, ConsoleColor.DarkMagenta);
  156. str += ($" {gen.Key}: '{result.trans_result[0].Dst}',{Environment.NewLine}");
  157. }
  158. }
  159. catch (Exception e)
  160. {
  161. LogError(e);
  162. }
  163. }
  164. if (str.Length > 0)
  165. {
  166. str = str.TrimStart();
  167. await FileHelper.InsertsStringAtSpecifiedLocationInFile(file, str, '}', 2, false);
  168. }
  169. }
  170. }
  171. catch (Exception e)
  172. {
  173. throw Oops.Bah(e.Message);
  174. }
  175. }
  176. /// <summary>
  177. /// 生成前端菜单i18n文件
  178. /// </summary>
  179. [DisplayName("生成前端菜单i18n文件")]
  180. [HttpPost]
  181. public async Task GenerateMenuI18nFile()
  182. {
  183. try
  184. {
  185. // 获取基础路径
  186. var i18nPath = AppContext.BaseDirectory;
  187. for (int i = 0; i < 6; i++)
  188. {
  189. i18nPath = Directory.GetParent(i18nPath).FullName;
  190. }
  191. i18nPath = Path.Combine(i18nPath, "Web", "src", "i18n", "menu");
  192. // 读取基础语言文件
  193. var dic = await ReadBaseLanguageFile(i18nPath);
  194. if (dic.Count == 0)
  195. {
  196. throw Oops.Bah("未查询到属性定义,不能生成");
  197. }
  198. // 并行处理所有语言文件
  199. var files = Directory.GetFiles(i18nPath, "*.ts").Where(f => !f.EndsWith("zh-CN.ts")).ToList();
  200. foreach (var file in files)
  201. {
  202. var langCode = Path.GetFileNameWithoutExtension(file);
  203. var langDic = await ReadLanguageFile(file);
  204. // 查询出没有生成的键值对
  205. // Linq查询
  206. // var notGen = dic.Where(kv => !langDic.ContainsKey(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
  207. // 转换为 HashSet 提升性能
  208. var langDicKey = new HashSet<string>(langDic.Keys);
  209. var notGen = dic.Where(kv => !langDicKey.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
  210. // 没有未生成的跳出
  211. if (notGen.Count == 0)
  212. {
  213. Console.WriteLine($"{langCode,-6} 语言包:{langDic.Count}/共:{dic.Count} 已全部生成,无需再次生成");
  214. continue;
  215. }
  216. var str = string.Empty;
  217. Console.WriteLine($"{langCode,-6}开始生成语言包,未生成:{notGen.Count}/已生成:{langDic.Count}/共{dic.Count}");
  218. foreach (var gen in notGen)
  219. {
  220. try
  221. {
  222. if (!langMap.TryGetValue(langCode, out var targetLang))
  223. {
  224. continue;
  225. }
  226. var result = await Translation("zh", targetLang, $"{gen.Value}");
  227. if (result.error_code.Equals("0"))
  228. {
  229. LogTranslationProgress(gen.Key, gen.Value, result.trans_result[0].Dst, ConsoleColor.DarkMagenta);
  230. str += ($" {gen.Key}: '{result.trans_result[0].Dst}',{Environment.NewLine}");
  231. }
  232. }
  233. catch (Exception e)
  234. {
  235. LogError(e);
  236. }
  237. }
  238. if (str.Length > 0)
  239. {
  240. str = str.TrimStart();
  241. await FileHelper.InsertsStringAtSpecifiedLocationInFile(file, str, '}', 2, false);
  242. }
  243. }
  244. }
  245. catch (Exception e)
  246. {
  247. throw Oops.Bah(e.Message);
  248. }
  249. }
  250. #region 辅助方法
  251. private static async Task<Dictionary<string, string>> ReadBaseLanguageFile(string i18nPath)
  252. {
  253. var baseFile = Path.Combine(i18nPath, "zh-CN.ts");
  254. if (!File.Exists(baseFile))
  255. {
  256. throw Oops.Bah("【zh-CN.ts】文件未找到");
  257. }
  258. var dic = new Dictionary<string, string>();
  259. using var reader = new StreamReader(baseFile, Encoding.UTF8);
  260. while (await reader.ReadLineAsync() is { } line)
  261. {
  262. if (line.Contains('{') || line.Contains('}')) continue;
  263. var cleanLine = line.Trim().TrimEnd(',').Replace("'", "");
  264. var parts = cleanLine.Split(new[] { ':' }, 2);
  265. if (parts.Length == 2) dic[parts[0].Trim()] = parts[1].Trim();
  266. }
  267. reader.Close();
  268. return dic;
  269. }
  270. private static async Task<Dictionary<string, string>> ReadLanguageFile(string filePath)
  271. {
  272. if (!File.Exists(filePath))
  273. {
  274. throw Oops.Bah($"【{filePath.Split('/').Last()}】文件未找到");
  275. }
  276. var dic = new Dictionary<string, string>();
  277. using var reader = new StreamReader(filePath, Encoding.UTF8);
  278. while (await reader.ReadLineAsync() is { } line)
  279. {
  280. if (line.Contains('{') || line.Contains('}')) continue;
  281. var cleanLine = line.Trim().TrimEnd(',').Replace("'", "");
  282. var parts = cleanLine.Split(new[] { ':' }, 2);
  283. if (parts.Length == 2) dic[parts[0].Trim()] = parts[1].Trim();
  284. }
  285. reader.Close();
  286. return dic;
  287. }
  288. private static void LogTranslationProgress(string key, string value, string res, ConsoleColor color)
  289. {
  290. Console.ForegroundColor = color;
  291. Console.WriteLine($"翻译属性: {key,-32}值: {value,-64}结果: {res}");
  292. Console.ResetColor();
  293. }
  294. private static void LogError(Exception e)
  295. {
  296. Console.ForegroundColor = ConsoleColor.DarkRed;
  297. Console.WriteLine($"{e.Message}");
  298. Console.ResetColor();
  299. }
  300. #endregion
  301. #endif
  302. // 计算MD5值
  303. [NonAction]
  304. private static string EncryptString(string str)
  305. {
  306. MD5 md5 = MD5.Create();
  307. // 将字符串转换成字节数组
  308. byte[] byteOld = Encoding.UTF8.GetBytes(str);
  309. // 调用加密方法
  310. byte[] byteNew = md5.ComputeHash(byteOld);
  311. // 将加密结果转换为字符串
  312. StringBuilder sb = new StringBuilder();
  313. foreach (byte b in byteNew)
  314. {
  315. // 将字节转换成16进制表示的字符串,
  316. sb.Append(b.ToString("x2"));
  317. }
  318. // 返回加密的字符串
  319. return sb.ToString();
  320. }
  321. }