diff --git a/libraries/localization/README.md b/libraries/localization/README.md new file mode 100644 index 00000000..93372f5e --- /dev/null +++ b/libraries/localization/README.md @@ -0,0 +1,31 @@ +# Localization API + +The Localization API provides a consistent access point for localized text, and adds support for localization in versions that do not natively do so. + +## Localizing Text + +The `L10n` class provides utilities for localizing text. + +```java +# localizing a translation key +String localizedText = L10n.get("example.translationKey"); + +# localizing a translation key with formatting arguments +String localizedTextWithArgs = L10n.get("example.translationKey", "Some Arg", 1); + +# localizing a translation key with a default value in case no localization exists +String localizedTextOrDefault = L10n.getOrDefault("example.translationKey", "Example Text"); + +# checking whether a localization for a translation key exists +boolean localizedTextExists = L10n.has("example.translationKey"); +``` + +## Providing Translations + +Translation files can be added to your mod's resources and will be loaded automatically. While Minecraft natively only supports `.lang` files in versions 18w01a and below, and only `.json` files in 18w02a and above, the Localization API ensures both `.lang` and `.json` files are supported in any Minecraft version. + +The Localization API also adds support for all-lowercase translation file names in 1.10.2 and below (e.g. `en_us.lang` instead of `en_US.lang`). + +## Localization in Minecraft Alpha + +Minecraft started localizing text elements in Minecraft Beta. The Localization API adds support for localization in Minecraft Alpha. Do note that no in-game text is modified in any version of the game, making this mostly a feature for modders to take advantage of. diff --git a/libraries/localization/build.gradle b/libraries/localization/build.gradle new file mode 100644 index 00000000..7bb43400 --- /dev/null +++ b/libraries/localization/build.gradle @@ -0,0 +1,5 @@ +setUpLibrary(project) + +dependencies { + implementation 'com.google.code.gson:gson:2.8.0' +} diff --git a/libraries/localization/gradle.properties b/libraries/localization/gradle.properties new file mode 100644 index 00000000..073388fa --- /dev/null +++ b/libraries/localization/gradle.properties @@ -0,0 +1,6 @@ +library_id = localization +library_name = Localization +library_description = Localization API and events. +library_version = 0.1.0-alpha.1 + +osl_dependencies = core:>=0.7.0,entrypoints:>=0.5.0,executors:>=0.1.0,text-components:>=0.1.0-,resource-loader:>=0.7.0- diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/build.gradle b/libraries/localization/localization-mc11w49a-mc1.5.2/build.gradle new file mode 100644 index 00000000..0a350fdb --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/build.gradle @@ -0,0 +1 @@ +setUpModule(project) diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/gradle.properties b/libraries/localization/localization-mc11w49a-mc1.5.2/gradle.properties new file mode 100644 index 00000000..b79368f9 --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/gradle.properties @@ -0,0 +1,8 @@ +min_mc_version = 11w49a +max_mc_version = 1.5.2 +minecraft_dependency = >=1.1-alpha.11.49.a <=1.5.2 + +minecraft_version = 1.5.2 +client_nests_build = 6 + +entrypoint_client_init = net.ornithemc.osl.localization.impl.LanguageReloader::init diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java new file mode 100644 index 00000000..3832d392 --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java @@ -0,0 +1,22 @@ +package net.ornithemc.osl.localization.impl; + +import net.ornithemc.osl.resource.loader.api.client.ClientResourceLoaderEvents; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; +import net.ornithemc.osl.resource.loader.api.resource.reload.ResourceReloadListener; + +public class LanguageReloader implements ResourceReloadListener { + + public static void init() { + ClientResourceLoaderEvents.INIT_RESOURCE_MANAGER.register(resourceManager -> { + resourceManager.addReloader(new LanguageReloader()); + }); + ClientResourceLoaderEvents.START_RESOURCE_RELOAD.register((resourceManager, context) -> { + Localization.getLanguageManager().reload(context.resourcePacks()); + }); + } + + @Override + public void resourcesReloaded(ResourceManager resourceManager) { + Localization.getLanguageManager().reloadLocale(resourceManager); + } +} diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageAccess.java b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageAccess.java new file mode 100644 index 00000000..5a103f5d --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageAccess.java @@ -0,0 +1,16 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.util.Properties; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.locale.Language; + +@Mixin(Language.class) +public interface LanguageAccess { + + @Accessor("translations") + Properties accessTranslations(); + +} diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java new file mode 100644 index 00000000..129e47f6 --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java @@ -0,0 +1,111 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.util.Properties; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +import net.minecraft.locale.Language; + +import net.ornithemc.osl.localization.impl.Localization; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; + +@Mixin(Language.class) +public class LanguageMixin { + + @Shadow + private static Language INSTANCE; + + @Shadow + private Properties translations; + @Shadow + private String currentCode; + + @Inject( + method = "", + at = @At( + value = "TAIL" + ) + ) + private static void osl$localization$initLocale(CallbackInfo ci) { + Localization.getLocale().wrap(((LanguageAccess) INSTANCE).accessTranslations()); + } + + @Inject( + method = "load", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$reloadLanguageManager(CallbackInfo ci) { + // each ServerPlayerEntity also holds an instance of this class + if ((Language) (Object) this == INSTANCE) { + Localization.reloadLanguageManager(); + } + } + + @Inject( + method = "loadLanguage", + at = @At( + value = "HEAD" + ) + ) + private void osl$localization$setLanguage(CallbackInfo ci) { + // each ServerPlayerEntity also holds an instance of this class + if ((Language) (Object) this == INSTANCE) { + Localization.getLanguageManager().setSelectedLanguage(this.currentCode); + } + } + + @Inject( + method = "loadLanguage", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/locale/Language;loadTranslations(Ljava/util/Properties;Ljava/lang/String;)V" + ) + ) + private void osl$localization$wrapTranslations(CallbackInfo ci, @Local String language, @Local Properties translations) { + // each ServerPlayerEntity also holds an instance of this class + if ((Language) (Object) this == INSTANCE) { + // the translations map is replaced with each reload + Localization.getLocale().wrap(translations); + } + } + + @WrapOperation( + method = "loadLanguage", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/locale/Language;loadTranslations(Ljava/util/Properties;Ljava/lang/String;)V" + ) + ) + private void osl$localization$loadExtraTranslations(Language self, Properties translations, String language, Operation op) { + // each ServerPlayerEntity also holds an instance of this class + if ((Language) (Object) this == INSTANCE) { + Localization.getLocale().loadLanguage(ResourceManager.client(), language); + } + + // no need to run the original operation + // op.call(self, translations, language); + } + + @Inject( + method = "loadLanguage", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$localeReloaded(CallbackInfo ci) { + // each ServerPlayerEntity also holds an instance of this class + if ((Language) (Object) this == INSTANCE) { + Localization.getLocale().setLastUpdateTime(); + } + } +} diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/resources/fabric.mod.json b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..b5173408 --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "osl-localization", + "version": "0.1.0-alpha.1+mc11w49a-mc1.5.2", + "environment": "*", + "entrypoints": { + "client-init": [ + "net.ornithemc.osl.localization.impl.LanguageReloader::init" + ] + }, + "mixins": [ + "osl.localization.mixins.json" + ], + "depends": { + "fabricloader": "\u003e\u003d0.18.0", + "minecraft": "\u003e\u003d1.1-alpha.11.49.a \u003c\u003d1.5.2", + "osl-core": "\u003e\u003d0.7.0", + "osl-entrypoints": "\u003e\u003d0.5.0", + "osl-executors": "\u003e\u003d0.1.0", + "osl-text-components": "\u003e\u003d0.1.0-", + "osl-resource-loader": "\u003e\u003d0.7.0-" + }, + "name": "OSL Localization", + "description": "Localization API and events.", + "authors": [ + "OrnitheMC" + ], + "contact": { + "homepage": "https://ornithemc.net/", + "issues": "https://github.com/OrnitheMC/ornithe-standard-libraries/issues", + "sources": "https://github.com/OrnitheMC/ornithe-standard-libraries" + }, + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/resources/osl.localization.mixins.json b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/resources/osl.localization.mixins.json new file mode 100644 index 00000000..35f377fb --- /dev/null +++ b/libraries/localization/localization-mc11w49a-mc1.5.2/src/main/resources/osl.localization.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.ornithemc.osl.localization.impl.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "client.LanguageAccess", + "client.LanguageMixin" + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/build.gradle b/libraries/localization/localization-mc13w26a-mc18w01a/build.gradle new file mode 100644 index 00000000..0a350fdb --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/build.gradle @@ -0,0 +1 @@ +setUpModule(project) diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/gradle.properties b/libraries/localization/localization-mc13w26a-mc18w01a/gradle.properties new file mode 100644 index 00000000..175faf2d --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/gradle.properties @@ -0,0 +1,8 @@ +min_mc_version = 13w26a +max_mc_version = 18w01a +minecraft_dependency = >=1.6-alpha.13.26.a <=1.13-alpha.18.1.a + +minecraft_version = 1.8.2-pre4 +raven_build = 1 +sparrow_build = 1 +nests_build = 3 diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/access/SimpleResourceAccess.java b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/access/SimpleResourceAccess.java new file mode 100644 index 00000000..716de1f9 --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/access/SimpleResourceAccess.java @@ -0,0 +1,9 @@ +package net.ornithemc.osl.localization.impl.access; + +import net.ornithemc.osl.core.api.util.NamespacedIdentifier; + +public interface SimpleResourceAccess { + + NamespacedIdentifier osl$localization$getLocation(); + +} diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageManagerMixin.java b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageManagerMixin.java new file mode 100644 index 00000000..6c87eab9 --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageManagerMixin.java @@ -0,0 +1,44 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.resource.language.LanguageManager; + +import net.ornithemc.osl.localization.api.language.Language; +import net.ornithemc.osl.localization.impl.Localization; + +@Mixin(LanguageManager.class) +public class LanguageManagerMixin { + + @Shadow + private Map languages; + + @Shadow + private String currentCode; + + @Inject( + method = "", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$initLanguageManager(CallbackInfo ci) { + Localization.getLanguageManager().wrap(this.languages, net.minecraft.client.resource.language.Language::new); + } + + @Inject( + method = "setLanguage", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$setLanguage(CallbackInfo ci) { + Localization.getLanguageManager().setSelectedLanguage(this.currentCode); + } +} diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java new file mode 100644 index 00000000..08c38f07 --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java @@ -0,0 +1,39 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.ornithemc.osl.localization.api.language.Language; + +@Mixin(net.minecraft.client.resource.language.Language.class) +public class LanguageMixin implements Language { + + @Shadow + private String code; + @Shadow + private String region; + @Shadow + private String name; + @Shadow + private boolean bidirectional; + + @Override + public String code() { + return this.code; + } + + @Override + public String name() { + return this.name; + } + + @Override + public String region() { + return this.region; + } + + @Override + public boolean bidirectional() { + return this.bidirectional; + } +} diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LocaleMixin.java b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LocaleMixin.java new file mode 100644 index 00000000..a5dad52f --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LocaleMixin.java @@ -0,0 +1,122 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +import net.minecraft.client.resource.Resource; +import net.minecraft.client.resource.language.Locale; +import net.minecraft.client.resource.manager.ResourceManager; +import net.minecraft.resource.Identifier; + +import net.ornithemc.osl.core.api.util.NamespacedIdentifier; +import net.ornithemc.osl.localization.impl.Localization; +import net.ornithemc.osl.resource.loader.impl.resource.ResourceLocationAccess; +import net.ornithemc.osl.resource.loader.impl.resource.pack.ResourcePacks; + +@Mixin(Locale.class) +public class LocaleMixin { + + @Shadow + private Map translations; + + @Shadow + private void load(List resources) { } + + @Inject( + method = "", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$initLocale(CallbackInfo ci) { + Localization.getLocale().wrap(this.translations); + } + + @Inject( + method = "load(Lnet/minecraft/client/resource/manager/ResourceManager;Ljava/util/List;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/resource/language/Locale;load(Ljava/util/List;)V", + shift = Shift.AFTER // ensure consistent loading order (first .lang, then .json) + ) + ) + private void osl$localization$loadExtraTranslations(ResourceManager resourceManager, List languageCodes, CallbackInfo ci, + @Local(ordinal = 0) String languageCode, @Local(ordinal = 2) String namespace) { + String pathFormat = "lang/%s%s"; + String[] paths; + + if (ResourcePacks.getSupportedFormat() < 3) { + paths = new String[] { + String.format(pathFormat, languageCode, ".json"), + String.format(pathFormat, languageCode.toLowerCase(java.util.Locale.ROOT), ".lang"), + String.format(pathFormat, languageCode.toLowerCase(java.util.Locale.ROOT), ".json") + }; + } else { + paths = new String[] { + String.format(pathFormat, languageCode, ".json") + }; + } + + + for (String path : paths) { + try { + load(resourceManager.getResources(new Identifier(namespace, path))); + } catch (IOException ignored) { + } + } + } + + @Inject( + method = "load(Lnet/minecraft/client/resource/manager/ResourceManager;Ljava/util/List;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/resource/manager/ResourceManager;getResources(Lnet/minecraft/resource/Identifier;)Ljava/util/List;" + ) + ) + private void osl$localization$localeReloaded(CallbackInfo ci) { + Localization.getLocale().setLastUpdateTime(); + } + + @WrapOperation( + method = "load(Ljava/util/List;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/resource/language/Locale;load(Ljava/io/InputStream;)V" + ) + ) + private void osl$localization$loadExtraTranslations(Locale instance, InputStream is, Operation original, @Local Resource resource) throws IOException{ + NamespacedIdentifier location = this.resourceLocation(resource); + + if (location.identifier().endsWith(".lang")){ + Localization.getLocale().loadFromLang(is); + } else if (location.identifier().endsWith(".json")) { + Localization.getLocale().loadFromJson(is); + } else { + original.call(instance, is); + } + } + + @Unique + private NamespacedIdentifier resourceLocation(Resource resource) throws IOException { + // in 14w21b and earlier, there is no accessor method in Vanilla! + if (resource instanceof ResourceLocationAccess) { + return ((ResourceLocationAccess) resource).resourceLocation(); + } else { + return resource.getLocation(); + } + } +} diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/fabric.mod.json b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..93886d30 --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "osl-localization", + "version": "0.1.0-alpha.1+mc13w26a-mc18w01a", + "environment": "*", + "mixins": [ + "osl.localization.mixins.json" + ], + "accessWidener": "osl.localization.classtweaker", + "depends": { + "fabricloader": "\u003e\u003d0.18.0", + "minecraft": "\u003e\u003d1.6-alpha.13.26.a \u003c\u003d1.13-alpha.18.1.a", + "osl-core": "\u003e\u003d0.7.0", + "osl-entrypoints": "\u003e\u003d0.5.0", + "osl-executors": "\u003e\u003d0.1.0", + "osl-text-components": "\u003e\u003d0.1.0-", + "osl-resource-loader": "\u003e\u003d0.7.0-" + }, + "name": "OSL Localization", + "description": "Localization API and events.", + "authors": [ + "OrnitheMC" + ], + "contact": { + "homepage": "https://ornithemc.net/", + "issues": "https://github.com/OrnitheMC/ornithe-standard-libraries/issues", + "sources": "https://github.com/OrnitheMC/ornithe-standard-libraries" + }, + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/osl.localization.classtweaker b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/osl.localization.classtweaker new file mode 100644 index 00000000..c90e8a5d --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/osl.localization.classtweaker @@ -0,0 +1,3 @@ +classTweaker v1 named + +inject-interface net/minecraft/client/resource/language/Language net/ornithemc/osl/localization/api/language/Language \ No newline at end of file diff --git a/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/osl.localization.mixins.json b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/osl.localization.mixins.json new file mode 100644 index 00000000..219f3ccb --- /dev/null +++ b/libraries/localization/localization-mc13w26a-mc18w01a/src/main/resources/osl.localization.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.ornithemc.osl.localization.impl.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "client.LanguageManagerMixin", + "client.LanguageMixin", + "client.LocaleMixin" + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/build.gradle b/libraries/localization/localization-mc18w02a-mc1.14.4/build.gradle new file mode 100644 index 00000000..0a350fdb --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/build.gradle @@ -0,0 +1 @@ +setUpModule(project) diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/gradle.properties b/libraries/localization/localization-mc18w02a-mc1.14.4/gradle.properties new file mode 100644 index 00000000..09201156 --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/gradle.properties @@ -0,0 +1,5 @@ +min_mc_version = 18w02a +max_mc_version = 1.14.4 +minecraft_dependency = >=1.13-alpha.18.2.a <=1.14.4 + +minecraft_version = 1.14.4 diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageManagerMixin.java b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageManagerMixin.java new file mode 100644 index 00000000..6c87eab9 --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageManagerMixin.java @@ -0,0 +1,44 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.resource.language.LanguageManager; + +import net.ornithemc.osl.localization.api.language.Language; +import net.ornithemc.osl.localization.impl.Localization; + +@Mixin(LanguageManager.class) +public class LanguageManagerMixin { + + @Shadow + private Map languages; + + @Shadow + private String currentCode; + + @Inject( + method = "", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$initLanguageManager(CallbackInfo ci) { + Localization.getLanguageManager().wrap(this.languages, net.minecraft.client.resource.language.Language::new); + } + + @Inject( + method = "setLanguage", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$setLanguage(CallbackInfo ci) { + Localization.getLanguageManager().setSelectedLanguage(this.currentCode); + } +} diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java new file mode 100644 index 00000000..08c38f07 --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java @@ -0,0 +1,39 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.ornithemc.osl.localization.api.language.Language; + +@Mixin(net.minecraft.client.resource.language.Language.class) +public class LanguageMixin implements Language { + + @Shadow + private String code; + @Shadow + private String region; + @Shadow + private String name; + @Shadow + private boolean bidirectional; + + @Override + public String code() { + return this.code; + } + + @Override + public String name() { + return this.name; + } + + @Override + public String region() { + return this.region; + } + + @Override + public boolean bidirectional() { + return this.bidirectional; + } +} diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LocaleMixin.java b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LocaleMixin.java new file mode 100644 index 00000000..8d66e8a0 --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LocaleMixin.java @@ -0,0 +1,92 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +import net.minecraft.client.resource.language.Locale; +import net.minecraft.resource.Identifier; +import net.minecraft.resource.Resource; +import net.minecraft.resource.manager.ResourceManager; + +import net.ornithemc.osl.localization.impl.Localization; + +@Mixin(Locale.class) +public class LocaleMixin { + + @Final + @Shadow + private Map translations; + + @Shadow + private void load(List resources) { } + + @Inject( + method = "", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$initLocale(CallbackInfo ci) { + // ensure OSL's Locale is backed by the same map + // this way changes to one are reflected in the other + Localization.getLocale().wrap(this.translations); + } + + @Inject( + method = "load(Lnet/minecraft/resource/manager/ResourceManager;Ljava/util/List;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/resource/manager/ResourceManager;getResources(Lnet/minecraft/resource/Identifier;)Ljava/util/List;" + ) + ) + private void osl$localization$loadExtraTranslations(ResourceManager resourceManager, List languageCodes, CallbackInfo ci, + @Local(ordinal = 0) String languageCode, @Local(ordinal = 2) String namespace) { + String path = String.format("lang/%s.lang", languageCode); + + try { + load(resourceManager.getResources(new Identifier(namespace, path))); + } catch (IOException ignored) { + } + } + + @Inject( + method = "load(Lnet/minecraft/resource/manager/ResourceManager;Ljava/util/List;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/resource/manager/ResourceManager;getResources(Lnet/minecraft/resource/Identifier;)Ljava/util/List;" + ) + ) + private void osl$localization$localeReloaded(CallbackInfo ci) { + Localization.getLocale().setLastUpdateTime(); + } + + @WrapOperation( + method = "load(Ljava/util/List;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/resource/language/Locale;load(Ljava/io/InputStream;)V" + ) + ) + private void osl$localization$loadExtraTranslations(Locale instance, InputStream is, Operation original, @Local Resource resource) throws IOException{ + if (resource.getLocation().getPath().endsWith(".lang")){ + Localization.getLocale().loadFromLang(is); + } else if (resource.getLocation().getPath().endsWith(".json")) { + Localization.getLocale().loadFromJson(is); + } else { + original.call(instance, is); + } + } +} diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/fabric.mod.json b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..7b0f8dad --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "osl-localization", + "version": "0.1.0-alpha.1+mc18w02a-mc1.14.4", + "environment": "*", + "mixins": [ + "osl.localization.mixins.json" + ], + "accessWidener": "osl.localization.classtweaker", + "depends": { + "fabricloader": "\u003e\u003d0.18.0", + "minecraft": "\u003e\u003d1.13-alpha.18.2.a \u003c\u003d1.14.4", + "osl-core": "\u003e\u003d0.7.0", + "osl-entrypoints": "\u003e\u003d0.5.0", + "osl-executors": "\u003e\u003d0.1.0", + "osl-text-components": "\u003e\u003d0.1.0-", + "osl-resource-loader": "\u003e\u003d0.7.0-" + }, + "name": "OSL Localization", + "description": "Localization API and events.", + "authors": [ + "OrnitheMC" + ], + "contact": { + "homepage": "https://ornithemc.net/", + "issues": "https://github.com/OrnitheMC/ornithe-standard-libraries/issues", + "sources": "https://github.com/OrnitheMC/ornithe-standard-libraries" + }, + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/osl.localization.classtweaker b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/osl.localization.classtweaker new file mode 100644 index 00000000..c90e8a5d --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/osl.localization.classtweaker @@ -0,0 +1,3 @@ +classTweaker v1 named + +inject-interface net/minecraft/client/resource/language/Language net/ornithemc/osl/localization/api/language/Language \ No newline at end of file diff --git a/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/osl.localization.mixins.json b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/osl.localization.mixins.json new file mode 100644 index 00000000..219f3ccb --- /dev/null +++ b/libraries/localization/localization-mc18w02a-mc1.14.4/src/main/resources/osl.localization.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.ornithemc.osl.localization.impl.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "client.LanguageManagerMixin", + "client.LanguageMixin", + "client.LocaleMixin" + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/libraries/localization/localization-mca1.0.1_01-mca1.2.6/build.gradle b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/build.gradle new file mode 100644 index 00000000..0a350fdb --- /dev/null +++ b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/build.gradle @@ -0,0 +1 @@ +setUpModule(project) diff --git a/libraries/localization/localization-mca1.0.1_01-mca1.2.6/gradle.properties b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/gradle.properties new file mode 100644 index 00000000..54fa790d --- /dev/null +++ b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/gradle.properties @@ -0,0 +1,11 @@ +environment = client +min_mc_version = a1.0.1_01 +max_mc_version = a1.2.6 +minecraft_dependency = >=1.0.0-alpha.0.1 <=1.0.0-alpha.2.6 + +minecraft_version = a1.2.6 +raven_build = 2 +sparrow_build = 2 +nests_build = 6 + +entrypoint_client_init = net.ornithemc.osl.localization.impl.LanguageReloader::init diff --git a/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java new file mode 100644 index 00000000..3832d392 --- /dev/null +++ b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java @@ -0,0 +1,22 @@ +package net.ornithemc.osl.localization.impl; + +import net.ornithemc.osl.resource.loader.api.client.ClientResourceLoaderEvents; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; +import net.ornithemc.osl.resource.loader.api.resource.reload.ResourceReloadListener; + +public class LanguageReloader implements ResourceReloadListener { + + public static void init() { + ClientResourceLoaderEvents.INIT_RESOURCE_MANAGER.register(resourceManager -> { + resourceManager.addReloader(new LanguageReloader()); + }); + ClientResourceLoaderEvents.START_RESOURCE_RELOAD.register((resourceManager, context) -> { + Localization.getLanguageManager().reload(context.resourcePacks()); + }); + } + + @Override + public void resourcesReloaded(ResourceManager resourceManager) { + Localization.getLanguageManager().reloadLocale(resourceManager); + } +} diff --git a/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/resources/fabric.mod.json b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..444b1e5d --- /dev/null +++ b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "osl-localization", + "version": "0.1.0-alpha.1+mca1.0.1_01-mca1.2.6", + "environment": "client", + "entrypoints": { + "client-init": [ + "net.ornithemc.osl.localization.impl.LanguageReloader::init" + ] + }, + "mixins": [ + "osl.localization.mixins.json" + ], + "depends": { + "fabricloader": "\u003e\u003d0.18.0", + "minecraft": "\u003e\u003d1.0.0-alpha.0.1 \u003c\u003d1.0.0-alpha.2.6", + "osl-core": "\u003e\u003d0.7.0", + "osl-entrypoints": "\u003e\u003d0.5.0", + "osl-executors": "\u003e\u003d0.1.0", + "osl-text-components": "\u003e\u003d0.1.0-", + "osl-resource-loader": "\u003e\u003d0.7.0-" + }, + "name": "OSL Localization", + "description": "Localization API and events.", + "authors": [ + "OrnitheMC" + ], + "contact": { + "homepage": "https://ornithemc.net/", + "issues": "https://github.com/OrnitheMC/ornithe-standard-libraries/issues", + "sources": "https://github.com/OrnitheMC/ornithe-standard-libraries" + }, + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/resources/osl.localization.mixins.json b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/resources/osl.localization.mixins.json new file mode 100644 index 00000000..cc541f37 --- /dev/null +++ b/libraries/localization/localization-mca1.0.1_01-mca1.2.6/src/main/resources/osl.localization.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.ornithemc.osl.localization.impl.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/libraries/localization/localization-mcb1.0-mc11w48a/build.gradle b/libraries/localization/localization-mcb1.0-mc11w48a/build.gradle new file mode 100644 index 00000000..0a350fdb --- /dev/null +++ b/libraries/localization/localization-mcb1.0-mc11w48a/build.gradle @@ -0,0 +1 @@ +setUpModule(project) diff --git a/libraries/localization/localization-mcb1.0-mc11w48a/gradle.properties b/libraries/localization/localization-mcb1.0-mc11w48a/gradle.properties new file mode 100644 index 00000000..0d746550 --- /dev/null +++ b/libraries/localization/localization-mcb1.0-mc11w48a/gradle.properties @@ -0,0 +1,8 @@ +min_mc_version = b1.0 +max_mc_version = 11w48a +minecraft_dependency = >=1.0.0-beta.0 <=1.1-alpha.11.48.a + +minecraft_version = 11w48a +client_nests_build = 11 + +entrypoint_client_init = net.ornithemc.osl.localization.impl.LanguageReloader::init diff --git a/libraries/localization/localization-mcb1.0-mc11w48a/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java new file mode 100644 index 00000000..3832d392 --- /dev/null +++ b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/java/net/ornithemc/osl/localization/impl/LanguageReloader.java @@ -0,0 +1,22 @@ +package net.ornithemc.osl.localization.impl; + +import net.ornithemc.osl.resource.loader.api.client.ClientResourceLoaderEvents; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; +import net.ornithemc.osl.resource.loader.api.resource.reload.ResourceReloadListener; + +public class LanguageReloader implements ResourceReloadListener { + + public static void init() { + ClientResourceLoaderEvents.INIT_RESOURCE_MANAGER.register(resourceManager -> { + resourceManager.addReloader(new LanguageReloader()); + }); + ClientResourceLoaderEvents.START_RESOURCE_RELOAD.register((resourceManager, context) -> { + Localization.getLanguageManager().reload(context.resourcePacks()); + }); + } + + @Override + public void resourcesReloaded(ResourceManager resourceManager) { + Localization.getLanguageManager().reloadLocale(resourceManager); + } +} diff --git a/libraries/localization/localization-mcb1.0-mc11w48a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java new file mode 100644 index 00000000..35cd69c5 --- /dev/null +++ b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/java/net/ornithemc/osl/localization/impl/mixin/client/LanguageMixin.java @@ -0,0 +1,31 @@ +package net.ornithemc.osl.localization.impl.mixin.client; + +import java.io.IOException; +import java.util.Properties; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.locale.Language; + +import net.ornithemc.osl.localization.impl.Localization; + +@Mixin(Language.class) +public class LanguageMixin { + + @Shadow + private Properties translations; + + @Inject( + method = "", + at = @At( + value = "TAIL" + ) + ) + private void osl$localization$initLocalization(CallbackInfo ci) throws IOException { + Localization.getLocale().wrap(this.translations); + } +} diff --git a/libraries/localization/localization-mcb1.0-mc11w48a/src/main/resources/fabric.mod.json b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..767230b6 --- /dev/null +++ b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "osl-localization", + "version": "0.1.0-alpha.1+mcb1.0-mc11w48a", + "environment": "*", + "entrypoints": { + "client-init": [ + "net.ornithemc.osl.localization.impl.LanguageReloader::init" + ] + }, + "mixins": [ + "osl.localization.mixins.json" + ], + "depends": { + "fabricloader": "\u003e\u003d0.18.0", + "minecraft": "\u003e\u003d1.0.0-beta.0 \u003c\u003d1.1-alpha.11.48.a", + "osl-core": "\u003e\u003d0.7.0", + "osl-entrypoints": "\u003e\u003d0.5.0", + "osl-executors": "\u003e\u003d0.1.0", + "osl-text-components": "\u003e\u003d0.1.0-", + "osl-resource-loader": "\u003e\u003d0.7.0-" + }, + "name": "OSL Localization", + "description": "Localization API and events.", + "authors": [ + "OrnitheMC" + ], + "contact": { + "homepage": "https://ornithemc.net/", + "issues": "https://github.com/OrnitheMC/ornithe-standard-libraries/issues", + "sources": "https://github.com/OrnitheMC/ornithe-standard-libraries" + }, + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/libraries/localization/localization-mcb1.0-mc11w48a/src/main/resources/osl.localization.mixins.json b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/resources/osl.localization.mixins.json new file mode 100644 index 00000000..b75a5ba4 --- /dev/null +++ b/libraries/localization/localization-mcb1.0-mc11w48a/src/main/resources/osl.localization.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.ornithemc.osl.localization.impl.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "client.LanguageMixin" + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/L10n.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/L10n.java new file mode 100644 index 00000000..e694041e --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/L10n.java @@ -0,0 +1,47 @@ +package net.ornithemc.osl.localization.api; + +import net.ornithemc.osl.localization.api.language.Language; +import net.ornithemc.osl.localization.impl.Localization; + +/** + * Utility methods for localization. + */ +public final class L10n { + + /** + * @return the currently selected language for localization. + */ + public static Language getLanguage() { + return Localization.getLanguage(); + } + + /** + * @return the localization of the given translation key. + */ + public static String get(String key) { + return Localization.get(key); + } + + /** + * @return the localization of the given translation key, + * with the given arguments applied. + */ + public static String get(String key, Object... args) { + return Localization.get(key, args); + } + + /** + * @return the localization of the given translation key, + * or the given default value if none exists. + */ + public static String getOrDefault(String key, String defaultLocalization) { + return Localization.getOrDefault(key, defaultLocalization); + } + + /** + * @return whether a localization exists for the given translation key. + */ + public static boolean has(String key) { + return Localization.has(key); + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/language/Language.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/language/Language.java new file mode 100644 index 00000000..e95b19ad --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/language/Language.java @@ -0,0 +1,18 @@ +package net.ornithemc.osl.localization.api.language; + +public interface Language { + + String code(); + + String name(); + + String region(); + + boolean bidirectional(); + + interface Factory { + + Language create(String code, String name, String region, boolean bidirectional); + + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/language/LanguageMetadata.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/language/LanguageMetadata.java new file mode 100644 index 00000000..26ac4d02 --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/api/language/LanguageMetadata.java @@ -0,0 +1,16 @@ +package net.ornithemc.osl.localization.api.language; + +import java.util.Collection; + +import net.ornithemc.osl.localization.impl.language.SimpleLanguageMetadata; +import net.ornithemc.osl.resource.loader.api.resource.ResourceMetadata; + +public interface LanguageMetadata { + + String NAME = "language"; + ResourceMetadata.Section.Serializer SERIALIZER = SimpleLanguageMetadata.SERIALIZER; + ResourceMetadata.Section SECTION = ResourceMetadata.Section.of(NAME, SERIALIZER); + + Collection getLanguages(); + +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/Locale.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/Locale.java new file mode 100644 index 00000000..e7f6b66b --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/Locale.java @@ -0,0 +1,206 @@ +package net.ornithemc.osl.localization.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.quiltmc.parsers.json.JsonReader; + +import net.ornithemc.osl.core.api.util.NamespacedIdentifier; +import net.ornithemc.osl.core.api.util.NamespacedIdentifiers; +import net.ornithemc.osl.resource.loader.api.resource.Resource; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; +import net.ornithemc.osl.resource.loader.impl.resource.pack.ResourcePacks; + +public final class Locale implements net.ornithemc.osl.text.impl.Locale { + + private static final Locale INSTANCE = new Locale(); + + public static Locale instance() { + return INSTANCE; + } + + // java.util.Map <=1.12.2, java.util.Properties >1.12.2 + private Map map; + private Properties properties; + + private long lastUpdateTime; + + private Locale() { + this.map = new HashMap<>(); + } + + public void wrap(Map map) { + this.init(map, null); + } + + public void wrap(Properties properties) { + this.init(null, properties); + } + + private void init(Map map, Properties properties) { + this.map = map; + this.properties = properties; + } + + public String get(String key) { + if (this.map != null) { + return this.map.get(key); + } else if (this.properties != null) { + return this.properties.getProperty(key); + } else { + return null; + } + } + + public String getOrDefault(String key, String defaultValue) { + if (this.map != null) { + return this.map.getOrDefault(key, defaultValue); + } else if (this.properties != null) { + return this.properties.getProperty(key, defaultValue); + } else { + return defaultValue; + } + } + + public boolean containsKey(String key) { + if (this.map != null) { + return this.map.containsKey(key); + } else if (this.properties != null) { + return this.properties.containsKey(key); + } else { + return false; + } + } + + private void set(String key, String translation) { + if (this.map != null) { + this.map.put(key, translation); + } else if (this.properties != null) { + this.properties.setProperty(key, translation); + } + } + + public long getLastUpdateTime() { + return this.lastUpdateTime; + } + + public void setLastUpdateTime() { + this.lastUpdateTime = System.currentTimeMillis(); + } + + public void reload(ResourceManager resourceManager, List languages) { + for (String language : languages) { + this.loadLanguage(resourceManager, language); + } + + this.setLastUpdateTime(); + } + + public void loadLanguage(ResourceManager resourceManager, String language) { + String pathFormat = "lang/%s%s"; + String[] paths; + + if (ResourcePacks.getSupportedFormat() < 3) { + String languageLowercase = language.toLowerCase(java.util.Locale.ROOT); + + paths = new String[] { + String.format(pathFormat, language, ".lang"), + String.format(pathFormat, language, ".json"), + String.format(pathFormat, languageLowercase, ".lang"), + String.format(pathFormat, languageLowercase, ".json") + }; + } else { + paths = new String[] { + String.format(pathFormat, language, ".lang"), + String.format(pathFormat, language, ".json") + }; + } + + for (String path : paths) { + try { + this.loadFromResources(path, resourceManager.getResourceStack(path)); + } catch (IOException ignored) { + } + + for (String namespace : resourceManager.getNamespaces()) { + NamespacedIdentifier location = NamespacedIdentifiers.from(namespace, path); + List resources = resourceManager.getResourceStack(location); + + this.loadFromResources(resources); + } + } + } + + public void loadFromResources(String path, List resources) { + for (InputStream resource : resources) { + try { + this.loadFromResource(path, resource); + } catch (IOException e) { + Localization.LOGGER.warn("Error parsing language file {}: {}", path, e); + } + } + } + + public void loadFromResources(List resources) { + for (Resource resource : resources) { + try { + this.loadFromResource(resource.location(), resource.open()); + } catch (IOException e) { + Localization.LOGGER.warn("Error parsing language file {} ({}): {}", resource.location(), resource.sourceName(), e); + } + } + } + + public void loadFromResource(String path, InputStream resource) throws IOException { + if (path.endsWith(".lang")) { + this.loadFromLang(resource); + } else if (path.endsWith(".json")) { + this.loadFromJson(resource); + } else { + Localization.LOGGER.warn("Skipping language file of unknown type: {}", path); + } + } + + public void loadFromResource(NamespacedIdentifier location, InputStream resource) throws IOException { + if (location.identifier().endsWith(".lang")) { + this.loadFromLang(resource); + } else if (location.identifier().endsWith(".json")) { + this.loadFromJson(resource); + } else { + Localization.LOGGER.warn("Skipping language file of unknown type: {}", location); + } + } + + public void loadFromLang(InputStream is) throws IOException { + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + String[] args = line.split("=", 2); + if (args.length != 2) { + continue; + } + this.set(args[0], args[1]); + } + } + } + + public void loadFromJson(InputStream is) throws IOException { + try (JsonReader reader = JsonReader.json(new InputStreamReader(is))) { + reader.beginObject(); + while (reader.hasNext()) { + this.set(reader.nextName() , reader.nextString()); + } + reader.endObject(); + } + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/Localization.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/Localization.java new file mode 100644 index 00000000..661dd418 --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/Localization.java @@ -0,0 +1,58 @@ +package net.ornithemc.osl.localization.impl; + +import java.util.IllegalFormatException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.ornithemc.osl.localization.api.language.Language; +import net.ornithemc.osl.localization.impl.language.LanguageManager; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; + +public final class Localization { + + public static final Logger LOGGER = LogManager.getLogger("OSL|Localization"); + + private static LanguageManager languageManager = LanguageManager.instance(); + private static Locale translations = Locale.instance(); + + public static LanguageManager getLanguageManager() { + return languageManager; + } + + public static Locale getLocale() { + return translations; + } + + public static Language getLanguage() { + return languageManager.getSelectedLanguage(); + } + + public static void setLanguage(Language language) { + languageManager.setSelectedLanguage(language); + } + + public static void reloadLanguageManager() { + languageManager.reload(ResourceManager.client().getResourcePacks()); + } + + public static void reloadLocale() { + languageManager.reloadLocale(ResourceManager.client()); + } + + public static String get(String key, Object... args) { + try { + return String.format(key = translations.get(key), args); + } catch (IllegalFormatException e) { + return "format error: " + key; + } + } + + public static String getOrDefault(String key, String defaultLocalization) { + return translations.getOrDefault(key, defaultLocalization); + } + + public static boolean has(String key) { + return translations.containsKey(key); + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/LanguageManager.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/LanguageManager.java new file mode 100644 index 00000000..a4b7d19f --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/LanguageManager.java @@ -0,0 +1,112 @@ +package net.ornithemc.osl.localization.impl.language; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import net.ornithemc.osl.localization.api.language.Language; +import net.ornithemc.osl.localization.api.language.LanguageMetadata; +import net.ornithemc.osl.localization.impl.Locale; +import net.ornithemc.osl.localization.impl.Localization; +import net.ornithemc.osl.resource.loader.api.resource.ResourceMetadata; +import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager; +import net.ornithemc.osl.resource.loader.api.resource.pack.ResourcePack; +import net.ornithemc.osl.resource.loader.impl.resource.pack.ResourcePacks; + +public final class LanguageManager { + + private static final LanguageManager INSTANCE = new LanguageManager(); + + public static final LanguageManager instance() { + return INSTANCE; + } + + public static final String EN_US = ResourcePacks.getSupportedFormat() < 3 ? "en_US" : "en_us"; + public static final String DEFAULT_LANGUAGE = EN_US; + public static final String FALLBACK_LANGUAGE = EN_US; + + static { + INSTANCE.selectedLanguage = DEFAULT_LANGUAGE; + } + + private final Locale locale = Locale.instance(); + + private boolean wrapper; + private Map languages; + private ResourceMetadata.Section metadataSection; + + private String selectedLanguage; + + private LanguageManager() { + this.languages = new HashMap<>(); + this.metadataSection = LanguageMetadata.SECTION; + } + + public void wrap(Map map, Language.Factory languageFactory) { + if (this.wrapper) { + throw new IllegalStateException("Attempted to initialize LanguageManager multiple times!"); + } else { + ResourceMetadata.Section.Serializer serializer = SimpleLanguageMetadata.serializer(languageFactory); + + this.languages = map; + this.metadataSection = ResourceMetadata.Section.of(LanguageMetadata.NAME, serializer); + } + } + + public Language getSelectedLanguage() { + String code = this.selectedLanguage; + if (!this.languages.containsKey(code)) { + code = DEFAULT_LANGUAGE; + } + + return this.languages.get(code); + } + + public void setSelectedLanguage(Language language) { + this.selectedLanguage = language.code(); + } + + public void setSelectedLanguage(String language) { + this.selectedLanguage = language; + } + + public SortedSet getLanguages() { + return new TreeSet<>(this.languages.values()); + } + + public Language getLanguage(String code) { + return this.languages.get(code); + } + + public void reload(List resourcePacks) { + this.languages.clear(); + + for (ResourcePack resourcePack : resourcePacks) { + try { + LanguageMetadata metadata = resourcePack.getMetadata(this.metadataSection); + + if (metadata != null) { + for (Language language : metadata.getLanguages()) { + this.languages.putIfAbsent(language.code(), language); + } + } + } catch (Exception e) { + Localization.LOGGER.warn("Unable to parse language metadata from resource pack: {}", resourcePack.getName(), e); + } + } + } + + public void reloadLocale(ResourceManager resourceManager) { + List languageCodes = new ArrayList<>(); + + if (!FALLBACK_LANGUAGE.equals(this.selectedLanguage)) { + languageCodes.add(FALLBACK_LANGUAGE); + } + languageCodes.add(this.selectedLanguage); + + this.locale.reload(resourceManager, languageCodes); + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/SimpleLanguage.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/SimpleLanguage.java new file mode 100644 index 00000000..a1fd71fe --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/SimpleLanguage.java @@ -0,0 +1,64 @@ +package net.ornithemc.osl.localization.impl.language; + +import net.ornithemc.osl.localization.api.language.Language; + +public class SimpleLanguage implements Language, Comparable { + + public static final Factory FACTORY = SimpleLanguage::new; + + private final String code; + private final String region; + private final String name; + private final boolean bidirectional; + + public SimpleLanguage(String code, String region, String name, boolean bidirectional) { + this.code = code; + this.region = region; + this.name = name; + this.bidirectional = bidirectional; + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (!(that instanceof Language)) { + return false; + } + + Language lang = (Language) that; + return this.code.equals(lang.code()); + } + + @Override + public int hashCode() { + return this.code.hashCode(); + } + + @Override + public String toString() { + return String.format("%s (%s)", this.name, this.region); + } + + @Override + public int compareTo(Language language) { + return this.code.compareTo(language.code()); + } + + public String code() { + return this.code; + } + + public String name() { + return this.name; + } + + public String region() { + return this.region; + } + + public boolean bidirectional() { + return this.bidirectional; + } +} diff --git a/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/SimpleLanguageMetadata.java b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/SimpleLanguageMetadata.java new file mode 100644 index 00000000..fc87658f --- /dev/null +++ b/libraries/localization/src/main/java/net/ornithemc/osl/localization/impl/language/SimpleLanguageMetadata.java @@ -0,0 +1,92 @@ +package net.ornithemc.osl.localization.impl.language; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import net.ornithemc.osl.localization.api.language.Language; +import net.ornithemc.osl.localization.api.language.LanguageMetadata; +import net.ornithemc.osl.resource.loader.api.resource.ResourceMetadata; + +public class SimpleLanguageMetadata implements LanguageMetadata { + + public static final Serializer SERIALIZER = serializer(SimpleLanguage.FACTORY); + + public static Serializer serializer(Language.Factory languageFactory) { + return new Serializer(languageFactory); + } + + private final Collection languages; + + public SimpleLanguageMetadata(Collection languages) { + this.languages = languages; + } + + @Override + public Collection getLanguages() { + return this.languages; + } + + public static class Serializer implements ResourceMetadata.Section.Serializer { + + private static final String REGION = "region"; + private static final String NAME = "name"; + private static final String BIDIRECTIONAL = "bidirectional"; + + private static final int LANGUAGE_CODE_MAX_LENGTH = 16; + + private final Language.Factory languageFactory; + + public Serializer(Language.Factory languageFactory) { + this.languageFactory = languageFactory; + } + + @Override + public LanguageMetadata deserialize(JsonObject json) { + Set languages = new HashSet<>(); + + for (Map.Entry entry : json.entrySet()) { + String code = entry.getKey(); + JsonElement element = entry.getValue(); + + if (code.length() > LANGUAGE_CODE_MAX_LENGTH) { + throw new JsonParseException("Invalid language code '" + code + "': cannot be more than " + LANGUAGE_CODE_MAX_LENGTH + " characters long"); + } + if (!element.isJsonObject()) { + throw new JsonParseException("Invalid language '" + code + "': expected object"); + } + + JsonObject languageJson = element.getAsJsonObject(); + + if (!languageJson.has(REGION)) { + throw new JsonParseException("Invalid language '" + code + "': no region!"); + } + if (!languageJson.has(NAME)) { + throw new JsonParseException("Invalid language '" + code + "': no name!"); + } + + String region = languageJson.getAsJsonPrimitive(REGION).getAsString(); + String name = languageJson.getAsJsonPrimitive(NAME).getAsString(); + boolean bidirectional = languageJson.has(BIDIRECTIONAL) && languageJson.getAsJsonPrimitive(BIDIRECTIONAL).getAsBoolean(); + + if (region.isEmpty()) { + throw new JsonParseException("Invalid language '" + code + "': region cannot be empty"); + } + if (name.isEmpty()) { + throw new JsonParseException("Invalid language '" + code + "' name cannot be empty"); + } + + if (!languages.add(this.languageFactory.create(code, region, name, bidirectional))) { + throw new JsonParseException("Duplicate language '" + code + "' defined"); + } + } + + return new SimpleLanguageMetadata(languages); + } + } +} diff --git a/settings.gradle b/settings.gradle index 91de4254..7b70cfad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -69,6 +69,13 @@ include ':libraries:lifecycle-events:lifecycle-events-mc12w21a-mc1.6.4' include ':libraries:lifecycle-events:lifecycle-events-mc13w36a-mc19w07a' include ':libraries:lifecycle-events:lifecycle-events-mc19w08a-mc1.14.4' +include ':libraries:localization' +include ':libraries:localization:localization-mca1.0.1_01-mca1.2.6' +include ':libraries:localization:localization-mcb1.0-mc11w48a' +include ':libraries:localization:localization-mc11w49a-mc1.5.2' +include ':libraries:localization:localization-mc13w26a-mc18w01a' +include ':libraries:localization:localization-mc18w02a-mc1.14.4' + include ':libraries:networking' include ':libraries:networking:networking-mca1.0.16-mca1.2.6' include ':libraries:networking:networking-mca0.1.0-mca0.2.1'