Skip to content

Commit

Permalink
1. add unit test code in a stupid way
Browse files Browse the repository at this point in the history
  • Loading branch information
xflcx1991 committed Sep 7, 2020
1 parent 44637fd commit d15bc9f
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
import java.util.HashMap;
import java.util.Map;

/*
1. 多语种
2. 代码结构
3. 增加缓存,减少网络请求
*/
public class YoudaoTranslate extends BaseTranslate {
/**
* 设置存储 key 的名字,读取和设置值由 OmegaT 提供 API 来操作
Expand Down Expand Up @@ -66,6 +71,7 @@ public static void unloadPlugins() {
*/
@Override
protected String getPreferenceName() {
System.out.println("YoudaoTranslate - getPreferenceName");
return "allow_caiyun_Youdao_translate";
}

Expand All @@ -76,6 +82,7 @@ protected String getPreferenceName() {
*/
@Override
public String getName() {
System.out.println("YoudaoTranslate - getName");
return "Youdao Translate";
}

Expand Down Expand Up @@ -155,6 +162,7 @@ protected String translate(Language sLang, Language tLang, String text) throws E
//判断翻译缓存里有没有
// U+2026 HORIZONTAL ELLIPSIS 水平省略号 …
String lvShortText = text.length() > 5000 ? text.substring(0, 4997) + "\u2026" : text;
// TODO:不写 putToCache 这里的cache是无用的
String prev = getFromCache(sLang, tLang, lvShortText);
if (prev != null) {
// 啊,有缓存,那就直接返回不用请求了
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package xyz.xffish.machinetranslators;


import org.omegat.util.Language;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public abstract class BaseTranslateClone implements IMacineTranslationClone {
// protected boolean enabled;
private final Map<String, String> cache = Collections.synchronizedMap(new HashMap<>());

// public boolean isEnabled() {
// return this.enabled;
// }
protected String getFromCache(Language sLang, Language tLang, String text) {
return this.cache.get(sLang + "/" + tLang + "/" + text);
}

protected String putToCache(Language sLang, Language tLang, String text, String result) {
return this.cache.put(sLang + "/" + tLang + "/" + text, result);
}

protected String getCredential(String id) {
String property = System.getProperty(id);
//return property != null ? property : (String) CredentialsManager.getInstance().retrieve(id).orElse("");
return property != null ? property : "";
}

protected void setCredential(String id, String value, boolean temporary) {
System.setProperty(id, value);

}

protected abstract String getPreferenceName();

protected abstract String translate(Language var1, Language var2, String var3) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package xyz.xffish.machinetranslators;

//import java.awt.Window;
//import org.omegat.util.Language;

public interface IMacineTranslationClone {

String getName();

// boolean isEnabled();

// default void setEnabled(boolean enabled) {
// }

// String getTranslation(Language var1, Language var2, String var3) throws Exception;

// String getCachedTranslation(Language var1, Language var2, String var3);

default boolean isConfigurable() {
return false;
}

// default void showConfigurationUI(Window parent) {
// }
}

254 changes: 254 additions & 0 deletions src/test/java/xyz/xffish/machinetranslators/YoudaoTranslateClone.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
参考了
https://github.com/yoyicue/omegat-tencent-plugin
https://github.com/omegat-org/omegat/blob/854b6b5a66a0306e5c27e74c0b5d656ed80b2bd4/src/org/omegat/core/machinetranslators/YandexTranslate.java
GoogleTranslateWithoutApiKey
的写法,感谢上述作者
彩云小译 API 文档:https://fanyi.caiyunapp.com/#/api
*/
package xyz.xffish.machinetranslators;

import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.omegat.core.Core;
import org.omegat.gui.exttrans.MTConfigDialog;
import org.omegat.util.Language;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.xffish.machinetranslators.util.ErrorCode2Desc;
import xyz.xffish.machinetranslators.util.OLang2YLang;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;

/**
* 搞不定脱离 OmegaT 环境的初始化,只能重新搞一个类似功能但不依赖 OmegaT 的功能类.
* BaseTranslate 构造函数用到了已经初始化的 Preferences 类,测试环境搞不定如何初始化。
* 只能手动维护 YoudaoTranslate 和 YoudaoTranslateClone 的同步
*/
public class YoudaoTranslateClone extends BaseTranslateClone{
/**
* 设置存储 key 的名字,读取和设置值由 OmegaT 提供 API 来操作
*/
private static final String PROPERTY_APP_KEY = "youdao.app.key";
private static final String PROPERTY_APP_ID = "youdao.app.id";
/**
* 有道官方没有提供测试 Key
*/
// protected static final String PROPERTY_API_OFFICIAL_TEST_SECRET_KEY = "";
/**
* 有道翻译请求 URL
*/
protected static final String URL = "https://openapi.youdao.com/api";

private static final Logger logger = LoggerFactory.getLogger(YoudaoTranslate.class);





/**
* 在软件启动时会自动调用该函数来注册插件
*/
public static void loadPlugins() {
logger.info("加载 YoudaoTranslate Plugin");

Core.registerMachineTranslationClass(YoudaoTranslate.class);
}

public static void unloadPlugins() {
}

/**
* 显示该插件介绍性的话
*
* @return 介绍性话语
*/
@Override
protected String getPreferenceName() {
System.out.println("YoudaoTranslate - getPreferenceName");
return "allow_caiyun_Youdao_translate";
}

/**
* 在软件里显示该翻译插件的名字
*
* @return 本翻译插件显示的名字
*/
@Override
public String getName() {
System.out.println("YoudaoTranslate - getName");
return "Youdao Translate";
}

/**
* 将 OmegaT 的语言代码转换成有道翻译识别的语言代码<br>
* 找不到就输出 auto
*
* @param sLang
* @return
*/
private String omegatLang2YoudaoLang(String sLang) {
String tLang = OLang2YLang.translateOLang2YLang(sLang);
// 找不到就改成自动识别
if (tLang == null) {
tLang = "auto";
}
return tLang;
}

/**
* 照抄有道官方 java 示例.
* http://ai.youdao.com/DOCSIRMA/html/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E7%BF%BB%E8%AF%91/API%E6%96%87%E6%A1%A3/%E6%96%87%E6%9C%AC%E7%BF%BB%E8%AF%91%E6%9C%8D%E5%8A%A1/%E6%96%87%E6%9C%AC%E7%BF%BB%E8%AF%91%E6%9C%8D%E5%8A%A1-API%E6%96%87%E6%A1%A3.html#section-14
* 截断带翻译的文字以便简化计算签名
*
* @param q
* @return
*/
public static String truncate(String q) {
if (q == null) {
return null;
}
int len = q.length();
String result;
return len <= 20 ? q : (q.substring(0, 10) + len + q.substring(len - 10, len));
}

/**
* 插件主体功能函数,接收原文,获得译文并返回
*
* @param sLang 原文的语言
* @param tLang 译文的语言
* @param text 原文内容
* @return 译文内容
* @throws Exception
*/
@Override
protected String translate(Language sLang, Language tLang, String text) throws Exception {
logger.info("translate,sLang={},tLang={},text={}", sLang, tLang, text);
/*
* 请求用 post+表单形式
* 调用API需要向接口发送以下字段来访问服务。
* 字段名 类型 含义 必填 备注
* q text 待翻译文本 True 必须是UTF-8编码
* from text 源语言 True 参考下方 支持语言 (可设置为auto)
* to text 目标语言 True 参考下方 支持语言 (可设置为auto)
* appKey text 应用ID True 可在 应用管理 查看
* salt text UUID True UUID
* sign text 签名 True sha256(应用ID+input+salt+curtime+应用密钥)
* signType text 签名类型 True v3
* curtime text 当前UTC时间戳(秒) true TimeStamp
* ext text 翻译结果音频格式,支持mp3 false mp3
* voice text 翻译结果发音选择 false 0为女声,1为男声。默认为女声
* strict text 是否严格按照指定from和to进行翻译:true/false false 如果为false,则会自动中译英,英译中。默认为false
*
* 签名生成方法如下:
* signType=v3;
* sign=sha256(应用ID+input+salt+curtime+应用密钥);
* 其中,input的计算方式为:input=q前10个字符 + q长度 + q后10个字符(当q长度大于20)或 input=q字符串(当q长度小于等于20);
*
* 注意:
*
* voice 没有男声的,会输出女声。
* 发音需要在控制台创建tts实例,并绑定应用才能使用,否则点击发音会报110错误。
* 接口salt+curtime来防重放(即一个请求不可以被请求2次),所以salt最好为UUID。
*/
// -----------------处理带翻译文本-----------------
//判断翻译缓存里有没有
// U+2026 HORIZONTAL ELLIPSIS 水平省略号 …
String lvShortText = text.length() > 5000 ? text.substring(0, 4997) + "\u2026" : text;
String prev = getFromCache(sLang, tLang, lvShortText);
if (prev != null) {
// 啊,有缓存,那就直接返回不用请求了
System.out.println("啊,有缓存,太美妙了:" + prev);
// logger.info("啊,有缓存,太美妙了:{}", prev);
return prev;
}


// -----------------转换语言代码-----------------
String lvSourceLang = sLang.getLanguageCode().substring(0, 2).toLowerCase();
lvSourceLang = omegatLang2YoudaoLang(lvSourceLang);

String lvTargetLang = tLang.getLanguageCode().substring(0, 2).toLowerCase();
lvTargetLang = omegatLang2YoudaoLang(lvTargetLang);

// 获取应用 ID——appKey
String appKey = getCredential(PROPERTY_APP_ID);

// -----------------获取UUID-----------------
// 官方 python3 示例就是带“-”的 UUID
String uuid = IdUtil.randomUUID();
;

// -----------------计算签名 sign-----------------
// sha256(应用ID+input+salt+curtime+应用密钥)

// 应用密钥
String secretKey = getCredential(PROPERTY_APP_KEY);


logger.info("secretKey = {}", secretKey);
String curtime = String.valueOf(System.currentTimeMillis() / 1000);
// 开始计算
String originSign = appKey + truncate(lvShortText) + uuid + curtime + secretKey;
String sign = SecureUtil.sha256(originSign);


// -----------------构造接口调用参数-----------------
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("q", lvShortText); //待翻译文本
paramMap.put("from", lvSourceLang); //源语言
paramMap.put("to", lvTargetLang); //目标语言
paramMap.put("appKey", appKey);
paramMap.put("salt", uuid); //UUID
paramMap.put("sign", sign); //签名
paramMap.put("signType", "v3"); //签名类型 写死 v3
paramMap.put("curtime", curtime); //当前UTC时间戳(秒)
//省略 ext 和 voice 参数
paramMap.put("strict", "true"); //是否严格按照指定from和to进行翻译


String paramMapStr = JSONUtil.toJsonStr(paramMap);
logger.info("paramMap = {}", paramMapStr);
// -----------------发 POST 请求-----------------
String responseBody = HttpUtil.post(URL, paramMap);


logger.info("response body = {}", responseBody);

JSONObject jsonObject = JSONUtil.parseObj(responseBody);

String translation = "";

String errorCode = jsonObject.getStr("errorCode");

if (errorCode.equals("0")) {
translation = jsonObject.getStr("translation");
putToCache(sLang, tLang, lvShortText, translation);
} else {
// 出错了,从 errorCode2DescMap 找错误描述信息
String errorCodeDesc = ErrorCode2Desc.translateErrorCode2Desc(errorCode);
translation = errorCodeDesc;
}

return translation;
}


/**
* 是否在设置界面允许该插件的配置按钮可用,如果 false,配置按钮是灰色不可点的,也就没法配置 Token 了
*/
@Override
public boolean isConfigurable() {
return true;
}

// 不做这个函数的测试
// public void showConfigurationUI(Window parent) {

}
Loading

0 comments on commit d15bc9f

Please sign in to comment.