// lang.go
package mstrans
import (
_ "embed"
"encoding/json"
"strings"
)
//go:embed all:lang.json
var langJSON []byte
// LangMap maps language codes to names (e.g., "zh-Hans": "Chinese Simplified")
var LangMap map[string]string
func init() {
// Load language map from embedded JSON (runs once at package init)
if err := json.Unmarshal(langJSON, &LangMap); err != nil {
panic("failed to load language map: " + err.Error())
}
}
// GetLangCode normalizes language input (e.g., "zh" → "zh-Hans", "english" → "en")
func GetLangCode(lang string) string {
if lang == "" || lang == "auto-detect" {
return "" // Auto-detect maps to empty from parameter
}
// Exact match (e.g., "zh-Hans" → "zh-Hans")
if _, ok := LangMap[lang]; ok {
return lang
}
// Case-insensitive match (e.g., "ZH" → "zh-Hans", "english" → "en")
lowerLang := strings.ToLower(lang)
for code, name := range LangMap {
if strings.ToLower(code) == lowerLang || strings.ToLower(name) == lowerLang {
return code
}
}
return "" // Unsupported language
}
// IsLangSupported checks if a language code/name is supported
func IsLangSupported(lang string) bool {
return GetLangCode(lang) != ""
}
mstrans.go
go复制代码
// mstrans.go
package mstrans
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
// DefaultUserAgent matches the JS default (ensures compatibility with Microsoft's API)
const (
DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
apiAuthURL = "https://edge.microsoft.com/translate/auth"
apiTranslateURL = "https://api.cognitive.microsofttranslator.com/translate"
)
// Client is the main struct for Microsoft Translator (thread-safe)
type Client struct {
client *http.Client
userAgent string
mu sync.Mutex // For token cache thread safety
token string
tokenExpiry time.Time
ManualToken string // 手动设置的 Token(优先级高于自动获取)
}
// TranslationResult represents a single translation result (matches MS API response)
type TranslationResult struct {
DetectedLanguage struct {
Language string `json:"language"`
Score float64 `json:"score"`
} `json:"detectedLanguage,omitempty"`
Translations []struct {
Text string `json:"text"`
To string `json:"to"`
Transliteration struct {
Text string `json:"text,omitempty"`
} `json:"transliteration,omitempty"`
} `json:"translations"`
}
// NewClient creates a new Translator client (custom timeout optional)
func NewClient(timeout ...time.Duration) *Client {
// Default timeout: 10 seconds
clientTimeout := 10 * time.Second
if len(timeout) > 0 {
clientTimeout = timeout[0]
}
return &Client{
client: &http.Client{
Timeout: clientTimeout,
},
userAgent: DefaultUserAgent,
}
}
// SetManualToken 方法,用于手动设置 Token
func (c *Client) SetManualToken(token string) {
c.mu.Lock()
defer c.mu.Unlock()
c.ManualToken = token
// 手动设置 Token 时,临时设置 expiry 为 1 小时后(避免被自动刷新)
c.tokenExpiry = time.Now().Add(1 * time.Hour)
}
// SetUserAgent overrides the default User-Agent (for customization)
func (c *Client) SetUserAgent(ua string) {
c.userAgent = ua
}
// fetchToken gets a new JWT token from Microsoft (caches it for ~10 minutes)
func (c *Client) fetchToken() error {
c.mu.Lock()
defer c.mu.Unlock()
// 优先使用手动设置的 Token
if c.ManualToken != "" {
c.token = c.ManualToken
return nil
}
// 原有自动获取逻辑(保留,供网络正常时使用)
if !c.tokenExpiry.IsZero() && time.Until(c.tokenExpiry) > 1*time.Minute {
return nil
}
req, err := http.NewRequest("GET", apiAuthURL, nil)
if err != nil {
return fmt.Errorf("create auth request: %w", err)
}
req.Header.Set("User-Agent", c.userAgent)
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("fetch token: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("auth request failed: %s", resp.Status)
}
var tokenBytes bytes.Buffer
if _, err := tokenBytes.ReadFrom(resp.Body); err != nil {
return fmt.Errorf("read token: %w", err)
}
tokenStr := tokenBytes.String()
parts := strings.Split(tokenStr, ".")
if len(parts) < 2 {
return fmt.Errorf("invalid JWT token format")
}
payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return fmt.Errorf("decode JWT payload: %w", err)
}
var payload struct {
Exp int64 `json:"exp"`
}
if err := json.Unmarshal(payloadBytes, &payload); err != nil {
return fmt.Errorf("parse JWT payload: %w", err)
}
c.token = tokenStr
c.tokenExpiry = time.Unix(payload.Exp, 0)
return nil
}
// Translate is the core API: translates text (single/multiple) between languages
// - text: string or []string (content to translate)
// - from: source language (use "" or "auto-detect" for auto-detection)
// - to: target language (e.g., "zh-Hans", "en")
func (c *Client) Translate(text any, from, to string) ([]TranslationResult, error) {
// Step 1: Validate inputs
fromCode := GetLangCode(from)
toCode := GetLangCode(to)
if !IsLangSupported(toCode) {
return nil, fmt.Errorf("unsupported target language: %s", to)
}
// Convert text to []string (supports single string or slice)
var textSlice []string
switch v := text.(type) {
case string:
if v == "" {
return nil, fmt.Errorf("text cannot be empty")
}
textSlice = []string{v}
case []string:
if len(v) == 0 || (len(v) == 1 && v[0] == "") {
return nil, fmt.Errorf("text slice cannot be empty")
}
textSlice = v
default:
return nil, fmt.Errorf("text must be string or []string (got %T)", text)
}
// Step 2: Ensure valid token
if err := c.fetchToken(); err != nil {
return nil, fmt.Errorf("get token: %w", err)
}
// Step 3: Prepare translation request payload
payload := make([]map[string]string, len(textSlice))
for i, t := range textSlice {
payload[i] = map[string]string{"Text": t}
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("marshal payload: %w", err)
}
// Step 4: Build translation URL with query params
query := url.Values{}
query.Set("api-version", "3.0")
if fromCode != "" {
query.Set("from", fromCode)
}
query.Set("to", toCode)
translateURL := fmt.Sprintf("%s?%s", apiTranslateURL, query.Encode())
// Step 5: Create POST request
req, err := http.NewRequest("POST", translateURL, bytes.NewReader(payloadBytes))
if err != nil {
return nil, fmt.Errorf("create translate request: %w", err)
}
// Set required headers
req.Header.Set("User-Agent", c.userAgent)
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
// Step 6: Send request and parse response
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("send translate request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// Read error details for debugging
var errBody bytes.Buffer
_, _ = errBody.ReadFrom(resp.Body)
return nil, fmt.Errorf("translation failed: %s (body: %s)", resp.Status, errBody.String())
}
// Step 7: Parse translation result
var results []TranslationResult
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, fmt.Errorf("parse translation response: %w", err)
}
return results, nil
}
// TranslateSimple is a simplified API for single-text translation (returns only translated text)
func (c *Client) TranslateSimple(text, from, to string) (string, error) {
results, err := c.Translate(text, from, to)
if err != nil {
return "", err
}
if len(results) == 0 || len(results[0].Translations) == 0 {
return "", fmt.Errorf("no translation result")
}
return results[0].Translations[0].Text, nil
}
now use this package
bash复制代码
mkdir mstrans-cli && cd mstrans-cli
go mod init mstrans-cli
# use your own repo by following example code
GOPRIVATE=gitee.com/EEPPEE_admin/mstrans
go get gitee.com/EEPPEE_admin/mstrans
touch main.go