IdlecloudX commited on
Commit
96c8569
·
verified ·
1 Parent(s): 4412065

Create translator.py

Browse files
Files changed (1) hide show
  1. translator.py +160 -0
translator.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ translator.py
3
+ 腾讯云 + 百度翻译 API 轮询封装
4
+ ⚠️ 需在 HF 空间的 “Variables” 页设置以下环境变量
5
+ ------------------------------------------------------------------
6
+ TENCENT_SECRET_ID 腾讯云 SecretId
7
+ TENCENT_SECRET_KEY 腾讯云 SecretKey
8
+ TENCENT_TRANSLATE_URL (可选) 默认 https://tmt.tencentcloudapi.com
9
+ BAIDU_TRANSLATE_URL (可选) 默认 https://fanyi-api.baidu.com/api/trans/vip/translate
10
+ BAIDU_CREDENTIALS_JSON 形如:
11
+ [
12
+ {"app_id": "xxxx", "secret_key": "yyyy"},
13
+ {"app_id": "aaaa", "secret_key": "bbbb"}
14
+ ]
15
+ ------------------------------------------------------------------
16
+ """
17
+ import hashlib, hmac, json, os, random, time
18
+ from datetime import datetime
19
+ from typing import List, Sequence, Optional
20
+
21
+ import requests
22
+
23
+ # ------------------------------------------------------------------
24
+ # 读取环境变量
25
+ # ------------------------------------------------------------------
26
+ TENCENT_SECRET_ID = os.environ.get("TENCENT_SECRET_ID")
27
+ TENCENT_SECRET_KEY = os.environ.get("TENCENT_SECRET_KEY")
28
+ TENCENT_TRANSLATE_URL = os.environ.get("TENCENT_TRANSLATE_URL", "https://tmt.tencentcloudapi.com")
29
+
30
+ BAIDU_TRANSLATE_URL = os.environ.get("BAIDU_TRANSLATE_URL", "https://fanyi-api.baidu.com/api/trans/vip/translate")
31
+ BAIDU_CREDENTIALS = json.loads(os.environ.get("BAIDU_CREDENTIALS_JSON", "[]"))
32
+
33
+ # 内部轮询索引
34
+ _baidu_idx: int = 0
35
+ def _next_baidu_cred():
36
+ global _baidu_idx
37
+ if not BAIDU_CREDENTIALS:
38
+ return None
39
+ cred = BAIDU_CREDENTIALS[_baidu_idx]
40
+ _baidu_idx = (_baidu_idx + 1) % len(BAIDU_CREDENTIALS)
41
+ return cred
42
+
43
+ # ------------------------------------------------------------------
44
+ # 腾讯翻译
45
+ # ------------------------------------------------------------------
46
+ def _sign(key: bytes, msg: str) -> bytes:
47
+ return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
48
+
49
+ def _tc3_signature(secret_key: str, date: str, service: str, string_to_sign: str) -> str:
50
+ secret_date = _sign(("TC3" + secret_key).encode(), date)
51
+ secret_service = _sign(secret_date, service)
52
+ secret_signing = _sign(secret_service, "tc3_request")
53
+ return hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
54
+
55
+ def _translate_with_tencent(texts: Sequence[str], src="auto", tgt="zh") -> Optional[List[str]]:
56
+ """优先使用腾讯云翻译。失败返回 None"""
57
+ if not (TENCENT_SECRET_ID and TENCENT_SECRET_KEY):
58
+ return None # 未配置凭证
59
+ service = "tmt"
60
+ host = "tmt.tencentcloudapi.com"
61
+ action = "TextTranslate"
62
+ version = "2018-03-21"
63
+ region = "ap-beijing"
64
+ ts = int(time.time())
65
+ date = datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d")
66
+ algorithm = "TC3-HMAC-SHA256"
67
+
68
+ payload = {
69
+ "SourceText": "\n".join(texts),
70
+ "Source": src,
71
+ "Target": tgt,
72
+ "ProjectId": 0,
73
+ }
74
+ payload_str = json.dumps(payload, ensure_ascii=False)
75
+
76
+ # ---------- step‑1 canonical request ----------
77
+ canonical_request = "\n".join([
78
+ "POST",
79
+ "/",
80
+ "",
81
+ f"content-type:application/json; charset=utf-8\nhost:{host}\nx-tc-action:{action.lower()}\n",
82
+ "content-type;host;x-tc-action",
83
+ hashlib.sha256(payload_str.encode()).hexdigest(),
84
+ ])
85
+
86
+ # ---------- step‑2 string to sign ----------
87
+ credential_scope = f"{date}/{service}/tc3_request"
88
+ string_to_sign = "\n".join([
89
+ algorithm, str(ts), credential_scope,
90
+ hashlib.sha256(canonical_request.encode()).hexdigest(),
91
+ ])
92
+
93
+ # ---------- step‑3 signature ----------
94
+ signature = _tc3_signature(TENCENT_SECRET_KEY, date, service, string_to_sign)
95
+
96
+ # ---------- step‑4 headers ----------
97
+ authorization = (
98
+ f"{algorithm} Credential={TENCENT_SECRET_ID}/{credential_scope}, "
99
+ f"SignedHeaders=content-type;host;x-tc-action, Signature={signature}"
100
+ )
101
+ headers = {
102
+ "Authorization": authorization,
103
+ "Content-Type": "application/json; charset=utf-8",
104
+ "Host": host,
105
+ "X-TC-Action": action,
106
+ "X-TC-Timestamp": str(ts),
107
+ "X-TC-Version": version,
108
+ "X-TC-Region": region,
109
+ }
110
+
111
+ # ---------- request ----------
112
+ try:
113
+ resp = requests.post(TENCENT_TRANSLATE_URL, headers=headers, data=payload_str, timeout=8)
114
+ resp.raise_for_status()
115
+ data = resp.json()
116
+ return data["Response"]["TargetText"].split("\n")
117
+ except Exception as e:
118
+ print(f"[translator] Tencent API error → {e}")
119
+ return None
120
+
121
+ # ------------------------------------------------------------------
122
+ # 百度翻译
123
+ # ------------------------------------------------------------------
124
+ def _translate_with_baidu(texts: Sequence[str], src="auto", tgt="zh") -> Optional[List[str]]:
125
+ creds = _next_baidu_cred()
126
+ if creds is None:
127
+ return None # 未配置凭证
128
+ app_id, secret_key = creds["app_id"], creds["secret_key"]
129
+ salt = random.randint(32768, 65536)
130
+ query = "\n".join(texts)
131
+ sign = hashlib.md5((app_id + query + str(salt) + secret_key).encode()).hexdigest()
132
+ params = {
133
+ "q": query, "from": src, "to": tgt,
134
+ "appid": app_id, "salt": salt, "sign": sign,
135
+ }
136
+ try:
137
+ resp = requests.get(BAIDU_TRANSLATE_URL, params=params, timeout=8)
138
+ resp.raise_for_status()
139
+ data = resp.json()
140
+ return [item["dst"] for item in data["trans_result"]]
141
+ except Exception as e:
142
+ print(f"[translator] Baidu API error → {e}")
143
+ return None
144
+
145
+ # ------------------------------------------------------------------
146
+ # 对外统一函数
147
+ # ------------------------------------------------------------------
148
+ def translate_texts(texts: Sequence[str],
149
+ src_lang: str = "auto",
150
+ tgt_lang: str = "zh") -> List[str]:
151
+ """
152
+ 优先 Tencent → 失败再 Baidu → 如果都失败,返回原文。
153
+ """
154
+ if not texts:
155
+ return []
156
+
157
+ out = _translate_with_tencent(texts, src_lang, tgt_lang)
158
+ if out is None:
159
+ out = _translate_with_baidu(texts, src_lang, tgt_lang)
160
+ return out or list(texts)