diff --git a/build.gradle b/build.gradle index 91fd43c5a..f351a3080 100644 --- a/build.gradle +++ b/build.gradle @@ -136,7 +136,7 @@ dependencies { // Tests testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21' - testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.21.0' + testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.22.2' testImplementation('com.googlecode.json-simple:json-simple:1.1.1') { exclude group: 'junit', module: 'junit' } diff --git a/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java b/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java index a8b2460c4..8bc71f293 100644 --- a/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java +++ b/src/main/java/org/mvplugins/multiverse/core/MultiverseCore.java @@ -27,7 +27,6 @@ import org.mvplugins.multiverse.core.api.MVCore; import org.mvplugins.multiverse.core.commands.CoreCommand; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; -import org.mvplugins.multiverse.core.commandtools.PluginLocales; import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.economy.MVEconomist; @@ -65,8 +64,6 @@ public class MultiverseCore extends JavaPlugin implements MVCore { private Provider metricsConfiguratorProvider; @Inject private Provider economistProvider; - @Inject - private Provider pluginLocalesProvider; // Counter for the number of plugins that have registered with us private int pluginCount; @@ -219,8 +216,7 @@ private void registerCommands() { */ private void setUpLocales() { Try.of(() -> commandManagerProvider.get()) - .andThen(commandManager -> commandManager.usePerIssuerLocale(true, true)) - .mapTry(commandManager -> pluginLocalesProvider.get()) + .mapTry(MVCommandManager::getLocales) .andThen(pluginLocales -> { pluginLocales.addFileResClassLoader(this); pluginLocales.addMessageBundles("multiverse-core"); diff --git a/src/main/java/org/mvplugins/multiverse/core/api/MVConfig.java b/src/main/java/org/mvplugins/multiverse/core/api/MVConfig.java index 4ba47a798..f7ebabfd5 100644 --- a/src/main/java/org/mvplugins/multiverse/core/api/MVConfig.java +++ b/src/main/java/org/mvplugins/multiverse/core/api/MVConfig.java @@ -6,6 +6,8 @@ import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle; import org.mvplugins.multiverse.core.placeholders.MultiverseCorePlaceholders; +import java.util.Locale; + @Contract public interface MVConfig { @@ -195,6 +197,30 @@ public interface MVConfig { */ boolean isRegisterPapiHook(); + /** + * Sets default locale used for messages + * @param defaultLocale The new value + */ + void setDefaultLocale(Locale defaultLocale); + + /** + * Gets default locale used for messages + * @return default locale + */ + Locale getDefaultLocale(); + + /** + * Sets whether to use each player's client locale. + * @param perPlayerLocale the new value + */ + void setPerPlayerLocale(boolean perPlayerLocale); + + /** + * Gets whether to use each player's client locale. + * @return True if per player locale should be used. + */ + boolean getPerPlayerLocale(); + /** * Sets globalDebug. * @param globalDebug The new value. diff --git a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandManager.java b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandManager.java index 751e5c351..179e1960f 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandManager.java +++ b/src/main/java/org/mvplugins/multiverse/core/commandtools/MVCommandManager.java @@ -32,6 +32,7 @@ public class MVCommandManager extends PaperCommandManager { private final Provider commandContextsProvider; private final Provider commandCompletionsProvider; private final MVCommandPermissions commandPermissions; + private final PluginLocales pluginLocales; @Inject MVCommandManager( @@ -49,24 +50,20 @@ public class MVCommandManager extends PaperCommandManager { this.commandContextsProvider = commandContextsProvider; this.commandCompletionsProvider = commandCompletionsProvider; this.commandPermissions = commandPermissions; + this.pluginLocales = new PluginLocales(this); + this.locales = this.pluginLocales; + this.pluginLocales.loadLanguages(); MVCommandConditions.load(this, worldManager, worldNameChecker); this.enableUnstableAPI("help"); } - void loadLanguages(PluginLocales locales) { - if (this.locales == null) { - this.locales = locales; - this.locales.loadLanguages(); - } - } - /** * {@inheritDoc} */ @Override public PluginLocales getLocales() { - return (PluginLocales) this.locales; + return this.pluginLocales; } /** diff --git a/src/main/java/org/mvplugins/multiverse/core/commandtools/PluginLocales.java b/src/main/java/org/mvplugins/multiverse/core/commandtools/PluginLocales.java index 39f21638b..9c267b667 100644 --- a/src/main/java/org/mvplugins/multiverse/core/commandtools/PluginLocales.java +++ b/src/main/java/org/mvplugins/multiverse/core/commandtools/PluginLocales.java @@ -11,7 +11,6 @@ /** * Locale manager with additional methods for loading locales from plugin's locales folder. */ -@Service public class PluginLocales extends BukkitLocales { private static final String DEFAULT_LOCALE_FOLDER_PATH = "locales"; @@ -21,10 +20,8 @@ public class PluginLocales extends BukkitLocales { * * @param manager The command manager. */ - @Inject public PluginLocales(MVCommandManager manager) { super(manager); - manager.loadLanguages(this); } /** diff --git a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java index 7c8e18fc8..0542b58b1 100644 --- a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java +++ b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Locale; import java.util.Objects; import com.dumptruckman.minecraft.util.Logging; @@ -15,6 +16,7 @@ import org.mvplugins.multiverse.core.MultiverseCore; import org.mvplugins.multiverse.core.api.MVConfig; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.configuration.handle.CommentedYamlConfigHandle; import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle; import org.mvplugins.multiverse.core.configuration.migration.BooleanMigratorAction; @@ -35,9 +37,9 @@ public class MVCoreConfig implements MVConfig { private final StringPropertyHandle stringPropertyHandle; @Inject - MVCoreConfig(@NotNull MultiverseCore core, @NotNull PluginManager pluginManager) { + MVCoreConfig(@NotNull MultiverseCore core, @NotNull PluginManager pluginManager, @NotNull MVCommandManager commandManager) { this.configPath = Path.of(core.getDataFolder().getPath(), CONFIG_FILENAME); - this.configNodes = new MVCoreConfigNodes(pluginManager); + this.configNodes = new MVCoreConfigNodes(pluginManager, commandManager); this.configHandle = CommentedYamlConfigHandle.builder(configPath, configNodes.getNodes()) .logger(Logging.getLogger()) .migrator(ConfigMigrator.builder(configNodes.VERSION) @@ -246,6 +248,26 @@ public boolean isRegisterPapiHook() { return configHandle.get(configNodes.REGISTER_PAPI_HOOK); } + @Override + public void setDefaultLocale(Locale defaultLocale) { + configHandle.set(configNodes.DEFAULT_LOCALE, defaultLocale); + } + + @Override + public Locale getDefaultLocale() { + return configHandle.get(configNodes.DEFAULT_LOCALE); + } + + @Override + public void setPerPlayerLocale(boolean perPlayerLocale) { + configHandle.set(configNodes.PER_PLAYER_LOCALE, perPlayerLocale); + } + + @Override + public boolean getPerPlayerLocale() { + return configHandle.get(configNodes.PER_PLAYER_LOCALE); + } + @Override public void setGlobalDebug(int globalDebug) { configHandle.set(configNodes.GLOBAL_DEBUG, globalDebug); diff --git a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfigNodes.java b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfigNodes.java index adcf30881..dd9fd0765 100644 --- a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfigNodes.java @@ -4,6 +4,8 @@ import io.vavr.control.Try; import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.configuration.node.ConfigHeaderNode; import org.mvplugins.multiverse.core.configuration.node.ConfigNode; import org.mvplugins.multiverse.core.configuration.node.Node; @@ -11,13 +13,17 @@ import org.mvplugins.multiverse.core.event.MVDebugModeEvent; import org.mvplugins.multiverse.core.exceptions.MultiverseException; +import java.util.Locale; + class MVCoreConfigNodes { private final NodeGroup nodes = new NodeGroup(); private PluginManager pluginManager; + private MVCommandManager commandManager; - MVCoreConfigNodes(PluginManager pluginManager) { + MVCoreConfigNodes(@NotNull PluginManager pluginManager, @NotNull MVCommandManager commandManager) { this.pluginManager = pluginManager; + this.commandManager = commandManager; } public NodeGroup getNodes() { @@ -187,6 +193,28 @@ private N node(N node) { .name("register-papi-hook") .build()); + final ConfigNode DEFAULT_LOCALE = node(ConfigNode.builder("messaging.default-locale", Locale.class) + .comment("") + .comment("This config option defines the default language Multiverse should use.") + .defaultValue(Locale.ENGLISH) + .name("default-locale") + .onSetValue((oldValue, newValue) -> { + commandManager.getLocales().setDefaultLocale(newValue); + }) + .build()); + + final ConfigNode PER_PLAYER_LOCALE = node(ConfigNode.builder("messaging.per-player-locale", Boolean.class) + .comment("") + .comment("This config option defines if Multiverse should use the player's language based on their client's language.") + .comment("If the player's language does not have a translation, it will use the default language set above instead.") + .defaultValue(true) + .name("per-player-locale") + .onSetValue((oldValue, newValue) -> { + // autoDetectFromClient will be done by MVLocalesListener instead + commandManager.usePerIssuerLocale(newValue, false); + }) + .build()); + private final ConfigHeaderNode MISC_HEADER = node(ConfigHeaderNode.builder("misc") .comment("") .comment("") diff --git a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java index e88c85583..b4e54d5cb 100644 --- a/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java +++ b/src/main/java/org/mvplugins/multiverse/core/configuration/functions/DefaultSerializerProvider.java @@ -1,6 +1,7 @@ package org.mvplugins.multiverse.core.configuration.functions; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import co.aikar.commands.ACFUtil; @@ -70,8 +71,25 @@ public Object serialize(Boolean object, Class type) { } }; + private static final NodeSerializer LOCALE_SERIALIZER = new NodeSerializer<>() { + @Override + public Locale deserialize(Object object, Class type) { + if (object instanceof Locale) { + return (Locale) object; + } + String[] split = String.valueOf(object).split("_", 2); + return split.length > 1 ? new Locale(split[0], split[1]) : new Locale(split[0]); + } + + @Override + public Object serialize(Locale object, Class type) { + return object.toLanguageTag(); + } + }; + static { addDefaultSerializer(Boolean.class, BOOLEAN_SERIALIZER); + addDefaultSerializer(Locale.class, LOCALE_SERIALIZER); } private DefaultSerializerProvider() { diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVLocalesListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVLocalesListener.java new file mode 100644 index 000000000..cb28855a3 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVLocalesListener.java @@ -0,0 +1,43 @@ +package org.mvplugins.multiverse.core.listeners; + +import com.dumptruckman.minecraft.util.Logging; +import jakarta.inject.Inject; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLocaleChangeEvent; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.commandtools.MVCommandManager; + +import java.util.Locale; + +@Service +public class MVLocalesListener implements CoreListener { + + private final MVCommandManager commandManager; + + @Inject + MVLocalesListener(@NotNull MVCommandManager commandManager) { + this.commandManager = commandManager; + } + + @EventHandler + void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Logging.finer(player.getName() + " joined with locale " + player.getLocale()); + commandManager.setPlayerLocale(player, serializeLocale(player.getLocale())); + } + + @EventHandler + void onLocaleChange(PlayerLocaleChangeEvent event) { + Player player = event.getPlayer(); + Logging.finer(player.getName() + " changed locale from " + player.getLocale() + " to " + event.getLocale()); + commandManager.setPlayerLocale(player, serializeLocale(event.getLocale())); + } + + private Locale serializeLocale(String locale) { + String[] split = locale.split("_"); + return split.length > 1 ? new Locale(split[0], split[1]) : new Locale(split[0]); + } +} diff --git a/src/test/java/org/mvplugins/multiverse/core/commands/BaseCommandTest.kt b/src/test/java/org/mvplugins/multiverse/core/commands/BaseCommandTest.kt index e2e1409b7..9536a168b 100644 --- a/src/test/java/org/mvplugins/multiverse/core/commands/BaseCommandTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/commands/BaseCommandTest.kt @@ -5,6 +5,7 @@ import org.bukkit.command.ConsoleCommandSender import org.bukkit.permissions.PermissionAttachment import org.mockbukkit.mockbukkit.entity.PlayerMock import org.mvplugins.multiverse.core.TestWithMockBukkit +import org.mvplugins.multiverse.core.commandtools.MVCommandManager import org.mvplugins.multiverse.core.commandtools.PluginLocales import org.mvplugins.multiverse.core.utils.message.Message import kotlin.test.BeforeTest @@ -21,8 +22,9 @@ abstract class BaseCommandTest : TestWithMockBukkit() { @BeforeTest fun setUpCommand() { - locales = serviceLocator.getActiveService(PluginLocales::class.java).takeIf { it != null } ?: run { - throw IllegalStateException("PluginLocales is not available as a service") } + val commandManager = serviceLocator.getActiveService(MVCommandManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("MVCommandManager is not available as a service") } + locales = commandManager.locales console = server.consoleSender player = server.addPlayer("benwoo1110"); diff --git a/src/test/java/org/mvplugins/multiverse/core/commandtools/LocalizationTest.kt b/src/test/java/org/mvplugins/multiverse/core/commandtools/LocalizationTest.kt index eed48b04c..39e867261 100644 --- a/src/test/java/org/mvplugins/multiverse/core/commandtools/LocalizationTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/commandtools/LocalizationTest.kt @@ -7,13 +7,16 @@ import org.bukkit.Bukkit import org.bukkit.command.CommandSender import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested +import org.mockbukkit.mockbukkit.entity.PlayerMock import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mvplugins.multiverse.core.TestWithMockBukkit +import org.mvplugins.multiverse.core.config.MVCoreConfig import org.mvplugins.multiverse.core.utils.MVCorei18n import org.mvplugins.multiverse.core.utils.message.Message import org.mvplugins.multiverse.core.utils.message.MessageReplacement.replace +import java.util.Locale import kotlin.test.* class LocalizationTest : TestWithMockBukkit() { @@ -23,8 +26,8 @@ class LocalizationTest : TestWithMockBukkit() { @BeforeTest fun setUpLocale() { - locales = assertNotNull(serviceLocator.getActiveService(PluginLocales::class.java)) commandManager = assertNotNull(serviceLocator.getActiveService(MVCommandManager::class.java)) + locales = commandManager.locales } @Nested @@ -307,4 +310,46 @@ class LocalizationTest : TestWithMockBukkit() { } } } + + @Nested + inner class LocaleConfiguration { + private lateinit var config: MVCoreConfig + private lateinit var player: PlayerMock + private lateinit var issuer: MVCommandIssuer + + @BeforeTest + fun setUp() { + config = assertNotNull(serviceLocator.getActiveService(MVCoreConfig::class.java)) + player = server.addPlayer("benji_0224") + issuer = commandManager.getCommandIssuer(player) + } + + @Test + fun `Change default locale to chinese without per player locale should get chinese message`() { + config.perPlayerLocale = false + config.defaultLocale = Locale.CHINESE + assertEquals("ab!", Message.of(MVCorei18n.GENERIC_SUCCESS, "").formatted(locales, issuer)) + } + + @Test + fun `Change default locale to chinese with per player locale should get default english message`() { + config.perPlayerLocale = true + config.defaultLocale = Locale.CHINESE + assertEquals("Success!", Message.of(MVCorei18n.GENERIC_SUCCESS, "").formatted(locales, issuer)) + } + + @Test + fun `PerPlayerLocale enabled - Player with chinese locale should get chinese message`() { + config.perPlayerLocale = true + player.setLocale(Locale.CHINESE) + assertEquals("ab!", Message.of(MVCorei18n.GENERIC_SUCCESS, "").formatted(locales, issuer)) + } + + @Test + fun `PerPlayerLocale disabled - Player with chinese locale should get default english message`() { + config.perPlayerLocale = false + player.setLocale(Locale.CHINESE) + assertEquals("Success!", Message.of(MVCorei18n.GENERIC_SUCCESS, "").formatted(locales, issuer)) + } + } } diff --git a/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt index 1dc0de8d4..dcc6d2e5a 100644 --- a/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/inject/InjectionTest.kt @@ -117,9 +117,4 @@ class InjectionTest : TestWithMockBukkit() { // Also making sure this is not loaded automatically since it's supposed to be disabled during tests assertNull(serviceLocator.getActiveService(MetricsConfigurator::class.java)) } - - @Test - fun `PluginLocales is available as a service`() { - assertNotNull(serviceLocator.getActiveService(PluginLocales::class.java)) - } } diff --git a/src/test/resources/multiverse-core_zh.properties b/src/test/resources/multiverse-core_zh.properties new file mode 100644 index 000000000..dc86e65bd --- /dev/null +++ b/src/test/resources/multiverse-core_zh.properties @@ -0,0 +1 @@ +mv-core.generic.success=ab!