@@ -98,11 +98,8 @@ public PersonNameFormatterImpl(Locale locale,
9898 /**
9999 * THIS IS A DUMMY CONSTRUCTOR JUST FOR THE USE OF THE UNIT TESTS TO CHECK SOME OF THE INTERNAL IMPLEMENTATION!
100100 */
101- public PersonNameFormatterImpl (Locale locale , String [] patterns ) {
101+ public PersonNameFormatterImpl (Locale locale , String [] gnFirstPatterns , String [] snFirstPatterns , String [] gnFirstLocales , String [] snFirstLocales ) {
102102 // first, set dummy values for the other fields
103- snFirstPatterns = null ;
104- gnFirstLocales = null ;
105- snFirstLocales = null ;
106103 length = PersonNameFormatter .Length .MEDIUM ;
107104 usage = PersonNameFormatter .Usage .REFERRING ;
108105 formality = PersonNameFormatter .Formality .FORMAL ;
@@ -114,10 +111,22 @@ public PersonNameFormatterImpl(Locale locale, String[] patterns) {
114111 nativeSpaceReplacement = " " ;
115112 formatterLocaleUsesSpaces = true ;
116113
117- // then, set values for the fields we actually care about
114+ // then, set values for the fields we actually care about (all but gnFirstPatterns are optional)
118115 this .locale = locale ;
119- gnFirstPatterns = PersonNamePattern .makePatterns (patterns , this );
120-
116+ this .gnFirstPatterns = PersonNamePattern .makePatterns (gnFirstPatterns , this );
117+ this .snFirstPatterns = (snFirstPatterns != null ) ? PersonNamePattern .makePatterns (snFirstPatterns , this ) : null ;
118+ if (gnFirstLocales != null ) {
119+ this .gnFirstLocales = new HashSet <>();
120+ Collections .addAll (this .gnFirstLocales , gnFirstLocales );
121+ } else {
122+ this .gnFirstLocales = null ;
123+ }
124+ if (snFirstLocales != null ) {
125+ this .snFirstLocales = new HashSet <>();
126+ Collections .addAll (this .snFirstLocales , snFirstLocales );
127+ } else {
128+ this .snFirstLocales = null ;
129+ }
121130 }
122131
123132 @ Override
@@ -193,6 +202,8 @@ public boolean shouldCapitalizeSurname() {
193202
194203 private final Set <String > LOCALES_THAT_DONT_USE_SPACES = new HashSet <>(Arrays .asList ("ja" , "zh" , "yue" , "km" , "lo" , "my" ));
195204
205+ static final Set NON_DEFAULT_SCRIPTS = new HashSet <>(Arrays .asList ("Hani" , "Hira" , "Kana" ));
206+
196207 /**
197208 * Returns the value of the resource, as a string array.
198209 * @param resource An ICUResourceBundle of type STRING or ARRAY. If ARRAY, this function just returns it
@@ -223,23 +234,57 @@ private boolean nameIsGnFirst(PersonName name) {
223234 return false ;
224235 }
225236
226- String localeStr = getNameLocale (name ).toString ();
237+ // Otherwise, search the gnFirstLocales and snFirstLocales for the locale's name.
238+ // For our purposes, the "locale's name" is the locale the name itself gives us (if it
239+ // has one), or the locale we guess for the name (if it doesn't).
240+ Locale nameLocale = name .getNameLocale ();
241+ if (nameLocale == null ) {
242+ nameLocale = getNameLocale (name );
243+ }
244+
245+ // this is a hack to deal with certain script codes that are valid, but not the default, for their locales--
246+ // to make the parent-chain lookup work right, we need to replace any of those script codes (in the name's locale)
247+ // with the appropriate default script for whatever language and region we have
248+ ULocale nameULocale = ULocale .forLocale (nameLocale );
249+ if (NON_DEFAULT_SCRIPTS .contains (nameULocale .getScript ())) {
250+ ULocale .Builder builder = new ULocale .Builder ();
251+ builder .setLocale (nameULocale );
252+ builder .setScript (null );
253+ nameULocale = ULocale .addLikelySubtags (builder .build ());
254+ }
255+
256+ // now search for the locale in the gnFirstLocales and snFirstLocales lists...
257+ String localeStr = nameULocale .getName ();
258+ String origLocaleStr = localeStr ;
259+ String languageCode = nameULocale .getLanguage ();
260+
227261 do {
262+ // first check if the locale is in one of those lists
228263 if (gnFirstLocales .contains (localeStr )) {
229264 return true ;
230265 } else if (snFirstLocales .contains (localeStr )) {
231266 return false ;
232267 }
233268
234- int lastUnderbarPos = localeStr .lastIndexOf ("_" );
235- if (lastUnderbarPos >= 0 ) {
236- localeStr = localeStr .substring (0 , lastUnderbarPos );
237- } else {
238- localeStr = "root" ;
269+ // if not, try again with "und" in place of the language code (this lets us use "und_CN" to match
270+ // all locales with a region code of "CN" and makes sure the last thing we try is always "und", which
271+ // is required to be in gnFirstLocales or snFirstLocales)
272+ String undStr = localeStr .replaceAll ("^" + languageCode , "und" );
273+ if (gnFirstLocales .contains (undStr )) {
274+ return true ;
275+ } else if (snFirstLocales .contains (undStr )) {
276+ return false ;
239277 }
240- } while (!localeStr .equals ("root" ));
241278
242- // should never get here-- "root" should always be in one of the locales
279+ // if we haven't found the locale ID yet, look up its parent locale ID and try again-- if getParentLocaleID()
280+ // returns null (i.e., we have a locale ID, such as "zh_Hant", that inherits directly from "root"), try again
281+ // with just the locale ID's language code (this fixes it so that "zh_Hant" matches "zh", even though "zh" isn't,
282+ // strictly speaking, its parent locale)
283+ String parentLocaleStr = ICUResourceBundle .getParentLocaleID (localeStr , origLocaleStr , ICUResourceBundle .OpenType .LOCALE_DEFAULT_ROOT );
284+ localeStr = (parentLocaleStr != null ) ? parentLocaleStr : languageCode ;
285+ } while (localeStr != null );
286+
287+ // should never get here ("und" should always be in gnFirstLocales or snFirstLocales), but if we do...
243288 return true ;
244289 }
245290
0 commit comments