BaiDuTranslationService.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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. continue;
  156. }
  157. var translationValue = result.trans_result[0].Dst;
  158. LogTranslationProgress(gen.Key, gen.Value, translationValue, ConsoleColor.DarkMagenta);
  159. // 如果翻译结果为空字符串不追加
  160. if (string.IsNullOrEmpty(translationValue))
  161. {
  162. continue;
  163. }
  164. // 如果翻译结果包含"'" 法语意大利语常出现 在"'"前加转义符
  165. if (translationValue.Contains("'"))
  166. {
  167. translationValue = translationValue.Replace("'", "\\'");
  168. }
  169. str += ($" {gen.Key}: '{translationValue}',{Environment.NewLine}");
  170. }
  171. catch (Exception e)
  172. {
  173. LogError(e);
  174. }
  175. }
  176. if (str.Length > 0)
  177. {
  178. str = str.TrimStart();
  179. await FileHelper.InsertsStringAtSpecifiedLocationInFile(file, str, '}', 2, false);
  180. }
  181. }
  182. }
  183. catch (Exception e)
  184. {
  185. throw Oops.Bah(e.Message);
  186. }
  187. }
  188. /// <summary>
  189. /// 生成前端菜单i18n文件
  190. /// </summary>
  191. [DisplayName("生成前端菜单i18n文件")]
  192. [HttpPost]
  193. public async Task GenerateMenuI18nFile()
  194. {
  195. try
  196. {
  197. // 获取基础路径
  198. var i18nPath = AppContext.BaseDirectory;
  199. for (int i = 0; i < 6; i++)
  200. {
  201. i18nPath = Directory.GetParent(i18nPath).FullName;
  202. }
  203. i18nPath = Path.Combine(i18nPath, "Web", "src", "i18n", "menu");
  204. // 读取基础语言文件
  205. var dic = await ReadBaseLanguageFile(i18nPath);
  206. if (dic.Count == 0)
  207. {
  208. throw Oops.Bah("未查询到属性定义,不能生成");
  209. }
  210. // 并行处理所有语言文件
  211. var files = Directory.GetFiles(i18nPath, "*.ts").Where(f => !f.EndsWith("zh-CN.ts")).ToList();
  212. foreach (var file in files)
  213. {
  214. var langCode = Path.GetFileNameWithoutExtension(file);
  215. var langDic = await ReadLanguageFile(file);
  216. // 查询出没有生成的键值对
  217. // Linq查询
  218. // var notGen = dic.Where(kv => !langDic.ContainsKey(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
  219. // 转换为 HashSet 提升性能
  220. var langDicKey = new HashSet<string>(langDic.Keys);
  221. var notGen = dic.Where(kv => !langDicKey.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
  222. // 没有未生成的跳出
  223. if (notGen.Count == 0)
  224. {
  225. Console.WriteLine($"{langCode,-6} 语言包:{langDic.Count}/共:{dic.Count} 已全部生成,无需再次生成");
  226. continue;
  227. }
  228. var str = string.Empty;
  229. Console.WriteLine($"{langCode,-6}开始生成语言包,未生成:{notGen.Count}/已生成:{langDic.Count}/共{dic.Count}");
  230. foreach (var gen in notGen)
  231. {
  232. try
  233. {
  234. if (!langMap.TryGetValue(langCode, out var targetLang))
  235. {
  236. continue;
  237. }
  238. var result = await Translation("zh", targetLang, $"{gen.Value}");
  239. if (!result.error_code.Equals("0"))
  240. {
  241. continue;
  242. }
  243. var translationValue = result.trans_result[0].Dst;
  244. LogTranslationProgress(gen.Key, gen.Value, translationValue, ConsoleColor.DarkMagenta);
  245. // 如果翻译结果为空字符串不追加
  246. if (string.IsNullOrEmpty(translationValue))
  247. {
  248. continue;
  249. }
  250. // 如果翻译结果包含"'" 法语意大利语常出现 在"'"前加转义符
  251. if (translationValue.Contains("'"))
  252. {
  253. translationValue = translationValue.Replace("'", "\\'");
  254. }
  255. str += ($" {gen.Key}: '{translationValue}',{Environment.NewLine}");
  256. }
  257. catch (Exception e)
  258. {
  259. LogError(e);
  260. }
  261. }
  262. if (str.Length > 0)
  263. {
  264. str = str.TrimStart();
  265. await FileHelper.InsertsStringAtSpecifiedLocationInFile(file, str, '}', 2, false);
  266. }
  267. }
  268. }
  269. catch (Exception e)
  270. {
  271. throw Oops.Bah(e.Message);
  272. }
  273. }
  274. #region 辅助方法
  275. private static async Task<Dictionary<string, string>> ReadBaseLanguageFile(string i18nPath)
  276. {
  277. var baseFile = Path.Combine(i18nPath, "zh-CN.ts");
  278. if (!File.Exists(baseFile))
  279. {
  280. throw Oops.Bah("【zh-CN.ts】文件未找到");
  281. }
  282. var dic = new Dictionary<string, string>();
  283. using var reader = new StreamReader(baseFile, Encoding.UTF8);
  284. while (await reader.ReadLineAsync() is { } line)
  285. {
  286. if (line.Contains('{') || line.Contains('}')) continue;
  287. var cleanLine = line.Trim().TrimEnd(',').Replace("'", "");
  288. var parts = cleanLine.Split(new[] { ':' }, 2);
  289. if (parts.Length == 2) dic[parts[0].Trim()] = parts[1].Trim();
  290. }
  291. reader.Close();
  292. return dic;
  293. }
  294. private static async Task<Dictionary<string, string>> ReadLanguageFile(string filePath)
  295. {
  296. if (!File.Exists(filePath))
  297. {
  298. throw Oops.Bah($"【{filePath.Split('/').Last()}】文件未找到");
  299. }
  300. var dic = new Dictionary<string, string>();
  301. using var reader = new StreamReader(filePath, Encoding.UTF8);
  302. while (await reader.ReadLineAsync() is { } line)
  303. {
  304. if (line.Contains('{') || line.Contains('}')) continue;
  305. var cleanLine = line.Trim().TrimEnd(',').Replace("'", "");
  306. var parts = cleanLine.Split(new[] { ':' }, 2);
  307. if (parts.Length == 2) dic[parts[0].Trim()] = parts[1].Trim();
  308. }
  309. reader.Close();
  310. return dic;
  311. }
  312. private static void LogTranslationProgress(string key, string value, string res, ConsoleColor color)
  313. {
  314. Console.ForegroundColor = color;
  315. Console.WriteLine($"翻译属性: {key,-32}值: {value,-64}结果: {res}");
  316. Console.ResetColor();
  317. }
  318. private static void LogError(Exception e)
  319. {
  320. Console.ForegroundColor = ConsoleColor.DarkRed;
  321. Console.WriteLine($"{e.Message}");
  322. Console.ResetColor();
  323. }
  324. #endregion
  325. #endif
  326. // 计算MD5值
  327. [NonAction]
  328. private static string EncryptString(string str)
  329. {
  330. MD5 md5 = MD5.Create();
  331. // 将字符串转换成字节数组
  332. byte[] byteOld = Encoding.UTF8.GetBytes(str);
  333. // 调用加密方法
  334. byte[] byteNew = md5.ComputeHash(byteOld);
  335. // 将加密结果转换为字符串
  336. StringBuilder sb = new StringBuilder();
  337. foreach (byte b in byteNew)
  338. {
  339. // 将字节转换成16进制表示的字符串,
  340. sb.Append(b.ToString("x2"));
  341. }
  342. // 返回加密的字符串
  343. return sb.ToString();
  344. }
  345. }