Skip to content

Commit

Permalink
ICU-22402 Add options to PersonNameFormatter and update nativeSpaceRe…
Browse files Browse the repository at this point in the history
…placement logic
  • Loading branch information
richgillam committed Aug 10, 2023
1 parent cc2ddc0 commit ca3fd47
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public class PersonNameFormatterImpl {
private final boolean capitalizeSurname;
private final String foreignSpaceReplacement;
private final String nativeSpaceReplacement;
private final boolean formatterLocaleUsesSpaces;
private final PersonNameFormatter.Length length;
private final PersonNameFormatter.Usage usage;
private final PersonNameFormatter.Formality formality;
Expand All @@ -54,8 +53,7 @@ public PersonNameFormatterImpl(Locale locale,
this.initialPattern = rb.getStringWithFallback("personNames/initialPattern/initial");
this.initialSequencePattern = rb.getStringWithFallback("personNames/initialPattern/initialSequence");
this.foreignSpaceReplacement = rb.getStringWithFallback("personNames/foreignSpaceReplacement");
this.formatterLocaleUsesSpaces = !LOCALES_THAT_DONT_USE_SPACES.contains(locale.getLanguage());
this.nativeSpaceReplacement = formatterLocaleUsesSpaces ? " " : "";
this.nativeSpaceReplacement = rb.getStringWithFallback("personNames/nativeSpaceReplacement");

// asjust for combinations of parameters that don't make sense in practice
if (usage == PersonNameFormatter.Usage.MONOGRAM) {
Expand All @@ -72,9 +70,12 @@ public PersonNameFormatterImpl(Locale locale,
// different for different names), load patterns for both given-first and surname-first names. (If the user has
// specified SORTING, we don't need to do this-- we just load the "sorting" patterns and ignore the name's order.)
final String RESOURCE_PATH_PREFIX = "personNames/namePattern/";
String resourceNameBody = length.toString().toLowerCase() + "-" + usage.toString().toLowerCase() + "-"
+ formality.toString().toLowerCase();
if (displayOrder == PersonNameFormatter.DisplayOrder.DEFAULT) {
String lengthStr = (length != PersonNameFormatter.Length.DEFAULT) ? length.toString().toLowerCase()
: rb.getStringWithFallback("personNames/parameterDefault/length");
String formalityStr = (formality != PersonNameFormatter.Formality.DEFAULT) ? formality.toString().toLowerCase()
: rb.getStringWithFallback("personNames/parameterDefault/formality");
String resourceNameBody = lengthStr + "-" + usage.toString().toLowerCase() + "-" + formalityStr;
if (displayOrder != PersonNameFormatter.DisplayOrder.SORTING) {
ICUResourceBundle gnFirstResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "givenFirst-" + resourceNameBody);
ICUResourceBundle snFirstResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "surnameFirst-" + resourceNameBody);

Expand Down Expand Up @@ -109,7 +110,6 @@ public PersonNameFormatterImpl(Locale locale, String[] gnFirstPatterns, String[]
capitalizeSurname = false;
foreignSpaceReplacement = " ";
nativeSpaceReplacement = " ";
formatterLocaleUsesSpaces = true;

// then, set values for the fields we actually care about (all but gnFirstPatterns are optional)
this.locale = locale;
Expand Down Expand Up @@ -200,8 +200,6 @@ public boolean shouldCapitalizeSurname() {
return capitalizeSurname;
}

private final Set<String> LOCALES_THAT_DONT_USE_SPACES = new HashSet<>(Arrays.asList("ja", "zh", "yue", "km", "lo", "my"));

static final Set<String> NON_DEFAULT_SCRIPTS = new HashSet<>(Arrays.asList("Hani", "Hira", "Kana"));

/**
Expand All @@ -227,6 +225,14 @@ private String[] asStringArray(ICUResourceBundle resource) {
* @return If true, use given-first order to format the name; if false, use surname-first order.
*/
private boolean nameIsGnFirst(PersonName name) {
// if the formatter has its display order set to one of the "force" values, that overrides
// all this logic and the name's preferred-order property
if (this.displayOrder == PersonNameFormatter.DisplayOrder.FORCE_GIVEN_FIRST) {
return true;
} else if (this.displayOrder == PersonNameFormatter.DisplayOrder.FORCE_SURNAME_FIRST) {
return false;
}

// the name can declare its order-- check that first (it overrides any locale-based calculation)
if (name.getPreferredOrder() == PersonName.PreferredOrder.GIVEN_FIRST) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ public enum Length {
* When Formality is INFORMAL, may only include one field.
* @draft ICU 73
*/
SHORT
SHORT,

/**
* The default name length for the locale. For most locales, this is the same as MEDIUM.
* @draft ICU 74
*/
DEFAULT
}

/**
Expand Down Expand Up @@ -138,7 +144,14 @@ public enum Formality {
* of the given name.
* @draft ICU 73
*/
INFORMAL
INFORMAL,

/**
* The default formality for the locale. For most locales, this is the same as FORMAL, but for English,
* this is the same as INFORMAL.
* @draft ICU 74
*/
DEFAULT
}

/**
Expand All @@ -158,7 +171,21 @@ public enum DisplayOrder {
* of the name: "Smith, John".
* @draft ICU 73
*/
SORTING
SORTING,

/**
* Forces the formatter to format the name in given-first order. If the name itself specifies
* a display order, this overrides it.
* @draft ICU 74
*/
FORCE_GIVEN_FIRST,

/**
* Forces the formatter to format the name in surname-first order. If the name itself specifies
* a display order, this overrides it.
* @draft ICU 74
*/
FORCE_SURNAME_FIRST,
}

private final PersonNameFormatterImpl impl;
Expand Down Expand Up @@ -260,9 +287,9 @@ private Builder() {
}

private Locale locale = Locale.getDefault();
private Length length = Length.MEDIUM;
private Length length = Length.DEFAULT;
private Usage usage = Usage.REFERRING;
private Formality formality = Formality.FORMAL;
private Formality formality = Formality.DEFAULT;
private DisplayOrder displayOrder = DisplayOrder.DEFAULT;
private boolean surnameAllCaps = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,35 @@ public void TestEnglishName() {
executeTestCases(new NameAndTestCases[]{
new NameAndTestCases("locale=en_US,title=Mr.,given=Richard,given-informal=Rich,given2=Theodore,surname=Gillam", new String[][] {
// test all the different combinations of parameters with the normal name order
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Mr. Richard Theodore Gillam" },
{ "en_US", "LONG", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "LONG", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "LONG", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },
{ "en_US", "MEDIUM", "REFERRING", "FORMAL", "DEFAULT", "", "Richard T. Gillam" },
{ "en_US", "MEDIUM", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "MEDIUM", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "MEDIUM", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },
//{ "en_US", "SHORT", "REFERRING", "FORMAL", "DEFAULT", "", "R. T. Gillam" },
{ "en_US", "SHORT", "REFERRING", "FORMAL", "DEFAULT", "", "R.T. Gillam" }, // result changed with CLDR 43-alpha1
{ "en_US", "SHORT", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich G." },
{ "en_US", "SHORT", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "SHORT", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Mr. Richard Theodore Gillam" },
{ "en_US", "LONG", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "LONG", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "LONG", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },
{ "en_US", "MEDIUM", "REFERRING", "FORMAL", "DEFAULT", "", "Richard T. Gillam" },
{ "en_US", "MEDIUM", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "MEDIUM", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "MEDIUM", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },
//{ "en_US", "SHORT", "REFERRING", "FORMAL", "DEFAULT", "", "R. T. Gillam" },
{ "en_US", "SHORT", "REFERRING", "FORMAL", "DEFAULT", "", "R.T. Gillam" }, // result changed with CLDR 43-alpha1
{ "en_US", "SHORT", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich G." },
{ "en_US", "SHORT", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "SHORT", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },

// test DEFAULT length (in English [and all other current locales], this is the same as MEDIUM)
{ "en_US", "DEFAULT", "REFERRING", "FORMAL", "DEFAULT", "", "Richard T. Gillam" },
{ "en_US", "DEFAULT", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "DEFAULT", "ADDRESSING", "FORMAL", "DEFAULT", "", "Mr. Gillam" },
{ "en_US", "DEFAULT", "ADDRESSING", "INFORMAL", "DEFAULT", "", "Rich" },

// test DEFAULT formality (in English, this is the same as INFORMAL)
{ "en_US", "LONG", "REFERRING", "DEFAULT", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "LONG", "ADDRESSING", "DEFAULT", "DEFAULT", "", "Rich" },
{ "en_US", "MEDIUM", "REFERRING", "DEFAULT", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "MEDIUM", "ADDRESSING", "DEFAULT", "DEFAULT", "", "Rich" },
{ "en_US", "SHORT", "REFERRING", "DEFAULT", "DEFAULT", "", "Rich G." },
{ "en_US", "SHORT", "ADDRESSING", "DEFAULT", "DEFAULT", "", "Rich" },
{ "en_US", "DEFAULT", "REFERRING", "DEFAULT", "DEFAULT", "", "Rich Gillam" },
{ "en_US", "DEFAULT", "ADDRESSING", "DEFAULT", "DEFAULT", "", "Rich" },

// test all the different combinations of parameters for "sorting" order
{ "en_US", "LONG", "REFERRING", "FORMAL", "SORTING", "", "Gillam, Richard Theodore" },
Expand Down Expand Up @@ -331,6 +347,35 @@ public void TestNameOrder() {
new NameAndTestCases("locale=ja_JP,given=Shinzo,surname=Abe,preferredOrder=givenFirst", new String[][] {
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Shinzo Abe" },
}),

// the formatter can also override the ordering and always format names in GN-first or SN-first order
// (this repeats some of the test cases above for clarity)
new NameAndTestCases("locale=en_US,given=Shinzo,surname=Abe", new String[][] {
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Shinzo Abe" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_GIVEN_FIRST", "", "Shinzo Abe" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_SURNAME_FIRST", "", "Abe Shinzo" },
}),
new NameAndTestCases("locale=ja_JP,given=Shinzo,surname=Abe", new String[][] {
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Abe Shinzo" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_GIVEN_FIRST", "", "Shinzo Abe" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_SURNAME_FIRST", "", "Abe Shinzo" },
}),
new NameAndTestCases("locale=ja_JP,given=晋三,surname=安倍", new String[][] {
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "安倍晋三" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_GIVEN_FIRST", "", "晋三安倍" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_SURNAME_FIRST", "", "安倍晋三" },
}),
new NameAndTestCases("locale=en_US,given=Shinzo,surname=Abe,preferredOrder=surnameFirst", new String[][] {
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Abe Shinzo" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_GIVEN_FIRST", "", "Shinzo Abe" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_SURNAME_FIRST", "", "Abe Shinzo" },
}),
new NameAndTestCases("locale=ja_JP,given=Shinzo,surname=Abe,preferredOrder=givenFirst", new String[][] {
{ "en_US", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "Shinzo Abe" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_GIVEN_FIRST", "", "Shinzo Abe" },
{ "en_US", "LONG", "REFERRING", "FORMAL", "FORCE_SURNAME_FIRST", "", "Abe Shinzo" },
}),

}, false);
}

Expand Down Expand Up @@ -387,13 +432,12 @@ public void TestNameSpacing() {
{ "ja_JP", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "宮崎駿" },
{ "zh_CN", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "宮崎駿" },
}),
// (Thai, despite not using spaces between words, DOES use spaces between the given name and surname_
// (Thai and Lao, despite not using spaces between words, DO use spaces between the given name and surname
new NameAndTestCases("locale=th_TH,given=ไอริณ,surname=กล้าหาญ", new String[][] {
{ "th_TH", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "ไอริณ กล้าหาญ" },
}),
// (Lao, on the other hand, does NOT put a space between the given name and surname)
new NameAndTestCases("locale=lo_LA,given=ໄອຣີນ,surname=ແອດເລີ", new String[][] {
{ "lo_LA", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "ໄອຣີນແອດເລີ" },
{ "lo_LA", "LONG", "REFERRING", "FORMAL", "DEFAULT", "", "ໄອຣີນ ແອດເລີ" },
}),
}, false);
}
Expand Down Expand Up @@ -598,4 +642,22 @@ public void TestNameOrderFromLocale() {
assertEquals("Wrong result for " + localeID, expectedResult, actualResult);
}
}

@Test
public void TestDefaultFormality() {
executeTestCases(new NameAndTestCases[]{
// in English, DEFAULT formality is the same as INFORMAL
new NameAndTestCases("locale=en_US,title=Mr.,given=Richard,given-informal=Rich,given2=Theodore,surname=Gillam", new String[][] {
{"en_US", "DEFAULT", "REFERRING", "FORMAL", "DEFAULT", "", "Richard T. Gillam"},
{"en_US", "DEFAULT", "REFERRING", "INFORMAL", "DEFAULT", "", "Rich Gillam"},
{"en_US", "DEFAULT", "REFERRING", "DEFAULT", "DEFAULT", "", "Rich Gillam"},
}),
// in other languages (we're using German here), DEFAULT formality is the same as FORMAL
new NameAndTestCases("locale=de_DE,title=Herr,given=Friedrich,given-informal=Fritz,given2=Georg,surname=Schellhammer", new String[][] {
{"de_DE", "DEFAULT", "REFERRING", "FORMAL", "DEFAULT", "", "Friedrich G. Schellhammer"},
{"de_DE", "DEFAULT", "REFERRING", "INFORMAL", "DEFAULT", "", "Fritz Schellhammer"},
{"de_DE", "DEFAULT", "REFERRING", "DEFAULT", "DEFAULT", "", "Friedrich G. Schellhammer"},
})
}, false);
}
}

0 comments on commit ca3fd47

Please sign in to comment.