mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-14 06:17:47 -04:00
优化中文适配 (#4370)
This commit is contained in:
parent
86085031f6
commit
28b222e3dd
@ -22,6 +22,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
|||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
import org.jackhuang.hmcl.Metadata;
|
||||||
import org.jackhuang.hmcl.util.Lazy;
|
import org.jackhuang.hmcl.util.Lazy;
|
||||||
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||||
@ -32,7 +33,6 @@ import java.net.MalformedURLException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
@ -69,10 +69,10 @@ public final class FontManager {
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String fcMatchPattern;
|
||||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()
|
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()
|
||||||
&& Locale.getDefault() != Locale.ROOT
|
&& !(fcMatchPattern = I18n.getLocale().getFcMatchPattern()).isEmpty())
|
||||||
&& !"en".equals(Locale.getDefault().getLanguage()))
|
return findByFcMatch(fcMatchPattern);
|
||||||
return findByFcMatch();
|
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@ -123,20 +123,15 @@ public final class FontManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font findByFcMatch() {
|
public static Font findByFcMatch(String pattern) {
|
||||||
Path fcMatch = SystemUtils.which("fc-match");
|
Path fcMatch = SystemUtils.which("fc-match");
|
||||||
if (fcMatch == null)
|
if (fcMatch == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Locale locale = Locale.getDefault();
|
|
||||||
if (locale.getLanguage().equals("lzh"))
|
|
||||||
locale = Locale.TRADITIONAL_CHINESE;
|
|
||||||
|
|
||||||
String result = SystemUtils.run(fcMatch.toString(),
|
String result = SystemUtils.run(fcMatch.toString(),
|
||||||
":lang=" + locale.toLanguageTag(),
|
pattern,
|
||||||
"--format", "%{family}\\n%{file}").trim();
|
"--format", "%{family}\\n%{file}").trim();
|
||||||
|
|
||||||
String[] results = result.split("\\n");
|
String[] results = result.split("\\n");
|
||||||
if (results.length != 2 || results[0].isEmpty() || results[1].isEmpty()) {
|
if (results.length != 2 || results[0].isEmpty() || results[1].isEmpty()) {
|
||||||
LOG.warning("Unexpected output from fc-match: " + result);
|
LOG.warning("Unexpected output from fc-match: " + result);
|
||||||
|
@ -43,7 +43,7 @@ public final class I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUseChinese() {
|
public static boolean isUseChinese() {
|
||||||
return locale.getLocale().getLanguage().equals("zh");
|
return Locales.isChinese(locale.getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceBundle getResourceBundle() {
|
public static ResourceBundle getResourceBundle() {
|
||||||
|
@ -28,9 +28,7 @@ import java.io.IOException;
|
|||||||
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.List;
|
import java.util.*;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
@ -184,35 +182,8 @@ public final class Locales {
|
|||||||
|
|
||||||
public ResourceBundle getResourceBundle() {
|
public ResourceBundle getResourceBundle() {
|
||||||
ResourceBundle bundle = resourceBundle;
|
ResourceBundle bundle = resourceBundle;
|
||||||
|
if (resourceBundle == null)
|
||||||
if (resourceBundle == null) {
|
resourceBundle = bundle = ResourceBundle.getBundle("assets.lang.I18N", locale, Control.INSTANCE);
|
||||||
resourceBundle = bundle = ResourceBundle.getBundle("assets.lang.I18N", locale, new ResourceBundle.Control() {
|
|
||||||
@Override
|
|
||||||
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
|
||||||
if (isSimplifiedChinese(locale)) {
|
|
||||||
return List.of(
|
|
||||||
Locale.SIMPLIFIED_CHINESE,
|
|
||||||
Locale.CHINESE,
|
|
||||||
Locale.ROOT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale.getLanguage().equals("lzh")) {
|
|
||||||
return List.of(
|
|
||||||
locale,
|
|
||||||
Locale.CHINESE,
|
|
||||||
Locale.ROOT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale.getLanguage().equals("en")) {
|
|
||||||
return List.of(Locale.ROOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.getCandidateLocales(baseName, locale);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
@ -229,6 +200,35 @@ public final class Locales {
|
|||||||
return version.getSelfVersion();
|
return version.getSelfVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFcMatchPattern() {
|
||||||
|
String language = locale.getLanguage();
|
||||||
|
String country = locale.getCountry();
|
||||||
|
|
||||||
|
if (isEnglish(locale))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (isChinese(locale)) {
|
||||||
|
String lang;
|
||||||
|
String charset;
|
||||||
|
|
||||||
|
if (isSimplifiedChinese(locale)) {
|
||||||
|
lang = country.equals("SG") || country.equals("MY")
|
||||||
|
? "zh-" + country
|
||||||
|
: "zh-CN";
|
||||||
|
charset = "0x6e38,0x620f";
|
||||||
|
} else {
|
||||||
|
lang = country.equals("HK") || country.equals("MO")
|
||||||
|
? "zh-" + country
|
||||||
|
: "zh-TW";
|
||||||
|
charset = "0x904a,0x6232";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ":lang=" + lang + ":charset=" + charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return country.isEmpty() ? language : language + "-" + country;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSameLanguage(SupportedLocale other) {
|
public boolean isSameLanguage(SupportedLocale other) {
|
||||||
return this.getLocale().getLanguage().equals(other.getLocale().getLanguage())
|
return this.getLocale().getLanguage().equals(other.getLocale().getLanguage())
|
||||||
|| isChinese(this.getLocale()) && isChinese(other.getLocale());
|
|| isChinese(this.getLocale()) && isChinese(other.getLocale());
|
||||||
@ -246,4 +246,51 @@ public final class Locales {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Control extends ResourceBundle.Control {
|
||||||
|
public static final Control INSTANCE = new Control();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
||||||
|
List<Locale> candidateLocales = super.getCandidateLocales(baseName, locale);
|
||||||
|
if (isChinese(locale)) {
|
||||||
|
int chineseIndex = candidateLocales.indexOf(Locale.CHINESE);
|
||||||
|
|
||||||
|
// For "lzh" and "cmn"
|
||||||
|
if (chineseIndex < 0) {
|
||||||
|
if (!(candidateLocales instanceof ArrayList))
|
||||||
|
candidateLocales = new ArrayList<>(candidateLocales);
|
||||||
|
|
||||||
|
int i = candidateLocales.size() - 1;
|
||||||
|
while (i >= 0) {
|
||||||
|
Locale l = candidateLocales.get(i);
|
||||||
|
if (!isEnglish(l))
|
||||||
|
break;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
chineseIndex = i + 1;
|
||||||
|
candidateLocales.add(chineseIndex, Locale.CHINESE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSimplifiedChinese(locale)) {
|
||||||
|
if (!candidateLocales.contains(Locale.SIMPLIFIED_CHINESE)) {
|
||||||
|
if (!(candidateLocales instanceof ArrayList))
|
||||||
|
candidateLocales = new ArrayList<>(candidateLocales);
|
||||||
|
candidateLocales.add(chineseIndex, Locale.SIMPLIFIED_CHINESE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidateLocales.size() == 1 && candidateLocales.get(0).getLanguage().isEmpty()) {
|
||||||
|
if (!(candidateLocales instanceof ArrayList))
|
||||||
|
candidateLocales = new ArrayList<>(candidateLocales);
|
||||||
|
|
||||||
|
candidateLocales.add(0, Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidateLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
*/
|
||||||
|
public final class LocalesTest {
|
||||||
|
private static void assertCandidateLocales(String languageTag, List<String> candidateLocales) {
|
||||||
|
assertEquals(candidateLocales,
|
||||||
|
Locales.Control.INSTANCE.getCandidateLocales("", Locale.forLanguageTag(languageTag))
|
||||||
|
.stream()
|
||||||
|
.map(Locale::toLanguageTag)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCandidateLocales() {
|
||||||
|
assertCandidateLocales("zh", List.of("zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-CN", List.of("zh-Hans-CN", "zh-Hans", "zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-Hans", List.of("zh-Hans", "zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-Hant", List.of("zh-Hant", "zh-TW", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-Hans-US", List.of("zh-Hans-US", "zh-Hans", "zh-US", "zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-US", List.of("zh-US", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-TW", List.of("zh-Hant-TW", "zh-Hant", "zh-TW", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-SG", List.of("zh-Hans-SG", "zh-Hans", "zh-SG", "zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("zh-MY", List.of("zh-MY", "zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("lzh", List.of("lzh", "zh", "und"));
|
||||||
|
assertCandidateLocales("cmn", List.of("cmn", "zh-CN", "zh", "und"));
|
||||||
|
assertCandidateLocales("cmn-Hans", List.of("cmn-Hans", "cmn", "zh-CN", "zh", "und"));
|
||||||
|
|
||||||
|
assertCandidateLocales("ja", List.of("ja", "und"));
|
||||||
|
assertCandidateLocales("ja-JP", List.of("ja-JP", "ja", "und"));
|
||||||
|
|
||||||
|
assertCandidateLocales("en", List.of("en", "und"));
|
||||||
|
assertCandidateLocales("und", List.of("en", "und"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsChinese() {
|
||||||
|
assertTrue(Locales.isChinese(Locale.CHINESE));
|
||||||
|
assertTrue(Locales.isChinese(Locale.SIMPLIFIED_CHINESE));
|
||||||
|
assertTrue(Locales.isChinese(Locale.TRADITIONAL_CHINESE));
|
||||||
|
assertTrue(Locales.isChinese(Locale.forLanguageTag("lzh")));
|
||||||
|
assertTrue(Locales.isChinese(Locale.forLanguageTag("cmn")));
|
||||||
|
assertTrue(Locales.isChinese(Locale.forLanguageTag("cmn-Hans")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsSimplifiedChinese() {
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.CHINESE));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-Hans")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-Hans-US")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-SG")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-MY")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("cmn")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("cmn-Hans")));
|
||||||
|
assertTrue(Locales.isSimplifiedChinese(Locale.forLanguageTag("cmn-CN")));
|
||||||
|
|
||||||
|
assertFalse(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-Hant")));
|
||||||
|
assertFalse(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-TW")));
|
||||||
|
assertFalse(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-HK")));
|
||||||
|
assertFalse(Locales.isSimplifiedChinese(Locale.forLanguageTag("zh-MO")));
|
||||||
|
assertFalse(Locales.isSimplifiedChinese(Locale.forLanguageTag("cmn-Hant")));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user