mirror of
https://github.com/lucko/LuckPerms.git
synced 2025-09-02 02:42:33 +02:00
Automatically install translation bundles
This commit is contained in:
@@ -310,6 +310,9 @@ log-notify: true
|
|||||||
log-notify-filtered-descriptions:
|
log-notify-filtered-descriptions:
|
||||||
# - "parent add example"
|
# - "parent add example"
|
||||||
|
|
||||||
|
# If LuckPerms should automatically install translation bundles and periodically update them.
|
||||||
|
auto-install-translations: true
|
||||||
|
|
||||||
# Defines the options for prefix and suffix stacking.
|
# Defines the options for prefix and suffix stacking.
|
||||||
#
|
#
|
||||||
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
||||||
|
@@ -318,6 +318,9 @@ log-notify: true
|
|||||||
log-notify-filtered-descriptions:
|
log-notify-filtered-descriptions:
|
||||||
# - "parent add example"
|
# - "parent add example"
|
||||||
|
|
||||||
|
# If LuckPerms should automatically install translation bundles and periodically update them.
|
||||||
|
auto-install-translations: true
|
||||||
|
|
||||||
# Defines the options for prefix and suffix stacking.
|
# Defines the options for prefix and suffix stacking.
|
||||||
#
|
#
|
||||||
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
||||||
|
@@ -25,9 +25,6 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.commands.misc;
|
package me.lucko.luckperms.common.commands.misc;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
import me.lucko.luckperms.common.command.CommandResult;
|
import me.lucko.luckperms.common.command.CommandResult;
|
||||||
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
|
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
|
||||||
import me.lucko.luckperms.common.command.access.CommandPermission;
|
import me.lucko.luckperms.common.command.access.CommandPermission;
|
||||||
@@ -36,36 +33,19 @@ import me.lucko.luckperms.common.command.utils.ArgumentList;
|
|||||||
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
|
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
|
||||||
import me.lucko.luckperms.common.locale.Message;
|
import me.lucko.luckperms.common.locale.Message;
|
||||||
import me.lucko.luckperms.common.locale.TranslationManager;
|
import me.lucko.luckperms.common.locale.TranslationManager;
|
||||||
|
import me.lucko.luckperms.common.locale.TranslationRepository.LanguageInfo;
|
||||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||||
import me.lucko.luckperms.common.sender.Sender;
|
import me.lucko.luckperms.common.sender.Sender;
|
||||||
import me.lucko.luckperms.common.util.MoreFiles;
|
|
||||||
import me.lucko.luckperms.common.util.Predicates;
|
import me.lucko.luckperms.common.util.Predicates;
|
||||||
import me.lucko.luckperms.common.util.gson.GsonProvider;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import okhttp3.ResponseBody;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class TranslationsCommand extends SingleCommand {
|
public class TranslationsCommand extends SingleCommand {
|
||||||
private static final String TRANSLATIONS_INFO_ENDPOINT = "https://metadata.luckperms.net/data/translations";
|
|
||||||
private static final String TRANSLATIONS_DOWNLOAD_ENDPOINT = "https://metadata.luckperms.net/translation/";
|
|
||||||
|
|
||||||
public TranslationsCommand() {
|
public TranslationsCommand() {
|
||||||
super(CommandSpec.TRANSLATIONS, "Translations", CommandPermission.TRANSLATIONS, Predicates.notInRange(0, 1));
|
super(CommandSpec.TRANSLATIONS, "Translations", CommandPermission.TRANSLATIONS, Predicates.notInRange(0, 1));
|
||||||
@@ -77,7 +57,7 @@ public class TranslationsCommand extends SingleCommand {
|
|||||||
|
|
||||||
List<LanguageInfo> availableTranslations;
|
List<LanguageInfo> availableTranslations;
|
||||||
try {
|
try {
|
||||||
availableTranslations = getAvailableTranslations(plugin);
|
availableTranslations = plugin.getTranslationRepository().getAvailableLanguages();
|
||||||
} catch (IOException | UnsuccessfulRequestException e) {
|
} catch (IOException | UnsuccessfulRequestException e) {
|
||||||
Message.TRANSLATIONS_SEARCHING_ERROR.send(sender);
|
Message.TRANSLATIONS_SEARCHING_ERROR.send(sender);
|
||||||
plugin.getLogger().warn("Unable to obtain a list of available translations", e);
|
plugin.getLogger().warn("Unable to obtain a list of available translations", e);
|
||||||
@@ -86,10 +66,7 @@ public class TranslationsCommand extends SingleCommand {
|
|||||||
|
|
||||||
if (args.size() >= 1 && args.get(0).equalsIgnoreCase("install")) {
|
if (args.size() >= 1 && args.get(0).equalsIgnoreCase("install")) {
|
||||||
Message.TRANSLATIONS_INSTALLING.send(sender);
|
Message.TRANSLATIONS_INSTALLING.send(sender);
|
||||||
|
plugin.getTranslationRepository().downloadAndInstallTranslations(availableTranslations, sender, true);
|
||||||
downloadTranslations(plugin, availableTranslations, sender);
|
|
||||||
plugin.getTranslationManager().reload();
|
|
||||||
|
|
||||||
Message.TRANSLATIONS_INSTALL_COMPLETE.send(sender);
|
Message.TRANSLATIONS_INSTALL_COMPLETE.send(sender);
|
||||||
return CommandResult.SUCCESS;
|
return CommandResult.SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -98,109 +75,11 @@ public class TranslationsCommand extends SingleCommand {
|
|||||||
|
|
||||||
Message.AVAILABLE_TRANSLATIONS_HEADER.send(sender);
|
Message.AVAILABLE_TRANSLATIONS_HEADER.send(sender);
|
||||||
for (LanguageInfo language : availableTranslations) {
|
for (LanguageInfo language : availableTranslations) {
|
||||||
Message.AVAILABLE_TRANSLATIONS_ENTRY.send(sender, language.locale.toString(), localeDisplayName(language.locale), language.progress, language.contributors);
|
Message.AVAILABLE_TRANSLATIONS_ENTRY.send(sender, language.locale().toString(), TranslationManager.localeDisplayName(language.locale()), language.progress(), language.contributors());
|
||||||
}
|
}
|
||||||
sender.sendMessage(Message.prefixed(Component.empty()));
|
sender.sendMessage(Message.prefixed(Component.empty()));
|
||||||
Message.TRANSLATIONS_DOWNLOAD_PROMPT.send(sender, label);
|
Message.TRANSLATIONS_DOWNLOAD_PROMPT.send(sender, label);
|
||||||
return CommandResult.SUCCESS;
|
return CommandResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadTranslations(LuckPermsPlugin plugin, List<LanguageInfo> languages, Sender sender) {
|
|
||||||
try {
|
|
||||||
MoreFiles.createDirectoriesIfNotExists(plugin.getTranslationManager().getTranslationsDirectory());
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
for (LanguageInfo language : languages) {
|
|
||||||
Message.TRANSLATIONS_INSTALLING_SPECIFIC.send(sender, language.locale.toString());
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.header("User-Agent", plugin.getBytebin().getUserAgent())
|
|
||||||
.url(TRANSLATIONS_DOWNLOAD_ENDPOINT + language.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Path file = plugin.getTranslationManager().getTranslationsDirectory().resolve(language.locale.toString() + ".properties");
|
|
||||||
|
|
||||||
try (Response response = plugin.getBytebin().makeHttpRequest(request)) {
|
|
||||||
try (ResponseBody responseBody = response.body()) {
|
|
||||||
if (responseBody == null) {
|
|
||||||
throw new RuntimeException("No response");
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream inputStream = responseBody.byteStream()) {
|
|
||||||
Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (UnsuccessfulRequestException | IOException e) {
|
|
||||||
Message.TRANSLATIONS_DOWNLOAD_ERROR.send(sender, language.locale.toString());
|
|
||||||
plugin.getLogger().warn("Unable to download translations", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<LanguageInfo> getAvailableTranslations(LuckPermsPlugin plugin) throws IOException, UnsuccessfulRequestException {
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.header("User-Agent", plugin.getBytebin().getUserAgent())
|
|
||||||
.url(TRANSLATIONS_INFO_ENDPOINT)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
JsonObject jsonResponse;
|
|
||||||
try (Response response = plugin.getBytebin().makeHttpRequest(request)) {
|
|
||||||
try (ResponseBody responseBody = response.body()) {
|
|
||||||
if (responseBody == null) {
|
|
||||||
throw new RuntimeException("No response");
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream inputStream = responseBody.byteStream()) {
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
|
||||||
jsonResponse = GsonProvider.normal().fromJson(reader, JsonObject.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<LanguageInfo> languages = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, JsonElement> language : jsonResponse.get("languages").getAsJsonObject().entrySet()) {
|
|
||||||
languages.add(new LanguageInfo(language.getKey(), language.getValue().getAsJsonObject()));
|
|
||||||
}
|
|
||||||
languages.removeIf(language -> language.progress <= 0);
|
|
||||||
return languages;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String localeDisplayName(Locale locale) {
|
|
||||||
if (locale.getLanguage().equals("zh")) {
|
|
||||||
if (locale.getCountry().equals("CN")) {
|
|
||||||
return "简体中文"; // Chinese (Simplified)
|
|
||||||
} else if (locale.getCountry().equals("TW")) {
|
|
||||||
return "繁體中文"; // Chinese (Traditional)
|
|
||||||
}
|
|
||||||
return locale.getDisplayCountry(locale) + locale.getDisplayLanguage(locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale.getLanguage().equals("en") && locale.getCountry().equals("PT")) {
|
|
||||||
return "Pirate";
|
|
||||||
}
|
|
||||||
|
|
||||||
return locale.getDisplayLanguage(locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LanguageInfo {
|
|
||||||
private final String id;
|
|
||||||
private final String name;
|
|
||||||
private final Locale locale;
|
|
||||||
private final int progress;
|
|
||||||
private final List<String> contributors;
|
|
||||||
|
|
||||||
LanguageInfo(String id, JsonObject data) {
|
|
||||||
this.id = id;
|
|
||||||
this.name = data.get("name").getAsString();
|
|
||||||
this.locale = Objects.requireNonNull(TranslationManager.parseLocale(data.get("localeTag").getAsString()));
|
|
||||||
this.progress = data.get("progress").getAsInt();
|
|
||||||
this.contributors = new ArrayList<>();
|
|
||||||
for (JsonElement contributor : data.get("contributors").getAsJsonArray()) {
|
|
||||||
this.contributors.add(contributor.getAsJsonObject().get("name").getAsString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -416,6 +416,11 @@ public final class ConfigKeys {
|
|||||||
.collect(ImmutableCollectors.toList());
|
.collect(ImmutableCollectors.toList());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If LuckPerms should automatically install translation bundles and periodically update them.
|
||||||
|
*/
|
||||||
|
public static final ConfigKey<Boolean> AUTO_INSTALL_TRANSLATIONS = notReloadable(booleanKey("auto-install-translations", true));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If auto op is enabled. Only used by the Bukkit platform.
|
* If auto op is enabled. Only used by the Bukkit platform.
|
||||||
*/
|
*/
|
||||||
|
@@ -159,7 +159,6 @@ public class TranslationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.registry.registerAll(locale, bundle, false);
|
this.registry.registerAll(locale, bundle, false);
|
||||||
this.plugin.getLogger().info("Registered additional translations for " + locale.toString());
|
|
||||||
this.installed.add(locale);
|
this.installed.add(locale);
|
||||||
return Maps.immutableEntry(locale, bundle);
|
return Maps.immutableEntry(locale, bundle);
|
||||||
}
|
}
|
||||||
@@ -186,4 +185,21 @@ public class TranslationManager {
|
|||||||
return locale == null ? null : Translator.parseLocale(locale);
|
return locale == null ? null : Translator.parseLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String localeDisplayName(Locale locale) {
|
||||||
|
if (locale.getLanguage().equals("zh")) {
|
||||||
|
if (locale.getCountry().equals("CN")) {
|
||||||
|
return "简体中文"; // Chinese (Simplified)
|
||||||
|
} else if (locale.getCountry().equals("TW")) {
|
||||||
|
return "繁體中文"; // Chinese (Traditional)
|
||||||
|
}
|
||||||
|
return locale.getDisplayCountry(locale) + locale.getDisplayLanguage(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale.getLanguage().equals("en") && locale.getCountry().equals("PT")) {
|
||||||
|
return "Pirate";
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale.getDisplayLanguage(locale);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,329 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of LuckPerms, licensed under the MIT License.
|
||||||
|
*
|
||||||
|
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package me.lucko.luckperms.common.locale;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
|
||||||
|
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||||
|
import me.lucko.luckperms.common.http.UnsuccessfulRequestException;
|
||||||
|
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||||
|
import me.lucko.luckperms.common.sender.Sender;
|
||||||
|
import me.lucko.luckperms.common.util.MoreFiles;
|
||||||
|
import me.lucko.luckperms.common.util.gson.GsonProvider;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class TranslationRepository {
|
||||||
|
private static final String TRANSLATIONS_INFO_ENDPOINT = "https://metadata.luckperms.net/data/translations";
|
||||||
|
private static final String TRANSLATIONS_DOWNLOAD_ENDPOINT = "https://metadata.luckperms.net/translation/";
|
||||||
|
private static final long MAX_BUNDLE_SIZE = 1048576L; // 1mb
|
||||||
|
private static final long CACHE_MAX_AGE = TimeUnit.DAYS.toMillis(1);
|
||||||
|
|
||||||
|
private final LuckPermsPlugin plugin;
|
||||||
|
|
||||||
|
public TranslationRepository(LuckPermsPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of available languages.
|
||||||
|
*
|
||||||
|
* @return a list of languages
|
||||||
|
* @throws IOException if an i/o error occurs
|
||||||
|
* @throws UnsuccessfulRequestException if the http request fails
|
||||||
|
*/
|
||||||
|
public List<LanguageInfo> getAvailableLanguages() throws IOException, UnsuccessfulRequestException {
|
||||||
|
return getTranslationsMetadata().languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a refresh of the current translations if necessary.
|
||||||
|
*/
|
||||||
|
public void scheduleRefresh() {
|
||||||
|
if (!this.plugin.getConfiguration().get(ConfigKeys.AUTO_INSTALL_TRANSLATIONS)) {
|
||||||
|
return; // skip
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
|
||||||
|
try {
|
||||||
|
refresh();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() throws Exception {
|
||||||
|
Path translationsDirectory = this.plugin.getTranslationManager().getTranslationsDirectory();
|
||||||
|
try {
|
||||||
|
MoreFiles.createDirectoriesIfNotExists(translationsDirectory);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
long lastRefresh = 0L;
|
||||||
|
|
||||||
|
Path repoStatusFile = translationsDirectory.resolve("repository.json");
|
||||||
|
if (Files.exists(repoStatusFile)) {
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(repoStatusFile, StandardCharsets.UTF_8)) {
|
||||||
|
JsonObject status = GsonProvider.normal().fromJson(reader, JsonObject.class);
|
||||||
|
if (status.has("lastRefresh")) {
|
||||||
|
lastRefresh = status.get("lastRefresh").getAsLong();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeSinceLastRefresh = System.currentTimeMillis() - lastRefresh;
|
||||||
|
if (timeSinceLastRefresh <= CACHE_MAX_AGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetadataResponse metadata = getTranslationsMetadata();
|
||||||
|
|
||||||
|
if (timeSinceLastRefresh <= metadata.cacheMaxAge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform a refresh!
|
||||||
|
downloadAndInstallTranslations(metadata.languages, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads and installs translations for the given languages.
|
||||||
|
*
|
||||||
|
* @param languages the languages to install translations for
|
||||||
|
* @param sender the sender to report progress to
|
||||||
|
* @param updateStatus if the status file should be updated
|
||||||
|
*/
|
||||||
|
public void downloadAndInstallTranslations(List<LanguageInfo> languages, @Nullable Sender sender, boolean updateStatus) {
|
||||||
|
TranslationManager manager = this.plugin.getTranslationManager();
|
||||||
|
Path translationsDirectory = manager.getTranslationsDirectory();
|
||||||
|
|
||||||
|
try {
|
||||||
|
MoreFiles.createDirectoriesIfNotExists(translationsDirectory);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LanguageInfo language : languages) {
|
||||||
|
if (sender != null) {
|
||||||
|
Message.TRANSLATIONS_INSTALLING_SPECIFIC.send(sender, language.locale().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.header("User-Agent", this.plugin.getBytebin().getUserAgent())
|
||||||
|
.url(TRANSLATIONS_DOWNLOAD_ENDPOINT + language.id())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Path file = translationsDirectory.resolve(language.locale().toString() + ".properties");
|
||||||
|
|
||||||
|
try (Response response = this.plugin.getBytebin().makeHttpRequest(request)) {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
if (responseBody == null) {
|
||||||
|
throw new IOException("No response");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = new LimitedInputStream(responseBody.byteStream(), MAX_BUNDLE_SIZE)) {
|
||||||
|
Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UnsuccessfulRequestException | IOException e) {
|
||||||
|
if (sender != null) {
|
||||||
|
Message.TRANSLATIONS_DOWNLOAD_ERROR.send(sender, language.locale().toString());
|
||||||
|
this.plugin.getLogger().warn("Unable to download translations", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateStatus) {
|
||||||
|
// update status file
|
||||||
|
Path repoStatusFile = translationsDirectory.resolve("repository.json");
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(repoStatusFile, StandardCharsets.UTF_8)) {
|
||||||
|
JsonObject status = new JsonObject();
|
||||||
|
status.add("lastRefresh", new JsonPrimitive(System.currentTimeMillis()));
|
||||||
|
GsonProvider.prettyPrinting().toJson(status, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plugin.getTranslationManager().reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MetadataResponse getTranslationsMetadata() throws IOException, UnsuccessfulRequestException {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.header("User-Agent", this.plugin.getBytebin().getUserAgent())
|
||||||
|
.url(TRANSLATIONS_INFO_ENDPOINT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JsonObject jsonResponse;
|
||||||
|
try (Response response = this.plugin.getBytebin().makeHttpRequest(request)) {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
if (responseBody == null) {
|
||||||
|
throw new RuntimeException("No response");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = new LimitedInputStream(responseBody.byteStream(), MAX_BUNDLE_SIZE)) {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||||
|
jsonResponse = GsonProvider.normal().fromJson(reader, JsonObject.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LanguageInfo> languages = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, JsonElement> language : jsonResponse.get("languages").getAsJsonObject().entrySet()) {
|
||||||
|
languages.add(new LanguageInfo(language.getKey(), language.getValue().getAsJsonObject()));
|
||||||
|
}
|
||||||
|
languages.removeIf(language -> language.progress() <= 0);
|
||||||
|
|
||||||
|
if (languages.size() >= 100) {
|
||||||
|
// just a precaution: if more than 100 languages have been
|
||||||
|
// returned then the metadata server is doing something silly
|
||||||
|
throw new IOException("More than 100 languages - cancelling download");
|
||||||
|
}
|
||||||
|
|
||||||
|
long cacheMaxAge = jsonResponse.get("cacheMaxAge").getAsLong();
|
||||||
|
|
||||||
|
return new MetadataResponse(cacheMaxAge, languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MetadataResponse {
|
||||||
|
private final long cacheMaxAge;
|
||||||
|
private final List<LanguageInfo> languages;
|
||||||
|
|
||||||
|
MetadataResponse(long cacheMaxAge, List<LanguageInfo> languages) {
|
||||||
|
this.cacheMaxAge = cacheMaxAge;
|
||||||
|
this.languages = languages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class LanguageInfo {
|
||||||
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
private final Locale locale;
|
||||||
|
private final int progress;
|
||||||
|
private final List<String> contributors;
|
||||||
|
|
||||||
|
LanguageInfo(String id, JsonObject data) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = data.get("name").getAsString();
|
||||||
|
this.locale = Objects.requireNonNull(TranslationManager.parseLocale(data.get("localeTag").getAsString()));
|
||||||
|
this.progress = data.get("progress").getAsInt();
|
||||||
|
this.contributors = new ArrayList<>();
|
||||||
|
for (JsonElement contributor : data.get("contributors").getAsJsonArray()) {
|
||||||
|
this.contributors.add(contributor.getAsJsonObject().get("name").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Locale locale() {
|
||||||
|
return this.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int progress() {
|
||||||
|
return this.progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> contributors() {
|
||||||
|
return this.contributors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LimitedInputStream extends FilterInputStream implements Closeable {
|
||||||
|
private final long limit;
|
||||||
|
private long count;
|
||||||
|
|
||||||
|
public LimitedInputStream(InputStream inputStream, long limit) {
|
||||||
|
super(inputStream);
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLimit() throws IOException {
|
||||||
|
if (this.count > this.limit) {
|
||||||
|
throw new IOException("Limit exceeded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
int res = super.read();
|
||||||
|
if (res != -1) {
|
||||||
|
this.count++;
|
||||||
|
checkLimit();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
int res = super.read(b, off, len);
|
||||||
|
if (res > 0) {
|
||||||
|
this.count += res;
|
||||||
|
checkLimit();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -43,6 +43,7 @@ import me.lucko.luckperms.common.http.BytebinClient;
|
|||||||
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
|
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
|
||||||
import me.lucko.luckperms.common.locale.Message;
|
import me.lucko.luckperms.common.locale.Message;
|
||||||
import me.lucko.luckperms.common.locale.TranslationManager;
|
import me.lucko.luckperms.common.locale.TranslationManager;
|
||||||
|
import me.lucko.luckperms.common.locale.TranslationRepository;
|
||||||
import me.lucko.luckperms.common.messaging.InternalMessagingService;
|
import me.lucko.luckperms.common.messaging.InternalMessagingService;
|
||||||
import me.lucko.luckperms.common.messaging.MessagingFactory;
|
import me.lucko.luckperms.common.messaging.MessagingFactory;
|
||||||
import me.lucko.luckperms.common.plugin.logging.PluginLogger;
|
import me.lucko.luckperms.common.plugin.logging.PluginLogger;
|
||||||
@@ -79,6 +80,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
|
|||||||
private LogDispatcher logDispatcher;
|
private LogDispatcher logDispatcher;
|
||||||
private LuckPermsConfiguration configuration;
|
private LuckPermsConfiguration configuration;
|
||||||
private BytebinClient bytebin;
|
private BytebinClient bytebin;
|
||||||
|
private TranslationRepository translationRepository;
|
||||||
private FileWatcher fileWatcher = null;
|
private FileWatcher fileWatcher = null;
|
||||||
private Storage storage;
|
private Storage storage;
|
||||||
private InternalMessagingService messagingService = null;
|
private InternalMessagingService messagingService = null;
|
||||||
@@ -124,6 +126,10 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
|
|||||||
|
|
||||||
this.bytebin = new BytebinClient(httpClient, getConfiguration().get(ConfigKeys.BYTEBIN_URL), "luckperms");
|
this.bytebin = new BytebinClient(httpClient, getConfiguration().get(ConfigKeys.BYTEBIN_URL), "luckperms");
|
||||||
|
|
||||||
|
// init translation repo and update bundle files
|
||||||
|
this.translationRepository = new TranslationRepository(this);
|
||||||
|
this.translationRepository.scheduleRefresh();
|
||||||
|
|
||||||
// now the configuration is loaded, we can create a storage factory and load initial dependencies
|
// now the configuration is loaded, we can create a storage factory and load initial dependencies
|
||||||
StorageFactory storageFactory = new StorageFactory(this);
|
StorageFactory storageFactory = new StorageFactory(this);
|
||||||
Set<StorageType> storageTypes = storageFactory.getRequiredTypes();
|
Set<StorageType> storageTypes = storageFactory.getRequiredTypes();
|
||||||
@@ -320,6 +326,11 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
|
|||||||
return this.bytebin;
|
return this.bytebin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TranslationRepository getTranslationRepository() {
|
||||||
|
return this.translationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<FileWatcher> getFileWatcher() {
|
public Optional<FileWatcher> getFileWatcher() {
|
||||||
return Optional.ofNullable(this.fileWatcher);
|
return Optional.ofNullable(this.fileWatcher);
|
||||||
|
@@ -38,6 +38,7 @@ import me.lucko.luckperms.common.extension.SimpleExtensionManager;
|
|||||||
import me.lucko.luckperms.common.http.BytebinClient;
|
import me.lucko.luckperms.common.http.BytebinClient;
|
||||||
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
|
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
|
||||||
import me.lucko.luckperms.common.locale.TranslationManager;
|
import me.lucko.luckperms.common.locale.TranslationManager;
|
||||||
|
import me.lucko.luckperms.common.locale.TranslationRepository;
|
||||||
import me.lucko.luckperms.common.messaging.InternalMessagingService;
|
import me.lucko.luckperms.common.messaging.InternalMessagingService;
|
||||||
import me.lucko.luckperms.common.model.Group;
|
import me.lucko.luckperms.common.model.Group;
|
||||||
import me.lucko.luckperms.common.model.Track;
|
import me.lucko.luckperms.common.model.Track;
|
||||||
@@ -175,6 +176,13 @@ public interface LuckPermsPlugin {
|
|||||||
*/
|
*/
|
||||||
TranslationManager getTranslationManager();
|
TranslationManager getTranslationManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the translation repository
|
||||||
|
*
|
||||||
|
* @return the translation repository
|
||||||
|
*/
|
||||||
|
TranslationRepository getTranslationRepository();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the dependency manager for the plugin
|
* Gets the dependency manager for the plugin
|
||||||
*
|
*
|
||||||
|
@@ -305,6 +305,9 @@ log-notify: true
|
|||||||
log-notify-filtered-descriptions:
|
log-notify-filtered-descriptions:
|
||||||
# - "parent add example"
|
# - "parent add example"
|
||||||
|
|
||||||
|
# If LuckPerms should automatically install translation bundles and periodically update them.
|
||||||
|
auto-install-translations: true
|
||||||
|
|
||||||
# Defines the options for prefix and suffix stacking.
|
# Defines the options for prefix and suffix stacking.
|
||||||
#
|
#
|
||||||
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
||||||
|
@@ -315,6 +315,9 @@ log-notify-filtered-descriptions = [
|
|||||||
# "parent add example"
|
# "parent add example"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# If LuckPerms should automatically install translation bundles and periodically update them.
|
||||||
|
auto-install-translations = true
|
||||||
|
|
||||||
# Defines the options for prefix and suffix stacking.
|
# Defines the options for prefix and suffix stacking.
|
||||||
#
|
#
|
||||||
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
||||||
|
@@ -309,6 +309,9 @@ log-notify: true
|
|||||||
log-notify-filtered-descriptions:
|
log-notify-filtered-descriptions:
|
||||||
# - "parent add example"
|
# - "parent add example"
|
||||||
|
|
||||||
|
# If LuckPerms should automatically install translation bundles and periodically update them.
|
||||||
|
auto-install-translations: true
|
||||||
|
|
||||||
# Defines the options for prefix and suffix stacking.
|
# Defines the options for prefix and suffix stacking.
|
||||||
#
|
#
|
||||||
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
|
||||||
|
Reference in New Issue
Block a user