feat(i18n): custom locale migration

This commit is contained in:
ineanto 2023-12-23 00:00:24 +01:00
parent 548d210a95
commit ce8f21c268
11 changed files with 165 additions and 86 deletions

View file

@ -1,13 +1,25 @@
1.0.9-RC1: Update n°6 (20/12/23)
1.1.0-RC1: Update n°6 (XX/12/23)
[BREAKING]
- The language system has been updated to use the Adventure API (https://docs.advntr.dev/index.html). This results in custom locale now breaking
Nicko upon usage of legacy color codes (e.g., "§6Nicko"). Your custom locale will be backed up upon starting this version and you will be able to
use the new default English locale to help you make your locale compatible with the new formatting.
[FEATURES]
-
- Players can now choose to get a random appearance via a list of more than 400 usernames and skins associated.
- Players can now toggle a setting to automatically get a random appearance upon joining.
- Introduced a version string inside Nicko's language files to plan future updates to the file. (see [BREAKING])
(Note: the random skin functionality is still work-in-progress and might break or not work at all because of
the lack of time that I have to test all the usernames and skins associated.)
[FIXES]
- bStats metrics should be properly sent now(?)
- Various optimizations and improvements.
- Internal refactoring
- bStats metrics are not minified anymore.
1.0.8-RC1: Update n°5 (19/12/23)
[FEATURES]
- Introduced a version string inside Nicko's configuration to plan future updated to the file. Your previous configuration file will automatically be migrated to this current version (with the backup of your old one included!)
- Introduced a version string inside Nicko's configuration to plan future updates to the file. Your previous configuration file will automatically be migrated to this current version (with the backup of your old one included!)
- Persistence and cache will now fallback to local alternatives when unreachable.
- Player check GUI has been updated to better reflect the current state of player's disguises.
- Developers can now listen to the PlayerDisguiseEvent and cancel the disguise process.

View file

@ -5,7 +5,7 @@ plugins {
}
group = "xyz.ineanto"
version = "1.1.1-RC1"
version = "1.1.0-RC1"
val shadowImplementation: Configuration by configurations.creating
configurations["implementation"].extendsFrom(shadowImplementation)

View file

@ -12,11 +12,10 @@ import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.ConfigurationManager;
import xyz.ineanto.nicko.event.PlayerJoinListener;
import xyz.ineanto.nicko.event.PlayerQuitListener;
import xyz.ineanto.nicko.i18n.Locale;
import xyz.ineanto.nicko.i18n.CustomLocale;
import xyz.ineanto.nicko.i18n.Locale;
import xyz.ineanto.nicko.migration.ConfigurationMigrator;
import xyz.ineanto.nicko.migration.CustomLocaleMigrator;
import xyz.ineanto.nicko.migration.Migrator;
import xyz.ineanto.nicko.mojang.MojangAPI;
import xyz.ineanto.nicko.placeholder.NickoExpansion;
import xyz.ineanto.nicko.storage.PlayerDataStore;
@ -27,9 +26,7 @@ import xyz.xenondevs.invui.gui.structure.Structure;
import xyz.xenondevs.invui.item.builder.ItemBuilder;
import xyz.xenondevs.invui.item.impl.SimpleItem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
public class NickoBukkit extends JavaPlugin {
private static NickoBukkit plugin;
@ -65,15 +62,7 @@ public class NickoBukkit extends JavaPlugin {
configurationManager = new ConfigurationManager(getDataFolder());
configurationManager.saveDefaultConfig();
mojangAPI = new MojangAPI();
dataStore = new PlayerDataStore(mojangAPI, getNickoConfig());
nameStore = new PlayerNameStore();
nameFetcher = new RandomNameFetcher(this);
if (!Bukkit.getOnlineMode()) {
getLogger().warning("Nicko has not been tested using offline mode!");
getLogger().warning("Issues regarding Nicko being used in offline mode will be ignored for now.");
}
if (!MinecraftVersion.WILD_UPDATE.atOrAbove()) {
getLogger().severe("This version (" + MinecraftVersion.getCurrentVersion().getVersion() + ") is not supported by Nicko!");
@ -81,6 +70,17 @@ public class NickoBukkit extends JavaPlugin {
Bukkit.getPluginManager().disablePlugin(this);
}
if (!Bukkit.getOnlineMode()) {
getLogger().warning("Nicko has not been tested using offline mode!");
getLogger().warning("Issues regarding Nicko being used in offline mode will be ignored for now.");
}
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServerInitEvent");
getLogger().warning("Nicko has not been tested against Folia and might not work at all!");
getLogger().warning("Issues regarding Nicko on Folia will be ignored for now.");
} catch (ClassNotFoundException ignored) { }
getLogger().info("Loading persistence...");
if (!dataStore.getStorage().getProvider().init()) {
getLogger().severe("Couldn't connect to distant persistence, falling back on local persistence.");
@ -98,28 +98,20 @@ public class NickoBukkit extends JavaPlugin {
}
if (!unitTesting) {
final List<Migrator> migrations = List.of(
new ConfigurationMigrator(this),
new CustomLocaleMigrator(this)
);
migrations.forEach(Migrator::migrate);
nameStore = new PlayerNameStore();
mojangAPI = new MojangAPI();
nameFetcher = new RandomNameFetcher(this);
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServerInitEvent");
getLogger().warning("Nicko has not been tested against Folia and might not work at all!");
getLogger().warning("Issues regarding Nicko on Folia will be ignored for now.");
} catch (ClassNotFoundException ignored) { }
new ConfigurationMigrator(this).migrate();
if (configuration.isCustomLocale()) {
try {
customLocale = new CustomLocale(this);
if (customLocale.dumpIntoFile(Locale.ENGLISH)) {
getLogger().info("Successfully loaded custom language file.");
} else {
getLogger().severe("Failed to load custom language file!");
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
CustomLocale.dumpIntoFile(Locale.ENGLISH);
customLocale = new CustomLocale();
new CustomLocaleMigrator(this, customLocale).migrate();
getLogger().info("Successfully loaded the custom locale.");
} catch (IOException e) {
getLogger().severe("Failed to load the custom locale!");
}
}
@ -147,7 +139,6 @@ public class NickoBukkit extends JavaPlugin {
@Override
public void onDisable() {
if (!getDataStore().getStorage().isError()) {
nameStore.clearStoredNames();
Bukkit.getOnlinePlayers().forEach(player -> dataStore.saveData(player));
if (!dataStore.getStorage().getProvider().close()) {
getLogger().severe("Failed to close persistence!");
@ -156,7 +147,10 @@ public class NickoBukkit extends JavaPlugin {
}
}
if (!unitTesting) metrics.shutdown();
if (!unitTesting) {
nameStore.clearStoredNames();
metrics.shutdown();
}
getLogger().info("Nicko (Bukkit) has been disabled.");
}

View file

@ -19,7 +19,7 @@ public class RandomNameFetcher {
}
public String getRandomUsername() {
final InputStream resource = instance.getResource("names.csv");
final InputStream resource = instance.getResource("names.txt");
final List<List<String>> records = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource))) {
String line;

View file

@ -3,55 +3,46 @@ package xyz.ineanto.nicko.i18n;
import com.github.jsixface.YamlConfig;
import xyz.ineanto.nicko.NickoBukkit;
import xyz.ineanto.nicko.version.Version;
import xyz.xenondevs.invui.util.IOUtils;
import java.io.*;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.nio.file.Files;
import java.util.logging.Logger;
public class CustomLocale {
private final Logger logger = Logger.getLogger("CustomLocale");
private final File directory = new File(NickoBukkit.getInstance().getDataFolder() + "/lang/");
private static final Logger logger = Logger.getLogger("CustomLocale");
private static final File directory = new File(NickoBukkit.getInstance().getDataFolder(), "/locale/");
private static final File file = new File(directory, "locale.yml");
private final File file;
private final File backupFile;
private final NickoBukkit instance;
private final String version;
private final Version versionObject;
private final BufferedInputStream inputStream;
private final BufferedOutputStream outputStream;
private final YamlConfig yamlFile;
public CustomLocale(NickoBukkit instance) throws FileNotFoundException {
this.instance = instance;
this.file = new File(directory, "lang.yml");
final String date = Instant.now()
.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"));
this.backupFile = new File(directory, "lang.old-" + date + ".yml");
this.inputStream = new BufferedInputStream(new FileInputStream(file));
this.outputStream = new BufferedOutputStream(new FileOutputStream(file));
this.yamlFile = new YamlConfig(inputStream);
public CustomLocale(InputStream input) throws IOException {
this.yamlFile = new YamlConfig(input);
this.version = yamlFile.getString("version");
this.versionObject = Version.fromString(version);
}
public boolean dumpIntoFile(Locale locale) {
if (locale == Locale.CUSTOM) return true;
if (file.exists()) return true;
public CustomLocale() throws IOException {
this.yamlFile = new YamlConfig(new FileInputStream(file));
this.version = yamlFile.getString("version");
this.versionObject = Version.fromString(version);
}
public static void dumpIntoFile(Locale locale) throws IOException {
if (locale == Locale.CUSTOM) return;
if (file.exists()) return;
if (!directory.exists()) directory.mkdirs();
final String localeFileName = locale.getCode() + ".yml";
try {
if (directory.mkdirs()) {
if (file.createNewFile()) {
IOUtils.copy(inputStream, outputStream, 8192);
final InputStream resource = NickoBukkit.getInstance().getResource(localeFileName);
if (resource != null) {
Files.copy(resource, file.toPath());
resource.close();
}
}
return true;
} catch (IOException e) {
logger.severe("Unable to dump Locale: " + locale.getCode() + "!");
return false;
}
}
@ -67,8 +58,8 @@ public class CustomLocale {
return yamlFile;
}
public File getBackupFile() {
return backupFile;
public File getDirectory() {
return directory;
}
public File getFile() {

View file

@ -4,31 +4,41 @@ import xyz.ineanto.nicko.NickoBukkit;
import xyz.ineanto.nicko.i18n.CustomLocale;
import xyz.ineanto.nicko.i18n.Locale;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class CustomLocaleMigrator implements Migrator {
private final NickoBukkit instance;
private final CustomLocale customLocale;
public CustomLocaleMigrator(NickoBukkit instance) {
public CustomLocaleMigrator(NickoBukkit instance, CustomLocale customLocale) {
this.instance = instance;
this.customLocale = customLocale;
}
@Override
public void migrate() {
final CustomLocale customLanguageFile = instance.getCustomLocale();
// Migrate custom locale (1.1.0-RC1)
if (customLanguageFile.getVersionObject() == null
|| customLanguageFile.getVersion().isEmpty()
|| customLanguageFile.getVersionObject().compareTo(Locale.VERSION) != 0) {
instance.getLogger().info("Migrating custom locale to match the current version...");
if (customLocale.getVersionObject() == null
|| customLocale.getVersion().isEmpty()
|| customLocale.getVersionObject().compareTo(Locale.VERSION) != 0) {
instance.getLogger().info("Migrating the custom locale (" + customLocale.getVersion() + ") to match the current version (" + Locale.VERSION + ")...");
final String date = Instant.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("dd-MM-yyyy"));
final File backupFile = new File(customLocale.getDirectory(), "locale-" + date + ".yml");
try {
Files.copy(customLanguageFile.getFile().toPath(), customLanguageFile.getBackupFile().toPath(), StandardCopyOption.ATOMIC_MOVE);
customLanguageFile.dumpIntoFile(Locale.ENGLISH);
Files.copy(customLocale.getFile().toPath(), backupFile.toPath());
if (customLocale.getFile().delete()) {
customLocale.dumpIntoFile(Locale.ENGLISH);
}
instance.getLogger().info("Successfully migrated the custom locale.");
} catch (IOException e) {
instance.getLogger().severe("Failed to migrate your custom locale!");
instance.getLogger().severe("Failed to migrate the custom locale!");
}
}
}

View file

@ -12,13 +12,11 @@ import xyz.ineanto.nicko.config.DefaultDataSources;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ConfigurationVersionTest {
private static NickoBukkit plugin;
@BeforeAll
public static void setup() {
MockBukkit.mock();
final Configuration configuration = Configuration.DEFAULT;
plugin = MockBukkit.load(NickoBukkit.class, configuration);
MockBukkit.load(NickoBukkit.class, configuration);
}
@Test

View file

@ -43,7 +43,6 @@ public class ItemTranslationTest {
test.lore().forEach(System.out::println);
final Translation translation = i18n.translate(I18NDict.GUI.Admin.Cache.STATISTICS, "1", "1");
translation.lore().forEach(System.out::println);
assertFalse(translation.lore().isEmpty());
assertEquals("Nombre de requêtes: <aqua>1</aqua>", translation.lore().get(0));
assertEquals("Nb. de skin dans le cache: <aqua>1</aqua>", translation.lore().get(1));

View file

@ -0,0 +1,71 @@
package xyz.ineanto.nicko.test.migration;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.NickoBukkit;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.DefaultDataSources;
import xyz.ineanto.nicko.i18n.CustomLocale;
import xyz.ineanto.nicko.migration.CustomLocaleMigrator;
import java.io.*;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MigrationTest {
private static NickoBukkit plugin;
private static File folder;
private static File localeFile;
@BeforeAll
public static void setup() throws IOException {
MockBukkit.mock();
final Configuration configuration = new Configuration(Configuration.VERSION.toString(),
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"§6Nicko §8§l| §r",
true);
plugin = MockBukkit.load(NickoBukkit.class, configuration);
folder = new File(plugin.getDataFolder(), "/locale/");
localeFile = new File(folder, "locale.yml");
folder.mkdirs();
localeFile.createNewFile();
}
@Test
public void testLanguageFileMigration() throws IOException {
final String content = """
# Nicko - Language File:
# hello I'm the invalid version
version: "1.0.0"
""";
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(localeFile));
outputStream.write(content.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
// Get wrong locale
final CustomLocale customLocaleBeforeMigration = new CustomLocale(new FileInputStream(localeFile));
assertEquals(customLocaleBeforeMigration.getVersion(), "1.0.0");
// Migrate the wrong locale to the correct one
final CustomLocaleMigrator localeMigrator = new CustomLocaleMigrator(plugin, customLocaleBeforeMigration);
localeMigrator.migrate();
// Get the migrated locale
final CustomLocale customLocaleMigrated = new CustomLocale(new FileInputStream(localeFile));
assertEquals(customLocaleMigrated.getVersion(), "1.1.0");
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
folder.delete();
localeFile.delete();
}
}

View file

@ -0,0 +1,4 @@
# Nicko ${version} - Language File:
# hello I'm the good version
version: "1.1.0"