app-starlock/langAi.py

561 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/python3
import base64
import datetime
import hashlib
import hmac
import json
import os
import sys
import time
import urllib.parse
from typing import Dict
import requests
# 运行格式方式python langAi.py youdao 备注aliyun 阿里云 youdao 有道翻译
# 语言文件路径 /Users/mac/www/app-starlock/lan
path = "./lan2/"
# 原语言
SOURCE_LANG = 'lan_en'
# 过滤不翻译的语言
FILTER_LANG = ['lan_zh', 'lan_en', 'lan_keys']
# 阿里云语言映射
ALIYUN_LANG_MAP = {
'lan_zh': {
'name': '中文',
'code': 'zh'
},
'lan_tw': {
'name': '繁体中文',
'code': 'zh-tw',
},
'lan_en': {
'name': '英文',
'code': 'en',
},
'lan_fr': {
'name': '法文',
'code': 'fr',
},
'lan_ru': {
'name': '俄文',
'code': 'ru',
},
'lan_de': {
'name': '德文',
'code': 'de',
},
'lan_ja': {
'name': '日文',
'code': 'ja',
},
'lan_ko': {
'name': '韩文',
'code': 'ko',
},
'lan_it': {
'name': '意大利文',
'code': 'it',
},
'lan_pt': {
'name': '葡萄牙文',
'code': 'pt',
},
'lan_es': {
'name': '西班牙文',
'code': 'es',
},
'lan_ar': {
'name': '阿拉伯文',
'code': 'ar',
},
'lan_vi': {
'name': '越南文',
'code': 'vi',
},
'lan_ms': {
'name': '马来文',
'code': 'ms',
},
'lan_nl': {
'name': '荷兰文',
'code': 'nl',
},
'lan_ro': {
'name': '罗马尼亚文',
'code': 'ro',
},
'lan_lt': {
'name': '立陶宛文',
'code': 'lt',
},
'lan_sv': {
'name': '瑞典文',
'code': 'sv',
},
'lan_et': {
'name': '爱沙尼亚文',
'code': 'et',
},
'lan_pl': {
'name': '波兰文',
'code': 'pl',
},
'lan_sk': {
'name': '斯洛伐克文',
'code': 'sk',
},
'lan_cs': {
'name': '捷克文',
'code': 'cs',
},
'lan_el': {
'name': '希腊文',
'code': 'el',
},
'lan_he': {
'name': '希伯来文',
'code': 'he',
},
'lan_tr': {
'name': '土耳其文',
'code': 'tr',
},
'lan_hu': {
'name': '匈牙利文',
'code': 'hu',
},
'lan_bg': {
'name': '保加利亚文',
'code': 'bg',
},
'lan_kk': {
'name': '哈萨克文',
'code': 'kk',
},
'lan_bn': {
'name': '孟加拉文',
'code': 'bn',
},
'lan_hr': {
'name': '克罗地亚文',
'code': 'hbs',
},
'lan_th': {
'name': '泰文',
'code': 'th',
},
'lan_id': {
'name': '印尼文',
'code': 'id',
},
'lan_fi': {
'name': '芬兰文',
'code': 'fi',
},
'lan_da': {
'name': '丹麦文',
'code': 'da',
},
}
# 有道语言映射
YOUDAO_LANG_MAP = {
'lan_zh': {
'name': '中文',
'code': 'zh-CHS',
},
'lan_tw': {
'name': '繁体中文(中国台湾)',
'code': 'zh-CHT',
},
'lan_hk': {
'name': '繁体中文(中国香港)',
'code': 'yue',
},
'lan_en': {
'name': '英文',
'code': 'en'
},
'lan_fr': {
'name': '法文',
'code': 'fr'
},
'lan_ru': {
'name': '俄文',
'code': 'ru'
},
'lan_de': {
'name': '德文',
'code': 'de'
},
'lan_ja': {
'name': '日文',
'code': 'ja'
},
'lan_ko': {
'name': '韩文',
'code': 'ko'
},
'lan_it': {
'name': '意大利文',
'code': 'it'
},
'lan_pt': {
'name': '葡萄牙文',
'code': 'pt'
},
'lan_es': {
'name': '西班牙文',
'code': 'es'
},
'lan_ar': {
'name': '阿拉伯文',
'code': 'ar'
},
'lan_vi': {
'name': '越南文',
'code': 'vi'
},
'lan_ms': {
'name': '马来文',
'code': 'ms'
},
'lan_nl': {
'name': '荷兰文',
'code': 'nl'
},
'lan_ro': {
'name': '罗马尼亚文',
'code': 'ro'
},
'lan_lt': {
'name': '立陶宛文',
'code': 'lt'
},
'lan_sv': {
'name': '瑞典文',
'code': 'sv'
},
'lan_et': {
'name': '爱沙尼亚文',
'code': 'et'
},
'lan_pl': {
'name': '波兰文',
'code': 'pl'
},
'lan_sk': {
'name': '斯洛伐克文',
'code': 'sk'
},
'lan_cs': {
'name': '捷克文',
'code': 'cs'
},
'lan_el': {
'name': '希腊文',
'code': 'el'
},
'lan_he': {
'name': '希伯来文',
'code': 'he'
},
'lan_tr': {
'name': '土耳其文',
'code': 'tr'
},
'lan_hu': {
'name': '匈牙利文',
'code': 'hu'
},
'lan_bg': {
'name': '保加利亚文',
'code': 'bg'
},
'lan_kk': {
'name': '哈萨克文',
'code': 'kk'
},
'lan_bn': {
'name': '孟加拉文',
'code': 'bn'
},
'lan_hr': {
'name': '克罗地亚文',
'code': 'hr'
},
'lan_th': {
'name': '泰文',
'code': 'th'
},
'lan_id': {
'name': '印尼文',
'code': 'id'
},
'lan_fi': {
'name': '芬兰文',
'code': 'fi'
},
'lan_da': {
'name': '丹麦文',
'code': 'da'
},
'lan_uk': {
'name': '乌克兰文',
'code': 'uk'
},
'lan_sr_cyrl': {
'name': '塞尔维亚语(西里尔文)',
'code': 'sr-Cyrl'
},
'lan_hy': {
'name': '亚美尼亚语',
'code': 'hy'
},
'lan_ka': {
'name': '格鲁吉亚语',
'code': 'ka'
},
}
# =============================== 以下为固定代码,非必要请勿修改 ===============================
# 阿里云配置
ALIYUN_CONFIG = {
'domain': 'mt.cn-hangzhou.aliyuncs.com/api/translate/web/ecommerce',
'accessKeyId': 'LTAI5tFke4yGaY94ondXkmHn',
'accessKeySecret': '08zPw8BoRqfqlKDTMX65k7gfeER33x',
}
# 有道翻译配置
YOUDAO_CONFIG = {
'domain': 'https://openapi.youdao.com/api',
'appKey': '3e4923bad470f8d2',
'appSecret': '5thWwZ7D0RIzT0aquCF6NKmaiakAyhqr'
}
CHANNELS = ['aliyun', 'youdao']
if len(sys.argv) < 2:
print('Usage: python langAi.py aliLang')
sys.exit(1)
# 检测传入参数是否合法
if sys.argv[1] not in CHANNELS:
print('channel must be one of {}'.format(CHANNELS))
sys.exit(1)
# 主函数
def main(channel):
en_path = path + SOURCE_LANG + ".json"
# 判断文件是否存在
if not os.path.exists(en_path):
print('文件不存在:', en_path)
sys.exit(1)
# 读取文件内容
en_us_content = read_file(en_path)
# 把json转数组
en_us_data = json.loads(en_us_content)
# 语言列表 如果channel为aliyun赋值ALIYUN_LANG_MAP否则赋值YOUDAO_LANG_MAP
lang_map = ALIYUN_LANG_MAP if channel == 'aliyun' else YOUDAO_LANG_MAP
# 遍历ALIYUN_LANG_MAP
for key in lang_map:
# 过滤掉不翻译的语言
if key in FILTER_LANG:
continue
lang_path = path + key + ".json"
# 验证文件是否存在,如果不存在即创建
if not os.path.exists(lang_path):
create_file(lang_path)
data_content = read_file(lang_path)
if data_content.strip(): # Check if the file is not empty
datas = json.loads(data_content)
else:
datas = {}
total = len(en_us_data) # 总数
current = 0 # 当前数
for en_us_key in en_us_data:
current += 1
print('语言:%s-%s 正在翻译处理第%d/%d批数据' % (key,lang_map[key]['code'], current, total))
# 过滤已存在的key
if en_us_key in datas:
continue
# 异常处理
try:
datas[en_us_key] = translation(channel, lang_map[SOURCE_LANG]['code'], lang_map[key]['code'],
en_us_data[en_us_key])
except Exception as e:
print('翻译异常:', e.args, 'key:', en_us_key, 'content:', en_us_data[en_us_key])
continue
# 写入文件
with open(lang_path, 'w', encoding='utf-8') as file:
json.dump(datas, file, ensure_ascii=False, indent=4)
# 翻译
def translation(channel, source_language, target_language, text):
translation_text = ''
if channel == 'aliyun':
translation_text = aliyun_translation(source_language, target_language, text)
elif channel == 'youdao':
translation_text = youdao_translation(source_language, target_language, text)
# 延迟2秒
time.sleep(2)
return translation_text
# 有道翻译
def youdao_translation(source_language, target_language, text):
post_body = {
'q': text,
'from': source_language,
'to': target_language,
'appKey': YOUDAO_CONFIG['appKey'],
'salt': hashlib.md5(os.urandom(32)).hexdigest(), # 随机字符串可使用UUID进行生产
'signType': 'v3',
'curtime': str(int(datetime.datetime.now().timestamp())),
}
# 签名生成方法如下:
# signType=v3
# sign=sha256(应用ID+input+salt+curtime+应用密钥)
# 其中input的计算方式为input=q前10个字符 + q长度 + q后10个字符当q长度大于20或 input=q字符串当q长度小于等于20
input_text = post_body['q']
if len(input_text) > 20:
input_text = input_text[:10] + str(len(input_text)) + input_text[-10:]
post_body['input'] = input_text
sign = hashlib.sha256((YOUDAO_CONFIG['appKey'] + input_text + post_body['salt'] + post_body['curtime'] +
YOUDAO_CONFIG['appSecret']).encode('utf-8')).hexdigest()
post_body['sign'] = sign
req = fetch_content(YOUDAO_CONFIG['domain'], 'POST', post_body)
req_json = json.loads(req)
try:
return req_json['translation'][0]
except KeyError as e:
print(f"[翻译异常][KeyError] 目标语言: {target_language}, key: {text}, API返回: {req_json}, 请求参数: {post_body}")
raise
except Exception as e:
print(f"[翻译异常][OtherError] 目标语言: {target_language}, key: {text}, API返回: {req_json}, 请求参数: {post_body}, 异常: {e}")
raise
# 阿里云翻译
def aliyun_translation(source_language, target_language, text):
headers = {
'Version': '2018-10-12',
}
post_body = {
"FormatType": "text",
"SourceLanguage": source_language,
"TargetLanguage": target_language,
"SourceText": text,
"Ccene": "general",
'Action': 'TranslateGeneral',
}
req = aliyun_request(ALIYUN_CONFIG['domain'], {**post_body, **headers})
if req['Code'] == '200':
return req['Data']['Translated']
else:
raise Exception(req)
def aliyun_request(domain: str, params: Dict, security: bool = True,
method: str = 'POST') -> Dict:
# Add common parameters
api_params = {
"SignatureMethod": "HMAC-SHA1",
"SignatureNonce": hashlib.md5(os.urandom(32)).hexdigest(),
"SignatureVersion": "1.0",
"AccessKeyId": ALIYUN_CONFIG['accessKeyId'],
"Timestamp": datetime.datetime.utcnow().isoformat("T"),
"Format": "JSON",
}
api_params.update(params)
# Sort parameters by key
sorted_params = sorted(api_params.items(), key=lambda x: x[0])
sorted_query_string_tmp = ""
for key, value in sorted(sorted_params):
sorted_query_string_tmp += "&" + aliyun_encode(key) + "=" + aliyun_encode(value)
string_to_sign = "{}&%2F&{}".format(method, aliyun_encode(sorted_query_string_tmp[1:]))
# Calculate the signature
signature = base64.b64encode(
hmac.new((ALIYUN_CONFIG['accessKeySecret'] + "&").encode('utf-8'), string_to_sign.encode('utf-8'),
hashlib.sha1).digest()).decode(
'utf-8')
# Add the signature to the parameters
api_params['Signature'] = signature
# Send the request
url = ("https" if security else "http") + "://" + domain + "/"
if method == 'POST':
response = requests.post(url, data=api_params)
else:
response = requests.get(url, params=api_params)
return response.json()
# 阿里云编码
def aliyun_encode(value):
res = urllib.parse.quote_plus(value)
res = res.replace("+", "%20")
res = res.replace("*", "%2A")
res = res.replace("%7E", "~")
return res
def fetch_content(url, method, body):
headers = {"x-sdk-client": "python/2.0.0"}
if method == 'POST':
response = requests.post(url, data=body, headers=headers)
else:
response = requests.get(url, params=body, headers=headers)
if response.status_code != 200:
raise Exception(f"Request failed with status {response.status_code}")
return response.text
# 创建文件
def create_file(file_path):
with open(file_path, 'w') as file:
pass
# 读取文件内容
def read_file(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
return content
# translation_text = youdao_translation('en', 'de', 'Your application <:name> has been approved.')
# print(translation_text)
main(sys.argv[1])