From 848b19ed78ca59ae2b184159ae8300c7533bf215 Mon Sep 17 00:00:00 2001 From: NebelNidas Date: Mon, 16 Oct 2023 13:05:26 +0200 Subject: [PATCH] Initial localization support --- .../mappingio/format/MappingFormat.java | 33 +++++--- .../net/fabricmc/mappingio/i18n/I18n.java | 76 +++++++++++++++++++ .../fabricmc/mappingio/i18n/MioLocale.java | 27 +++++++ src/main/resources/i18n/en_us.properties | 9 +++ .../net/fabricmc/mappingio/i18n/I18nTest.java | 43 +++++++++++ 5 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 src/main/java/net/fabricmc/mappingio/i18n/I18n.java create mode 100644 src/main/java/net/fabricmc/mappingio/i18n/MioLocale.java create mode 100644 src/main/resources/i18n/en_us.properties create mode 100644 src/test/java/net/fabricmc/mappingio/i18n/I18nTest.java diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java index e79e48a7..a14aa963 100644 --- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java +++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java @@ -16,6 +16,11 @@ package net.fabricmc.mappingio.format; +import java.util.Locale; + +import net.fabricmc.mappingio.i18n.I18n; +import net.fabricmc.mappingio.i18n.MioLocale; + /** * Represents a supported mapping format. Feature comparison table: * @@ -98,48 +103,48 @@ public enum MappingFormat { /** * The {@code Tiny} mapping format, as specified here. */ - TINY_FILE("Tiny file", "tiny", true, true, false, false, false), + TINY_FILE("tiny", true, true, false, false, false), /** * The {@code Tiny v2} mapping format, as specified here. */ - TINY_2_FILE("Tiny v2 file", "tiny", true, true, true, true, true), + TINY_2_FILE("tiny", true, true, true, true, true), /** * Enigma's mapping format, as specified here. */ - ENIGMA_FILE("Enigma file", "mapping", false, true, true, true, false), + ENIGMA_FILE("mapping", false, true, true, true, false), /** * Enigma's mapping format (in directory form), as specified here. */ - ENIGMA_DIR("Enigma directory", null, false, true, true, true, false), + ENIGMA_DIR(null, false, true, true, true, false), /** * The {@code SRG} ({@code Searge RetroGuard}) mapping format, as specified here. */ - SRG_FILE("SRG file", "srg", false, false, false, false, false), + SRG_FILE("srg", false, false, false, false, false), /** * The {@code TSRG} ({@code Tiny SRG}, since it saves disk space over SRG) mapping format, as specified here. */ - TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false), + TSRG_FILE("tsrg", false, false, false, false, false), /** * The {@code TSRG v2} mapping format, as specified here. */ - TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false), + TSRG_2_FILE("tsrg", true, true, false, true, false), /** * ProGuard's mapping format, as specified here. */ - PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false); + PROGUARD_FILE("txt", false, true, false, false, false); - MappingFormat(String name, String fileExt, - boolean hasNamespaces, boolean hasFieldDescriptors, + MappingFormat(String fileExt, boolean hasNamespaces, boolean hasFieldDescriptors, boolean supportsComments, boolean supportsArgs, boolean supportsLocals) { - this.name = name; this.fileExt = fileExt; + this.translationKey = "format." + name().toLowerCase(Locale.ROOT); + this.name = getName(MioLocale.EN_US); this.hasNamespaces = hasNamespaces; this.hasFieldDescriptors = hasFieldDescriptors; this.supportsComments = supportsComments; @@ -147,6 +152,10 @@ public enum MappingFormat { this.supportsLocals = supportsLocals; } + public String getName(MioLocale locale) { + return I18n.translate(translationKey, locale); + } + public boolean hasSingleFile() { return fileExt != null; } @@ -157,6 +166,7 @@ public String getGlobPattern() { return "*."+fileExt; } + /** @deprecated Use {@link #getName()} instead. */ public final String name; public final String fileExt; public final boolean hasNamespaces; @@ -164,4 +174,5 @@ public String getGlobPattern() { public final boolean supportsComments; public final boolean supportsArgs; public final boolean supportsLocals; + private final String translationKey; } diff --git a/src/main/java/net/fabricmc/mappingio/i18n/I18n.java b/src/main/java/net/fabricmc/mappingio/i18n/I18n.java new file mode 100644 index 00000000..41e11095 --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/i18n/I18n.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.mappingio.i18n; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class I18n { + private I18n() { + } + + public static String translate(String key, MioLocale locale, Object... args) { + return String.format(translate(key, locale), args); + } + + public static String translate(String key, MioLocale locale) { + try { + return messageBundles.getOrDefault(locale, load(locale)).getString(key); + } catch (Exception e) { + System.err.println("Exception while translating key " + key + " to locale " + locale.id + ": " + e.getMessage()); + if (locale == fallbackLocale) return key; + + try { + return messageBundles.getOrDefault(fallbackLocale, load(fallbackLocale)).getString(key); + } catch (Exception e2) { + System.err.println("Exception while translating key " + key + " to fallback locale: " + e2.getMessage()); + return key; + } + } + } + + private static ResourceBundle load(MioLocale locale) { + ResourceBundle resBundle; + String resName = String.format("/i18n/%s.properties", locale.id); + URL resUrl = I18n.class.getResource(resName); + + if (resUrl == null) { + throw new RuntimeException("Locale resource not found: " + resName); + } + + try (Reader reader = new InputStreamReader(resUrl.openStream(), StandardCharsets.UTF_8)) { + resBundle = new PropertyResourceBundle(reader); + messageBundles.put(locale, resBundle); + return resBundle; + } catch (IOException e) { + throw new RuntimeException("Failed to load " + resName, e); + } + } + + private static final MioLocale fallbackLocale = MioLocale.EN_US; + private static final Map messageBundles = new HashMap<>(); +} diff --git a/src/main/java/net/fabricmc/mappingio/i18n/MioLocale.java b/src/main/java/net/fabricmc/mappingio/i18n/MioLocale.java new file mode 100644 index 00000000..0a535a9a --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/i18n/MioLocale.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.mappingio.i18n; + +public enum MioLocale { + EN_US("en_us"); + + MioLocale(String id) { + this.id = id; + } + + final String id; +} diff --git a/src/main/resources/i18n/en_us.properties b/src/main/resources/i18n/en_us.properties new file mode 100644 index 00000000..244556f0 --- /dev/null +++ b/src/main/resources/i18n/en_us.properties @@ -0,0 +1,9 @@ +format.tiny_file = Tiny File +format.tiny_2_file = Tiny v2 File +format.enigma_file = Enigma File +format.enigma_dir = Enigma Directory +format.mcp_dir = MCP Directory +format.srg_file = SRG File +format.tsrg_file = TSRG File +format.tsrg_2_file = TSRG2 File +format.proguard_file = ProGuard File diff --git a/src/test/java/net/fabricmc/mappingio/i18n/I18nTest.java b/src/test/java/net/fabricmc/mappingio/i18n/I18nTest.java new file mode 100644 index 00000000..e79f9e70 --- /dev/null +++ b/src/test/java/net/fabricmc/mappingio/i18n/I18nTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.mappingio.i18n; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import net.fabricmc.mappingio.format.MappingFormat; + +public class I18nTest { + @Test + public void mappingFormats() { + for (MappingFormat format : MappingFormat.values()) { + String enUsName = format.getName(MioLocale.EN_US); + assertTrue(format.name.equals(enUsName)); + + for (MioLocale locale : MioLocale.values()) { + String translatedName = format.getName(locale); + assertFalse(translatedName.startsWith("format.")); + + if (locale != MioLocale.EN_US) { + assertFalse(translatedName.equals(enUsName)); + } + } + } + } +}