195 lines
7.1 KiB
Java
195 lines
7.1 KiB
Java
/*
|
|
* WorldEdit, a Minecraft world manipulation toolkit
|
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
|
* Copyright (C) WorldEdit team and 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.sk89q.worldedit.util.translation;
|
|
|
|
import com.google.common.collect.Maps;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.reflect.TypeToken;
|
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
|
import com.sk89q.worldedit.util.formatting.text.renderer.TranslatableComponentRenderer;
|
|
import com.sk89q.worldedit.util.io.ResourceLoader;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.Reader;
|
|
import java.lang.reflect.Type;
|
|
import java.net.URL;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.text.MessageFormat;
|
|
import java.util.HashSet;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
import static java.util.stream.Collectors.toMap;
|
|
|
|
/**
|
|
* Handles translations for the plugin.
|
|
*
|
|
* <p>
|
|
* These should be in the following format:
|
|
* plugin.component.message[.meta]*
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Where,
|
|
* plugin = worldedit
|
|
* component = The part of the plugin, eg expand
|
|
* message = A descriptor for which message, eg, expanded
|
|
* meta = Any extra information such as plural/singular (Can have none to infinite)
|
|
* </p>
|
|
*/
|
|
public class TranslationManager {
|
|
|
|
private static final Gson gson = new GsonBuilder().create();
|
|
private static final Type STRING_MAP_TYPE = new TypeToken<Map<String, String>>() {}.getType();
|
|
|
|
public static String makeTranslationKey(String type, String id) {
|
|
String[] parts = id.split(":", 2);
|
|
return type + '.' + parts[0] + '.' + parts[1].replace('/', '.');
|
|
}
|
|
|
|
private final Map<Locale, Map<String, String>> translationMap = new ConcurrentHashMap<>();
|
|
private final TranslatableComponentRenderer<Locale> friendlyComponentRenderer = TranslatableComponentRenderer.from(
|
|
(locale, key) -> {
|
|
String translation = getTranslationMap(locale).get(key);
|
|
if (translation == null) {
|
|
// let it pass through (for e.g. MC messages)
|
|
return null;
|
|
}
|
|
return new MessageFormat(translation, locale);
|
|
}
|
|
);
|
|
private Locale defaultLocale = Locale.ENGLISH;
|
|
|
|
private final ResourceLoader resourceLoader;
|
|
|
|
private final Set<Locale> checkedLocales = new HashSet<>();
|
|
|
|
public TranslationManager(ResourceLoader resourceLoader) {
|
|
checkNotNull(resourceLoader);
|
|
this.resourceLoader = resourceLoader;
|
|
}
|
|
|
|
public void setDefaultLocale(Locale defaultLocale) {
|
|
this.defaultLocale = defaultLocale;
|
|
}
|
|
|
|
private Map<String, String> filterTranslations(Map<String, String> translations) {
|
|
return translations.entrySet().stream()
|
|
.filter(e -> !e.getValue().isEmpty())
|
|
.map(e -> Maps.immutableEntry(e.getKey(), e.getValue().replace("'", "''")))
|
|
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
}
|
|
|
|
private Map<String, String> parseTranslationFile(InputStream inputStream) throws IOException {
|
|
try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
|
return filterTranslations(gson.fromJson(reader, STRING_MAP_TYPE));
|
|
}
|
|
}
|
|
|
|
private Optional<Map<String, String>> loadTranslationFile(String filename) {
|
|
Map<String, String> baseTranslations = new ConcurrentHashMap<>();
|
|
|
|
try {
|
|
URL resource = resourceLoader.getRootResource("lang/" + filename);
|
|
if (resource != null) {
|
|
try (InputStream stream = resource.openStream()) {
|
|
baseTranslations = parseTranslationFile(stream);
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
// Seem to be missing base. If the user has provided a file use that.
|
|
}
|
|
|
|
Path localFile = resourceLoader.getLocalResource("lang/" + filename);
|
|
if (Files.exists(localFile)) {
|
|
try (InputStream stream = Files.newInputStream(localFile)) {
|
|
baseTranslations.putAll(parseTranslationFile(stream));
|
|
} catch (IOException e) {
|
|
// Failed to parse custom language file. Worth printing.
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
return baseTranslations.size() == 0 ? Optional.empty() : Optional.of(baseTranslations);
|
|
}
|
|
|
|
private boolean tryLoadTranslations(Locale locale) {
|
|
if (checkedLocales.contains(locale)) {
|
|
return false;
|
|
}
|
|
checkedLocales.add(locale);
|
|
// Make a copy of the default language file
|
|
Map<String, String> baseTranslations = new ConcurrentHashMap<>();
|
|
if (!locale.equals(defaultLocale)) {
|
|
baseTranslations.putAll(getTranslationMap(defaultLocale));
|
|
}
|
|
Optional<Map<String, String>> langData = Optional.empty();
|
|
if (!locale.getCountry().isEmpty()) {
|
|
langData = loadTranslationFile(locale.getLanguage() + "-" + locale.getCountry() + "/strings.json");
|
|
}
|
|
if (!langData.isPresent()) {
|
|
langData = loadTranslationFile(locale.getLanguage() + "/strings.json");
|
|
}
|
|
if (langData.isPresent()) {
|
|
baseTranslations.putAll(langData.get());
|
|
translationMap.put(locale, baseTranslations);
|
|
return true;
|
|
}
|
|
if (locale.equals(defaultLocale)) {
|
|
translationMap.put(Locale.ENGLISH, loadTranslationFile("strings.json").orElseThrow(
|
|
() -> new RuntimeException("Failed to load WorldEdit strings!")
|
|
));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Map<String, String> getTranslationMap(Locale locale) {
|
|
Map<String, String> translations = translationMap.get(locale);
|
|
if (translations == null) {
|
|
if (tryLoadTranslations(locale)) {
|
|
return getTranslationMap(locale);
|
|
}
|
|
if (!locale.equals(defaultLocale)) {
|
|
translations = getTranslationMap(defaultLocale);
|
|
}
|
|
}
|
|
|
|
return translations;
|
|
}
|
|
|
|
public Component convertText(Component component, Locale locale) {
|
|
return friendlyComponentRenderer.render(component, locale);
|
|
}
|
|
|
|
public Locale getDefaultLocale() {
|
|
return defaultLocale;
|
|
}
|
|
}
|