[translate] Implement LibreTranslate support
This commit is contained in:
parent
dcc3dec8f1
commit
9df0e04508
|
@ -5,7 +5,7 @@ pages:
|
|||
image: python:3.8-buster
|
||||
stage: deploy
|
||||
script:
|
||||
- pip install mkdocs
|
||||
- pip install mkdocs jinja2==3.0.0
|
||||
- mkdocs build
|
||||
artifacts:
|
||||
paths:
|
||||
|
|
|
@ -5,6 +5,6 @@ If one of my mods depends on this, it will be mentioned in its page.
|
|||
Apart from useful classes for mods, LibJF also adds two new item tags:
|
||||
- `raut:overpowered`: if an entity only wears armor items with this tag, it will become invulnerable
|
||||
- `raut:shulker_boxes_illegal`: items with this tag cannot be placed inside shulker boxes.
|
||||
Inteded to be used for backpacks or similar items
|
||||
Intended to be used for backpacks or similar items
|
||||
|
||||
If you want to use LibJF yourself, you can find documentation [here](https://jfmods.gitlab.io/LibJF/)
|
||||
|
|
|
@ -40,6 +40,11 @@ ConfigHolder.getInstance().get("yourmod").write();
|
|||
LibJF config is only intended for simple config screens, it does not support nested classes, multiple pages or controls like sliders.
|
||||
Use something else for those
|
||||
|
||||
## Translations
|
||||
Config keys are translated as `<mod id>.jfconfig.<field name>`.
|
||||
You may add a tooltip as follows: `<mod id>.jfconfig.<field name>.tooltip`.
|
||||
Enum keys are translated as follows: `<mod id>.jfconfig.enum.<enum class name>.<entry name>`
|
||||
|
||||
## Presets
|
||||
libjf-config-v0 provides a preset system to automatically fill in certain values based on a function.
|
||||
To add a snippet, add a public static method to your config class and annotate it with @Preset.
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
# libjf-translate-v0
|
||||
libjf-translate-v0 provides a utility class for translating strings through Google Translate.
|
||||
# libjf-translate-v1
|
||||
libjf-translate-v1 provides a utility class for translating strings through user-configurable services.
|
||||
|
||||
To use this, first obtain a TranslateService instance. You can use `TranslateService.getConfigured()` to do so.
|
||||
Please be aware that due to the nature of java generics, a workaround as seen in the example may be needed for successful compilation.
|
||||
You can also directly access implementations, however, this is not recommended.
|
||||
Please be aware that due to the nature of java generics, using var instead of a specific type for instances is recommended.
|
||||
You can also directly access implementations, however, this is not recommended and is not subject to the API stability promise.
|
||||
The TranslateService interface exposes all relevant functionality.
|
||||
|
||||
Example:
|
||||
```java
|
||||
public void onInitialize() {
|
||||
try {
|
||||
runTest(TranslateService.getConfigured());
|
||||
} catch (Throwable e) {
|
||||
LibJf.LOGGER.error("Could not verify translation validity", e);
|
||||
}
|
||||
}
|
||||
TranslateService::
|
||||
|
||||
private <T> void runTest(TranslateService<T> ts) throws TranslateException {
|
||||
final String source = "Cogito, ergo sum";
|
||||
final String expected = "I think, therefore I am";
|
||||
assert expected.equals(ts.translate(source, ts.detect(source), ts.parseLang("en")));
|
||||
assert expected.equals(ts.translate(source, ts.parseLang("la"), ts.parseLang("en")));
|
||||
}
|
||||
```
|
||||
| Name | Explanation |
|
||||
|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| `static TranslateService<?> getConfigured()` | Returns the TranslateService the user configured. Implementations may change without notice. |
|
||||
| `static List<TranslateService<?>> getAvailable()` | Returns all available TranslateServices. Please use getConfigured() instead where possible. |
|
||||
| `String translate(String textToTranslate, T translateFrom, T translateTo` | Translates a string from the specified source language (or null to auto-detect) to the target language. |
|
||||
| `T detect(String text)` | Detects the language used in the specified string. |
|
||||
| `T parseLang(Stirng lang)` | Gets the language for the specified ID |
|
||||
| `List<T> getAvailableLanguages()` | Get all available languages for the configured service. |
|
||||
| `String getName()` | Get the name of this translate service. |
|
||||
|
||||
Language:
|
||||
|
||||
| Name | Explanation |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `String getDisplayName()` | Returns the string to show in UIs for this language |
|
||||
| `String getIdentifier()` | Returns the ID for internal use (TranslateService.parseLang for example) |
|
|
@ -0,0 +1,163 @@
|
|||
package io.gitlab.jfronny.libjf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class HttpUtils {
|
||||
private static final HttpClient CLIENT = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build();
|
||||
|
||||
private enum Method {
|
||||
GET, POST
|
||||
}
|
||||
|
||||
public static class Request {
|
||||
private static final Pattern CURSEFORGE_API = Pattern.compile("(?:http(s)?://)?addons-ecs\\.forgesvc\\.net/api/+");
|
||||
private final String url;
|
||||
private final HttpRequest.Builder builder;
|
||||
private Method method;
|
||||
private int sent = 0;
|
||||
|
||||
public Request(Method method, String url) throws URISyntaxException {
|
||||
this.url = url.replace(" ", "%20");
|
||||
this.builder = HttpRequest.newBuilder()
|
||||
.uri(new URI(this.url))
|
||||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Request bearer(String token) {
|
||||
builder.header("Authorization", "Bearer " + token);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request header(String name, String value) {
|
||||
builder.header(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request bodyString(String string) {
|
||||
builder.header("Content-Type", "text/plain");
|
||||
builder.method(method.name(), HttpRequest.BodyPublishers.ofString(string));
|
||||
method = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request bodyForm(String string) {
|
||||
builder.header("Content-Type", "application/x-www-form-urlencoded");
|
||||
builder.method(method.name(), HttpRequest.BodyPublishers.ofString(string));
|
||||
method = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request bodyForm(Map<String, String> entries) {
|
||||
return bodyForm(entries.entrySet()
|
||||
.stream()
|
||||
.map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + '=' + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
|
||||
.collect(Collectors.joining("&")));
|
||||
}
|
||||
|
||||
public Request bodyJson(String string) {
|
||||
builder.header("Content-Type", "application/json");
|
||||
builder.method(method.name(), HttpRequest.BodyPublishers.ofString(string));
|
||||
method = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request bodyJson(Object object) {
|
||||
builder.header("Content-Type", "application/json");
|
||||
builder.method(method.name(), HttpRequest.BodyPublishers.ofString(LibJf.GSON.toJson(object)));
|
||||
method = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private <T> T _send(String accept, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException {
|
||||
sent++;
|
||||
if (sent > 3) throw new IOException("Attempted third redirect, stopping");
|
||||
builder.header("Accept", accept);
|
||||
if (method != null) builder.method(method.name(), HttpRequest.BodyPublishers.noBody());
|
||||
|
||||
HttpResponse<T> res;
|
||||
try {
|
||||
res = CLIENT.send(builder.build(), responseBodyHandler);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Could not send request", e);
|
||||
}
|
||||
if (res.statusCode() == 200) return res.body();
|
||||
Optional<String> location = res.headers().firstValue("location");
|
||||
// Redirect
|
||||
if (location.isPresent() && (res.statusCode() == 302 || res.statusCode() == 307) && method == Method.GET) {
|
||||
try {
|
||||
return HttpUtils.get(location.get())._send(accept, responseBodyHandler);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException("Could not follow redirect", e);
|
||||
}
|
||||
}
|
||||
// CurseForge serverside error
|
||||
if (CURSEFORGE_API.matcher(url).matches() && res.statusCode() >= 500 && res.statusCode() < 600) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Could not sleep before resending request" + e);
|
||||
}
|
||||
return _send(accept, responseBodyHandler);
|
||||
}
|
||||
throw new IOException("Unexpected return method: " + res.statusCode() + " (URL=" + url + ")");
|
||||
}
|
||||
|
||||
public void send() throws IOException {
|
||||
_send("*/*", HttpResponse.BodyHandlers.discarding());
|
||||
}
|
||||
|
||||
public InputStream sendInputStream() throws IOException {
|
||||
return _send("*/*", HttpResponse.BodyHandlers.ofInputStream());
|
||||
}
|
||||
|
||||
public String sendString() throws IOException {
|
||||
return _send("*/*", HttpResponse.BodyHandlers.ofString());
|
||||
}
|
||||
|
||||
public Stream<String> sendLines() throws IOException {
|
||||
return _send("*/*", HttpResponse.BodyHandlers.ofLines());
|
||||
}
|
||||
|
||||
public <T> T sendJson(Type type) throws IOException {
|
||||
InputStream in = _send("application/json", HttpResponse.BodyHandlers.ofInputStream());
|
||||
return in == null ? null : LibJf.GSON.fromJson(new InputStreamReader(in), type);
|
||||
}
|
||||
|
||||
private String getString(Object a) throws IOException {
|
||||
if (a instanceof InputStream s) return new String(s.readAllBytes());
|
||||
if (a instanceof String s) return s;
|
||||
if (a instanceof Stream s) return ((Stream<String>)s).collect(Collectors.joining());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static Request get(String url) throws URISyntaxException {
|
||||
return new Request(Method.GET, url);
|
||||
}
|
||||
|
||||
public static Request post(String url) throws URISyntaxException {
|
||||
return new Request(Method.POST, url);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import net.fabricmc.loader.api.FabricLoader;
|
|||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class CoProcessManager implements ModInitializer {
|
||||
|
@ -28,7 +29,9 @@ public class CoProcessManager implements ModInitializer {
|
|||
}
|
||||
|
||||
private void stop() {
|
||||
for (CoProcess coProcess : coProcesses) {
|
||||
Iterator<CoProcess> procs = coProcesses.iterator();
|
||||
while (procs.hasNext()) {
|
||||
CoProcess coProcess = procs.next();
|
||||
coProcess.stop();
|
||||
if (coProcess instanceof Closeable cl) {
|
||||
try {
|
||||
|
@ -37,6 +40,7 @@ public class CoProcessManager implements ModInitializer {
|
|||
LibJf.LOGGER.error("Could not close co-process", e);
|
||||
}
|
||||
}
|
||||
procs.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class EntryInfoWidgetBuilder {
|
|||
}, func);
|
||||
} else if (type.isEnum()) {
|
||||
List<?> values = Arrays.asList(info.field.getType().getEnumConstants());
|
||||
Function<Object,Text> func = value -> new TranslatableText(config.getModId() + ".jfconfig." + "enum." + type.getSimpleName() + "." + info.value.toString());
|
||||
Function<Object,Text> func = value -> new TranslatableText(config.getModId() + ".jfconfig.enum." + type.getSimpleName() + "." + info.value.toString());
|
||||
info.widget = new AbstractMap.SimpleEntry<ButtonWidget.PressAction, Function<Object, Text>>(button -> {
|
||||
int index = values.indexOf(info.value) + 1;
|
||||
info.value = values.get(index >= values.size()? 0 : index);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
archivesBaseName = "libjf-translate-v1"
|
||||
|
||||
dependencies {
|
||||
moduleDependencies(project, ["libjf-base"])
|
||||
moduleDependencies(project, ["libjf-base", "libjf-config-v0"])
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.translate.api;
|
||||
|
||||
public interface Language {
|
||||
String getDisplayName();
|
||||
String getIdentifier();
|
||||
}
|
|
@ -1,15 +1,43 @@
|
|||
package io.gitlab.jfronny.libjf.translate.api;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.TranslateConfig;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.google.GoogleTranslateService;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.LibreTranslateService;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.noop.NoopTranslateService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TranslateService<T> {
|
||||
public interface TranslateService<T extends Language> {
|
||||
static TranslateService<?> getConfigured() {
|
||||
return new GoogleTranslateService();
|
||||
return switch (TranslateConfig.translationService) {
|
||||
case Noop -> NoopTranslateService.INSTANCE;
|
||||
case Google -> GoogleTranslateService.INSTANCE;
|
||||
case LibreTranslate -> {
|
||||
try {
|
||||
yield LibreTranslateService.get(TranslateConfig.libreTranslateHost);
|
||||
} catch (TranslateException e) {
|
||||
LibJf.LOGGER.error("Could not use the specified LibreTranslate host, using NOOP", e);
|
||||
yield NoopTranslateService.INSTANCE;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static List<TranslateService<?>> getAvailable() {
|
||||
LibreTranslateService lts = null;
|
||||
try {
|
||||
lts = LibreTranslateService.get(TranslateConfig.libreTranslateHost);
|
||||
} catch (TranslateException ignored) {
|
||||
}
|
||||
return lts == null
|
||||
? List.of(GoogleTranslateService.INSTANCE, NoopTranslateService.INSTANCE)
|
||||
: List.of(GoogleTranslateService.INSTANCE, lts, NoopTranslateService.INSTANCE);
|
||||
}
|
||||
|
||||
String translate(String textToTranslate, T translateFrom, T translateTo) throws TranslateException;
|
||||
T detect(String text) throws TranslateException;
|
||||
T parseLang(String lang);
|
||||
List<T> getAvailableLanguages();
|
||||
String getName();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.Entry;
|
||||
import io.gitlab.jfronny.libjf.config.api.JfConfig;
|
||||
import io.gitlab.jfronny.libjf.config.api.Verifier;
|
||||
|
||||
public class TranslateConfig implements JfConfig {
|
||||
@Entry public static Translator translationService = Translator.Google;
|
||||
@Entry public static String libreTranslateHost = "https://translate.argosopentech.com";
|
||||
|
||||
@Verifier
|
||||
public static void ensureValid() {
|
||||
if (translationService == null) translationService = Translator.Google;
|
||||
if (translationService == Translator.LibreTranslate && libreTranslateHost == null || libreTranslateHost.isBlank())
|
||||
libreTranslateHost = "https://translate.argosopentech.com";
|
||||
}
|
||||
|
||||
public enum Translator {
|
||||
Google, LibreTranslate, Noop
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package io.gitlab.jfronny.libjf.translate.impl.google;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum Language {
|
||||
public enum GoogleTranslateLanguage implements io.gitlab.jfronny.libjf.translate.api.Language {
|
||||
AUTO_DETECT("AUTO_DETECT", "auto"), ARABIC("ARABIC", "ar"), CHINESE_SIMPLIFIED("CHINESE_SIMPLIFIED", "zh-CN"),
|
||||
CHINESE_TRADITIONAL("CHINESE_TRADITIONAL", "zh-TW"), ENGLISH("ENGLISH", "en"), FILIPINO("FILIPINO", "tl"),
|
||||
FRENCH("FRENCH", "fr"), GERMAN("GERMAN", "de"), GREEK("GREEK", "el"), INDONESIAN("INDONESIAN", "id"),
|
||||
|
@ -12,22 +12,22 @@ public enum Language {
|
|||
RUSSIAN("RUSSIAN", "ru"), SPANISH("SPANISH", "es"), SWEDISH("SWEDISH", "sv"), THAI("THAI", "th"),
|
||||
VIETNAMESE("VIETNAMESE", "vi");
|
||||
|
||||
private static final Map<String, Language> LANGUAGE_BY_VALUE = new HashMap<>();
|
||||
private static final Map<String, GoogleTranslateLanguage> LANGUAGE_BY_ID = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Language language : Language.values()) {
|
||||
LANGUAGE_BY_VALUE.put(language.id, language);
|
||||
for (GoogleTranslateLanguage language : GoogleTranslateLanguage.values()) {
|
||||
LANGUAGE_BY_ID.put(language.id, language);
|
||||
}
|
||||
}
|
||||
|
||||
public static Language byId(String value) {
|
||||
return LANGUAGE_BY_VALUE.getOrDefault(value, AUTO_DETECT);
|
||||
public static GoogleTranslateLanguage byId(String value) {
|
||||
return LANGUAGE_BY_ID.getOrDefault(value, AUTO_DETECT);
|
||||
}
|
||||
|
||||
public final String name;
|
||||
public final String id;
|
||||
|
||||
Language(String name, String id) {
|
||||
GoogleTranslateLanguage(String name, String id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
|
@ -36,4 +36,14 @@ public enum Language {
|
|||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.google;
|
||||
|
||||
import io.gitlab.jfronny.libjf.HttpUtils;
|
||||
import io.gitlab.jfronny.libjf.translate.api.TranslateException;
|
||||
import io.gitlab.jfronny.libjf.translate.api.TranslateService;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
@ -19,12 +16,18 @@ import java.util.List;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GoogleTranslateService implements TranslateService<Language> {
|
||||
public class GoogleTranslateService implements TranslateService<GoogleTranslateLanguage> {
|
||||
public static final GoogleTranslateService INSTANCE = new GoogleTranslateService();
|
||||
private static final Pattern TRANSLATION_RESULT = Pattern.compile("class=\"result-container\">([^<]*)</div>", Pattern.MULTILINE);
|
||||
|
||||
private GoogleTranslateService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(String textToTranslate, Language translateFrom, Language translateTo) throws TranslateException {
|
||||
public String translate(String textToTranslate, GoogleTranslateLanguage translateFrom, GoogleTranslateLanguage translateTo) throws TranslateException {
|
||||
if (textToTranslate == null) throw new TranslateException("textToTranslate must not be null");
|
||||
if (translateFrom == null) translateFrom = GoogleTranslateLanguage.AUTO_DETECT;
|
||||
if (translateTo == null) throw new TranslateException("translateTo must not be null");
|
||||
String pageSource = "";
|
||||
try {
|
||||
pageSource = getPageSource(textToTranslate, translateFrom.id, translateTo.id);
|
||||
|
@ -48,45 +51,32 @@ public class GoogleTranslateService implements TranslateService<Language> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Language detect(String text) throws TranslateException {
|
||||
return Language.AUTO_DETECT;
|
||||
public GoogleTranslateLanguage detect(String text) throws TranslateException {
|
||||
return GoogleTranslateLanguage.AUTO_DETECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language parseLang(String lang) {
|
||||
return Language.byId(lang);
|
||||
public GoogleTranslateLanguage parseLang(String lang) {
|
||||
return GoogleTranslateLanguage.byId(lang);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Language> getAvailableLanguages() {
|
||||
List<Language> langs = new ArrayList<>(Arrays.asList(Language.values()));
|
||||
langs.remove(Language.AUTO_DETECT);
|
||||
public List<GoogleTranslateLanguage> getAvailableLanguages() {
|
||||
List<GoogleTranslateLanguage> langs = new ArrayList<>(Arrays.asList(GoogleTranslateLanguage.values()));
|
||||
langs.remove(GoogleTranslateLanguage.AUTO_DETECT);
|
||||
return langs;
|
||||
}
|
||||
|
||||
private static String getPageSource(String textToTranslate, String translateFrom, String translateTo)
|
||||
throws Exception {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Google";
|
||||
}
|
||||
|
||||
private static String getPageSource(String textToTranslate, String translateFrom, String translateTo) throws Exception {
|
||||
if (textToTranslate == null)
|
||||
return null;
|
||||
String pageUrl = String.format("https://translate.google.com/m?hl=en&sl=%s&tl=%s&ie=UTF-8&prev=_m&q=%s",
|
||||
translateFrom, translateTo, URLEncoder.encode(textToTranslate.trim(), StandardCharsets.UTF_8));
|
||||
URL url = new URL(pageUrl);
|
||||
HttpURLConnection connection = null;
|
||||
StringBuilder pageSource = new StringBuilder();
|
||||
try {
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
|
||||
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
pageSource.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
return pageSource.toString();
|
||||
} finally {
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
}
|
||||
return HttpUtils.get(pageUrl).sendString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.libretranslate;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import io.gitlab.jfronny.libjf.HttpUtils;
|
||||
import io.gitlab.jfronny.libjf.translate.api.TranslateException;
|
||||
import io.gitlab.jfronny.libjf.translate.api.TranslateService;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.model.LibreTranslateDetectResult;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.model.LibreTranslateLanguage;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.model.LibreTranslateRequest;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.model.LibreTranslateResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LibreTranslateService implements TranslateService<LibreTranslateLanguage> {
|
||||
private static final Type languageListType = new TypeToken<List<LibreTranslateLanguage.ApiResult>>(){}.getType();
|
||||
private static final Type translateDetectResultListType = new TypeToken<List<LibreTranslateDetectResult>>(){}.getType();
|
||||
private static final LibreTranslateLanguage autoDetect = new LibreTranslateLanguage("auto", "AUTO_DETECT");
|
||||
private static final Map<String, LibreTranslateService> knownInstances = new HashMap<>();
|
||||
|
||||
public static LibreTranslateService get(String host) throws TranslateException {
|
||||
LibreTranslateService lts;
|
||||
if (knownInstances.containsKey(host)) {
|
||||
lts = knownInstances.get(host);
|
||||
if (lts == null) throw new TranslateException("Translate service previously failed to initialize. Not trying again");
|
||||
return lts;
|
||||
}
|
||||
try {
|
||||
lts = new LibreTranslateService(host);
|
||||
} catch (TranslateException e) {
|
||||
knownInstances.put(host, null);
|
||||
throw new TranslateException("Could not instantiate translate service", e);
|
||||
}
|
||||
knownInstances.put(host, lts);
|
||||
return lts;
|
||||
}
|
||||
|
||||
private final String host;
|
||||
private final List<LibreTranslateLanguage> knownLanguages;
|
||||
private final Map<String, LibreTranslateLanguage> languageById = new HashMap<>();
|
||||
private LibreTranslateService(String host) throws TranslateException {
|
||||
if (host.endsWith("/")) host = host.substring(0, host.length() - 1);
|
||||
this.host = host;
|
||||
try {
|
||||
ArrayList<LibreTranslateLanguage> langs = new ArrayList<>();
|
||||
langs.add(autoDetect);
|
||||
for (LibreTranslateLanguage.ApiResult lang : HttpUtils.get(host + "/languages").<ArrayList<LibreTranslateLanguage.ApiResult>>sendJson(languageListType)) {
|
||||
LibreTranslateLanguage langR = lang.toLanguage();
|
||||
langs.add(langR);
|
||||
languageById.put(lang.code, langR);
|
||||
}
|
||||
this.knownLanguages = List.copyOf(langs);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new TranslateException("Could not get known languages for LibreTranslate backend", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(String textToTranslate, LibreTranslateLanguage translateFrom, LibreTranslateLanguage translateTo) throws TranslateException {
|
||||
if (textToTranslate == null) throw new TranslateException("textToTranslate must not be null");
|
||||
if (translateFrom == null) translateFrom = autoDetect;
|
||||
if (translateTo == null) throw new TranslateException("translateTo must not be null");
|
||||
LibreTranslateRequest.Translate request = new LibreTranslateRequest.Translate();
|
||||
request.q = textToTranslate;
|
||||
request.source = translateFrom.getIdentifier();
|
||||
request.target = translateTo.getIdentifier();
|
||||
LibreTranslateResult result = null;
|
||||
try {
|
||||
result = HttpUtils.post(host + "/translate").bodyJson(request).sendJson(LibreTranslateResult.class);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new TranslateException("Could not translate text", e);
|
||||
}
|
||||
return result.translatedText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LibreTranslateLanguage detect(String text) throws TranslateException {
|
||||
LibreTranslateRequest request = new LibreTranslateRequest();
|
||||
request.q = text;
|
||||
List<LibreTranslateDetectResult> result;
|
||||
try {
|
||||
result = HttpUtils.post(host + "/detect").bodyJson(request).sendJson(translateDetectResultListType);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new TranslateException("Could not detect language", e);
|
||||
}
|
||||
LibreTranslateDetectResult resCurr = null;
|
||||
for (LibreTranslateDetectResult res : result) {
|
||||
if (resCurr == null || res.confidence > resCurr.confidence)
|
||||
resCurr = res;
|
||||
}
|
||||
if (resCurr == null) throw new TranslateException("Could not identify any valid language");
|
||||
return parseLang(resCurr.language);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LibreTranslateLanguage parseLang(String lang) {
|
||||
return languageById.get(lang);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LibreTranslateLanguage> getAvailableLanguages() {
|
||||
return knownLanguages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "LibreTranslate";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.libretranslate.model;
|
||||
|
||||
public class LibreTranslateDetectResult {
|
||||
public float confidence;
|
||||
public String language;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.libretranslate.model;
|
||||
|
||||
import io.gitlab.jfronny.libjf.translate.api.Language;
|
||||
|
||||
public record LibreTranslateLanguage(String code, String name) implements Language {
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static class ApiResult {
|
||||
public String code;
|
||||
public String name;
|
||||
|
||||
public LibreTranslateLanguage toLanguage() {
|
||||
return new LibreTranslateLanguage(code, name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.libretranslate.model;
|
||||
|
||||
public class LibreTranslateRequest {
|
||||
public String q;
|
||||
|
||||
public static class Translate extends LibreTranslateRequest {
|
||||
public String source;
|
||||
public String target;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.libretranslate.model;
|
||||
|
||||
public class LibreTranslateResult {
|
||||
public String translatedText;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.gitlab.jfronny.libjf.translate.impl.noop;
|
||||
|
||||
import io.gitlab.jfronny.libjf.translate.api.Language;
|
||||
|
||||
public class NoopLanguage implements Language {
|
||||
static final NoopLanguage INSTANCE = new NoopLanguage();
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "none";
|
||||
}
|
||||
}
|
|
@ -5,24 +5,33 @@ import io.gitlab.jfronny.libjf.translate.api.TranslateService;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public class NoopTranslateService implements TranslateService<Object> {
|
||||
public class NoopTranslateService implements TranslateService<NoopLanguage> {
|
||||
public static final NoopTranslateService INSTANCE = new NoopTranslateService();
|
||||
private NoopTranslateService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(String textToTranslate, Object translateFrom, Object translateTo) throws TranslateException {
|
||||
public String translate(String textToTranslate, NoopLanguage translateFrom, NoopLanguage translateTo) throws TranslateException {
|
||||
return textToTranslate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object detect(String text) throws TranslateException {
|
||||
return "none";
|
||||
public NoopLanguage detect(String text) throws TranslateException {
|
||||
return NoopLanguage.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseLang(String lang) {
|
||||
return "none";
|
||||
public NoopLanguage parseLang(String lang) {
|
||||
return NoopLanguage.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getAvailableLanguages() {
|
||||
return List.of("none");
|
||||
public List<NoopLanguage> getAvailableLanguages() {
|
||||
return List.of(NoopLanguage.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Noop";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"libjf-translate-v1.jfconfig.title": "LibJF Translate v1",
|
||||
"libjf-translate-v1.jfconfig.translationService": "Translation Service",
|
||||
"libjf-translate-v1.jfconfig.translationService.tooltip": "The service to use for translation. Other mods may access services directly, but this should be used",
|
||||
"libjf-translate-v1.jfconfig.libreTranslateHost": "LibreTranslate Host",
|
||||
"libjf-translate-v1.jfconfig.libreTranslateHost.tooltip": "The host of LibreTranslate to use if that is selected",
|
||||
"libjf-translate-v1.jfconfig.enum.Translator.Google": "Google",
|
||||
"libjf-translate-v1.jfconfig.enum.Translator.LibreTranslate": "LibreTranslate",
|
||||
"libjf-translate-v1.jfconfig.enum.Translator.Noop": "NOOP"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-translate-v0",
|
||||
"id": "libjf-translate-v1",
|
||||
"name": "LibJF Translate",
|
||||
"version": "${version}",
|
||||
"environment": "*",
|
||||
|
@ -17,6 +17,9 @@
|
|||
"minecraft": "*",
|
||||
"libjf-base": ">=${version}"
|
||||
},
|
||||
"entrypoints": {
|
||||
"libjf:config": ["io.gitlab.jfronny.libjf.translate.impl.TranslateConfig"]
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"parent": "libjf",
|
||||
|
|
|
@ -1,24 +1,38 @@
|
|||
package io.gitlab.jfronny.libjf.translate.test;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.translate.api.TranslateService;
|
||||
import io.gitlab.jfronny.libjf.translate.api.TranslateException;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.google.GoogleTranslateService;
|
||||
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.LibreTranslateService;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TestEntrypoint implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
try {
|
||||
runTest(TranslateService.getConfigured());
|
||||
{
|
||||
GoogleTranslateService ts = GoogleTranslateService.INSTANCE;
|
||||
LibJf.LOGGER.info("Testing Google Translate");
|
||||
final String sourceLA = "Cogito, ergo sum";
|
||||
assertEqual("auto", ts.detect(sourceLA).getIdentifier());
|
||||
assertEqual("I think, therefore I am", ts.translate(sourceLA, ts.parseLang("la"), ts.parseLang("en")));
|
||||
}
|
||||
|
||||
{
|
||||
LibreTranslateService ts = LibreTranslateService.get("https://translate.argosopentech.com");
|
||||
LibJf.LOGGER.info("Testing LibreTranslate");
|
||||
final String sourceEN = "Hello, World!";
|
||||
assertEqual("en", ts.detect(sourceEN).getIdentifier());
|
||||
assertEqual("Hallo, Welt!", ts.translate(sourceEN, ts.parseLang("en"), ts.parseLang("de")));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LibJf.LOGGER.error("Could not verify translation validity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void runTest(TranslateService<T> ts) throws TranslateException {
|
||||
final String source = "Cogito, ergo sum";
|
||||
final String expected = "I think, therefore I am";
|
||||
assert expected.equals(ts.translate(source, ts.detect(source), ts.parseLang("en")));
|
||||
assert expected.equals(ts.translate(source, ts.parseLang("la"), ts.parseLang("en")));
|
||||
private static void assertEqual(Object o1, Object o2) {
|
||||
if (!Objects.equals(o1, o2))
|
||||
throw new AssertionError("Assertion not met: expected " + o1 + " but got " + o2);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue