优化对于 ISO 639-3 语言代码的支持 (#4455)
This commit is contained in:
@@ -111,36 +111,27 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String normalizedLanguageTag(Locale locale, GameVersionNumber gameVersion) {
|
private static String normalizedLanguageTag(Locale locale, GameVersionNumber gameVersion) {
|
||||||
String language = locale.getLanguage();
|
|
||||||
String region = locale.getCountry();
|
String region = locale.getCountry();
|
||||||
|
|
||||||
switch (language) {
|
return switch (LocaleUtils.getISO1Language(locale)) {
|
||||||
case "ru":
|
case "es" -> "es_ES";
|
||||||
return "ru_RU";
|
case "ja" -> "ja_JP";
|
||||||
case "uk":
|
case "ru" -> "ru_RU";
|
||||||
return "uk_UA";
|
case "uk" -> "uk_UA";
|
||||||
case "es":
|
case "zh" -> {
|
||||||
return "es_ES";
|
if ("lzh".equals(locale.getLanguage()) && gameVersion.compareTo("1.16") >= 0)
|
||||||
case "ja":
|
yield "lzh";
|
||||||
return "ja_JP";
|
|
||||||
case "lzh":
|
|
||||||
return gameVersion.compareTo("1.16") >= 0
|
|
||||||
? "lzh"
|
|
||||||
: "";
|
|
||||||
case "zh":
|
|
||||||
default:
|
|
||||||
if (LocaleUtils.isChinese(locale)) {
|
|
||||||
String script = LocaleUtils.getScript(locale);
|
|
||||||
if ("Hant".equals(script)) {
|
|
||||||
if ((region.equals("HK") || region.equals("MO") && gameVersion.compareTo("1.16") >= 0))
|
|
||||||
return "zh_HK";
|
|
||||||
return "zh_TW";
|
|
||||||
}
|
|
||||||
return "zh_CN";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
String script = LocaleUtils.getScript(locale);
|
||||||
}
|
if ("Hant".equals(script)) {
|
||||||
|
if ((region.equals("HK") || region.equals("MO") && gameVersion.compareTo("1.16") >= 0))
|
||||||
|
yield "zh_HK";
|
||||||
|
yield "zh_TW";
|
||||||
|
}
|
||||||
|
yield "zh_CN";
|
||||||
|
}
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -34,45 +34,39 @@ public final class Locales {
|
|||||||
private Locales() {
|
private Locales() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Locale getDefaultLocale() {
|
|
||||||
String language = System.getenv("HMCL_LANGUAGE");
|
|
||||||
if (StringUtils.isNotBlank(language))
|
|
||||||
return Locale.forLanguageTag(language);
|
|
||||||
else
|
|
||||||
return LocaleUtils.SYSTEM_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final SupportedLocale DEFAULT = new SupportedLocale("def", getDefaultLocale()) {
|
public static final SupportedLocale DEFAULT;
|
||||||
@Override
|
|
||||||
public boolean isDefault() {
|
static {
|
||||||
return true;
|
String language = System.getenv("HMCL_LANGUAGE");
|
||||||
}
|
DEFAULT = new SupportedLocale(true, "def",
|
||||||
};
|
StringUtils.isBlank(language) ? LocaleUtils.SYSTEM_DEFAULT : Locale.forLanguageTag(language));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* English
|
* English
|
||||||
*/
|
*/
|
||||||
public static final SupportedLocale EN = new SupportedLocale("en", Locale.ENGLISH);
|
public static final SupportedLocale EN = new SupportedLocale("en");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spanish
|
* Spanish
|
||||||
*/
|
*/
|
||||||
public static final SupportedLocale ES = new SupportedLocale("es", Locale.forLanguageTag("es"));
|
public static final SupportedLocale ES = new SupportedLocale("es");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Russian
|
* Russian
|
||||||
*/
|
*/
|
||||||
public static final SupportedLocale RU = new SupportedLocale("ru", Locale.forLanguageTag("ru"));
|
public static final SupportedLocale RU = new SupportedLocale("ru");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ukrainian
|
* Ukrainian
|
||||||
*/
|
*/
|
||||||
public static final SupportedLocale UK = new SupportedLocale("uk", Locale.forLanguageTag("uk"));
|
public static final SupportedLocale UK = new SupportedLocale("uk");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Japanese
|
* Japanese
|
||||||
*/
|
*/
|
||||||
public static final SupportedLocale JA = new SupportedLocale("ja", Locale.JAPANESE);
|
public static final SupportedLocale JA = new SupportedLocale("ja");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chinese (Simplified)
|
* Chinese (Simplified)
|
||||||
@@ -87,7 +81,7 @@ public final class Locales {
|
|||||||
/**
|
/**
|
||||||
* Wenyan (Classical Chinese)
|
* Wenyan (Classical Chinese)
|
||||||
*/
|
*/
|
||||||
public static final SupportedLocale WENYAN = new SupportedLocale("lzh", Locale.forLanguageTag("lzh"));
|
public static final SupportedLocale WENYAN = new SupportedLocale("lzh");
|
||||||
|
|
||||||
public static final List<SupportedLocale> LOCALES = List.of(DEFAULT, EN, ES, JA, RU, UK, ZH_HANS, ZH_HANT, WENYAN);
|
public static final List<SupportedLocale> LOCALES = List.of(DEFAULT, EN, ES, JA, RU, UK, ZH_HANS, ZH_HANT, WENYAN);
|
||||||
|
|
||||||
@@ -103,20 +97,30 @@ public final class Locales {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonAdapter(SupportedLocale.TypeAdapter.class)
|
@JsonAdapter(SupportedLocale.TypeAdapter.class)
|
||||||
public static class SupportedLocale {
|
public static final class SupportedLocale {
|
||||||
|
private final boolean isDefault;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private ResourceBundle resourceBundle;
|
private ResourceBundle resourceBundle;
|
||||||
private DateTimeFormatter dateTimeFormatter;
|
private DateTimeFormatter dateTimeFormatter;
|
||||||
private List<Locale> candidateLocales;
|
private List<Locale> candidateLocales;
|
||||||
|
|
||||||
SupportedLocale(String name, Locale locale) {
|
SupportedLocale(boolean isDefault, String name, Locale locale) {
|
||||||
|
this.isDefault = isDefault;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SupportedLocale(String name) {
|
||||||
|
this(false, name, Locale.forLanguageTag(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
SupportedLocale(String name, Locale locale) {
|
||||||
|
this(false, name, locale);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDefault() {
|
public boolean isDefault() {
|
||||||
return false;
|
return isDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -128,9 +132,6 @@ public final class Locales {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayName(SupportedLocale inLocale) {
|
public String getDisplayName(SupportedLocale inLocale) {
|
||||||
if (inLocale.locale.getLanguage().equals("lzh"))
|
|
||||||
inLocale = ZH_HANT;
|
|
||||||
|
|
||||||
if (isDefault()) {
|
if (isDefault()) {
|
||||||
try {
|
try {
|
||||||
return inLocale.getResourceBundle().getString("lang.default");
|
return inLocale.getResourceBundle().getString("lang.default");
|
||||||
@@ -140,17 +141,32 @@ public final class Locales {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Locale inJavaLocale = inLocale.getLocale();
|
||||||
|
if (LocaleUtils.isISO3Language(inJavaLocale.getLanguage())) {
|
||||||
|
String iso1 = LocaleUtils.getISO1Language(inJavaLocale);
|
||||||
|
if (LocaleUtils.isISO1Language(iso1)) {
|
||||||
|
Locale.Builder builder = new Locale.Builder()
|
||||||
|
.setLocale(inJavaLocale)
|
||||||
|
.setLanguage(iso1);
|
||||||
|
|
||||||
|
if (inJavaLocale.getScript().isEmpty())
|
||||||
|
builder.setScript(LocaleUtils.getScript(inJavaLocale));
|
||||||
|
|
||||||
|
inJavaLocale = builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.locale.getLanguage().equals("lzh")) {
|
if (this.locale.getLanguage().equals("lzh")) {
|
||||||
if (LocaleUtils.isChinese(inLocale.locale))
|
if (inJavaLocale.getLanguage().equals("zh"))
|
||||||
return "文言";
|
return "文言";
|
||||||
|
|
||||||
String name = locale.getDisplayName(inLocale.getLocale());
|
String name = locale.getDisplayName(inJavaLocale);
|
||||||
return name.equals("lzh") || name.equals("Literary Chinese")
|
return name.equals("lzh") || name.equals("Literary Chinese")
|
||||||
? "Chinese (Classical)"
|
? "Chinese (Classical)"
|
||||||
: name;
|
: name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return locale.getDisplayName(inLocale.getLocale());
|
return locale.getDisplayName(inJavaLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceBundle getResourceBundle() {
|
public ResourceBundle getResourceBundle() {
|
||||||
@@ -237,8 +253,8 @@ public final class Locales {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSameLanguage(SupportedLocale other) {
|
public boolean isSameLanguage(SupportedLocale other) {
|
||||||
return (this.getLocale().getLanguage().equals(other.getLocale().getLanguage()))
|
return LocaleUtils.getISO1Language(this.getLocale())
|
||||||
|| (LocaleUtils.isChinese(this.getLocale()) && LocaleUtils.isChinese(other.getLocale()));
|
.equals(LocaleUtils.getISO1Language(other.getLocale()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import java.util.ResourceBundle;
|
|||||||
/// - For all Chinese locales, `zh-CN` is always added to the candidate list. If `zh-Hans` already exists in the candidate list,
|
/// - For all Chinese locales, `zh-CN` is always added to the candidate list. If `zh-Hans` already exists in the candidate list,
|
||||||
/// `zh-CN` is inserted before `zh`; otherwise, it is inserted after `zh`.
|
/// `zh-CN` is inserted before `zh`; otherwise, it is inserted after `zh`.
|
||||||
/// - For all Traditional Chinese locales, `zh-TW` is always added to the candidate list (before `zh`).
|
/// - For all Traditional Chinese locales, `zh-TW` is always added to the candidate list (before `zh`).
|
||||||
/// - For all Chinese variants (such as `lzh`, `cmn`, `yue`, etc.), a candidate list with the language code replaced by `zh`
|
/// - For all [supported][LocaleUtils#toISO1Language(String)] ISO 639-3 language code (such as `eng`, `zho`, `lzh`, etc.),
|
||||||
/// is added to the end of the candidate list.
|
/// a candidate list with the language code replaced by the ISO 639-1 (Macro)language code is added to the end of the candidate list.
|
||||||
///
|
///
|
||||||
/// @author Glavo
|
/// @author Glavo
|
||||||
public class DefaultResourceBundleControl extends ResourceBundle.Control {
|
public class DefaultResourceBundleControl extends ResourceBundle.Control {
|
||||||
@@ -52,53 +52,57 @@ public class DefaultResourceBundleControl extends ResourceBundle.Control {
|
|||||||
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
||||||
if (locale.getLanguage().isEmpty())
|
if (locale.getLanguage().isEmpty())
|
||||||
return getCandidateLocales(baseName, Locale.ENGLISH);
|
return getCandidateLocales(baseName, Locale.ENGLISH);
|
||||||
|
else if (LocaleUtils.isChinese(locale)) {
|
||||||
if (LocaleUtils.isChinese(locale)) {
|
|
||||||
String language = locale.getLanguage();
|
|
||||||
String script = locale.getScript();
|
String script = locale.getScript();
|
||||||
|
|
||||||
if (script.isEmpty()) {
|
if (script.isEmpty()) {
|
||||||
script = LocaleUtils.getScript(locale);
|
script = LocaleUtils.getScript(locale);
|
||||||
locale = new Locale.Builder()
|
if (!script.isEmpty())
|
||||||
.setLocale(locale)
|
return getCandidateLocales(baseName, new Locale.Builder()
|
||||||
.setScript(script)
|
.setLocale(locale)
|
||||||
.build();
|
.setScript(script)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Locale> locales = super.getCandidateLocales("", locale);
|
String language = locale.getLanguage();
|
||||||
|
|
||||||
if (language.equals("zh")) {
|
List<Locale> locales = super.getCandidateLocales(baseName, locale);
|
||||||
if (locales.contains(LocaleUtils.LOCALE_ZH_HANT) && !locales.contains(Locale.TRADITIONAL_CHINESE)) {
|
|
||||||
locales = ensureEditable(locales);
|
|
||||||
int chineseIdx = locales.indexOf(Locale.CHINESE);
|
|
||||||
if (chineseIdx >= 0)
|
|
||||||
locales.add(chineseIdx, Locale.TRADITIONAL_CHINESE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!locales.contains(Locale.SIMPLIFIED_CHINESE)) {
|
// Is ISO 639-3 language tag
|
||||||
int chineseIdx = locales.indexOf(Locale.CHINESE);
|
if (language.length() == 3) {
|
||||||
|
String iso1 = LocaleUtils.toISO1Language(locale.getLanguage());
|
||||||
|
|
||||||
if (chineseIdx >= 0) {
|
if (iso1.length() == 2) {
|
||||||
locales = ensureEditable(locales);
|
|
||||||
if (locales.contains(LocaleUtils.LOCALE_ZH_HANS))
|
|
||||||
locales.add(chineseIdx, Locale.SIMPLIFIED_CHINESE);
|
|
||||||
else
|
|
||||||
locales.add(chineseIdx + 1, Locale.SIMPLIFIED_CHINESE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
locales = ensureEditable(locales);
|
locales = ensureEditable(locales);
|
||||||
locales.removeIf(it -> !it.getLanguage().equals(language));
|
locales.removeIf(it -> !it.getLanguage().equals(language));
|
||||||
|
|
||||||
locales.addAll(getCandidateLocales("", new Locale.Builder()
|
locales.addAll(getCandidateLocales(baseName, new Locale.Builder()
|
||||||
.setLocale(locale)
|
.setLocale(locale)
|
||||||
.setLanguage("zh")
|
.setLanguage(iso1)
|
||||||
.build()));
|
.build()));
|
||||||
}
|
}
|
||||||
|
} else if (language.equals("zh")) {
|
||||||
|
if (locales.contains(LocaleUtils.LOCALE_ZH_HANT) && !locales.contains(Locale.TRADITIONAL_CHINESE)) {
|
||||||
|
locales = ensureEditable(locales);
|
||||||
|
int chineseIdx = locales.indexOf(Locale.CHINESE);
|
||||||
|
if (chineseIdx >= 0)
|
||||||
|
locales.add(chineseIdx, Locale.TRADITIONAL_CHINESE);
|
||||||
|
}
|
||||||
|
|
||||||
return locales;
|
if (!locales.contains(Locale.SIMPLIFIED_CHINESE)) {
|
||||||
|
int chineseIdx = locales.indexOf(Locale.CHINESE);
|
||||||
|
|
||||||
|
if (chineseIdx >= 0) {
|
||||||
|
locales = ensureEditable(locales);
|
||||||
|
if (locales.contains(LocaleUtils.LOCALE_ZH_HANS))
|
||||||
|
locales.add(chineseIdx, Locale.SIMPLIFIED_CHINESE);
|
||||||
|
else
|
||||||
|
locales.add(chineseIdx + 1, Locale.SIMPLIFIED_CHINESE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getCandidateLocales(baseName, locale);
|
return locales;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,20 @@ public final class LocaleUtils {
|
|||||||
: locale.stripExtensions().toLanguageTag();
|
: locale.stripExtensions().toLanguageTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isISO1Language(String language) {
|
||||||
|
return language.length() == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isISO3Language(String language) {
|
||||||
|
return language.length() == 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull String getISO1Language(Locale locale) {
|
||||||
|
String language = locale.getLanguage();
|
||||||
|
if (language.isEmpty()) return "en";
|
||||||
|
return isISO3Language(language) ? toISO1Language(language) : language;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the script of the locale. If the script is empty and the language is Chinese,
|
/// Get the script of the locale. If the script is empty and the language is Chinese,
|
||||||
/// the script will be inferred based on the language, the region and the variant.
|
/// the script will be inferred based on the language, the region and the variant.
|
||||||
public static @NotNull String getScript(Locale locale) {
|
public static @NotNull String getScript(Locale locale) {
|
||||||
@@ -164,20 +178,29 @@ public final class LocaleUtils {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
|
/// Try to convert ISO 639-3 language codes to ISO 639-1 language codes.
|
||||||
|
public static String toISO1Language(String languageTag) {
|
||||||
|
return switch (languageTag) {
|
||||||
|
case "eng" -> "en";
|
||||||
|
case "spa" -> "es";
|
||||||
|
case "jpa" -> "ja";
|
||||||
|
case "rus" -> "ru";
|
||||||
|
case "ukr" -> "uk";
|
||||||
|
case "zho", "cmn", "lzh", "cdo", "cjy", "cpx", "czh",
|
||||||
|
"gan", "hak", "hsn", "mnp", "nan", "wuu", "yue" -> "zh";
|
||||||
|
default -> languageTag;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isEnglish(Locale locale) {
|
public static boolean isEnglish(Locale locale) {
|
||||||
return locale.getLanguage().equals("en") || locale.getLanguage().isEmpty();
|
return "en".equals(getISO1Language(locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Set<String> CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO");
|
public static final Set<String> CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO");
|
||||||
public static final Set<String> CHINESE_LATN_VARIANTS = Set.of("pinyin", "wadegile", "tongyong");
|
public static final Set<String> CHINESE_LATN_VARIANTS = Set.of("pinyin", "wadegile", "tongyong");
|
||||||
public static final Set<String> CHINESE_LANGUAGES = Set.of(
|
|
||||||
"zh",
|
|
||||||
"zho", "cmn", "lzh", "cdo", "cjy", "cpx", "czh",
|
|
||||||
"gan", "hak", "hsn", "mnp", "nan", "wuu", "yue"
|
|
||||||
);
|
|
||||||
|
|
||||||
public static boolean isChinese(Locale locale) {
|
public static boolean isChinese(Locale locale) {
|
||||||
return CHINESE_LANGUAGES.contains(locale.getLanguage());
|
return "zh".equals(getISO1Language(locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocaleUtils() {
|
private LocaleUtils() {
|
||||||
|
|||||||
@@ -71,8 +71,23 @@ public final class LocaleUtilsTest {
|
|||||||
|
|
||||||
assertCandidateLocales("ja", List.of("ja", "und"));
|
assertCandidateLocales("ja", List.of("ja", "und"));
|
||||||
assertCandidateLocales("ja-JP", List.of("ja-JP", "ja", "und"));
|
assertCandidateLocales("ja-JP", List.of("ja-JP", "ja", "und"));
|
||||||
|
assertCandidateLocales("jpa", List.of("jpa", "ja", "und"));
|
||||||
|
assertCandidateLocales("jpa-JP", List.of("jpa-JP", "jpa", "ja-JP", "ja", "und"));
|
||||||
|
|
||||||
assertCandidateLocales("en", List.of("en", "und"));
|
assertCandidateLocales("en", List.of("en", "und"));
|
||||||
|
assertCandidateLocales("en-US", List.of("en-US", "en", "und"));
|
||||||
|
assertCandidateLocales("eng", List.of("eng", "en", "und"));
|
||||||
|
assertCandidateLocales("eng-US", List.of("eng-US", "eng", "en-US", "en", "und"));
|
||||||
|
|
||||||
|
assertCandidateLocales("es", List.of("es", "und"));
|
||||||
|
assertCandidateLocales("spa", List.of("spa", "es", "und"));
|
||||||
|
|
||||||
|
assertCandidateLocales("ru", List.of("ru", "und"));
|
||||||
|
assertCandidateLocales("rus", List.of("rus", "ru", "und"));
|
||||||
|
|
||||||
|
assertCandidateLocales("uk", List.of("uk", "und"));
|
||||||
|
assertCandidateLocales("ukr", List.of("ukr", "uk", "und"));
|
||||||
|
|
||||||
assertCandidateLocales("und", List.of("en", "und"));
|
assertCandidateLocales("und", List.of("en", "und"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user