diff --git a/apps/services/sys/aliyun/translate/translate.go b/apps/services/sys/aliyun/translate/translate.go index e0f98676..049c89b5 100644 --- a/apps/services/sys/aliyun/translate/translate.go +++ b/apps/services/sys/aliyun/translate/translate.go @@ -1,6 +1,7 @@ package translate import ( + "bytes" "context" "encoding/json" "fmt" @@ -41,14 +42,46 @@ func newSys(options Options) (sys *AliTranslate, err error) { return } +// flexStr 兼容阿里同一个字段的两种形状:字符串或数字。 +// +// ⚠️ 这不是洁癖,是一次真实事故(2026-09-19):TranslateGeneral 正常响应里 +// `Code` 是字符串 "200",而**鉴权层**的错误(产品未开通)回的是**数字**: +// +// {"Message":"Auth check failed! ... service:[alimt] is not activated.","Code":10009} +// +// 字段声明成 string 时这里直接 unmarshal 失败,错误变成 +// 「cannot unmarshal number into Go struct field ... of type string」—— +// 真正的原因(产品没开通)被盖住,而且 comm.IsLangUnsupportedErr 那套靠匹配 +// `code=10005` 分流的逻辑也一并失效,所有阿里翻译错误都退化成「请稍后再试」, +// 用户只会一直点重试。 +type flexStr string + +func (f *flexStr) UnmarshalJSON(b []byte) error { + b = bytes.TrimSpace(b) + if len(b) == 0 || string(b) == "null" { + *f = "" + return nil + } + if b[0] == '"' { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + *f = flexStr(s) + return nil + } + *f = flexStr(b) // 数字/布尔等,原样按字面量取 + return nil +} + // translateGeneralResponse Aliyun MT TranslateGeneral 响应 type translateGeneralResponse struct { - RequestId string `json:"RequestId"` - Code string `json:"Code"` - Message string `json:"Message,omitempty"` + RequestId string `json:"RequestId"` + Code flexStr `json:"Code"` + Message string `json:"Message,omitempty"` Data struct { - WordCount string `json:"WordCount"` - Translated string `json:"Translated"` + WordCount flexStr `json:"WordCount"` + Translated string `json:"Translated"` // SourceLanguage 传 auto 时,阿里会在这里回填它检测出的源语种(短码)。 // 文本翻译的「自动检测」靠它把语种显示回界面。 DetectedLanguage string `json:"DetectedLanguage"` @@ -78,6 +111,11 @@ func (this *AliTranslate) translateOne(from, to, text string) (translated, detec return "", "", fmt.Errorf("unmarshal response: %w body=%s", err, body) } if out.Code != "" && out.Code != "200" { + // 「产品没开通」是配置问题,重试一万次也没用,单独把话说明白, + // 免得只看到「翻译失败,请稍后再试」而一直等它自己好。 + if out.Code == "10009" || strings.Contains(out.Message, "is not activated") { + return "", "", fmt.Errorf("translate failed: code=%s msg=%s(阿里云机器翻译 alimt 未开通:去阿里云控制台开通,或把后台 MT 服务换成别家)", out.Code, out.Message) + } return "", "", fmt.Errorf("translate failed: code=%s msg=%s", out.Code, out.Message) } return out.Data.Translated, out.Data.DetectedLanguage, nil