mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-13 13:56:55 -04:00
优化文言文翻译 (#4361)
This commit is contained in:
parent
5af6858d4c
commit
73531dbf60
@ -181,7 +181,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
}
|
||||
setGraphic(pane);
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
content.setTitle(I18n.getDisplaySelfVersion(remoteVersion));
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
|
@ -17,10 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.*;
|
||||
|
||||
@ -33,12 +32,10 @@ public final class I18n {
|
||||
|
||||
private static volatile SupportedLocale locale = Locales.DEFAULT;
|
||||
private static volatile ResourceBundle resourceBundle = locale.getResourceBundle();
|
||||
private static volatile DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
public static void setLocale(SupportedLocale locale) {
|
||||
I18n.locale = locale;
|
||||
resourceBundle = locale.getResourceBundle();
|
||||
dateTimeFormatter = null;
|
||||
}
|
||||
|
||||
public static boolean isUseChinese() {
|
||||
@ -75,12 +72,11 @@ public final class I18n {
|
||||
}
|
||||
|
||||
public static String formatDateTime(TemporalAccessor time) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null) {
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format")).withZone(ZoneId.systemDefault());
|
||||
return locale.formatDateTime(time);
|
||||
}
|
||||
|
||||
return formatter.format(time);
|
||||
public static String getDisplaySelfVersion(RemoteVersion version) {
|
||||
return locale.getDisplaySelfVersion(version);
|
||||
}
|
||||
|
||||
public static boolean hasKey(String key) {
|
||||
|
@ -20,8 +20,14 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
@ -65,7 +71,20 @@ public final class Locales {
|
||||
/**
|
||||
* Wenyan (Classical Chinese)
|
||||
*/
|
||||
public static final SupportedLocale WENYAN = new SupportedLocale(Locale.forLanguageTag("lzh"));
|
||||
public static final SupportedLocale WENYAN = new SupportedLocale(Locale.forLanguageTag("lzh")) {
|
||||
@Override
|
||||
public String formatDateTime(TemporalAccessor time) {
|
||||
return WenyanUtils.formatDateTime(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplaySelfVersion(RemoteVersion version) {
|
||||
if (version instanceof GameRemoteVersion)
|
||||
return WenyanUtils.translateGameVersion(GameVersionNumber.asGameVersion(version.getSelfVersion()));
|
||||
else
|
||||
return WenyanUtils.translateGenericVersion(version.getSelfVersion());
|
||||
}
|
||||
};
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = List.of(DEFAULT, EN, ES, JA, RU, ZH_CN, ZH, WENYAN);
|
||||
|
||||
@ -104,9 +123,10 @@ public final class Locales {
|
||||
}
|
||||
|
||||
@JsonAdapter(SupportedLocale.TypeAdapter.class)
|
||||
public static final class SupportedLocale {
|
||||
public static class SupportedLocale {
|
||||
private final Locale locale;
|
||||
private ResourceBundle resourceBundle;
|
||||
private DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
SupportedLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
@ -162,6 +182,18 @@ public final class Locales {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public String formatDateTime(TemporalAccessor time) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null)
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format"))
|
||||
.withZone(ZoneId.systemDefault());
|
||||
return formatter.format(time);
|
||||
}
|
||||
|
||||
public String getDisplaySelfVersion(RemoteVersion version) {
|
||||
return version.getSelfVersion();
|
||||
}
|
||||
|
||||
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
||||
@Override
|
||||
public void write(JsonWriter out, SupportedLocale value) throws IOException {
|
||||
|
212
HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/WenyanUtils.java
Normal file
212
HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/WenyanUtils.java
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WenyanUtils {
|
||||
private static final String DOT = "點";
|
||||
|
||||
private static final String[] NUMBERS = {
|
||||
"〇", "一", "二", "三", "四", "五", "六", "七", "八", "九",
|
||||
"十", "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九",
|
||||
"廿", "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九",
|
||||
"卅", "卅一", "卅二", "卅三", "卅四", "卅五", "卅六", "卅七", "卅八", "卅九",
|
||||
"卌", "卌一", "卌二", "卌三", "卌四", "卌五", "卌六", "卌七", "卌八", "卌九",
|
||||
"五十", "五十一", "五十二", "五十三", "五十四", "五十五", "五十六", "五十七", "五十八", "五十九",
|
||||
"六十", "六十一", "六十二", "六十三", "六十四", "六十五", "六十六", "六十七", "六十八", "六十九",
|
||||
};
|
||||
|
||||
private static final char[] TIAN_GAN = {'甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'};
|
||||
private static final char[] DI_ZHI = {'子', '醜', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'};
|
||||
|
||||
private static String digitToString(char digit) {
|
||||
return digit >= '0' && digit <= '9'
|
||||
? NUMBERS[digit - '0']
|
||||
: String.valueOf(digit);
|
||||
}
|
||||
|
||||
private static String numberToString(int number) {
|
||||
return number >= 0 && number < NUMBERS.length ? NUMBERS[number] : String.valueOf(number);
|
||||
}
|
||||
|
||||
private static void appendDigitByDigit(StringBuilder builder, String number) {
|
||||
for (int i = 0; i < number.length(); i++) {
|
||||
builder.append(digitToString(number.charAt(i)));
|
||||
}
|
||||
}
|
||||
|
||||
private static int mod(int a, int b) {
|
||||
int r = a % b;
|
||||
return r >= 0 ? r : r + b;
|
||||
}
|
||||
|
||||
static void appendYear(StringBuilder builder, int year) {
|
||||
int yearOffset = year - 1984;
|
||||
|
||||
builder.append(TIAN_GAN[mod(yearOffset, TIAN_GAN.length)]);
|
||||
builder.append(DI_ZHI[mod(yearOffset, DI_ZHI.length)]);
|
||||
}
|
||||
|
||||
public static String formatDateTime(TemporalAccessor time) {
|
||||
LocalDateTime localDateTime;
|
||||
if (time instanceof Instant)
|
||||
localDateTime = ((Instant) time).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
else
|
||||
localDateTime = LocalDateTime.from(time);
|
||||
|
||||
StringBuilder builder = new StringBuilder(16);
|
||||
|
||||
appendYear(builder, localDateTime.getYear());
|
||||
builder.append('年');
|
||||
builder.append(numberToString(localDateTime.getMonthValue()));
|
||||
builder.append('月');
|
||||
builder.append(numberToString(localDateTime.getDayOfMonth()));
|
||||
builder.append('日');
|
||||
|
||||
builder.append(' ');
|
||||
|
||||
builder.append(numberToString(localDateTime.getHour()));
|
||||
builder.append('时');
|
||||
builder.append(numberToString(localDateTime.getMinute()));
|
||||
builder.append('分');
|
||||
builder.append(numberToString(localDateTime.getSecond()));
|
||||
builder.append('秒');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String translateGameVersion(GameVersionNumber gameVersion) {
|
||||
if (gameVersion instanceof GameVersionNumber.Release) {
|
||||
var release = (GameVersionNumber.Release) gameVersion;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
appendDigitByDigit(builder, String.valueOf(release.getMajor()));
|
||||
builder.append(DOT);
|
||||
appendDigitByDigit(builder, String.valueOf(release.getMinor()));
|
||||
|
||||
if (release.getPatch() != 0) {
|
||||
builder.append(DOT);
|
||||
appendDigitByDigit(builder, String.valueOf(release.getPatch()));
|
||||
}
|
||||
|
||||
if (release.getEaType() == GameVersionNumber.Release.TYPE_GA) {
|
||||
// do nothing
|
||||
} else if (release.getEaType() == GameVersionNumber.Release.TYPE_PRE) {
|
||||
builder.append("之預");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
} else if (release.getEaType() == GameVersionNumber.Release.TYPE_RC) {
|
||||
builder.append("之候");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
} else {
|
||||
// Unsupported
|
||||
return gameVersion.toString();
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
} else if (gameVersion instanceof GameVersionNumber.Snapshot) {
|
||||
var snapshot = (GameVersionNumber.Snapshot) gameVersion;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
appendDigitByDigit(builder, String.valueOf(snapshot.getYear()));
|
||||
builder.append('週');
|
||||
appendDigitByDigit(builder, String.valueOf(snapshot.getWeek()));
|
||||
|
||||
char suffix = snapshot.getSuffix();
|
||||
if (suffix >= 'a' && (suffix - 'a') < TIAN_GAN.length)
|
||||
builder.append(TIAN_GAN[suffix - 'a']);
|
||||
else
|
||||
builder.append(suffix);
|
||||
|
||||
return builder.toString();
|
||||
} else if (gameVersion instanceof GameVersionNumber.Special) {
|
||||
String version = gameVersion.toString();
|
||||
switch (version.toLowerCase(Locale.ROOT)) {
|
||||
case "2.0":
|
||||
return "二點〇";
|
||||
case "2.0_blue":
|
||||
return "二點〇藍";
|
||||
case "2.0_red":
|
||||
return "二點〇赤";
|
||||
case "2.0_purple":
|
||||
return "二點〇紫";
|
||||
case "1.rv-pre1":
|
||||
return "一點真視之預一";
|
||||
case "3d shareware v1.34":
|
||||
return "躍然享件一點三四";
|
||||
case "20w14infinite":
|
||||
case "20w14~":
|
||||
case "20w14∞":
|
||||
return "二〇週一四宇";
|
||||
case "22w13oneblockatatime":
|
||||
return "二二週一三典";
|
||||
case "23w13a_or_b":
|
||||
return "二三週一三暨";
|
||||
case "24w14potato":
|
||||
return "二四週一四芋";
|
||||
case "25w14craftmine":
|
||||
return "二五週一四礦";
|
||||
default:
|
||||
return version;
|
||||
}
|
||||
|
||||
} else {
|
||||
return gameVersion.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern GENERIC_VERSION_PATTERN =
|
||||
Pattern.compile("^[0-9]+(\\.[0-9]+)*");
|
||||
|
||||
public static String translateGenericVersion(String version) {
|
||||
Matcher matcher = GENERIC_VERSION_PATTERN.matcher(version);
|
||||
if (matcher.find()) {
|
||||
String prefix = matcher.group();
|
||||
StringBuilder builder = new StringBuilder(version.length());
|
||||
|
||||
for (int i = 0; i < prefix.length(); i++) {
|
||||
char ch = prefix.charAt(i);
|
||||
if (ch >= '0' && ch <= '9')
|
||||
builder.append(digitToString(ch));
|
||||
else if (ch == '.')
|
||||
builder.append(DOT);
|
||||
else
|
||||
builder.append(ch);
|
||||
}
|
||||
builder.append(version, prefix.length(), version.length());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private WenyanUtils() {
|
||||
}
|
||||
}
|
@ -1197,7 +1197,7 @@ version.update=更模組包
|
||||
|
||||
wiki.tooltip=礦藝大典
|
||||
wiki.version.game=https://zh.minecraft.wiki/w/Special:Search?search=%s&variant=zh-tw
|
||||
wiki.version.game.search=爪哇版%s
|
||||
wiki.version.game.search=Java版%s
|
||||
|
||||
wizard.prev=< 前步
|
||||
wizard.failed=未成
|
||||
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WenyanUtilsTest {
|
||||
|
||||
private static void assertYearToString(String value, int year) {
|
||||
StringBuilder builder = new StringBuilder(2);
|
||||
WenyanUtils.appendYear(builder, year);
|
||||
assertEquals(value, builder.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testYearToString() {
|
||||
assertYearToString("甲子", 1984);
|
||||
assertYearToString("乙巳", 2025);
|
||||
assertYearToString("甲子", -2996);
|
||||
assertYearToString("庚子", 1000);
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.versioning;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@ -135,7 +136,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
return value;
|
||||
}
|
||||
|
||||
static final class Old extends GameVersionNumber {
|
||||
public static final class Old extends GameVersionNumber {
|
||||
static Old parse(String value) {
|
||||
Type type;
|
||||
int prefixLength = 1;
|
||||
@ -211,16 +212,16 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
}
|
||||
}
|
||||
|
||||
static final class Release extends GameVersionNumber {
|
||||
public static final class Release extends GameVersionNumber {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("1\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?((?<eaType>(-[a-zA-Z]+| Pre-Release ))(?<eaVersion>.+))?");
|
||||
|
||||
static final int TYPE_GA = Integer.MAX_VALUE;
|
||||
public static final int TYPE_GA = Integer.MAX_VALUE;
|
||||
|
||||
static final int TYPE_UNKNOWN = 0;
|
||||
static final int TYPE_EXP = 1;
|
||||
static final int TYPE_PRE = 2;
|
||||
static final int TYPE_RC = 3;
|
||||
public static final int TYPE_UNKNOWN = 0;
|
||||
public static final int TYPE_EXP = 1;
|
||||
public static final int TYPE_PRE = 2;
|
||||
public static final int TYPE_RC = 3;
|
||||
|
||||
static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, VersionNumber.ZERO);
|
||||
|
||||
@ -259,6 +260,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
|
||||
@MagicConstant(intValues = {TYPE_GA, TYPE_UNKNOWN, TYPE_EXP, TYPE_PRE, TYPE_RC})
|
||||
private final int eaType;
|
||||
private final VersionNumber eaVersion;
|
||||
|
||||
@ -326,6 +328,26 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
throw new AssertionError(other.getClass());
|
||||
}
|
||||
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
public int getPatch() {
|
||||
return patch;
|
||||
}
|
||||
|
||||
public int getEaType() {
|
||||
return eaType;
|
||||
}
|
||||
|
||||
public VersionNumber getEaVersion() {
|
||||
return eaVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(major, minor, patch, eaType, eaVersion);
|
||||
@ -340,7 +362,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
}
|
||||
}
|
||||
|
||||
static final class Snapshot extends GameVersionNumber {
|
||||
public static final class Snapshot extends GameVersionNumber {
|
||||
static Snapshot parse(String value) {
|
||||
if (value.length() != 6 || value.charAt(2) != 'w')
|
||||
throw new IllegalArgumentException(value);
|
||||
@ -391,6 +413,18 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
throw new AssertionError(other.getClass());
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return (intValue >> 16) & 0xff;
|
||||
}
|
||||
|
||||
public int getWeek() {
|
||||
return (intValue >> 8) & 0xff;
|
||||
}
|
||||
|
||||
public char getSuffix() {
|
||||
return (char) (intValue & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@ -405,7 +439,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
}
|
||||
}
|
||||
|
||||
static final class Special extends GameVersionNumber {
|
||||
public static final class Special extends GameVersionNumber {
|
||||
private VersionNumber versionNumber;
|
||||
|
||||
private GameVersionNumber prev;
|
||||
|
Loading…
x
Reference in New Issue
Block a user