Translations

This commit is contained in:
Vankka 2022-04-13 14:07:43 +03:00
parent c3c5104dfa
commit 8a673875a6
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
7 changed files with 211 additions and 7 deletions

View File

@ -33,6 +33,7 @@ import com.discordsrv.bukkit.player.BukkitPlayerProvider;
import com.discordsrv.bukkit.plugin.BukkitPluginManager; import com.discordsrv.bukkit.plugin.BukkitPluginManager;
import com.discordsrv.bukkit.scheduler.BukkitScheduler; import com.discordsrv.bukkit.scheduler.BukkitScheduler;
import com.discordsrv.common.command.game.handler.ICommandHandler; import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.component.translation.Translation;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager; import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.common.debug.data.OnlineMode; import com.discordsrv.common.debug.data.OnlineMode;
@ -40,6 +41,7 @@ import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.messageforwarding.game.MinecraftToDiscordChatModule; import com.discordsrv.common.messageforwarding.game.MinecraftToDiscordChatModule;
import com.discordsrv.common.plugin.PluginManager; import com.discordsrv.common.plugin.PluginManager;
import com.discordsrv.common.server.ServerDiscordSRV; import com.discordsrv.common.server.ServerDiscordSRV;
import com.fasterxml.jackson.databind.JsonNode;
import dev.vankka.dependencydownload.classpath.ClasspathAppender; import dev.vankka.dependencydownload.classpath.ClasspathAppender;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.Server; import org.bukkit.Server;
@ -47,8 +49,14 @@ import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConnectionConfig> { public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConnectionConfig> {
@ -169,13 +177,63 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
return configManager; return configManager;
} }
private URL findResource(String name) {
ClassLoader classLoader = getClass().getClassLoader();
URL url = null;
while (classLoader != null && url == null) {
url = classLoader.getResource(name);
classLoader = classLoader.getParent();
}
return url;
}
private void loadMCTranslations() {
Map<String, Translation> translations = new HashMap<>();
try {
URL enUS = findResource("assets/minecraft/lang/en_US.lang");
if (enUS == null) {
enUS = findResource("assets/minecraft/lang/en_us.lang");
}
if (enUS != null) {
Properties properties = new Properties();
try (InputStream inputStream = enUS.openStream()) {
properties.load(inputStream);
}
properties.forEach((k, v) -> translations.put((String) k, Translation.stringFormat((String) v)));
}
} catch (Throwable t) {
logger().debug("Failed to load locale", t);
}
try {
URL enUS = findResource("assets/minecraft/lang/en_us.json");
if (enUS != null) {
JsonNode node = json().readTree(enUS);
node.fields().forEachRemaining(entry -> translations.put(
entry.getKey(),
Translation.stringFormat(entry.getValue().textValue()))
);
}
} catch (Throwable t) {
logger().debug("Failed to load locale", t);
}
if (translations.isEmpty()) {
logger().warning("No Minecraft translations were found, some components may not render correctly");
} else {
componentFactory().translationRegistry().register(Locale.US, translations);
logger().debug("Found " + translations.size() + " Minecraft translations for en_us");
}
}
@Override @Override
protected void enable() throws Throwable { protected void enable() throws Throwable {
// Service provider // Service provider
server().getServicesManager().register(DiscordSRVApi.class, this, plugin(), ServicePriority.Normal); server().getServicesManager().register(DiscordSRVApi.class, this, plugin(), ServicePriority.Normal);
// Adventure audiences // Adventure related stuff
this.audiences = BukkitAudiences.create(bootstrap.getPlugin()); this.audiences = BukkitAudiences.create(bootstrap.getPlugin());
loadMCTranslations();
// Command handler // Command handler
commandHandler = AbstractBukkitCommandHandler.get(this); commandHandler = AbstractBukkitCommandHandler.get(this);

View File

@ -52,7 +52,7 @@ dependencies {
runtimeDownloadApi 'org.spongepowered:configurate-hocon:' + rootProject.configurateVersion runtimeDownloadApi 'org.spongepowered:configurate-hocon:' + rootProject.configurateVersion
// Jackson (transitive in :api) // Jackson (transitive in :api)
compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.13.1' compileOnlyApi 'com.fasterxml.jackson.core:jackson-databind:2.10.1'
// Logging // Logging
compileOnlyApi project(':common:common-slf4j-hack') compileOnlyApi project(':common:common-slf4j-hack')

View File

@ -23,25 +23,49 @@ import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentFactory; import com.discordsrv.api.component.MinecraftComponentFactory;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer; import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer;
import com.discordsrv.common.component.translation.Translation;
import com.discordsrv.common.component.translation.TranslationRegistry;
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer; import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializerOptions; import dev.vankka.mcdiscordreserializer.discord.DiscordSerializerOptions;
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializer; import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializer;
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializerOptions; import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializerOptions;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Locale;
public class ComponentFactory implements MinecraftComponentFactory { public class ComponentFactory implements MinecraftComponentFactory {
private final DiscordSRV discordSRV; private final DiscordSRV discordSRV;
private final MinecraftSerializer minecraftSerializer; private final MinecraftSerializer minecraftSerializer;
private final DiscordSerializer discordSerializer; private final DiscordSerializer discordSerializer;
// Not the same as Adventure's TranslationRegistry
private final TranslationRegistry translationRegistry = new TranslationRegistry();
public ComponentFactory(DiscordSRV discordSRV) { public ComponentFactory(DiscordSRV discordSRV) {
this.discordSRV = discordSRV; this.discordSRV = discordSRV;
this.minecraftSerializer = new MinecraftSerializer( this.minecraftSerializer = new MinecraftSerializer(
MinecraftSerializerOptions.defaults() MinecraftSerializerOptions.defaults()
.addRenderer(new DiscordSRVMinecraftRenderer(discordSRV)) .addRenderer(new DiscordSRVMinecraftRenderer(discordSRV))
); );
this.discordSerializer = new DiscordSerializer(DiscordSerializerOptions.defaults()); this.discordSerializer = new DiscordSerializer();
discordSerializer.setDefaultOptions(
DiscordSerializerOptions.defaults()
.withTranslationProvider(translatableComponent -> {
Translation translation = translationRegistry.lookup(Locale.US, translatableComponent.key());
if (translation == null) {
return null;
}
return translation.translate(
translatableComponent.args()
.stream()
.map(discordSerializer::serialize)
.map(str -> (Object) str)
.toArray(Object[]::new)
);
})
);
} }
@Override @Override
@ -61,4 +85,8 @@ public class ComponentFactory implements MinecraftComponentFactory {
public DiscordSerializer discordSerializer() { public DiscordSerializer discordSerializer() {
return discordSerializer; return discordSerializer;
} }
public TranslationRegistry translationRegistry() {
return translationRegistry;
}
} }

View File

@ -0,0 +1,33 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.component.translation;
public class StringFormatTranslation implements Translation {
private final String format;
public StringFormatTranslation(String format) {
this.format = format;
}
@Override
public String translate(Object[] arguments) {
return String.format(format, arguments);
}
}

View File

@ -0,0 +1,29 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.component.translation;
@FunctionalInterface
public interface Translation {
static Translation stringFormat(String format) {
return new StringFormatTranslation(format);
}
String translate(Object[] arguments);
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.component.translation;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Our own simple class that stores abstract {@link Translation}s instead of MessageFormats,
* to be more flexible, and support Minecraft's own language files.
*/
public class TranslationRegistry {
private static final Locale DEFAULT_LOCALE = Locale.US;
private final Map<Locale, Map<String, Translation>> translations = new LinkedHashMap<>();
public TranslationRegistry() {}
public void register(Locale locale, Map<String, Translation> translations) {
synchronized (this.translations) {
this.translations.computeIfAbsent(locale, key -> new LinkedHashMap<>()).putAll(translations);
}
}
@Nullable
public Translation lookup(Locale locale, String key) {
synchronized (translations) {
return translations.getOrDefault(
locale, // Try the suggested locale first
translations.getOrDefault(
DEFAULT_LOCALE, // Then try the default locale
Collections.emptyMap() // Then fail
)
).get(key);
}
}
}

View File

@ -307,6 +307,7 @@ public class DiscordAPIImpl implements DiscordAPI {
try { try {
return mapExceptions(futureSupplier.get()); return mapExceptions(futureSupplier.get());
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace();
CompletableFuture<T> future = new CompletableFuture<>(); CompletableFuture<T> future = new CompletableFuture<>();
future.completeExceptionally(t); future.completeExceptionally(t);
return future; return future;
@ -314,16 +315,16 @@ public class DiscordAPIImpl implements DiscordAPI {
} }
public <T> CompletableFuture<T> mapExceptions(CompletableFuture<T> future) { public <T> CompletableFuture<T> mapExceptions(CompletableFuture<T> future) {
return future.handle((msg, t) -> { return future.handle((response, t) -> {
if (t instanceof ErrorResponseException) { if (t instanceof ErrorResponseException) {
ErrorResponseException exception = (ErrorResponseException) t; ErrorResponseException exception = (ErrorResponseException) t;
int code = exception.getErrorCode(); int code = exception.getErrorCode();
ErrorResponse response = exception.getErrorResponse(); ErrorResponse errorResponse = exception.getErrorResponse();
throw new RestErrorResponseException(code, response != null ? response.getMeaning() : "Unknown", t); throw new RestErrorResponseException(code, errorResponse != null ? errorResponse.getMeaning() : "Unknown", t);
} else if (t != null) { } else if (t != null) {
throw (RuntimeException) t; throw (RuntimeException) t;
} }
return msg; return response;
}); });
} }