first I am going to teach you how to develop a go package
git clone <your own repo>
mkdir mstrans-cli && cd mstrans-cli
go mod init <your repo url as package id>
touch mstrans.go lang.go lang.json
{
"af": "Afrikaans",
"am": "Amharic",
"ar": "Arabic",
"as": "Assamese",
"az": "Azerbaijani",
"ba": "Bashkir",
"be": "Belarusian",
"bg": "Bulgarian",
"bho": "Bhojpuri",
"bn": "Bangla",
"bo": "Tibetan",
"brx": "Bodo",
"bs": "Bosnian",
"ca": "Catalan",
"cs": "Czech",
"cy": "Welsh",
"da": "Danish",
"de": "German",
"doi": "Dogri",
"dsb": "Lower Sorbian",
"dv": "Divehi",
"el": "Greek",
"en": "English",
"es": "Spanish",
"et": "Estonian",
"eu": "Basque",
"fa": "Persian",
"fi": "Finnish",
"fil": "Filipino",
"fj": "Fijian",
"fo": "Faroese",
"fr": "French",
"fr-CA": "French (Canada)",
"ga": "Irish",
"gl": "Galician",
"gom": "Konkani",
"gu": "Gujarati",
"ha": "Hausa",
"he": "Hebrew",
"hi": "Hindi",
"hne": "Chhattisgarhi",
"hr": "Croatian",
"hsb": "Upper Sorbian",
"ht": "Haitian Creole",
"hu": "Hungarian",
"hy": "Armenian",
"id": "Indonesian",
"ig": "Igbo",
"ikt": "Inuinnaqtun",
"is": "Icelandic",
"it": "Italian",
"iu": "Inuktitut",
"iu-Latn": "Inuktitut (Latin)",
"ja": "Japanese",
"ka": "Georgian",
"kk": "Kazakh",
"km": "Khmer",
"kmr": "Kurdish (Northern)",
"kn": "Kannada",
"ko": "Korean",
"ks": "Kashmiri",
"ku": "Kurdish (Central)",
"ky": "Kyrgyz",
"lb": "Luxembourgish",
"ln": "Lingala",
"lo": "Lao",
"lt": "Lithuanian",
"lug": "Ganda",
"lv": "Latvian",
"lzh": "Chinese (Literary)",
"mai": "Maithili",
"mg": "Malagasy",
"mi": "Māori",
"mk": "Macedonian",
"ml": "Malayalam",
"mn-Cyrl": "Mongolian (Cyrillic)",
"mn-Mong": "Mongolian (Traditional)",
"mni": "Manipuri",
"mr": "Marathi",
"ms": "Malay",
"mt": "Maltese",
"mww": "Hmong Daw",
"my": "Myanmar (Burmese)",
"nb": "Norwegian",
"ne": "Nepali",
"nl": "Dutch",
"nso": "Sesotho sa Leboa",
"nya": "Nyanja",
"or": "Odia",
"otq": "Querétaro Otomi",
"pa": "Punjabi",
"pl": "Polish",
"prs": "Dari",
"ps": "Pashto",
"pt": "Portuguese (Brazil)",
"pt-PT": "Portuguese (Portugal)",
"ro": "Romanian",
"ru": "Russian",
"run": "Rundi",
"rw": "Kinyarwanda",
"sd": "Sindhi",
"si": "Sinhala",
"sk": "Slovak",
"sl": "Slovenian",
"sm": "Samoan",
"sn": "Shona",
"so": "Somali",
"sq": "Albanian",
"sr-Cyrl": "Serbian (Cyrillic)",
"sr-Latn": "Serbian (Latin)",
"st": "Sesotho",
"sv": "Swedish",
"sw": "Swahili",
"ta": "Tamil",
"te": "Telugu",
"th": "Thai",
"ti": "Tigrinya",
"tk": "Turkmen",
"tlh-Latn": "Klingon (Latin)",
"tlh-Piqd": "Klingon (pIqaD)",
"tn": "Setswana",
"to": "Tongan",
"tr": "Turkish",
"tt": "Tatar",
"ty": "Tahitian",
"ug": "Uyghur",
"uk": "Ukrainian",
"ur": "Urdu",
"uz": "Uzbek (Latin)",
"vi": "Vietnamese",
"xh": "Xhosa",
"yo": "Yoruba",
"yua": "Yucatec Maya",
"yue": "Cantonese (Traditional)",
"zh-Hans": "Chinese Simplified",
"zh-Hant": "Chinese Traditional",
"zu": "Zulu"
}
// 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
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
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
package main
import (
"fmt"
"io"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
// TODO: should change to your own repo
"gitee.com/EEPPEE_admin/mstrans"
"github.com/atotto/clipboard"
"github.com/spf13/cobra"
)
// 程序版本信息
const version = "v0.0.1"
// 全局CLI参数
var (
fromLang string
toLang string
manualToken string
timeout int // 超时时间(秒)
)
// startProgress 启动翻译进度动画
func startProgress() chan struct{} {
stop := make(chan struct{})
chars := []rune{'|', '/', '-', '\\'}
go func() {
i := 0
for {
select {
case <-stop:
// 清除进度行
fmt.Printf("\r\033[K")
return
default:
fmt.Printf("\r\033[34m[正在翻译]\033[0m %c", chars[i%4])
i++
time.Sleep(100 * time.Millisecond)
}
}
}()
return stop
}
// truncateText 截断长文本(用于剪贴板预览)
func truncateText(text string, maxLen int) string {
runes := []rune(text)
if len(runes) <= maxLen {
return text
}
return string(runes[:maxLen]) + "..."
}
// readInput 读取输入(优先级:命令行参数 > 剪贴板 > 标准输入)
func readInput(args []string) (string, error) {
// 1. 优先读取命令行参数
if len(args) > 0 {
return strings.Join(args, " "), nil
}
// 2. 读取剪贴板内容
clipboardText, err := clipboard.ReadAll()
if err == nil && strings.TrimSpace(clipboardText) != "" {
fmt.Printf("\033[36m[提示]\033[0m 已从剪贴板读取文本:%s\n", truncateText(clipboardText, 50))
return strings.TrimSpace(clipboardText), nil
}
// 3. 读取标准输入
fmt.Println("\033[36m[提示]\033[0m 剪贴板为空,读取标准输入(按Ctrl+D结束):")
bytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("读取标准输入失败: %w", err)
}
inputText := strings.TrimSpace(string(bytes))
inputText = strings.ReplaceAll(inputText, "\n", " ")
if inputText == "" {
return "", fmt.Errorf("输入文本不能为空")
}
return inputText, nil
}
// printTranslationResult 格式化输出翻译结果
func printTranslationResult(inputText string, results []mstrans.TranslationResult) {
if len(results) == 0 {
fmt.Printf("\033[31m[错误]\033[0m 未获取到翻译结果\n")
return
}
// 遍历结果(支持多文本翻译)
for i, result := range results {
// 显示检测到的源语言(如果是自动检测)
if result.DetectedLanguage.Language != "" {
fmt.Printf("\033[35m[检测语言]\033[0m %s (置信度: %.2f)\n",
result.DetectedLanguage.Language, result.DetectedLanguage.Score)
}
// 原文(多文本时显示序号)
if len(results) > 1 {
fmt.Printf("\033[34m[原文 %d]\033[0m %s\n", i+1, inputText)
} else {
fmt.Printf("\033[34m[原文]\033[0m %s\n", inputText)
}
// 翻译结果
for _, trans := range result.Translations {
fmt.Printf("\033[32m[翻译]\033[0m %s\n", trans.Text)
// 显示音译(如果有)
if trans.Transliteration.Text != "" {
fmt.Printf("\033[36m[音译]\033[0m %s\n", trans.Transliteration.Text)
}
}
fmt.Println()
}
}
// 根命令
var rootCmd = &cobra.Command{
Use: "mstrans-cli",
Short: "微软翻译CLI工具 - 轻量、高效的命令行翻译工具",
Long: `微软翻译CLI工具 (mstrans-cli)
基于微软Edge翻译API实现,支持多语言互译
核心功能:
1. 自动/手动指定源语言/目标语言
2. 支持手动设置认证Token
3. 智能输入:命令行参数 > 剪贴板 > 标准输入
4. 翻译进度实时显示
5. 自动检测源语言+置信度展示
6. 支持音译结果输出
支持的语言码示例:
中文(简): zh-Hans, 英文: en, 日语: ja, 韩语: ko, 法语: fr
德语: de, 俄语: ru, 西班牙语: es, 葡萄牙语: pt`,
Version: version,
Run: func(cmd *cobra.Command, args []string) {
// 处理中断信号(优雅停止进度动画)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
var wg sync.WaitGroup
wg.Add(1)
go func() {
<-sigChan
fmt.Printf("\r\033[K\033[31m[中断]\033[0m 翻译请求已取消\n")
os.Exit(1)
}()
defer wg.Done()
// 1. 读取输入文本
inputText, err := readInput(args)
if err != nil {
fmt.Printf("\033[31m[错误]\033[0m %v\n", err)
os.Exit(1)
}
// 2. 初始化微软翻译客户端
clientTimeout := time.Duration(timeout) * time.Second
client := mstrans.NewClient(clientTimeout)
// 设置手动Token(如果指定)
if manualToken != "" {
client.SetManualToken(manualToken)
fmt.Printf("\033[36m[提示]\033[0m 已使用手动指定的认证Token\n")
}
// 3. 处理语言参数(标准化)
fromCode := mstrans.GetLangCode(fromLang)
toCode := mstrans.GetLangCode(toLang)
// 验证目标语言
if !mstrans.IsLangSupported(toCode) {
fmt.Printf("\033[31m[错误]\033[0m 不支持的目标语言:%s\n", toLang)
os.Exit(1)
}
// 4. 执行翻译(带进度显示)
stopProgress := startProgress()
results, err := client.Translate(inputText, fromCode, toCode)
close(stopProgress) // 停止进度动画
if err != nil {
fmt.Printf("\033[31m[错误]\033[0m 翻译失败: %v\n", err)
os.Exit(1)
}
// 5. 格式化输出结果
printTranslationResult(inputText, results)
},
}
// translate子命令(显式翻译,和根命令逻辑一致)
var translateCmd = &cobra.Command{
Use: "translate [文本]",
Short: "翻译指定文本(核心功能)",
Args: cobra.MinimumNArgs(0), // 允许无参数(从剪贴板/标准输入读)
Run: func(cmd *cobra.Command, args []string) {
rootCmd.Run(cmd, args)
},
}
func init() {
// 全局参数配置
rootCmd.PersistentFlags().StringVarP(&fromLang, "from", "f", "auto-detect",
"源语言(auto-detect=自动检测,支持码/名称:zh-Hans, en, 中文, english)")
rootCmd.PersistentFlags().StringVarP(&toLang, "to", "t", "zh-Hans",
"目标语言(默认:zh-Hans,支持码/名称:en, ja, 英文, 日语)")
rootCmd.PersistentFlags().StringVarP(&manualToken, "token", "k", "",
"手动设置认证Token(优先级高于自动获取)")
rootCmd.PersistentFlags().IntVarP(&timeout, "timeout", "o", 10,
"请求超时时间(秒,默认:10)")
// 添加子命令
rootCmd.AddCommand(translateCmd)
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Printf("\033[31m[错误]\033[0m 执行失败: %v\n", err)
os.Exit(1)
}
}
- so now, you maybe get some idea from this article
for youdao translate api
ref links