BaiDuTranslationService.cs 15 KB

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