mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2025-02-28 03:42:46 +01:00
Translations
This commit is contained in:
parent
c3c5104dfa
commit
8a673875a6
@ -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);
|
||||||
|
@ -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')
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user