mirror of
https://github.com/xxnuo/MTranServer.git
synced 2026-06-20 19:26:16 +08:00
Create deepl.go
This commit is contained in:
249
internal/handlers/deepl.go
Normal file
249
internal/handlers/deepl.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xxnuo/MTranServer/internal/services"
|
||||
)
|
||||
|
||||
// deeplLangToBCP47 DeepL 语言代码转 BCP47
|
||||
var deeplLangToBCP47 = map[string]string{
|
||||
// Source languages
|
||||
"AR": "ar",
|
||||
"BG": "bg",
|
||||
"CS": "cs",
|
||||
"DA": "da",
|
||||
"DE": "de",
|
||||
"EL": "el",
|
||||
"EN": "en",
|
||||
"ES": "es",
|
||||
"ET": "et",
|
||||
"FI": "fi",
|
||||
"FR": "fr",
|
||||
"HU": "hu",
|
||||
"ID": "id",
|
||||
"IT": "it",
|
||||
"JA": "ja",
|
||||
"KO": "ko",
|
||||
"LT": "lt",
|
||||
"LV": "lv",
|
||||
"NB": "no",
|
||||
"NL": "nl",
|
||||
"PL": "pl",
|
||||
"PT": "pt",
|
||||
"RO": "ro",
|
||||
"RU": "ru",
|
||||
"SK": "sk",
|
||||
"SL": "sl",
|
||||
"SV": "sv",
|
||||
"TR": "tr",
|
||||
"UK": "uk",
|
||||
"ZH": "zh-Hans",
|
||||
// Target languages with variants
|
||||
"EN-GB": "en-GB",
|
||||
"EN-US": "en-US",
|
||||
"PT-BR": "pt-BR",
|
||||
"PT-PT": "pt-PT",
|
||||
"ZH-CN": "zh-Hans",
|
||||
"ZH-TW": "zh-Hant",
|
||||
}
|
||||
|
||||
// bcp47ToDeeplLang BCP47 转 DeepL 语言代码
|
||||
var bcp47ToDeeplLang = map[string]string{
|
||||
"ar": "AR",
|
||||
"bg": "BG",
|
||||
"cs": "CS",
|
||||
"da": "DA",
|
||||
"de": "DE",
|
||||
"el": "EL",
|
||||
"en": "EN",
|
||||
"en-GB": "EN-GB",
|
||||
"en-US": "EN-US",
|
||||
"es": "ES",
|
||||
"et": "ET",
|
||||
"fi": "FI",
|
||||
"fr": "FR",
|
||||
"hu": "HU",
|
||||
"id": "ID",
|
||||
"it": "IT",
|
||||
"ja": "JA",
|
||||
"ko": "KO",
|
||||
"lt": "LT",
|
||||
"lv": "LV",
|
||||
"no": "NB",
|
||||
"nl": "NL",
|
||||
"pl": "PL",
|
||||
"pt": "PT",
|
||||
"pt-BR": "PT-BR",
|
||||
"pt-PT": "PT-PT",
|
||||
"ro": "RO",
|
||||
"ru": "RU",
|
||||
"sk": "SK",
|
||||
"sl": "SL",
|
||||
"sv": "SV",
|
||||
"tr": "TR",
|
||||
"uk": "UK",
|
||||
"zh-Hans": "ZH",
|
||||
"zh-CN": "ZH-CN",
|
||||
"zh-Hant": "ZH-TW",
|
||||
"zh-TW": "ZH-TW",
|
||||
}
|
||||
|
||||
// convertDeeplLangToBCP47 将 DeepL 语言代码转换为 BCP47
|
||||
func convertDeeplLangToBCP47(deeplLang string) string {
|
||||
// 转换为大写进行匹配
|
||||
upperLang := strings.ToUpper(deeplLang)
|
||||
if bcp47, ok := deeplLangToBCP47[upperLang]; ok {
|
||||
return bcp47
|
||||
}
|
||||
// 如果不在映射表中,返回小写版本
|
||||
return strings.ToLower(deeplLang)
|
||||
}
|
||||
|
||||
// convertBCP47ToDeeplLang 将 BCP47 语言代码转换为 DeepL
|
||||
func convertBCP47ToDeeplLang(bcp47Lang string) string {
|
||||
if deeplLang, ok := bcp47ToDeeplLang[bcp47Lang]; ok {
|
||||
return deeplLang
|
||||
}
|
||||
// 如果不在映射表中,返回大写版本
|
||||
return strings.ToUpper(bcp47Lang)
|
||||
}
|
||||
|
||||
// DeeplTranslateRequest DeepL 翻译请求
|
||||
type DeeplTranslateRequest struct {
|
||||
Text []string `json:"text" binding:"required" example:"Hello, world!"`
|
||||
SourceLang string `json:"source_lang,omitempty" example:"EN"`
|
||||
TargetLang string `json:"target_lang" binding:"required" example:"DE"`
|
||||
SplitSentences string `json:"split_sentences,omitempty" example:"1"`
|
||||
PreserveFormatting string `json:"preserve_formatting,omitempty" example:"0"`
|
||||
Formality string `json:"formality,omitempty" example:"default"`
|
||||
GlossaryID string `json:"glossary_id,omitempty"`
|
||||
TagHandling string `json:"tag_handling,omitempty" example:"xml"`
|
||||
OutlineDetection string `json:"outline_detection,omitempty" example:"1"`
|
||||
NonSplittingTags []string `json:"non_splitting_tags,omitempty"`
|
||||
SplittingTags []string `json:"splitting_tags,omitempty"`
|
||||
IgnoreTags []string `json:"ignore_tags,omitempty"`
|
||||
ModelType string `json:"model_type,omitempty" example:"quality_optimized"`
|
||||
Context string `json:"context,omitempty"`
|
||||
EnableBetaLanguages bool `json:"enable_beta_languages,omitempty"`
|
||||
}
|
||||
|
||||
// DeeplTranslation 翻译结果
|
||||
type DeeplTranslation struct {
|
||||
DetectedSourceLanguage string `json:"detected_source_language" example:"EN"`
|
||||
Text string `json:"text" example:"Hallo, Welt!"`
|
||||
}
|
||||
|
||||
// DeeplTranslateResponse DeepL 翻译响应
|
||||
type DeeplTranslateResponse struct {
|
||||
Translations []DeeplTranslation `json:"translations"`
|
||||
}
|
||||
|
||||
// HandleDeeplTranslate DeepL 翻译兼容接口
|
||||
// @Summary DeepL 翻译兼容接口
|
||||
// @Description 兼容 DeepL API v2 的翻译接口
|
||||
// @Tags 插件
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param token query string false "API Token"
|
||||
// @Param request body DeeplTranslateRequest true "DeepL 翻译请求"
|
||||
// @Success 200 {object} DeeplTranslateResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 401 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /deepl [post]
|
||||
func HandleDeeplTranslate(apiToken string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 检查 token - 兼容 DeepL API 认证方式
|
||||
if apiToken != "" {
|
||||
// 支持 DeepL 标准认证: Authorization: DeepL-Auth-Key [key]
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
token := ""
|
||||
|
||||
if strings.HasPrefix(authHeader, "DeepL-Auth-Key ") {
|
||||
token = strings.TrimPrefix(authHeader, "DeepL-Auth-Key ")
|
||||
} else if authHeader != "" {
|
||||
// 也支持标准 Bearer token
|
||||
token = strings.TrimPrefix(authHeader, "Bearer ")
|
||||
} else {
|
||||
// 兼容 query 参数方式
|
||||
token = c.Query("token")
|
||||
}
|
||||
|
||||
if token != apiToken {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Unauthorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
var req DeeplTranslateRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 转换语言代码:DeepL -> BCP47
|
||||
sourceLang := "auto"
|
||||
if req.SourceLang != "" {
|
||||
sourceLang = convertDeeplLangToBCP47(req.SourceLang)
|
||||
}
|
||||
targetLang := convertDeeplLangToBCP47(req.TargetLang)
|
||||
|
||||
// 获取或创建翻译引擎
|
||||
m, err := services.GetOrCreateEngine(sourceLang, targetLang)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": fmt.Sprintf("Failed to get engine: %v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 批量翻译
|
||||
translations := make([]DeeplTranslation, len(req.Text))
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 确定是否需要 HTML 处理
|
||||
isHTML := req.TagHandling == "html" || req.TagHandling == "xml"
|
||||
|
||||
for i, text := range req.Text {
|
||||
var result string
|
||||
if isHTML {
|
||||
result, err = m.TranslateHTML(ctx, text)
|
||||
} else {
|
||||
result, err = m.Translate(ctx, text)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": fmt.Sprintf("Translation failed at index %d: %v", i, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回 DeepL 格式的语言代码
|
||||
detectedLang := req.SourceLang
|
||||
if detectedLang == "" {
|
||||
detectedLang = convertBCP47ToDeeplLang(sourceLang)
|
||||
}
|
||||
|
||||
translations[i] = DeeplTranslation{
|
||||
DetectedSourceLanguage: detectedLang,
|
||||
Text: result,
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, DeeplTranslateResponse{
|
||||
Translations: translations,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user