Plan/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/LocaleSystem.java

278 lines
10 KiB
Java

/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.settings.locale;
import com.djrapitops.plan.SubSystem;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.web.AssetVersions;
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.PluginSettings;
import com.djrapitops.plan.settings.locale.lang.*;
import com.djrapitops.plan.settings.upkeep.FileWatcher;
import com.djrapitops.plan.settings.upkeep.WatchedFile;
import com.djrapitops.plan.storage.file.FileResource;
import com.djrapitops.plan.storage.file.PlanFiles;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* System in charge of {@link Locale}.
*
* @author AuroraLS3
*/
@Singleton
public class LocaleSystem implements SubSystem {
private final PlanFiles files;
private final PlanConfig config;
private final AssetVersions assetVersions;
private final PluginLogger logger;
private final ErrorLogger errorLogger;
private final Locale locale;
private FileWatcher fileWatcher;
@Inject
public LocaleSystem(
PlanFiles files,
PlanConfig config,
AssetVersions assetVersions,
PluginLogger logger,
ErrorLogger errorLogger
) {
this.files = files;
this.config = config;
this.assetVersions = assetVersions;
this.logger = logger;
this.errorLogger = errorLogger;
this.locale = new Locale();
}
/**
* Get the txt keys of all Lang entries (legacy locale files that need yml conversion).
*
* @return Map of txt key (eg {@code "HTML - LOGIN_CREATE_ACCOUNT"}) - Lang (eg. {@link HtmlLang#LOGIN_CREATE_ACCOUNT})
*/
public static Map<String, Lang> getIdentifiers() {
return Arrays.stream(getValuesArray())
.flatMap(Arrays::stream)
.collect(Collectors.toMap(Lang::getIdentifier, Function.identity()));
}
/**
* Get the yml keys of all Lang entries.
*
* @return Map of yml key (eg. {@code "html.login.register"}) - Lang (eg. {@link HtmlLang#LOGIN_CREATE_ACCOUNT})
*/
public static Map<String, Lang> getKeys() {
return Arrays.stream(getValuesArray())
.flatMap(Arrays::stream)
.collect(Collectors.toMap(Lang::getKey, Function.identity()));
}
private static Lang[][] getValuesArray() {
return new Lang[][]{
CommandLang.values(),
DeepHelpLang.values(),
ErrorPageLang.values(),
FailReason.values(),
FilterLang.values(),
GenericLang.values(),
HelpLang.values(),
HtmlLang.values(),
JSLang.values(),
PluginLang.values(),
WebPermission.nonDeprecatedValues(),
};
}
@Override
public void enable() {
convertFromLegacyFormat();
File localeFile = files.getLocaleFile();
if (config.isTrue(PluginSettings.WRITE_NEW_LOCALE)) {
writeNewDefaultLocale(localeFile);
}
Optional<Locale> loaded;
if (localeFile.exists()) {
writeNewDefaultLocale(localeFile);
loaded = loadFromFile(localeFile);
fileWatcher = prepareFileWatcher(localeFile);
fileWatcher.start();
} else {
loaded = loadSettingLocale();
}
loaded.ifPresent(locale::loadFromAnotherLocale);
LangCode langCode = locale.getLangCode();
logger.info("Locale: '" + langCode.getName() + "' by " + langCode.getAuthors());
if (config.isTrue(PluginSettings.LOG_NEW_LOCALE_LINES)) {
logDefaultKeys(locale);
}
}
private void logDefaultKeys(Locale locale) {
Map<String, Lang> keys = getKeys();
for (Map.Entry<String, Lang> entry : keys.entrySet()) {
String key = entry.getKey();
Lang lang = entry.getValue();
if (lang.getDefault().equals(locale.getString(lang))) {
logger.info("Untranslated line: " + key);
}
}
}
public FileWatcher prepareFileWatcher(File localeFile) {
FileWatcher watcher = new FileWatcher(files.getDataDirectory(), errorLogger);
watcher.addToWatchlist(new WatchedFile(localeFile, this::reloadCustomLocale));
return watcher;
}
private void reloadCustomLocale() {
File localeFile = files.getLocaleFile();
if (localeFile.exists()) {
loadFromFile(localeFile).ifPresent(locale::loadFromAnotherLocale);
logger.info(locale.getString(PluginLang.RELOAD_LOCALE));
}
}
private void writeNewDefaultLocale(File localeFile) {
try {
Locale writing = loadSettingLocale().orElse(locale);
if (localeFile.exists()) {
writing.putAll(Locale.fromFile(localeFile));
}
new LocaleFileWriter(writing).writeToFile(localeFile);
} catch (IOException | IllegalStateException e) {
errorLogger.error(e, ErrorContext.builder().whatToDo("Fix write permissions to " + localeFile.getAbsolutePath()).build());
}
resetWriteConfigSetting();
}
private void resetWriteConfigSetting() {
try {
config.set(PluginSettings.WRITE_NEW_LOCALE, false);
config.save();
} catch (IOException | IllegalStateException e) {
errorLogger.error(e, ErrorContext.builder().whatToDo("Fix write permissions to " + config.getConfigFilePath()).build());
}
}
private void convertFromLegacyFormat() {
File oldCustomFile = files.getFileFromPluginFolder("locale.txt");
if (!files.getLocaleFile().exists() && oldCustomFile.exists()) {
try {
logger.info("Converting locale.txt to yml...");
Locale loaded = new LocaleFileReader(new FileResource("locale.txt", oldCustomFile)).loadLegacy(LangCode.CUSTOM);
new LocaleFileWriter(loaded).writeToFile(files.getLocaleFile());
} catch (IOException e) {
errorLogger.error(e, ErrorContext.builder().whatToDo("Fix write permissions to " + files.getLocaleFile().toString()).build());
}
}
for (LangCode code : LangCode.values()) {
if (code == LangCode.CUSTOM) continue;
File oldFile = files.getFileFromPluginFolder("locale_" + code + ".txt");
if (!files.getFileFromPluginFolder(code.getFileName()).exists() && oldFile.exists()) {
try {
logger.info("Converting " + oldFile.getName() + " to yml...");
Locale loaded = new LocaleFileReader(new FileResource(oldFile.getName(), oldFile)).loadLegacy(LangCode.CUSTOM);
new LocaleFileWriter(loaded).writeToFile(files.getFileFromPluginFolder(code.getFileName()));
} catch (IOException e) {
errorLogger.error(e, ErrorContext.builder().whatToDo("Fix write permissions to " + files.getFileFromPluginFolder(code.getFileName()).toString()).build());
}
}
}
}
private Optional<Locale> loadSettingLocale() {
try {
String setting = config.get(PluginSettings.LOCALE);
if ("write-all".equalsIgnoreCase(setting)) {
for (LangCode code : LangCode.values()) {
if (code == LangCode.CUSTOM) continue;
Locale writing = Locale.forLangCode(code, files);
new LocaleFileWriter(writing).writeToFile(
files.getDataDirectory().resolve("locale_" + code.name() + ".yml").toFile()
);
}
return Optional.empty();
}
if (!"default".equalsIgnoreCase(setting)) {
return Optional.of(Locale.forLangCodeString(files, setting));
}
} catch (IOException e) {
logger.warn("Failed to read locale from jar: " + config.get(PluginSettings.LOCALE) + ", " + e);
logger.warn("Using Default Locale as a fallback (EN)");
}
return Optional.empty();
}
private Optional<Locale> loadFromFile(File localeFile) {
try {
return Optional.of(Locale.fromFile(localeFile));
} catch (IOException e) {
logger.warn("Failed to read locale file at " + localeFile.getAbsolutePath() + ", " + e);
logger.warn("Using Default Locale as a fallback (EN)");
}
return Optional.empty();
}
@Override
public void disable() {
if (fileWatcher != null) {
fileWatcher.interrupt();
}
}
public Locale getLocale() {
return locale;
}
public long getMaxLocaleVersion() {
return assetVersions.getLatestWebAssetVersion().orElse(0L);
}
public Optional<Long> getLocaleVersion(LangCode langCode) {
return assetVersions.getAssetVersion(langCode.getFileName());
}
public Optional<Long> getCustomLocaleVersion() {
File localeFile = files.getLocaleFile();
if (!localeFile.exists()) {
return Optional.empty();
}
return Optional.of(localeFile.lastModified());
}
}