From 484b65b5139b12b7e0455cc24f036f4ca980e1eb Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 2 Oct 2025 15:21:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=B9=E9=A2=A0=E5=80=92?= =?UTF-8?q?=E7=9A=84=E8=8B=B1=E8=AF=AD=E7=9A=84=E6=94=AF=E6=8C=81=20(#4577?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/main/MainPage.java | 5 ++ .../org/jackhuang/hmcl/util/i18n/I18n.java | 4 ++ .../hmcl/util/i18n/UpsideDownUtils.java | 53 ++++++++------ .../resources/assets/lang/upside_down.txt | 71 +++++++++++++++++++ .../hmcl/gradle/l10n/UpsideDownTranslate.java | 52 +++++++------- 5 files changed, 138 insertions(+), 47 deletions(-) create mode 100644 HMCLCore/src/main/resources/assets/lang/upside_down.txt diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 17af90f9b..67f33d904 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -72,6 +72,7 @@ import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; @@ -115,6 +116,10 @@ public final class MainPage extends StackPane implements DecoratorPage { ImageView titleIcon = new ImageView(FXUtils.newBuiltinImage("/assets/img/icon-title.png")); Label titleLabel = new Label(Metadata.FULL_TITLE); + if (I18n.isUpsideDown()) { + titleIcon.setRotate(180); + titleLabel.setRotate(180); + } titleLabel.getStyleClass().add("jfx-decorator-title"); titleNode.getChildren().setAll(titleIcon, titleLabel); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java index d54af4464..281d09722 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java @@ -43,6 +43,10 @@ public final class I18n { return locale; } + public static boolean isUpsideDown() { + return LocaleUtils.getScript(locale.getLocale()).equals("Qabs"); + } + public static boolean isUseChinese() { return LocaleUtils.isChinese(locale.getLocale()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/UpsideDownUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/UpsideDownUtils.java index 5856436a9..f951ce82c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/UpsideDownUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/UpsideDownUtils.java @@ -17,37 +17,46 @@ */ package org.jackhuang.hmcl.util.i18n; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + /// @author Glavo public final class UpsideDownUtils { - private static final Map MAPPER = new LinkedHashMap<>(); + private static final Map MAPPER = loadMap(); - private static void putChars(char baseChar, String upsideDownChars) { - for (int i = 0; i < upsideDownChars.length(); i++) { - MAPPER.put(baseChar + i, (int) upsideDownChars.charAt(i)); + private static Map loadMap() { + var map = new LinkedHashMap(); + + InputStream inputStream = UpsideDownUtils.class.getResourceAsStream("/assets/lang/upside_down.txt"); + if (inputStream != null) { + try (inputStream) { + new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> { + if (line.isBlank() || line.startsWith("#")) + return; + + if (line.length() != 2) { + LOG.warning("Invalid line: " + line); + return; + } + + map.put((int) line.charAt(0), (int) line.charAt(1)); + }); + } catch (IOException e) { + LOG.warning("Failed to load upside_down.txt", e); + } + } else { + LOG.warning("upside_down.txt not found"); } - } - - private static void putChars(String baseChars, String upsideDownChars) { - if (baseChars.length() != upsideDownChars.length()) { - throw new IllegalArgumentException("baseChars and upsideDownChars must have same length"); - } - - for (int i = 0; i < baseChars.length(); i++) { - MAPPER.put((int) baseChars.charAt(i), (int) upsideDownChars.charAt(i)); - } - } - - static { - putChars('a', "ɐqɔpǝɟbɥıظʞןɯuodbɹsʇnʌʍxʎz"); - putChars('A', "ⱯᗺƆᗡƎℲ⅁HIſʞꞀWNOԀὉᴚS⟘∩ΛMXʎZ"); - putChars('0', "0ƖᘔƐㄣϛ9ㄥ86"); - putChars("_,;.?!/\\'", "‾'⸵˙¿¡/\\,"); + return Collections.unmodifiableMap(map); } public static String translate(String str) { @@ -56,7 +65,7 @@ public final class UpsideDownUtils { return builder.reverse().toString(); } - private static DateTimeFormatter BASE_FORMATTER = DateTimeFormatter.ofPattern("MMM d, yyyy, h:mm:ss a") + private static final DateTimeFormatter BASE_FORMATTER = DateTimeFormatter.ofPattern("MMM d, yyyy, h:mm:ss a") .withZone(ZoneId.systemDefault()); public static String formatDateTime(TemporalAccessor time) { diff --git a/HMCLCore/src/main/resources/assets/lang/upside_down.txt b/HMCLCore/src/main/resources/assets/lang/upside_down.txt new file mode 100644 index 000000000..051965d51 --- /dev/null +++ b/HMCLCore/src/main/resources/assets/lang/upside_down.txt @@ -0,0 +1,71 @@ +aɐ +bq +cɔ +dp +eǝ +fɟ +gᵷ +hɥ +iᴉ +jɾ +kʞ +lꞁ +mɯ +nu +oo +pd +qb +rɹ +ss +tʇ +un +vʌ +wʍ +xx +yʎ +zz +AⱯ +Bᗺ +CƆ +Dᗡ +EƎ +FℲ +G⅁ +HH +II +Jſ +Kʞ +LꞀ +MW +NN +OO +PԀ +QὉ +Rᴚ +SS +T⟘ +U∩ +VΛ +WM +XX +Yʎ +ZZ +00 +1Ɩ +2ᘔ +3Ɛ +4ㄣ +5ϛ +69 +7ㄥ +88 +96 +_‾ +,' +;⸵ +.˙ +?¿ +!¡ +// +\\ +', \ No newline at end of file diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/UpsideDownTranslate.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/UpsideDownTranslate.java index 28b049a53..f1c3cbb6b 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/UpsideDownTranslate.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/UpsideDownTranslate.java @@ -18,18 +18,18 @@ package org.jackhuang.hmcl.gradle.l10n; import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -75,29 +75,31 @@ public abstract class UpsideDownTranslate extends DefaultTask { } static final class Translator { - private static final Map MAPPER = new LinkedHashMap<>(); + private static final Map MAPPER = loadMap(); - private static void putChars(char baseChar, String upsideDownChars) { - for (int i = 0; i < upsideDownChars.length(); i++) { - MAPPER.put(baseChar + i, (int) upsideDownChars.charAt(i)); + private static Map loadMap() { + var map = new LinkedHashMap(); + + InputStream inputStream = UpsideDownTranslate.class.getResourceAsStream("upside_down.txt"); + if (inputStream != null) { + try (inputStream) { + new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> { + if (line.isBlank() || line.startsWith("#")) + return; + + if (line.length() != 2) { + throw new GradleException("Invalid line: " + line); + } + + map.put((int) line.charAt(0), (int) line.charAt(1)); + }); + } catch (IOException e) { + throw new GradleException("Failed to load upside_down.txt", e); + } + } else { + throw new GradleException("upside_down.txt not found"); } - } - - private static void putChars(String baseChars, String upsideDownChars) { - if (baseChars.length() != upsideDownChars.length()) { - throw new IllegalArgumentException("baseChars and upsideDownChars must have same length"); - } - - for (int i = 0; i < baseChars.length(); i++) { - MAPPER.put((int) baseChars.charAt(i), (int) upsideDownChars.charAt(i)); - } - } - - static { - putChars('a', "ɐqɔpǝɟbɥıظʞןɯuodbɹsʇnʌʍxʎz"); - putChars('A', "ⱯᗺƆᗡƎℲ⅁HIſʞꞀWNOԀὉᴚS⟘∩ΛMXʎZ"); - putChars('0', "0ƖᘔƐㄣϛ9ㄥ86"); - putChars("_,;.?!/\\'", "‾'⸵˙¿¡/\\,"); + return Collections.unmodifiableMap(map); } private static final Pattern FORMAT_PATTERN = Pattern.compile("^%(\\d\\$)?(\\d+)?(\\.\\d+)?([sdf])");