2626import java .nio .file .Files ;
2727import java .nio .file .Path ;
2828import java .util .*;
29+ import java .util .concurrent .ConcurrentHashMap ;
30+ import java .util .concurrent .ConcurrentMap ;
2931import java .util .stream .Stream ;
3032
3133import static org .jackhuang .hmcl .util .logging .Logger .LOG ;
@@ -42,6 +44,16 @@ public final class LocaleUtils {
4244
4345 public static final String DEFAULT_LANGUAGE_KEY = "default" ;
4446
47+ private static Locale getInstance (String language , String script , String region ,
48+ String variant ) {
49+ Locale .Builder builder = new Locale .Builder ();
50+ if (!language .isEmpty ()) builder .setLanguage (language );
51+ if (!script .isEmpty ()) builder .setScript (script );
52+ if (!region .isEmpty ()) builder .setRegion (region );
53+ if (!variant .isEmpty ()) builder .setVariant (variant );
54+ return builder .build ();
55+ }
56+
4557 /// Convert a locale to the language key.
4658 ///
4759 /// The language key is in the format of BCP 47 language tag.
@@ -52,18 +64,24 @@ public static String toLanguageKey(Locale locale) {
5264 : locale .stripExtensions ().toLanguageTag ();
5365 }
5466
55- public static boolean isISO1Language (String language ) {
56- return language .length () == 2 ;
57- }
58-
59- public static boolean isISO3Language (String language ) {
60- return language .length () == 3 ;
61- }
62-
6367 public static @ NotNull String getISO1Language (Locale locale ) {
6468 String language = locale .getLanguage ();
6569 if (language .isEmpty ()) return "en" ;
66- return isISO3Language (language ) ? toISO1Language (language ) : language ;
70+ if (language .length () <= 2 )
71+ return language ;
72+
73+ String lang = language ;
74+ while (lang != null ) {
75+ if (lang .length () <= 2 )
76+ return lang ;
77+ else {
78+ String iso1 = mapToISO1Language (lang );
79+ if (iso1 != null )
80+ return iso1 ;
81+ }
82+ lang = getParentLanguage (lang );
83+ }
84+ return language ;
6785 }
6886
6987 /// Get the script of the locale. If the script is empty and the language is Chinese,
@@ -83,10 +101,108 @@ public static boolean isISO3Language(String language) {
83101 return locale .getScript ();
84102 }
85103
104+ private static final ConcurrentMap <Locale , List <Locale >> CANDIDATE_LOCALES = new ConcurrentHashMap <>();
105+
86106 public static @ NotNull List <Locale > getCandidateLocales (Locale locale ) {
87- return DefaultResourceBundleControl .INSTANCE .getCandidateLocales ("" , locale );
107+ return CANDIDATE_LOCALES .computeIfAbsent (locale , LocaleUtils ::createCandidateLocaleList );
108+ }
109+
110+ // -------------
111+
112+ private static List <Locale > createCandidateLocaleList (Locale locale ) {
113+ String language = locale .getLanguage ();
114+ if (language .isEmpty ())
115+ return List .of (Locale .ENGLISH , Locale .ROOT );
116+
117+ String script = getScript (locale );
118+ String region = locale .getCountry ();
119+ List <String > variants = locale .getVariant ().isEmpty ()
120+ ? List .of ()
121+ : List .of (locale .getVariant ().split ("[_\\ -]" ));
122+
123+ ArrayList <Locale > result = new ArrayList <>();
124+ do {
125+ List <String > languages ;
126+
127+ if (language .isEmpty ()) {
128+ result .add (Locale .ROOT );
129+ break ;
130+ } else if (language .length () <= 2 ) {
131+ languages = List .of (language );
132+ } else {
133+ String iso1Language = mapToISO1Language (language );
134+ languages = iso1Language != null
135+ ? List .of (language , iso1Language )
136+ : List .of (language );
137+ }
138+
139+ addCandidateLocales (result , languages , script , region , variants );
140+ } while ((language = getParentLanguage (language )) != null );
141+
142+ return List .copyOf (result );
88143 }
89144
145+ private static void addCandidateLocales (ArrayList <Locale > list ,
146+ List <String > languages ,
147+ String script ,
148+ String region ,
149+ List <String > variants ) {
150+ if (!variants .isEmpty ()) {
151+ for (String v : variants ) {
152+ for (String language : languages ) {
153+ list .add (getInstance (language , script , region , v ));
154+ }
155+ }
156+ }
157+ if (!region .isEmpty ()) {
158+ for (String language : languages ) {
159+ list .add (getInstance (language , script , region , "" ));
160+ }
161+ }
162+ if (!script .isEmpty ()) {
163+ for (String language : languages ) {
164+ list .add (getInstance (language , script , "" , "" ));
165+ }
166+ if (!variants .isEmpty ()) {
167+ for (String v : variants ) {
168+ for (String language : languages ) {
169+ list .add (getInstance (language , "" , region , v ));
170+ }
171+ }
172+ }
173+ if (!region .isEmpty ()) {
174+ for (String language : languages ) {
175+ list .add (getInstance (language , "" , region , "" ));
176+ }
177+ }
178+ }
179+
180+ for (String language : languages ) {
181+ list .add (getInstance (language , "" , "" , "" ));
182+ }
183+
184+ if (languages .contains ("zh" )) {
185+ if (list .contains (LocaleUtils .LOCALE_ZH_HANT ) && !list .contains (Locale .TRADITIONAL_CHINESE )) {
186+ int chineseIdx = list .indexOf (Locale .CHINESE );
187+ if (chineseIdx >= 0 )
188+ list .add (chineseIdx , Locale .TRADITIONAL_CHINESE );
189+ }
190+
191+ if (!list .contains (Locale .SIMPLIFIED_CHINESE )) {
192+ int chineseIdx = list .indexOf (Locale .CHINESE );
193+
194+ if (chineseIdx >= 0 ) {
195+ if (list .contains (LocaleUtils .LOCALE_ZH_HANS ))
196+ list .add (chineseIdx , Locale .SIMPLIFIED_CHINESE );
197+ else
198+ list .add (chineseIdx + 1 , Locale .SIMPLIFIED_CHINESE );
199+ }
200+ }
201+ }
202+ }
203+
204+ // -------------
205+
90206 public static <T > @ Nullable T getByCandidateLocales (Map <String , T > map , List <Locale > candidateLocales ) {
91207 for (Locale locale : candidateLocales ) {
92208 String key = toLanguageKey (locale );
@@ -178,17 +294,25 @@ else if (fileName.length() > defaultFileNameLength + 1 && fileName.charAt(baseNa
178294
179295 // ---
180296
181- /// Try to convert ISO 639-3 language codes to ISO 639-1 language codes.
182- public static String toISO1Language (String languageTag ) {
183- return switch (languageTag ) {
297+ /// Map ISO 639-3 language codes to ISO 639-1 language codes.
298+ public static @ Nullable String mapToISO1Language (String iso3Language ) {
299+ return switch (iso3Language ) {
184300 case "eng" -> "en" ;
185301 case "spa" -> "es" ;
186302 case "jpa" -> "ja" ;
187303 case "rus" -> "ru" ;
188304 case "ukr" -> "uk" ;
189- case "zho" , "cmn" , "lzh" , "cdo" , "cjy" , "cpx" , "czh" ,
305+ case "zho" -> "zh" ;
306+ default -> null ;
307+ };
308+ }
309+
310+ public static @ Nullable String getParentLanguage (String language ) {
311+ return switch (language ) {
312+ case "cmn" , "lzh" , "cdo" , "cjy" , "cpx" , "czh" ,
190313 "gan" , "hak" , "hsn" , "mnp" , "nan" , "wuu" , "yue" -> "zh" ;
191- default -> languageTag ;
314+ case "" -> null ;
315+ default -> "" ;
192316 };
193317 }
194318
0 commit comments