通过解析 IANA 语言子标签注册表增强本地化功能 (#4675)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -30,7 +30,7 @@ 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,
|
||||
/// `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 [supported][LocaleUtils#mapToISO2Language(String)] ISO 639-3 language code (such as `eng`, `zho`, `lzh`, etc.),
|
||||
/// - For all supported ISO 639-3 language code (such as `eng`, `zho`, `lzh`, etc.),
|
||||
/// 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
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
||||
@@ -29,6 +27,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
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.time.Duration;
|
||||
@@ -48,66 +48,74 @@ public final class LocaleUtils {
|
||||
|
||||
public static final Locale SYSTEM_DEFAULT = Locale.getDefault();
|
||||
|
||||
public static final boolean IS_CHINA_MAINLAND = isChinaMainland();
|
||||
|
||||
private static boolean isChinaMainland() {
|
||||
if ("Asia/Shanghai".equals(ZoneId.systemDefault().getId()))
|
||||
return true;
|
||||
|
||||
// Check if the time zone is UTC+8
|
||||
if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {
|
||||
if ("CN".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))
|
||||
return true;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
|
||||
Kernel32 kernel32 = Kernel32.INSTANCE;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
|
||||
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final Locale LOCALE_ZH_HANS = Locale.forLanguageTag("zh-Hans");
|
||||
public static final Locale LOCALE_ZH_HANT = Locale.forLanguageTag("zh-Hant");
|
||||
|
||||
public static final String DEFAULT_LANGUAGE_KEY = "default";
|
||||
|
||||
private static final Map<String, String> subLanguageToParent = new HashMap<>();
|
||||
private static final Map<String, String> iso3To2 = new HashMap<>();
|
||||
private static final Set<String> rtl = new HashSet<>();
|
||||
private static final Map<String, String> PARENT_LANGUAGE = loadCSV("sublanguages.csv");
|
||||
private static final Map<String, String> NORMALIZED_TAG = loadCSV("language_aliases.csv");
|
||||
private static final Map<String, String> DEFAULT_SCRIPT = loadCSV("default_script.csv");
|
||||
private static final Map<String, String> PREFERRED_LANGUAGE = Map.of("zh", "cmn");
|
||||
private static final Set<String> RTL_SCRIPTS = Set.of("Qabs", "Arab", "Hebr");
|
||||
private static final Set<String> CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO");
|
||||
|
||||
static {
|
||||
try {
|
||||
for (String line : Lang.toIterable(IOUtils.readFullyAsString(LocaleUtils.class.getResourceAsStream("/assets/lang/sublanguages.csv")).lines())) {
|
||||
if (line.startsWith("#") || line.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] languages = line.split(",");
|
||||
if (languages.length < 2) {
|
||||
LOG.warning("Invalid line in sublanguages.csv: " + line);
|
||||
continue;
|
||||
}
|
||||
|
||||
String parent = languages[0];
|
||||
for (int i = 1; i < languages.length; i++) {
|
||||
subLanguageToParent.put(languages[i], parent);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load sublanguages.csv", e);
|
||||
/// Load CSV files located in `/assets/lang/`.
|
||||
/// Each line in these files contains at least two elements.
|
||||
///
|
||||
/// For example, if a file contains `value0,value1,value2`, the return value will be `{value1=value0, value2=value0}`.
|
||||
private static Map<String, String> loadCSV(String fileName) {
|
||||
InputStream resource = LocaleUtils.class.getResourceAsStream("/assets/lang/" + fileName);
|
||||
if (resource == null) {
|
||||
LOG.warning("Can't find file: " + fileName);
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
try {
|
||||
// Line Format: (?<iso2>[a-z]{2}),(?<iso3>[a-z]{3})
|
||||
for (String line : Lang.toIterable(IOUtils.readFullyAsString(LocaleUtils.class.getResourceAsStream("/assets/lang/iso_languages.csv")).lines())) {
|
||||
if (line.startsWith("#") || line.isBlank()) {
|
||||
continue;
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
try (resource) {
|
||||
new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {
|
||||
if (line.startsWith("#") || line.isBlank())
|
||||
return;
|
||||
|
||||
String[] items = line.split(",");
|
||||
if (items.length < 2) {
|
||||
LOG.warning("Invalid line in " + fileName + ": " + line);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] parts = line.split(",", 3);
|
||||
if (parts.length != 2) {
|
||||
LOG.warning("Invalid line in iso_languages.csv: " + line);
|
||||
continue;
|
||||
String parent = items[0];
|
||||
for (int i = 1; i < items.length; i++) {
|
||||
result.put(items[i], parent);
|
||||
}
|
||||
|
||||
iso3To2.put(parts[1], parts[0]);
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load iso_languages.csv", e);
|
||||
LOG.warning("Failed to load " + fileName, e);
|
||||
}
|
||||
|
||||
try {
|
||||
for (String line : Lang.toIterable(IOUtils.readFullyAsString(LocaleUtils.class.getResourceAsStream("/assets/lang/rtl.txt")).lines())) {
|
||||
if (line.startsWith("#") || line.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
rtl.add(line.trim());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load rtl.txt", e);
|
||||
}
|
||||
return Map.copyOf(result);
|
||||
}
|
||||
|
||||
private static Locale getInstance(String language, String script, String region,
|
||||
@@ -130,6 +138,31 @@ public final class LocaleUtils {
|
||||
: locale.stripExtensions().toLanguageTag();
|
||||
}
|
||||
|
||||
public static boolean isEnglish(Locale locale) {
|
||||
return "en".equals(getRootLanguage(locale));
|
||||
}
|
||||
|
||||
public static boolean isChinese(Locale locale) {
|
||||
return "zh".equals(getRootLanguage(locale));
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
/// Normalize the language code to the code in the IANA Language Subtag Registry.
|
||||
/// Typically, it normalizes ISO 639 alpha-3 codes to ISO 639 alpha-2 codes.
|
||||
public static @NotNull String normalizeLanguage(String language) {
|
||||
return language.isEmpty()
|
||||
? "en"
|
||||
: NORMALIZED_TAG.getOrDefault(language, language);
|
||||
}
|
||||
|
||||
/// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage),
|
||||
/// return the macrolanguage; otherwise, return `null`.
|
||||
public static @Nullable String getParentLanguage(String language) {
|
||||
return PARENT_LANGUAGE.get(language);
|
||||
}
|
||||
|
||||
/// @see #getRootLanguage(String)
|
||||
public static @NotNull String getRootLanguage(Locale locale) {
|
||||
return getRootLanguage(locale.getLanguage());
|
||||
}
|
||||
@@ -140,54 +173,54 @@ public final class LocaleUtils {
|
||||
/// - If `language` is empty, return `en`;
|
||||
/// - Otherwise, return the `language`.
|
||||
public static @NotNull String getRootLanguage(String language) {
|
||||
if (language.isEmpty()) return "en";
|
||||
if (language.length() <= 2)
|
||||
return language;
|
||||
|
||||
String iso2 = mapToISO2Language(language);
|
||||
if (iso2 != null)
|
||||
return iso2;
|
||||
language = normalizeLanguage(language);
|
||||
|
||||
String parent = getParentLanguage(language);
|
||||
return parent != null ? parent : language;
|
||||
}
|
||||
|
||||
/// If `language` is a macrolanguage, try to map it to the most commonly used individual language.
|
||||
///
|
||||
/// For example, if `language` is `zh`, this method will return `cmn`.
|
||||
public static @NotNull String getPreferredLanguage(String language) {
|
||||
language = normalizeLanguage(language);
|
||||
return PREFERRED_LANGUAGE.getOrDefault(language, language);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
public static @NotNull String getScript(Locale locale) {
|
||||
if (locale.getScript().isEmpty()) {
|
||||
if (isEnglish(locale)) {
|
||||
if ("UD".equals(locale.getCountry())) {
|
||||
return "Qabs";
|
||||
}
|
||||
if (!locale.getVariant().isEmpty()) {
|
||||
String script = DEFAULT_SCRIPT.get(locale.getVariant());
|
||||
if (script != null)
|
||||
return script;
|
||||
}
|
||||
|
||||
if (isChinese(locale)) {
|
||||
if (CHINESE_LATN_VARIANTS.contains(locale.getVariant()))
|
||||
return "Latn";
|
||||
if (locale.getLanguage().equals("lzh") || CHINESE_TRADITIONAL_REGIONS.contains(locale.getCountry()))
|
||||
return "Hant";
|
||||
else
|
||||
return "Hans";
|
||||
if ("UD".equals(locale.getCountry())) {
|
||||
return "Qabs";
|
||||
}
|
||||
|
||||
String script = DEFAULT_SCRIPT.get(normalizeLanguage(locale.getLanguage()));
|
||||
if (script != null)
|
||||
return script;
|
||||
|
||||
if (isChinese(locale)) {
|
||||
return CHINESE_TRADITIONAL_REGIONS.contains(locale.getCountry())
|
||||
? "Hant"
|
||||
: "Hans";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
return locale.getScript();
|
||||
}
|
||||
|
||||
public static @NotNull TextDirection getTextDirection(Locale locale) {
|
||||
TextDirection direction = rtl.contains(getRootLanguage(locale))
|
||||
return RTL_SCRIPTS.contains(getScript(locale))
|
||||
? TextDirection.RIGHT_TO_LEFT
|
||||
: TextDirection.LEFT_TO_RIGHT;
|
||||
|
||||
if ("Qabs".equals(getScript(locale))) {
|
||||
direction = switch (direction) {
|
||||
case RIGHT_TO_LEFT -> TextDirection.LEFT_TO_RIGHT;
|
||||
case LEFT_TO_RIGHT -> TextDirection.RIGHT_TO_LEFT;
|
||||
};
|
||||
}
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
private static final ConcurrentMap<Locale, List<Locale>> CANDIDATE_LOCALES = new ConcurrentHashMap<>();
|
||||
@@ -196,13 +229,8 @@ public final class LocaleUtils {
|
||||
return CANDIDATE_LOCALES.computeIfAbsent(locale, LocaleUtils::createCandidateLocaleList);
|
||||
}
|
||||
|
||||
// -------------
|
||||
|
||||
private static List<Locale> createCandidateLocaleList(Locale locale) {
|
||||
String language = locale.getLanguage();
|
||||
if (language.isEmpty())
|
||||
return List.of(Locale.ENGLISH, Locale.ROOT);
|
||||
|
||||
String language = getPreferredLanguage(locale.getLanguage());
|
||||
String script = getScript(locale);
|
||||
String region = locale.getCountry();
|
||||
List<String> variants = locale.getVariant().isEmpty()
|
||||
@@ -211,18 +239,7 @@ public final class LocaleUtils {
|
||||
|
||||
ArrayList<Locale> result = new ArrayList<>();
|
||||
do {
|
||||
String currentLanguage;
|
||||
|
||||
if (language.length() <= 2) {
|
||||
currentLanguage = language;
|
||||
} else {
|
||||
String iso2 = mapToISO2Language(language);
|
||||
currentLanguage = iso2 != null
|
||||
? iso2
|
||||
: language;
|
||||
}
|
||||
|
||||
addCandidateLocales(result, currentLanguage, script, region, variants);
|
||||
addCandidateLocales(result, language, script, region, variants);
|
||||
} while ((language = getParentLanguage(language)) != null);
|
||||
|
||||
result.add(Locale.ROOT);
|
||||
@@ -367,54 +384,6 @@ public final class LocaleUtils {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
/// Map ISO 639 alpha-3 language codes to ISO 639 alpha-2 language codes.
|
||||
/// Returns `null` if there is no corresponding ISO 639 alpha-2 language code.
|
||||
public static @Nullable String mapToISO2Language(String iso3Language) {
|
||||
return iso3To2.get(iso3Language);
|
||||
}
|
||||
|
||||
/// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage),
|
||||
/// return the macrolanguage; otherwise, return `null`.
|
||||
public static @Nullable String getParentLanguage(String language) {
|
||||
return subLanguageToParent.get(language);
|
||||
}
|
||||
|
||||
public static boolean isEnglish(Locale locale) {
|
||||
return "en".equals(getRootLanguage(locale));
|
||||
}
|
||||
|
||||
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 boolean isChinese(Locale locale) {
|
||||
return "zh".equals(getRootLanguage(locale));
|
||||
}
|
||||
|
||||
public static final boolean IS_CHINA_MAINLAND = isChinaMainland();
|
||||
|
||||
private static boolean isChinaMainland() {
|
||||
if ("Asia/Shanghai".equals(ZoneId.systemDefault().getId()))
|
||||
return true;
|
||||
|
||||
// Check if the time zone is UTC+8
|
||||
if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {
|
||||
if ("CN".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))
|
||||
return true;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
|
||||
Kernel32 kernel32 = Kernel32.INSTANCE;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
|
||||
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private LocaleUtils() {
|
||||
}
|
||||
}
|
||||
|
||||
29
HMCLCore/src/main/resources/assets/lang/default_script.csv
Normal file
29
HMCLCore/src/main/resources/assets/lang/default_script.csv
Normal file
@@ -0,0 +1,29 @@
|
||||
Arab,ar,fa,ps,ur
|
||||
Armn,hy
|
||||
Beng,as,bn
|
||||
Blis,zbl
|
||||
Cyrl,ab,be,bg,kk,mk,ru,uk
|
||||
Deva,hi,mr,ne,kok,mai
|
||||
Ethi,am,ti
|
||||
Geor,ka
|
||||
Grek,el
|
||||
Gujr,gu
|
||||
Guru,pa
|
||||
Hant,lzh
|
||||
Hebr,he,yi
|
||||
Jpan,ja
|
||||
Khmr,km
|
||||
Knda,kn
|
||||
Kore,ko
|
||||
Laoo,lo
|
||||
Latn,af,ay,bs,ca,ch,cs,cy,da,de,en,eo,es,et,eu,fi,fj,fo,fr,fy,ga,gl,gn,gv,hr,ht,hu,id,is,it,kl,la,lb,ln,lt,lv,mg,mh,ms,mt,na,nb,nd,nl,nn,no,nr,ny,om,pl,pt,qu,rm,rn,ro,rw,sg,sk,sl,sm,so,sq,ss,st,sv,sw,tl,tn,to,tr,ts,ve,vi,xh,zu,dsb,frr,frs,gsw,hsb,men,nds,niu,nso,tem,tkl,tmh,tpi,tvl,tailo,pinyin,hepburn,pehoeji,tongyong,wadegile
|
||||
Mlym,ml
|
||||
Mymr,my
|
||||
Nkoo,nqo
|
||||
Orya,or
|
||||
Sinh,si
|
||||
Taml,ta
|
||||
Telu,te
|
||||
Thaa,dv
|
||||
Thai,th
|
||||
Tibt,dz
|
||||
|
@@ -73,7 +73,6 @@ io,ido
|
||||
is,isl
|
||||
it,ita
|
||||
iu,iku
|
||||
iw,heb
|
||||
ja,jpn
|
||||
ji,yid
|
||||
jv,jav
|
||||
|
@@ -1,6 +0,0 @@
|
||||
ar
|
||||
fa
|
||||
he
|
||||
ps
|
||||
ur
|
||||
yi
|
||||
@@ -1 +1,63 @@
|
||||
zh,cdo,cjy,cmn,cnp,cpx,csp,czh,czo,gan,hak,hnm,hsn,luh,lzh,mnp,nan,sjc,wuu,yue
|
||||
ak,tw,fat
|
||||
ar,aao,abh,abv,acm,acq,acw,acx,acy,adf,aeb,aec,afb,apc,apd,arb,arq,ars,ary,arz,auz,avl,ayh,ayl,ayn,ayp,pga,shu,ssh
|
||||
ay,ayc,ayr
|
||||
az,azb,azj
|
||||
cr,crj,crk,crl,crm,csw,cwd
|
||||
et,ekk,vro
|
||||
fa,pes,prs
|
||||
ff,ffm,fub,fuc,fue,fuf,fuh,fui,fuq,fuv
|
||||
gn,gnw,gug,gui,gun,nhd
|
||||
ik,esi,esk
|
||||
iu,ike,ikt
|
||||
kg,kng,kwy,ldi
|
||||
kr,kby,knc,krt
|
||||
ku,ckb,kmr,sdh
|
||||
kv,koi,kpv
|
||||
lv,ltg,lvs
|
||||
mg,bhr,bmm,bzc,msh,plt,skg,tdx,tkg,txy,xmv,xmw
|
||||
mn,khk,mvf
|
||||
ms,id,bjn,btj,bve,bvu,coa,dup,hji,jak,jax,kvb,kvr,kxd,lce,lcf,liw,max,meo,mfa,mfb,min,mqg,msi,mui,orn,ors,pel,pse,tmw,urk,vkk,vkt,xmm,zlm,zmi,zsm
|
||||
ne,dty,npi
|
||||
no,nb,nn
|
||||
oj,ciw,ojb,ojc,ojg,ojs,ojw,otw
|
||||
om,gax,gaz,hae,orc
|
||||
or,ory,spv
|
||||
ps,pbt,pbu,pst
|
||||
qu,qub,qud,quf,qug,quh,quk,qul,qup,qur,qus,quw,qux,quy,quz,qva,qvc,qve,qvh,qvi,qvj,qvl,qvm,qvn,qvo,qvp,qvs,qvw,qvz,qwa,qwc,qwh,qws,qxa,qxc,qxh,qxl,qxn,qxo,qxp,qxr,qxt,qxu,qxw
|
||||
sa,cls,vsn
|
||||
sc,sdc,sdn,src,sro
|
||||
sh,bs,hr,sr,cnr
|
||||
sq,aae,aat,aln,als
|
||||
sw,swc,swh
|
||||
uz,uzn,uzs
|
||||
yi,ydd,yih
|
||||
za,zch,zeh,zgb,zgm,zgn,zhd,zhn,zlj,zln,zlq,zqe,zyb,zyg,zyj,zyn,zzj
|
||||
zh,cdo,cjy,cmn,cnp,cpx,csp,czh,czo,gan,hak,hnm,hsn,luh,lzh,mnp,nan,sjc,wuu,yue
|
||||
bal,bcc,bgn,bgp
|
||||
bik,bcl,bln,bto,cts,fbl,lbl,rbl,ubl
|
||||
bnc,ebk,lbk,obk,rbk,vbk
|
||||
bua,bxm,bxr,bxu
|
||||
chm,mhr,mrj
|
||||
del,umu,unm
|
||||
den,scs,xsl
|
||||
din,dib,dik,dip,diw,dks
|
||||
doi,dgo,xnr
|
||||
gba,bdt,gbp,gbq,gmm,gso,gya
|
||||
gon,esg,gno,wsg
|
||||
grb,gbo,gec,grj,grv,gry
|
||||
hai,hax,hdn
|
||||
hmn,cqd,hea,hma,hmc,hmd,hme,hmg,hmh,hmi,hmj,hml,hmm,hmp,hmq,hms,hmw,hmy,hmz,hnj,hrm,huj,mmr,muq,mww,sfm
|
||||
jrb,aju,jye,yhd,yud
|
||||
kln,enb,eyo,niq,oki,pko,sgc,spy,tec,tuy
|
||||
kok,gom,knn
|
||||
kpe,gkp,xpe
|
||||
lah,hnd,hno,jat,phr,pnb,skr,xhe
|
||||
luy,bxk,ida,lkb,lko,lks,lri,lrm,lsm,lto,lts,lwg,nle,nyd,rag
|
||||
man,emk,mku,mlq,mnk,msc,mwk
|
||||
mwr,dhd,mtr,mve,rwr,swv,wry
|
||||
raj,bgq,gda,gju,hoj,mup,wbr
|
||||
rom,rmc,rmf,rml,rmn,rmo,rmw,rmy
|
||||
syr,aii,cld
|
||||
tmh,taq,thv,thz,ttq
|
||||
zap,zaa,zab,zac,zad,zae,zaf,zai,zam,zao,zaq,zar,zas,zat,zav,zaw,zax,zca,zcd,zoo,zpa,zpb,zpc,zpd,zpe,zpf,zpg,zph,zpi,zpj,zpk,zpl,zpm,zpn,zpo,zpp,zpq,zpr,zps,zpt,zpu,zpv,zpw,zpx,zpy,zpz,zsr,zte,ztg,ztl,ztm,ztn,ztp,ztq,zts,ztt,ztu,ztx,zty
|
||||
zza,diq,kiu
|
||||
|
||||
|
@@ -28,7 +28,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@@ -41,55 +40,76 @@ public final class LocaleUtilsTest {
|
||||
LocaleUtils.getCandidateLocales(Locale.forLanguageTag(languageTag))
|
||||
.stream()
|
||||
.map(Locale::toLanguageTag)
|
||||
.collect(Collectors.toList()));
|
||||
.toList());
|
||||
}
|
||||
|
||||
private static void assertCandidateLocalesEquals(String l1, String l2) {
|
||||
assertEquals(
|
||||
LocaleUtils.getCandidateLocales(Locale.forLanguageTag(l1)),
|
||||
LocaleUtils.getCandidateLocales(Locale.forLanguageTag(l2))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCandidateLocales() {
|
||||
assertCandidateLocales("zh", List.of("zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-CN", List.of("zh-Hans-CN", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-SG", List.of("zh-Hans-SG", "zh-Hans", "zh-SG", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-MY", List.of("zh-Hans-MY", "zh-Hans", "zh-MY", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-US", List.of("zh-Hans-US", "zh-Hans", "zh-US", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-TW", List.of("zh-Hant-TW", "zh-Hant", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-HK", List.of("zh-Hant-HK", "zh-Hant", "zh-HK", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-MO", List.of("zh-Hant-MO", "zh-Hant", "zh-MO", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-Hans", List.of("zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-Hant", List.of("zh-Hant", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-Hans-US", List.of("zh-Hans-US", "zh-Hans", "zh-US", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-Hant-CN", List.of("zh-Hant-CN", "zh-Hant", "zh-CN", "zh-TW", "zh", "und"));
|
||||
assertCandidateLocales("zh-Hans-TW", List.of("zh-Hans-TW", "zh-Hans", "zh-TW", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-Latn", List.of("zh-Latn", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-Latn-CN", List.of("zh-Latn-CN", "zh-Latn", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-pinyin", List.of("zh-Latn-pinyin", "zh-Latn", "zh-pinyin", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zho", List.of("zh-Hans", "zh-CN", "zh", "und"));
|
||||
// English
|
||||
|
||||
assertCandidateLocales("en", List.of("en-Latn", "en", "und"));
|
||||
assertCandidateLocales("en-US", List.of("en-Latn-US", "en-Latn", "en-US", "en", "und"));
|
||||
assertCandidateLocalesEquals("en", "eng");
|
||||
assertCandidateLocalesEquals("en-US", "eng-US");
|
||||
assertCandidateLocalesEquals("und", "en");
|
||||
|
||||
// Spanish
|
||||
|
||||
assertCandidateLocales("es", List.of("es-Latn", "es", "und"));
|
||||
assertCandidateLocalesEquals("es", "spa");
|
||||
|
||||
// Japanese
|
||||
|
||||
assertCandidateLocales("ja", List.of("ja-Jpan", "ja", "und"));
|
||||
assertCandidateLocales("ja-JP", List.of("ja-Jpan-JP", "ja-Jpan", "ja-JP", "ja", "und"));
|
||||
assertCandidateLocalesEquals("ja", "jpn");
|
||||
assertCandidateLocalesEquals("ja-JP", "jpn-JP");
|
||||
|
||||
// Russian
|
||||
|
||||
assertCandidateLocales("ru", List.of("ru-Cyrl", "ru", "und"));
|
||||
assertCandidateLocalesEquals("ru", "rus");
|
||||
|
||||
// Ukrainian
|
||||
|
||||
assertCandidateLocales("uk", List.of("uk-Cyrl", "uk", "und"));
|
||||
assertCandidateLocalesEquals("uk", "ukr");
|
||||
|
||||
// Chinese
|
||||
|
||||
assertCandidateLocales("zh", List.of("cmn-Hans", "cmn", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-CN", List.of("cmn-Hans-CN", "cmn-Hans", "cmn-CN", "cmn", "zh-Hans-CN", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("zh-SG", List.of("cmn-Hans-SG", "cmn-Hans", "cmn-SG", "cmn", "zh-Hans-SG", "zh-Hans", "zh-SG", "zh-CN", "zh", "und"));
|
||||
|
||||
assertCandidateLocales("zh-TW", List.of("cmn-Hant-TW", "cmn-Hant", "cmn-TW", "cmn", "zh-Hant-TW", "zh-Hant", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-HK", List.of("cmn-Hant-HK", "cmn-Hant", "cmn-HK", "cmn", "zh-Hant-HK", "zh-Hant", "zh-HK", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("zh-Hant-CN", List.of("cmn-Hant-CN", "cmn-Hant", "cmn-CN", "cmn", "zh-Hant-CN", "zh-Hant", "zh-CN", "zh-TW", "zh", "und"));
|
||||
|
||||
assertCandidateLocales("zh-pinyin", List.of("cmn-Latn-pinyin", "cmn-Latn", "cmn-pinyin", "cmn", "zh-Latn-pinyin", "zh-Latn", "zh-pinyin", "zh", "zh-CN", "und"));
|
||||
|
||||
assertCandidateLocales("lzh", List.of("lzh-Hant", "lzh", "zh-Hant", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("lzh-Hant", List.of("lzh-Hant", "lzh", "zh-Hant", "zh-TW", "zh", "zh-CN", "und"));
|
||||
assertCandidateLocales("lzh-Hans", List.of("lzh-Hans", "lzh", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("cmn", List.of("cmn-Hans", "cmn", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("cmn-Hans", List.of("cmn-Hans", "cmn", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
assertCandidateLocales("yue", List.of("yue-Hans", "yue", "zh-Hans", "zh-CN", "zh", "und"));
|
||||
|
||||
assertCandidateLocales("ja", List.of("ja", "und"));
|
||||
assertCandidateLocales("jpn", List.of("ja", "und"));
|
||||
assertCandidateLocales("ja-JP", List.of("ja-JP", "ja", "und"));
|
||||
assertCandidateLocales("jpn-JP", List.of("ja-JP", "ja", "und"));
|
||||
|
||||
assertCandidateLocales("en", List.of("en", "und"));
|
||||
assertCandidateLocales("eng", List.of("en", "und"));
|
||||
assertCandidateLocales("en-US", List.of("en-US", "en", "und"));
|
||||
assertCandidateLocales("eng-US", List.of("en-US", "en", "und"));
|
||||
|
||||
assertCandidateLocales("es", List.of("es", "und"));
|
||||
assertCandidateLocales("spa", List.of("es", "und"));
|
||||
|
||||
assertCandidateLocales("ru", List.of("ru", "und"));
|
||||
assertCandidateLocales("rus", List.of("ru", "und"));
|
||||
|
||||
assertCandidateLocales("uk", List.of("uk", "und"));
|
||||
assertCandidateLocales("ukr", List.of("uk", "und"));
|
||||
|
||||
assertCandidateLocales("und", List.of("en", "und"));
|
||||
assertCandidateLocalesEquals("zh", "cmn-Hans");
|
||||
assertCandidateLocalesEquals("zh-CN", "cmn-Hans-CN");
|
||||
assertCandidateLocalesEquals("zh-SG", "cmn-Hans-SG");
|
||||
assertCandidateLocalesEquals("zh-MY", "cmn-Hans-MY");
|
||||
assertCandidateLocalesEquals("zh-TW", "cmn-Hant-TW");
|
||||
assertCandidateLocalesEquals("zh-HK", "cmn-Hant-HK");
|
||||
assertCandidateLocalesEquals("zh-Hans", "cmn-Hans");
|
||||
assertCandidateLocalesEquals("zh-Hant", "cmn-Hant");
|
||||
assertCandidateLocalesEquals("zh-Hant-CN", "cmn-Hant-CN");
|
||||
assertCandidateLocalesEquals("zh-Hant-SG", "cmn-Hant-SG");
|
||||
assertCandidateLocalesEquals("zh-Latn", "cmn-Latn");
|
||||
assertCandidateLocalesEquals("zh-pinyin", "cmn-Latn-pinyin");
|
||||
assertCandidateLocalesEquals("zho", "zh");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,7 +154,9 @@ public final class LocaleUtilsTest {
|
||||
assertEquals("Hant", LocaleUtils.getScript(Locale.forLanguageTag("lzh-Hant")));
|
||||
assertEquals("Hant", LocaleUtils.getScript(Locale.forLanguageTag("lzh-CN")));
|
||||
|
||||
assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("en")));
|
||||
assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("zh-pinyin")));
|
||||
assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("ja-hepburn")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -192,20 +214,17 @@ public final class LocaleUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapToISO2Language() {
|
||||
assertEquals("en", LocaleUtils.mapToISO2Language("eng"));
|
||||
assertEquals("es", LocaleUtils.mapToISO2Language("spa"));
|
||||
assertEquals("ja", LocaleUtils.mapToISO2Language("jpn"));
|
||||
assertEquals("ru", LocaleUtils.mapToISO2Language("rus"));
|
||||
assertEquals("uk", LocaleUtils.mapToISO2Language("ukr"));
|
||||
assertEquals("zh", LocaleUtils.mapToISO2Language("zho"));
|
||||
assertEquals("zu", LocaleUtils.mapToISO2Language("zul"));
|
||||
|
||||
assertNull(LocaleUtils.mapToISO2Language(null));
|
||||
assertNull(LocaleUtils.mapToISO2Language(""));
|
||||
assertNull(LocaleUtils.mapToISO2Language("cmn"));
|
||||
assertNull(LocaleUtils.mapToISO2Language("lzh"));
|
||||
assertNull(LocaleUtils.mapToISO2Language("tlh"));
|
||||
public void testNormalizeLanguage() {
|
||||
assertEquals("en", LocaleUtils.normalizeLanguage(""));
|
||||
assertEquals("en", LocaleUtils.normalizeLanguage("eng"));
|
||||
assertEquals("es", LocaleUtils.normalizeLanguage("spa"));
|
||||
assertEquals("ja", LocaleUtils.normalizeLanguage("jpn"));
|
||||
assertEquals("ru", LocaleUtils.normalizeLanguage("rus"));
|
||||
assertEquals("uk", LocaleUtils.normalizeLanguage("ukr"));
|
||||
assertEquals("zh", LocaleUtils.normalizeLanguage("zho"));
|
||||
assertEquals("zu", LocaleUtils.normalizeLanguage("zul"));
|
||||
assertEquals("en", LocaleUtils.normalizeLanguage(""));
|
||||
assertEquals("cmn", LocaleUtils.normalizeLanguage("cmn"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -228,10 +247,11 @@ public final class LocaleUtilsTest {
|
||||
assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("zh")));
|
||||
assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("zh-Hans")));
|
||||
assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("zh-CN")));
|
||||
assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("ar-Qabs")));
|
||||
|
||||
assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("en-Qabs")));
|
||||
assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("ar")));
|
||||
assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("ara")));
|
||||
assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("he")));
|
||||
assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("heb")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user