优化对颠倒的英语的支持 (#4577)

This commit is contained in:
Glavo
2025-10-02 15:21:03 +08:00
committed by GitHub
parent 19f02e3967
commit 484b65b513
5 changed files with 138 additions and 47 deletions

View File

@@ -72,6 +72,7 @@ import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction; 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.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList; 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")); ImageView titleIcon = new ImageView(FXUtils.newBuiltinImage("/assets/img/icon-title.png"));
Label titleLabel = new Label(Metadata.FULL_TITLE); Label titleLabel = new Label(Metadata.FULL_TITLE);
if (I18n.isUpsideDown()) {
titleIcon.setRotate(180);
titleLabel.setRotate(180);
}
titleLabel.getStyleClass().add("jfx-decorator-title"); titleLabel.getStyleClass().add("jfx-decorator-title");
titleNode.getChildren().setAll(titleIcon, titleLabel); titleNode.getChildren().setAll(titleIcon, titleLabel);

View File

@@ -43,6 +43,10 @@ public final class I18n {
return locale; return locale;
} }
public static boolean isUpsideDown() {
return LocaleUtils.getScript(locale.getLocale()).equals("Qabs");
}
public static boolean isUseChinese() { public static boolean isUseChinese() {
return LocaleUtils.isChinese(locale.getLocale()); return LocaleUtils.isChinese(locale.getLocale());
} }

View File

@@ -17,37 +17,46 @@
*/ */
package org.jackhuang.hmcl.util.i18n; 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.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/// @author Glavo /// @author Glavo
public final class UpsideDownUtils { public final class UpsideDownUtils {
private static final Map<Integer, Integer> MAPPER = new LinkedHashMap<>(); private static final Map<Integer, Integer> MAPPER = loadMap();
private static void putChars(char baseChar, String upsideDownChars) { private static Map<Integer, Integer> loadMap() {
for (int i = 0; i < upsideDownChars.length(); i++) { var map = new LinkedHashMap<Integer, Integer>();
MAPPER.put(baseChar + i, (int) upsideDownChars.charAt(i));
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");
} }
} return Collections.unmodifiableMap(map);
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("_,;.?!/\\'", "‾'⸵˙¿¡/\\,");
} }
public static String translate(String str) { public static String translate(String str) {
@@ -56,7 +65,7 @@ public final class UpsideDownUtils {
return builder.reverse().toString(); 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()); .withZone(ZoneId.systemDefault());
public static String formatDateTime(TemporalAccessor time) { public static String formatDateTime(TemporalAccessor time) {

View File

@@ -0,0 +1,71 @@
bq
dp
gᵷ
iᴉ
lꞁ
mɯ
nu
oo
pd
qb
ss
un
xx
zz
AⱯ
Bᗺ
Dᗡ
FℲ
G⅁
HH
II
Jſ
LꞀ
MW
NN
OO
QὉ
Rᴚ
SS
T⟘
U∩
WM
XX
ZZ
00
2ᘔ
4ㄣ
69
7ㄥ
88
96
_‾
,'
;⸵
?¿
//
\\
',

View File

@@ -18,18 +18,18 @@
package org.jackhuang.hmcl.gradle.l10n; package org.jackhuang.hmcl.gradle.l10n;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.LinkedHashMap; import java.util.*;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -75,29 +75,31 @@ public abstract class UpsideDownTranslate extends DefaultTask {
} }
static final class Translator { static final class Translator {
private static final Map<Integer, Integer> MAPPER = new LinkedHashMap<>(); private static final Map<Integer, Integer> MAPPER = loadMap();
private static void putChars(char baseChar, String upsideDownChars) { private static Map<Integer, Integer> loadMap() {
for (int i = 0; i < upsideDownChars.length(); i++) { var map = new LinkedHashMap<Integer, Integer>();
MAPPER.put(baseChar + i, (int) upsideDownChars.charAt(i));
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");
} }
} return Collections.unmodifiableMap(map);
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("_,;.?!/\\'", "‾'⸵˙¿¡/\\,");
} }
private static final Pattern FORMAT_PATTERN = Pattern.compile("^%(\\d\\$)?(\\d+)?(\\.\\d+)?([sdf])"); private static final Pattern FORMAT_PATTERN = Pattern.compile("^%(\\d\\$)?(\\d+)?(\\.\\d+)?([sdf])");