[translate] Implement LibreTranslate support

This commit is contained in:
Johannes Frohnmeyer 2022-03-25 18:29:34 +01:00
parent dcc3dec8f1
commit 9df0e04508
Signed by: Johannes
GPG Key ID: E76429612C2929F4
23 changed files with 529 additions and 84 deletions

View File

@ -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:

View File

@ -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/)

View File

@ -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.

View File

@ -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) |

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);

View File

@ -1,5 +1,5 @@
archivesBaseName = "libjf-translate-v1"
dependencies {
moduleDependencies(project, ["libjf-base"])
moduleDependencies(project, ["libjf-base", "libjf-config-v0"])
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.libjf.translate.api;
public interface Language {
String getDisplayName();
String getIdentifier();
}

View File

@ -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();
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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";
}
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.libjf.translate.impl.libretranslate.model;
public class LibreTranslateDetectResult {
public float confidence;
public String language;
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.translate.impl.libretranslate.model;
public class LibreTranslateResult {
public String translatedText;
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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"
}

View File

@ -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",

View File

@ -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);
}
}