// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
/*
*━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 文件名称:BaiDuTranslationService
* 创建时间:2025年03月25日 星期二 20:54:04
* 创 建 者:莫闻啼
*━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 功能描述:
* 调用百度翻译Api接口在线翻译,在DeBug模式下生成前端i18n Ts翻译key value,需要先维护对应目录下的zh-CN.ts,对比对应语言包下不存在的key,将value进行翻译并新增到对应语言包文件中
*
*━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
*/
using System.Security.Cryptography;
namespace Admin.NET.Core;
///
/// 百度翻译
///
[ApiDescriptionSettings("Extend", Module = "Extend", Order = 200)]
public class BaiDuTranslationService : IDynamicApiController, ITransient
{
// http远程请求
private readonly IHttpRemoteService _httpRemoteService;
///
/// 百度翻译appId
///
private static readonly string _appId = "xxxxxxxxxxx";
///
/// 百度翻译appKey
///
private static readonly string _appKey = "xxxxxxxxxxx";
///
/// 百度翻译api地址
///
private static readonly string _baseUrl = "https://fanyi-api.baidu.com/api/trans/vip/translate?";
// 语言映射字典
private static readonly Dictionary langMap = new Dictionary
{
["en"] = "en",
["de"] = "de",
["fi"] = "fin",
["es"] = "spa",
["fr"] = "fra",
["it"] = "it",
["ja"] = "jp",
["ko"] = "kor",
["no"] = "nor",
["pl"] = "pl",
["pt"] = "pt",
["ru"] = "ru",
["th"] = "th",
["id"] = "id",
["ms"] = "may",
["vi"] = "vie",
["zh-HK"] = "yue",
["zh-TW"] = "cht"
};
///
/// 初始化一个类型的新实例.
///
///
public BaiDuTranslationService(IHttpRemoteService httpRemoteService)
{
_httpRemoteService = httpRemoteService;
}
///
/// 百度在线翻译
///
/// 翻译源语种
/// 翻译目标语种
/// 文本内容
///
///源语种和目标语种支持:
///zh:简体中文
///cht:繁體中文(台灣)
///yue:繁體中文(香港)
///en:英语
///de:德语
///spa:西班牙语
///fin:芬兰语
///fra:法语
///it:意大利语
///jp:日语
///kor:韩语
///nor:挪威语
///pl:波兰语
///pt:葡萄牙语
///ru:俄语
///th:泰语
///id:印度尼西亚语
///may:马来西亚
///vie:越南语
///
///更多语种请查看:https://api.fanyi.baidu.com/doc/21
///
/// 翻译后的文本内容
[DisplayName("百度在线翻译")]
[HttpGet]
public async Task Translation([FromQuery][Required] string from, [FromQuery][Required] string to, [FromQuery][Required] string content)
{
// 标准版API授权只能翻译基础18语言,201种需要企业尊享版支持见百度api 文档
Random rd = new Random();
string salt = rd.Next(100000).ToString();
// 改成您的密钥
string secretKey = _appKey;
string sign = EncryptString(_appId + content + salt + secretKey);
string url = $"{_baseUrl}q={HttpUtility.UrlEncode(content)}&from={from}&to={to}&appid={_appId}&salt={salt}&sign={sign}";
var res = await _httpRemoteService.GetAsAsync(url);
if (!res.error_code.Equals("0"))
{
throw Oops.Bah($"翻译失败,错误码:{res.error_code},错误信息:{res.error_msg}");
}
return res;
}
#if DEBUG
///
/// 生成前端页面i18n文件
///
[DisplayName("生成前端页面i18n文件")]
[HttpPost]
public async Task GeneratePageI18nFile()
{
try
{
// 获取基础路径
var i18nPath = AppContext.BaseDirectory;
for (int i = 0; i < 6; i++)
{
i18nPath = Directory.GetParent(i18nPath).FullName;
}
i18nPath = Path.Combine(i18nPath, "Web", "src", "i18n", "pages", "systemMenu");
// 读取基础语言文件
var dic = await ReadBaseLanguageFile(i18nPath);
if (dic.Count == 0)
{
throw Oops.Bah("未查询到属性定义,不能生成");
}
// 并行处理所有语言文件
var files = Directory.GetFiles(i18nPath, "*.ts").Where(f => !f.EndsWith("zh-CN.ts")).ToList();
foreach (var file in files)
{
var langCode = Path.GetFileNameWithoutExtension(file);
var langDic = await ReadLanguageFile(file);
// 查询出没有生成的键值对
// Linq查询
// var notGen = dic.Where(kv => !langDic.ContainsKey(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
// 转换为 HashSet 提升性能
var langDicKey = new HashSet(langDic.Keys);
var notGen = dic.Where(kv => !langDicKey.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
// 没有未生成的跳出
if (notGen.Count == 0)
{
Console.WriteLine($"{langCode,-6} 语言包:{langDic.Count}/共:{dic.Count} 已全部生成,无需再次生成");
continue;
}
var str = string.Empty;
Console.WriteLine($"{langCode,-6}开始生成语言包,未生成:{notGen.Count}/已生成:{langDic.Count}/共{dic.Count}");
foreach (var gen in notGen)
{
try
{
if (!langMap.TryGetValue(langCode, out var targetLang))
{
continue;
}
var result = await Translation("zh", targetLang, $"{gen.Value}");
if (!result.error_code.Equals("0"))
{
continue;
}
var translationValue = result.trans_result[0].Dst;
LogTranslationProgress(gen.Key, gen.Value, translationValue, ConsoleColor.DarkMagenta);
// 如果翻译结果为空字符串不追加
if (string.IsNullOrEmpty(translationValue))
{
continue;
}
// 如果翻译结果包含"'" 法语意大利语常出现 在"'"前加转义符
if (translationValue.Contains("'"))
{
translationValue = translationValue.Replace("'", "\\'");
}
str += ($" {gen.Key}: '{translationValue}',{Environment.NewLine}");
}
catch (Exception e)
{
LogError(e);
}
}
if (str.Length > 0)
{
str = str.TrimStart();
await FileHelper.InsertsStringAtSpecifiedLocationInFile(file, str, '}', 2, false);
}
}
}
catch (Exception e)
{
throw Oops.Bah(e.Message);
}
}
///
/// 生成前端菜单i18n文件
///
[DisplayName("生成前端菜单i18n文件")]
[HttpPost]
public async Task GenerateMenuI18nFile()
{
try
{
// 获取基础路径
var i18nPath = AppContext.BaseDirectory;
for (int i = 0; i < 6; i++)
{
i18nPath = Directory.GetParent(i18nPath).FullName;
}
i18nPath = Path.Combine(i18nPath, "Web", "src", "i18n", "menu");
// 读取基础语言文件
var dic = await ReadBaseLanguageFile(i18nPath);
if (dic.Count == 0)
{
throw Oops.Bah("未查询到属性定义,不能生成");
}
// 并行处理所有语言文件
var files = Directory.GetFiles(i18nPath, "*.ts").Where(f => !f.EndsWith("zh-CN.ts")).ToList();
foreach (var file in files)
{
var langCode = Path.GetFileNameWithoutExtension(file);
var langDic = await ReadLanguageFile(file);
// 查询出没有生成的键值对
// Linq查询
// var notGen = dic.Where(kv => !langDic.ContainsKey(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
// 转换为 HashSet 提升性能
var langDicKey = new HashSet(langDic.Keys);
var notGen = dic.Where(kv => !langDicKey.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
// 没有未生成的跳出
if (notGen.Count == 0)
{
Console.WriteLine($"{langCode,-6} 语言包:{langDic.Count}/共:{dic.Count} 已全部生成,无需再次生成");
continue;
}
var str = string.Empty;
Console.WriteLine($"{langCode,-6}开始生成语言包,未生成:{notGen.Count}/已生成:{langDic.Count}/共{dic.Count}");
foreach (var gen in notGen)
{
try
{
if (!langMap.TryGetValue(langCode, out var targetLang))
{
continue;
}
var result = await Translation("zh", targetLang, $"{gen.Value}");
if (!result.error_code.Equals("0"))
{
continue;
}
var translationValue = result.trans_result[0].Dst;
LogTranslationProgress(gen.Key, gen.Value, translationValue, ConsoleColor.DarkMagenta);
// 如果翻译结果为空字符串不追加
if (string.IsNullOrEmpty(translationValue))
{
continue;
}
// 如果翻译结果包含"'" 法语意大利语常出现 在"'"前加转义符
if (translationValue.Contains("'"))
{
translationValue = translationValue.Replace("'", "\\'");
}
str += ($" {gen.Key}: '{translationValue}',{Environment.NewLine}");
}
catch (Exception e)
{
LogError(e);
}
}
if (str.Length > 0)
{
str = str.TrimStart();
await FileHelper.InsertsStringAtSpecifiedLocationInFile(file, str, '}', 2, false);
}
}
}
catch (Exception e)
{
throw Oops.Bah(e.Message);
}
}
#region 辅助方法
private static async Task> ReadBaseLanguageFile(string i18nPath)
{
var baseFile = Path.Combine(i18nPath, "zh-CN.ts");
if (!File.Exists(baseFile))
{
throw Oops.Bah("【zh-CN.ts】文件未找到");
}
var dic = new Dictionary();
using var reader = new StreamReader(baseFile, Encoding.UTF8);
while (await reader.ReadLineAsync() is { } line)
{
if (line.Contains('{') || line.Contains('}')) continue;
var cleanLine = line.Trim().TrimEnd(',').Replace("'", "");
var parts = cleanLine.Split(new[] { ':' }, 2);
if (parts.Length == 2) dic[parts[0].Trim()] = parts[1].Trim();
}
reader.Close();
return dic;
}
private static async Task> ReadLanguageFile(string filePath)
{
if (!File.Exists(filePath))
{
throw Oops.Bah($"【{filePath.Split('/').Last()}】文件未找到");
}
var dic = new Dictionary();
using var reader = new StreamReader(filePath, Encoding.UTF8);
while (await reader.ReadLineAsync() is { } line)
{
if (line.Contains('{') || line.Contains('}')) continue;
var cleanLine = line.Trim().TrimEnd(',').Replace("'", "");
var parts = cleanLine.Split(new[] { ':' }, 2);
if (parts.Length == 2) dic[parts[0].Trim()] = parts[1].Trim();
}
reader.Close();
return dic;
}
private static void LogTranslationProgress(string key, string value, string res, ConsoleColor color)
{
Console.ForegroundColor = color;
Console.WriteLine($"翻译属性: {key,-32}值: {value,-64}结果: {res}");
Console.ResetColor();
}
private static void LogError(Exception e)
{
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.WriteLine($"{e.Message}");
Console.ResetColor();
}
#endregion 辅助方法
#endif
// 计算MD5值
[NonAction]
private static string EncryptString(string str)
{
MD5 md5 = MD5.Create();
// 将字符串转换成字节数组
byte[] byteOld = Encoding.UTF8.GetBytes(str);
// 调用加密方法
byte[] byteNew = md5.ComputeHash(byteOld);
// 将加密结果转换为字符串
StringBuilder sb = new StringBuilder();
foreach (byte b in byteNew)
{
// 将字节转换成16进制表示的字符串,
sb.Append(b.ToString("x2"));
}
// 返回加密的字符串
return sb.ToString();
}
}