diff --git a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
index 5c20d06e110..dbee70454e5 100644
--- a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
+++ b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
@@ -161,6 +161,59 @@ public interface GeyserApi extends GeyserApiBase {
@NonNull
CommandSource consoleCommandSource();
+ /**
+ * Gets the default locale used within Geyser
+ * @return the default locale
+ */
+ @NonNull
+ String defaultLocale();
+
+ /**
+ * Get's the translation string associated with the key from the locale specified
+ * @param locale the locale to use
+ * @param key the key of the translation
+ * @return the translated message, or the key if there is none
+ */
+ @NonNull
+ default String translationString(@NonNull String locale, @NonNull String key) {
+ return translationStringOrDefault(locale, key, key);
+ }
+
+ /**
+ * Get's the translation string associated with the key from the locale specified
+ * @param locale the locale to use
+ * @param key the key of the translation
+ * @param defaultValue the fallback value for this translation
+ * @return the translated message, or the key if there is none
+ */
+ @NonNull
+ String translationStringOrDefault(@NonNull String locale, @NonNull String key, @NonNull String defaultValue);
+
+ /**
+ * Get's the translation string associated with the key from the locale specified
+ * using the parameters specified
+ * @param locale the locale to use
+ * @param key the key of the translation
+ * @param parameters the parameters of the translation
+ * @return the translated message, or the key if there is none
+ */
+ @NonNull
+ default String translationString(@NonNull String locale, @NonNull String key, @NonNull String... parameters) {
+ return translationStringOrDefault(locale, key, key, parameters);
+ }
+
+ /**
+ * Get's the translation string associated with the key from the locale specified
+ * using the parameters specified
+ * @param locale the locale to use
+ * @param key the key of the translation
+ * @param defaultValue the fallback value for this translation
+ * @param parameters the parameters of the translation
+ * @return the translated message, or the key if there is none
+ */
+ @NonNull
+ String translationStringOrDefault(@NonNull String locale, @NonNull String key, @NonNull String defaultValue, @NonNull String... parameters);
+
/**
* Gets the current {@link GeyserApiBase} instance.
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
index c1453f5798d..13ce785be80 100644
--- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
+++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
@@ -27,6 +27,7 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.connection.GeyserConnection;
import java.util.UUID;
@@ -61,6 +62,44 @@ default void sendMessage(String[] messages) {
}
}
+ /**
+ * Translates the given message using the key and source's locale then sends the message
+ * @param key the translation key
+ */
+ default void sendTranslatedMessage(String key) {
+ sendMessage(GeyserApi.api().getTranslationString(locale(), key));
+ }
+
+ /**
+ * Translates the given message using the key and source's locale then sends the message
+ * @param key the translation key
+ * @param defaultValue the fallback value if the translation does not exist
+ */
+ default void sendTranslatedOrDefaultMessage(String key, String defaultValue) {
+ sendMessage(GeyserApi.api().getTranslationStringOrDefault(locale(), key, defaultValue));
+ }
+
+ /**
+ * Translates the given message using the key and source's locale then sends the message
+ * using the provided parameters
+ * @param key the translation key
+ * @param parameters the parameters for the translation
+ */
+ default void sendTranslatedMessage(String key, String... parameters) {
+ sendMessage(GeyserApi.api().getTranslationString(locale(), key, parameters));
+ }
+
+ /**
+ * Translates the given message using the key and source's locale then sends the message
+ * using the provided parameters
+ * @param key the translation key
+ * @param defaultValue the fallback value if the translation does not exist
+ * @param parameters the parameters for the translation
+ */
+ default void sendTranslatedOrDefaultMessage(String key, String defaultValue, String... parameters) {
+ sendMessage(GeyserApi.api().getTranslationStringOrDefault(locale(), key, defaultValue, parameters));
+ }
+
/**
* If this source is the console.
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomTranslationsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomTranslationsEvent.java
new file mode 100644
index 00000000000..5cdbf137053
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomTranslationsEvent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * 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.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.event.lifecycle;
+
+import org.geysermc.event.Event;
+import org.geysermc.geyser.api.language.LanguageProvider;
+
+/**
+ * Called when {@link LanguageProvider}s are to be registered within Geyser
+ *
+ * This event allows you to register a {@link LanguageProvider} to Geyser in
+ * order to provide Geyser with translation strings.
+ */
+public interface GeyserDefineCustomTranslationsEvent extends Event {
+
+ /**
+ * Registers the given {@link LanguageProvider} to Geyser.
+ * @param languageProvider the language provider to register
+ */
+ void register(LanguageProvider languageProvider);
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/language/LanguageProvider.java b/api/src/main/java/org/geysermc/geyser/api/language/LanguageProvider.java
new file mode 100644
index 00000000000..33f4c1bb16c
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/language/LanguageProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * 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.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.language;
+
+public interface LanguageProvider {
+ /**
+ * Loads locale data in the provided {@link LocaleManager}
+ * @param localeManager The locale manager to load data into
+ */
+ void loadLocale(LocaleManager localeManager);
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/language/LocaleManager.java b/api/src/main/java/org/geysermc/geyser/api/language/LocaleManager.java
new file mode 100644
index 00000000000..aec682e15dd
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/language/LocaleManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * 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.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.language;
+
+import java.util.Map;
+
+public interface LocaleManager {
+ /**
+ * Get the locale code for this locale manager
+ * @return the locale code
+ */
+ String localeCode();
+
+ /**
+ * Register a translation string
+ * @param key the key of the translation string
+ * @param value the value of the translation string
+ */
+ void registerTranslationString(String key, String value);
+
+ /**
+ * Checks whether or not a translation key has already been registered
+ * @param key the key of the translation string
+ * @return whether a translation with that key exists
+ */
+ boolean hasTranslationString(String key);
+
+ /**
+ * Gets all translations for this locale, the returned map is immutable
+ * @return All translations for this locale
+ */
+ Map translationStrings();
+}
diff --git a/build-logic/.kotlin/sessions/kotlin-compiler-10364697687613482546.salive b/build-logic/.kotlin/sessions/kotlin-compiler-10364697687613482546.salive
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
index 86d80ee75c8..83102bec6fa 100644
--- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
+++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
@@ -54,12 +54,14 @@
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.event.EventRegistrar;
+import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomTranslationsEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPreReloadEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
+import org.geysermc.geyser.api.language.LanguageProvider;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
@@ -73,6 +75,7 @@
import org.geysermc.geyser.event.type.SessionDisconnectEventImpl;
import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.impl.MinecraftVersionImpl;
+import org.geysermc.geyser.language.LanguageManager;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol;
@@ -100,6 +103,7 @@
import org.geysermc.geyser.util.NewsHandler;
import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils;
+import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileWriter;
@@ -152,6 +156,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
private static final Pattern IP_REGEX = Pattern.compile("\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b");
private final SessionManager sessionManager = new SessionManager();
+ private final LanguageManager languageManager = new LanguageManager();
private FloodgateCipher cipher;
private FloodgateSkinUploader skinUploader;
@@ -251,6 +256,9 @@ public void initialize() {
EntityDefinitions.init();
MessageTranslator.init();
+ // Register LanguageProviders before loading any Locale
+ eventBus.fire((GeyserDefineCustomTranslationsEvent) languageManager::registerLanguageProvider);
+
// Download the latest asset list and cache it
AssetUtils.generateAssetCache().whenComplete((aVoid, ex) -> {
if (ex != null) {
@@ -817,6 +825,30 @@ public PlatformType platformType() {
return getLogger();
}
+ @Override
+ public @NonNull String defaultLocale() {
+ return GeyserLocale.getDefaultLocale();
+ }
+
+ @Override
+ public @NonNull String translationStringOrDefault(@NonNull String locale, @NonNull String key, @NonNull String defaultValue) {
+ String translation = MinecraftLocale.getLocaleStringIfPresent(key, locale);
+ if (translation == null) translation = defaultValue;
+
+ return translation;
+ }
+
+ @Override
+ public @NonNull String translationStringOrDefault(@NonNull String locale, @NonNull String key, @NonNull String defaultValue, @NotNull @NonNull String... parameters) {
+ String translation = translationStringOrDefault(locale, key, defaultValue);
+ int order = 0;
+ for (String parameter : parameters) {
+ translation = translation.replaceFirst("%s", parameter).replace("%" + order + "$s", parameter);
+ }
+
+ return translation;
+ }
+
public int buildNumber() {
if (!this.isProductionEnvironment()) {
return 0;
diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
index 2be6d2f8fba..851be8214c0 100644
--- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
+++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
@@ -41,12 +41,18 @@
import org.geysermc.geyser.api.extension.ExtensionManager;
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
+import org.geysermc.geyser.api.language.LanguageProvider;
+import org.geysermc.geyser.api.language.LocaleManager;
import org.geysermc.geyser.extension.event.GeyserExtensionEventBus;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.ThrowingBiConsumer;
+import java.io.File;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.Reader;
+import java.net.URL;
+import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -59,10 +65,14 @@
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@@ -123,7 +133,52 @@ public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescript
private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder, ExtensionEventBus eventBus) {
GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.id());
- return new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus);
+ GeyserExtensionContainer container = new GeyserExtensionContainer(extension, dataFolder, description, this, logger, eventBus);
+
+ try {
+ for (URL url : ((URLClassLoader) extension.getClass().getClassLoader()).getURLs()) {
+ File file = new File(url.toURI());
+ JarFile jarFile = new JarFile(file);
+ JarEntry languagesEntry = jarFile.getJarEntry("languages/");
+
+ if (languagesEntry != null && languagesEntry.isDirectory()) {
+ Map> translationStrings = new HashMap<>();
+
+ for (JarEntry entry : jarFile.stream().toList()) {
+ if (entry.getName().startsWith("languages/") && entry.getName().endsWith(".properties")) {
+ String localeCode = entry.getName().substring(entry.getName().indexOf('/') + 1, entry.getName().length() - 11).toLowerCase(Locale.ROOT);
+
+ Properties properties = new Properties();
+ properties.load(jarFile.getInputStream(entry));
+
+ if (!properties.isEmpty())
+ translationStrings.put(localeCode, new HashMap<>(properties));
+ }
+ }
+
+ eventBus.subscribe(GeyserDefineCustomTranslationsEvent.class, event -> {
+ event.register(localeManager -> {
+ String localeCode = localeManager.getLocaleCode().toLowerCase(Locale.ROOT);
+ if (translationStrings.containsKey(localeCode)) {
+ translationStrings.get(localeCode).forEach(
+ (key, value) ->
+ localeManager.registerTranslationString(
+ key.toString(),
+ value.toString()
+ )
+ );
+ }
+ });
+ });
+ }
+
+ jarFile.close();
+ }
+ } catch (Exception e) {
+ GeyserImpl.getInstance().getLogger().error("Unable to load locale files from extension.", e);
+ }
+
+ return container;
}
public GeyserExtensionDescription extensionDescription(Path path) throws InvalidDescriptionException {
diff --git a/core/src/main/java/org/geysermc/geyser/language/LanguageManager.java b/core/src/main/java/org/geysermc/geyser/language/LanguageManager.java
new file mode 100644
index 00000000000..82a83b89bf7
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/language/LanguageManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * 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.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.language;
+
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.language.LanguageProvider;
+import org.geysermc.geyser.api.language.LocaleManager;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public final class LanguageManager {
+ private final Set languageProviders = new HashSet<>();
+
+ /**
+ * Registers a {@link LanguageProvider}, should only be done before Minecraft locales are loaded!
+ * @param provider the {@link LanguageProvider} to register
+ * @return if the {@link LanguageProvider} was registered successfully
+ */
+ public boolean registerLanguageProvider(LanguageProvider provider) {
+ return languageProviders.add(provider);
+ }
+
+ /**
+ * Load locales into the given {@link LocaleManager}
+ * @param localeManager the {@link LocaleManager}
+ */
+ public void registerTranslationStrings(LocaleManager localeManager) {
+ languageProviders.forEach(provider -> provider.loadLocale(localeManager));
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java
index 74ab1e7e6af..cd9682f0c14 100644
--- a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java
+++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java
@@ -28,6 +28,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.language.LocaleManager;
import org.geysermc.geyser.util.AssetUtils;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.WebUtils;
@@ -38,6 +39,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
@@ -175,6 +177,29 @@ private static boolean loadLocale(String locale) {
langMap.putAll(parseLangFile(localeOverride, lowercaseLocale));
}
+ // Load locales from extensions (the ultimate override)
+ GeyserImpl.getInstance().getLanguageManager().registerTranslationStrings(new LocaleManager() {
+ @Override
+ public String localeCode() {
+ return locale;
+ }
+
+ @Override
+ public void registerTranslationString(String key, String value) {
+ langMap.put(key, value);
+ }
+
+ @Override
+ public boolean hasTranslationString(String key) {
+ return langMap.containsKey(key);
+ }
+
+ @Override
+ public Map translationStrings() {
+ return Collections.unmodifiableMap(langMap);
+ }
+ });
+
if (!langMap.isEmpty()) {
LOCALE_MAPPINGS.put(lowercaseLocale, langMap);
return true;