README.md

# Localize Person Names

Locale-aware person name formatting built on the Unicode CLDR [Person Names](https://www.unicode.org/reports/tr35/tr35-personNames.html) specification and the [Localize](https://hexdocs.pm/localize/) library.

## Installation

```elixir
def deps do
  [
    {:localize_person_names, "~> 0.1.0"}
  ]
end
```

## Usage

```elixir
# Create a person name
{:ok, name} = Localize.PersonName.new(title: "Mr.", given_name: "José", surname: "Valim", credentials: "Ph.D.", locale: "pt")

# Format with defaults (locale-driven length, formality, addressing usage)
Localize.PersonName.to_string(name)
#=> {:ok, "José"}

# Format with explicit options
Localize.PersonName.to_string(name, format: :long, formality: :formal, usage: :referring)
#=> {:ok, "Mr. José Valim Ph.D."}

# Format in a different locale
Localize.PersonName.to_string(name, locale: :ja)
#=> {:ok, "バリム・ジョゼ"}
```

### Options

* `:format` — `:short`, `:medium`, or `:long`. Controls how many name parts appear. Default is derived from the formatting locale.

* `:usage` — `:addressing` (speaking to someone), `:referring` (speaking about someone), or `:monogram` (abbreviated, e.g., initials). Default is `:addressing`.

* `:formality` — `:formal` or `:informal`. Controls whether titles, credentials, and full names are used versus nicknames and abbreviated forms. Default is derived from the formatting locale.

* `:order` — `:given_first`, `:surname_first`, or `:sorting`. Default is derived from the person name's locale and the formatting locale.

* `:locale` — The formatting locale. Default is `Localize.get_locale()`.

### Person Name Fields

| Field | Description | Example |
|-------|-------------|---------|
| `given_name` | Primary given name (required) | "José" |
| `surname` | Family name | "Valim" |
| `title` | Honorific | "Mr.", "Dr." |
| `other_given_names` | Middle name(s) or patronymic | "Carlos" |
| `informal_given_name` | Nickname or casual form | "Zé" |
| `surname_prefix` | Tussenvoegsel / particle | "von", "de" |
| `other_surnames` | Secondary/maternal surname | "González" |
| `generation` | Generation marker | "Jr.", "III" |
| `credentials` | Accreditation | "Ph.D.", "MD" |
| `preferred_order` | Explicit ordering preference | `:given_first`, `:surname_first` |
| `locale` | Locale of the name data | `"pt"`, `Localize.LanguageTag.t()` |

## Integrating existing structs

Any struct can participate in person name formatting, either through the `Localize.PersonName.Convertible` protocol or through the `Localize.PersonName` behaviour. See [Integrating existing name structs](https://github.com/elixir-localize/localize_person_names/blob/v0.1.0/guides/integrating_existing_structs.md) for the full comparison and recommendations.

## MF2 message formatting

A `:personName` MF2 function is provided as `Localize.PersonName.MF2` for use with [Localize.Message](https://hexdocs.pm/localize/Localize.Message.html). See [Using Localize.PersonName with Localize.Message](https://github.com/elixir-localize/localize_person_names/blob/v0.1.0/guides/message_formatting.md) for formal and informal worked examples.

## Guides

* [Integrating existing name structs](https://github.com/elixir-localize/localize_person_names/blob/v0.1.0/guides/integrating_existing_structs.md) — two ways to wire existing domain structs (`%User{}`, `%Customer{}`, etc.) into the formatter: the `Localize.PersonName.Convertible` protocol (recommended) and the `Localize.PersonName` behaviour.

* [Using Localize.PersonName with Localize.Message](https://github.com/elixir-localize/localize_person_names/blob/v0.1.0/guides/message_formatting.md) — integrating person name formatting into MF2 message templates via a custom function, with formal and informal worked examples.

## Conformance

### Test coverage

39,731 tests across 120 CLDR locales, 0 failures. Test data is from the CLDR person name test suite, which provides format conformance tests for each locale covering all combinations of order, length, usage, and formality.

### Locale coverage

Of the 128 CLDR locale test data files available, 120 pass all tests. The 8 excluded locales and their reasons are documented in `specification_deviances.md` and summarised below:

| Locales | Failures | Cause |
|---------|----------|-------|
| si, my, km, ml | 61 | Word-break segmentation differences for transliterated foreign names in these scripts. |
| yo_BJ | 27 | CLDR test data expects initials but locale format data has no `-initial` modifier. |
| es_US, es_MX, es_419 | 28 | Format selection tiebreaker discrepancy between spec text and CLDR test data. |

### Specification deviances

See [specification_deviances.md](https://github.com/elixir-localize/localize_person_names/blob/v0.1.0/specification_deviances.md) for detailed analysis of four issues:

1. **Format selection tiebreaker** (es_US, es_MX, es_419) — Spec says "fewest unpopulated fields" but test data expects different selection.

2. **Empty field removal with grouping punctuation** (cs, sk) — Fixed in this implementation by detecting parentheses/brackets during literal coalescing.

3. **Initial derivation requires UAX #29 grapheme clusters** — Spec says "first grapheme cluster" without specifying UAX #29. This implementation uses UAX #29 extended grapheme clusters via `unicode_string`, which is required for correct initials in Brahmic scripts.

4. **Test data / locale data mismatch** (yo_BJ) — Test expectations don't match the current format patterns.

## Known Limitations

See [TODO.md](https://github.com/elixir-localize/localize_person_names/blob/v0.1.0/TODO.md) for tracked implementation gaps.