SPARK-1457 Update Translator plugin (#774)

* Update Translator plugin to use other translation API

* Add Preference panel

* update libretranslate-java to 1.0.5
This commit is contained in:
ilyaHlevnoy 2023-03-15 22:06:57 +03:00 committed by GitHub
parent 91333459a9
commit cb972ae18c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 398 additions and 229 deletions

View File

@ -10,7 +10,7 @@
</parent>
<artifactId>translator</artifactId>
<version>1.7</version>
<version>2.0</version>
<name>Translator Plugin</name>
<description>Translates instant messages between users using Google Translation Service.</description>
@ -25,17 +25,24 @@
</contributors>
<dependencies>
<dependency>
<groupId>httpunit</groupId>
<artifactId>httpunit</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.clojars.suuft</groupId>
<artifactId>libretranslate-java</artifactId>
<version>1.0.5</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>clojars.org</id>
<url>https://repo.clojars.org</url>
</repository>
</repositories>
</project>

View File

@ -19,6 +19,9 @@ import java.awt.Color;
import javax.swing.JComboBox;
import net.suuft.libretranslate.Language;
import net.suuft.libretranslate.Translator;
import org.apache.commons.lang3.StringUtils;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.spark.ChatManager;
import org.jivesoftware.spark.SparkManager;
@ -40,6 +43,10 @@ public class TranslatorPlugin implements Plugin {
* Called after Spark is loaded to initialize the new plugin.
*/
public void initialize() {
TranslatorPreference pref = new TranslatorPreference();
SparkManager.getPreferenceManager().addPreference(pref);
// Retrieve ChatManager from the SparkManager
final ChatManager chatManager = SparkManager.getChatManager();
@ -47,11 +54,18 @@ public class TranslatorPlugin implements Plugin {
chatManager.addChatRoomListener(new ChatRoomListenerAdapter() {
public void chatRoomOpened(ChatRoom room) {
// only do the translation for single chat
if (room instanceof ChatRoomImpl) {
if (room instanceof ChatRoomImpl && TranslatorProperties.getInstance().getEnabledTranslator()) {
final ChatRoomImpl roomImpl = (ChatRoomImpl)room;
//Set server LibreTranslate API
if(TranslatorProperties.getInstance().getUseCustomUrl() && !StringUtils.isBlank(TranslatorProperties.getInstance().getUrl())){
Translator.setUrlApi(TranslatorProperties.getInstance().getUrl());
} else {
Translator.setUrlApi(TranslatorUtil.getDefaultUrl());
}
// Create a new ChatRoomButton.
final JComboBox<TranslatorUtil.TranslationType> translatorBox = new JComboBox<>(TranslatorUtil.TranslationType.getTypes());
final JComboBox<Object> translatorBox = new JComboBox<>(TranslatorUtil.getLanguage());
translatorBox.addActionListener( e -> {
// Set the focus back to the message box.
@ -64,21 +78,16 @@ public class TranslatorPlugin implements Plugin {
final MessageEventListener messageListener = new MessageEventListener() {
public void sendingMessage(Message message) {
String currentBody = message.getBody();
String oldBody = message.getBody();
TranslatorUtil.TranslationType type =
(TranslatorUtil.TranslationType)translatorBox.getSelectedItem();
if (type != null && type != TranslatorUtil.TranslationType.None) {
Language lang = (Language) translatorBox.getSelectedItem();
if (lang != null && lang != Language.NONE) {
message.setBody(null);
currentBody = TranslatorUtil.translate(currentBody, type);
TranscriptWindow transcriptWindow = chatManager.getChatRoom( message.getTo().asEntityBareJidOrThrow() ).getTranscriptWindow();
if(oldBody.equals(currentBody.substring(0,currentBody.length()-1)))
{
transcriptWindow.insertNotificationMessage("Could not translate: "+currentBody, ChatManager.ERROR_COLOR);
}
else
{
try {
currentBody = TranslatorUtil.translate(currentBody, lang);
transcriptWindow.insertNotificationMessage("-> "+currentBody, Color.gray);
message.setBody(currentBody);
message.setBody(currentBody);
} catch (Exception e){
transcriptWindow.insertNotificationMessage(TranslatorResource.getString("translator.error"), ChatManager.ERROR_COLOR);
}
}
}

View File

@ -0,0 +1,88 @@
package org.jivesoftware.spark.translator;
import org.jivesoftware.spark.preference.Preference;
import org.jivesoftware.spark.util.log.Log;
import javax.swing.*;
import java.awt.*;
public class TranslatorPreference implements Preference {
private TranslatorPreferencePanel _prefPanel;
private final TranslatorProperties _props;
public TranslatorPreference() {
_props = TranslatorProperties.getInstance();
try {
if (EventQueue.isDispatchThread()) {
_prefPanel = new TranslatorPreferencePanel();
} else {
EventQueue.invokeAndWait( () -> _prefPanel = new TranslatorPreferencePanel() );
}
} catch (Exception e) {
Log.error(e);
}
}
@Override
public String getTitle() {
return TranslatorResource.getString("translator.title");
}
@Override
public Icon getIcon() {
ClassLoader cl = getClass().getClassLoader();
return new ImageIcon(cl.getResource("translator.png"));
}
@Override
public String getTooltip() {
return TranslatorResource.getString("translator.title");
}
@Override
public String getListName() {
return TranslatorResource.getString("translator.title");
}
@Override
public String getNamespace() {
return TranslatorResource.getString("translator.title");
}
@Override
public JComponent getGUI() {
return _prefPanel;
}
@Override
public void load() {
_prefPanel.initializeValues();
}
@Override
public void commit() {
_prefPanel.storeValues();
}
@Override
public boolean isDataValid() {
return true;
}
@Override
public String getErrorMessage() {
return null;
}
@Override
public Object getData() {
return _props;
}
@Override
public void shutdown() {
}
}

View File

@ -0,0 +1,99 @@
package org.jivesoftware.spark.translator;
import org.jivesoftware.spark.component.VerticalFlowLayout;
import javax.swing.*;
import java.awt.*;
public class TranslatorPreferencePanel extends JPanel {
private final JCheckBox _enabledCheckbox;
private final JTextField _url;
private final JCheckBox _useCustomUrl;
private final Insets INSETS = new Insets(5, 5, 5, 5);
public TranslatorPreferencePanel() {
this.setLayout(new BorderLayout());
_enabledCheckbox = new JCheckBox(TranslatorResource.getString("translator.enabled"));
_useCustomUrl = new JCheckBox(TranslatorResource.getString("translator.custom.url"));
_url = new JTextField();
updateGUI();
add(makeGeneralSettingsPanel());
}
private JComponent makeGeneralSettingsPanel() {
JPanel generalPanel = new JPanel();
generalPanel.setLayout(new GridBagLayout());
generalPanel.setBorder(BorderFactory.createTitledBorder(TranslatorResource.getString("translator.settings")));
int rowcount = 0;
generalPanel.add(_enabledCheckbox,
new GridBagConstraints(0, rowcount, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, INSETS, 0, 0));
rowcount++;
generalPanel.add(_useCustomUrl,
new GridBagConstraints(0, rowcount, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, INSETS, 0, 0));
rowcount++;
generalPanel.add(new JLabel(TranslatorResource.getString("translator.url")),
new GridBagConstraints(0, rowcount, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, INSETS, 0, 0));
generalPanel.add(_url,
new GridBagConstraints(1, rowcount, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, INSETS, 0, 0));
rowcount++;
JLabel placeHolder = new JLabel();
generalPanel.add(placeHolder,
new GridBagConstraints(1, rowcount, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, INSETS, 0, 0));
JPanel panel = new JPanel(new VerticalFlowLayout());
panel.add(generalPanel);
return new JScrollPane(panel);
}
public void initializeValues() {
TranslatorProperties props = TranslatorProperties.getInstance();
_enabledCheckbox.setSelected(props.getEnabledTranslator());
_useCustomUrl.setSelected(props.getUseCustomUrl());
_url.setText(props.getUrl());
}
public void storeValues(){
TranslatorProperties props = TranslatorProperties.getInstance();
props.setEnabledTranslator(_enabledCheckbox.isSelected());
props.setUseCustomUrl(_useCustomUrl.isSelected());
props.setUrl(_url.getText());
props.save();
}
private void updateGUI(){
_enabledCheckbox.addActionListener( e -> {
if (_enabledCheckbox.isSelected()) {
_useCustomUrl.setEnabled(true);
_url.setEnabled(_useCustomUrl.isSelected());
} else {
_url.setEnabled(false);
_useCustomUrl.setEnabled(false);
}
});
_useCustomUrl.addActionListener( e -> {
if(_useCustomUrl.isSelected()){
_url.setEnabled(true);
} else {
_url.setEnabled(false);
}
});
if (!TranslatorProperties.getInstance().getEnabledTranslator()) {
_url.setEnabled(false);
_useCustomUrl.setEnabled(false);
}
if(!TranslatorProperties.getInstance().getUseCustomUrl()){
_url.setEnabled(false);
}
}
}

View File

@ -0,0 +1,96 @@
package org.jivesoftware.spark.translator;
import org.jivesoftware.Spark;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class TranslatorProperties {
private final Properties props;
private File configFile;
public static final String ACTIVE = "active";
public static final String USE_CUSTOM_URL = "useCustomUrl";
public static final String URL = "url";
private static final Object LOCK = new Object();
private static TranslatorProperties instance = null;
/**
* returns the Instance of this Properties file
*
* @return
*/
public static TranslatorProperties getInstance() {
synchronized (LOCK) {
if (instance == null) {
instance = new TranslatorProperties();
}
return instance;
}
}
private TranslatorProperties() {
this.props = new Properties();
try {
props.load(new FileInputStream(getConfigFile()));
} catch (IOException e) {
// Can't load ConfigFile
}
}
private File getConfigFile() {
if (configFile == null)
configFile = new File(Spark.getSparkUserHome(), "translator.properties");
return configFile;
}
public void save() {
try {
props.store(new FileOutputStream(getConfigFile()), "Storing Translator properties");
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean getEnabledTranslator(){
return getBoolean(ACTIVE,false);
}
public void setEnabledTranslator(boolean enable){
setBoolean(ACTIVE,enable);
}
public boolean getUseCustomUrl(){
return getBoolean(USE_CUSTOM_URL, false);
}
public void setUseCustomUrl(boolean enable){
setBoolean(USE_CUSTOM_URL,enable);
}
public String getUrl(){
return props.getProperty(URL);
}
public void setUrl(String url){
props.setProperty(URL,url);
}
public boolean getBoolean(String property, boolean defaultValue) {
return Boolean.parseBoolean(props.getProperty(property, Boolean.toString(defaultValue)));
}
public void setBoolean(String property, boolean value) {
props.setProperty(property, Boolean.toString(value));
}
}

View File

@ -0,0 +1,37 @@
package org.jivesoftware.spark.translator;
import org.jivesoftware.resource.UTF8Control;
import org.jivesoftware.spark.util.log.Log;
import java.text.MessageFormat;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
public class TranslatorResource {
private static PropertyResourceBundle prb;
static ClassLoader cl = TranslatorResource.class.getClassLoader();
static {
prb = (PropertyResourceBundle) ResourceBundle.getBundle("i18n/translator_i18n", new UTF8Control());
}
public static String getString(String propertyName) {
try {
return prb.getString(propertyName);
}
catch (Exception e) {
Log.error(e);
return propertyName;
}
}
public static String getString(String propertyName, Object... obj) {
String str = prb.getString(propertyName);
if (str == null) {
return null;
}
return MessageFormat.format(str, obj);
}
}

View File

@ -15,21 +15,11 @@
*/
package org.jivesoftware.spark.translator;
import java.io.IOException;
import java.net.MalformedURLException;
import net.suuft.libretranslate.Language;
import net.suuft.libretranslate.Translator;
import org.apache.commons.lang3.ArrayUtils;
import javax.xml.transform.TransformerException;
import org.jivesoftware.spark.util.log.Log;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.meterware.httpunit.GetMethodWebRequest;
import com.meterware.httpunit.HttpUnitOptions;
import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebResponse;
import com.sun.org.apache.xpath.internal.XPathAPI;
import java.util.*;
/**
* A utility class that uses google's translation service to translate text to various languages.
@ -39,198 +29,21 @@ public class TranslatorUtil {
}
public static String translate(String text, TranslationType type) {
if (type == null) {
public static String translate(String text, Language language) {
if (language == Language.NONE) {
return text;
}
return useGoogleTranslator(text, type);
return Translator.translate(language,text);
}
private static String useGoogleTranslator(String text, TranslationType type) {
StringBuilder response = new StringBuilder();
String urlString = "http://translate.google.com/translate_t?text=" + text + "&langpair=" + type.getID();
// disable scripting to avoid requiring js.jar
HttpUnitOptions.setScriptingEnabled(false);
// create the conversation object which will maintain state for us
WebConversation wc = new WebConversation();
// Obtain the google translation page
WebRequest webRequest = new GetMethodWebRequest(urlString);
// required to prevent a 403 forbidden error from google
webRequest.setHeaderField("User-agent", "Mozilla/4.0");
try {
WebResponse webResponse = wc.getResponse(webRequest);
//NodeList list = webResponse.getDOM().getDocumentElement().getElementsByTagName("div");
try {
NodeList list2 = XPathAPI.selectNodeList(webResponse.getDOM(), "//SPAN[@id='result_box']/SPAN/text()");
for (int i = 0; i < list2.getLength(); ++i)
{
response.append(list2.item(i).getNodeValue()).append(" ");
}
} catch (TransformerException e) {
Log.warning("Translator error",e);
}
// int length = list.getLength();
// for (int i = 0; i < length; i++) {
// Element element = (Element)list.item(i);
// if ("result_box".equals(element.getAttribute("id"))) {
// Node translation = element.getFirstChild();
// if (translation != null) {
// response = translation.getNodeValue();
// }
// }
// }
}
catch (MalformedURLException e) {
Log.error("Could not for url: " + e);
}
catch (IOException e) {
Log.error("Could not get response: " + e);
}
catch (SAXException e) {
Log.error("Could not parse response content: " + e);
}
return response.toString();
public static Object[] getLanguage(){
Object[] languageList = Arrays.stream(Language.values()).filter( x -> !x.equals(Language.NONE)).sorted(Comparator.comparing(Language::name)).toArray();
return ArrayUtils.addAll(new Object[]{Language.NONE},languageList);
}
/**
* A typesafe enum class for translation types.
*/
public static final class TranslationType {
private final String id;
private final String name;
private TranslationType(String id, String name) {
this.id = id;
this.name = name;
}
public String getID() {
return id;
}
public String getName() {
return name;
}
public static TranslationType[] getTypes() {
return types;
}
public String toString() {
return name;
}
/**
* Type representing no translation.
*/
public static final TranslationType None = new TranslationType("", "No Translation");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToGerman = new TranslationType("en%7Cde", "English to German");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToSpanish = new TranslationType("en%7Ces", "English to Spanish");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToFrench = new TranslationType("en%7Cfr", "English to French");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToItalian = new TranslationType("en%7Cit", "English to Italian");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToPortuguese = new TranslationType("en%7Cpt", "English to Portuguese");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToJapanese = new TranslationType("en%7Cja", "English to Japanese");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToKorean = new TranslationType("en%7Cko", "English to Korean");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType EnglishToChineseSimplified = new TranslationType("en%7Czh-CN", "English to Chinese (Simplified)");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType GermanToEnglish = new TranslationType("de%7Cen", "German to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType GermanToFrench = new TranslationType("de%7Cfr", "German to French");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType SpanishToEnglish = new TranslationType("es%7Cen", "Spanish to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType FrenchToEnglish = new TranslationType("fr%7Cen", "French to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType FrenchToGerman = new TranslationType("fr%7Cde", "French to German");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType ItalianToEnglish = new TranslationType("it%7Cen", "Italian to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType PortugueseToEnglish = new TranslationType("pt%7Cen", "Portuguese to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType JapaneseToEnglish = new TranslationType("ja%7Cen", "Japanese to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType KoreanToEnglish = new TranslationType("ko%7Cen", "Korean to English");
/**
* Type representing a translation from english to german.
*/
public static final TranslationType ChineseSimplifiedToEnglish = new TranslationType("zh-CN%7Cen", "Chinese (Simplified) to English");
/**
* Array containing all TranslationTypes
*/
private static final TranslationType[] types = {
None,
EnglishToGerman,
EnglishToSpanish,
EnglishToFrench,
EnglishToItalian,
EnglishToPortuguese,
EnglishToJapanese,
EnglishToKorean,
EnglishToChineseSimplified,
GermanToEnglish,
GermanToFrench,
SpanishToEnglish,
FrenchToEnglish,
FrenchToGerman,
ItalianToEnglish,
PortugueseToEnglish,
JapaneseToEnglish,
KoreanToEnglish,
ChineseSimplifiedToEnglish
};
public static String getDefaultUrl(){
return "https://translate.fedilab.app/translate";
}
}

View File

@ -43,6 +43,12 @@
<h1>
Translator Spark plug Changelog
</h1>
<p><b>2.0</b> -- December 30th, 2022</p>
<ul>
<li>Replace Google API with LibreTranslate API</li>
<li>Add Preference panel</li>
<li>Add defaults and i18n</li>
</ul>
<p><b>1.4</b> -- March 17th, 2011</p>
<ul>
<li>Adjusted to Google's new translator layout for Spark 2.6.x</li>

View File

@ -0,0 +1,7 @@
translator.title = Translator
translator.enabled = Enable Translator
translator.custom.url = Use custom url LibreTranslate
translator.url = URL:
translator.settings = Translator settings
translator.error = Could not translate!

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View File

@ -15,8 +15,8 @@
*/
package com.jivesoftware.spark.translator;
import net.suuft.libretranslate.Language;
import org.jivesoftware.spark.translator.TranslatorUtil;
import org.jivesoftware.spark.translator.TranslatorUtil.TranslationType;
import org.junit.Test;
import java.io.BufferedReader;
@ -35,7 +35,7 @@ public class TestTranslator {
}
public static void main(String[] args) {
Map<Integer, TranslationType> translationMap = initalizeTranslationMap();
Map<Integer, Language> translationMap = initalizeTranslationMap();
boolean again = true;
while (again) {
@ -55,8 +55,8 @@ public class TestTranslator {
System.out.println("Great, now enter the translation id. Below are the following choices:");
for (Map.Entry<Integer, TranslationType> o : translationMap.entrySet()) {
System.out.println(o.getKey() + " - " + o.getValue().getName());
for (Map.Entry<Integer, Language> o : translationMap.entrySet()) {
System.out.println(o.getKey() + " - " + o.getValue());
}
String translationID = "";
@ -67,7 +67,7 @@ public class TestTranslator {
}
Integer id = Integer.valueOf(translationID);
TranslatorUtil.TranslationType type = translationMap.get(id);
Language type = translationMap.get(id);
if (type == null) {
System.out.println("Not a valid translation type.");
@ -77,7 +77,7 @@ public class TestTranslator {
String result = TranslatorUtil.translate(text, type);
System.out.println("Your original text:\n" + text);
System.out.println("Has been translated from: " + type.getName());
System.out.println("Has been translated from: " + type);
System.out.println("The result is:\n" + result);
System.out.println("Do you want to continue testing?");
@ -91,9 +91,9 @@ public class TestTranslator {
}
}
private static Map<Integer, TranslationType> initalizeTranslationMap() {
TranslatorUtil.TranslationType[] types = TranslatorUtil.TranslationType.getTypes();
Map<Integer,TranslatorUtil.TranslationType> map = new TreeMap<>(Integer::compareTo);
private static Map<Integer, Language> initalizeTranslationMap() {
Language[] types = Language.values();
Map<Integer,Language> map = new TreeMap<>(Integer::compareTo);
for (int i = 1; i < types.length; i++) {
map.put(i, types[i]);
@ -101,4 +101,11 @@ public class TestTranslator {
return map;
}
@Test public void testSort(){
for (int i = 0; i < TranslatorUtil.getLanguage().length-1; i++) {
System.out.println(TranslatorUtil.getLanguage()[i]);
}
}
}