From 60943ed1cf7ab62b49ee06c81b941ceaa09d6d6a Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Thu, 30 Jul 2026 18:41:05 -0700 Subject: [PATCH] =?UTF-8?q?Add=20the=20Ukrainian=20conjunction=20=D0=B9=20?= =?UTF-8?q?(#267=20comment,=20#269=20follow-up)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ukrainian writes і and й for the same conjunction, alternating on the surrounding vowel and consonant for euphony ("Олесь і Олена" but "Марія й Петро"), so real data carries both spellings. #269's candidate list read "Ukrainian і/та" and й was never considered; it therefore shipped recognizing one of the two and reading the other as a name piece -- 'Олесь й Олена Коваленки' put the й in middle. Pure data: no code path changes, and the entry inherits both existing guards. The single-letter carve-out (Google Code issue 11) still holds it back until the segment has enough rootname pieces, so a 3-piece 'Іван й Олена' stays split exactly as 'і' and 'y' do; and the initial regex's dotted branch is Unicode-aware, so a punctuated 'Й.' remains an initial. The second is worth a standing test rather than a comment -- и/і/й each open real given names (Игорь, Ірина, Йосип) -- so pin all three dotted forms. The differential harness needs nothing: no corpus name contains й, and the #269 rule in expected_changes.toml already classifies this diff shape (Cyrillic, fields title/first/middle) had one appeared. Co-Authored-By: Claude Opus 5 --- docs/release_log.rst | 1 + nameparser/config/conjunctions.py | 6 +++++- tests/v2/test_locales.py | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/release_log.rst b/docs/release_log.rst index afd6221..f8ab13e 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -26,6 +26,7 @@ Release Log - Fix the katakana middle dot ``・`` (U+30FB, and its halfwidth twin U+FF65) being read as part of a name rather than as the divider it is: it now separates tokens exactly as a space does. A transcribed foreign name therefore divides into its parts and, being wholly katakana, keeps its source order — ``"マイケル・ジャクソン"`` gives given ``マイケル``, family ``ジャクソン``, where 1.x left the whole string in ``first`` — while a kanji pair written the same way takes the family-first rule (``"高橋・一郎"`` → family ``高橋``). Native Japanese names never contain this character and no other script's names use it, so the separation is unconditional, which also means it is **not** covered by the two policy opt-outs. Rendering follows, the way the Korean split's does: the dot comes back as a space, so ``str(HumanName("マイケル・ジャクソン"))`` is now ``"マイケル ジャクソン"``, and that reaches delimited content too — the nickname in ``"山田 太郎 (マイケル・ジャクソン)"`` renders ``"マイケル ジャクソン"``. The Chinese interpunct ``·`` is not an unconditional separator like these two: U+00B7 is also the Catalan punt volat and appears inside legitimate names (``Gal·la``), so it divides only between classified-script characters — the transcription treatment it marks is #298's (#272) - Fix 间隔号-divided transcriptions parsing as one unsplit token: U+00B7 — the interpunct Chinese text divides a transcribed foreign name with, ``威廉·莎士比亚`` for William Shakespeare — is now a token separator between characters of a classified script, and a name it divides keeps its source order and is never segmented: the dot is the transcription marker, playing the role pure katakana plays in the kana license. It divides ONLY between classified-script characters, so the Catalan punt volat interior to names like ``Gal·la`` is untouched. The Japanese nakaguro is deliberately not a transcription marker — ``高橋・一郎`` is roster formatting, 姓・名, and keeps its family-first reading — so a transcription typed with the wrong dot (``威廉・莎士比亚``) reads by the convention of the codepoint it was typed with; like the spaced form, only the Chinese dot rescues it. Rendering stays space-joined (``str(HumanName("威廉·莎士比亚"))`` is ``"威廉 莎士比亚"``); a custom ``string_format`` such as ``"{first}·{last}"`` reinstates the dot (#298) - Fix NFD-decomposed input missing the East Asian defaults entirely: script classification now normalizes to NFC before deciding, so a Korean or Japanese name typed on macOS — where decomposed text is routine — gets the same order rule as its composed twin, which it silently did not before. Segmentation MATCHING deliberately stays raw, so an unspaced NFD hangul name is ordered correctly but not split, rather than being split in the wrong place. One gotcha worth stating plainly: parse output preserves the encoding it was given, so for NFD input ``name.family == "김"`` is ``False`` even though it is the same name — compare NFC-normalized text when comparing across encodings (#272) + - Fix the Ukrainian conjunction ``й`` not joining the pieces around it: it is the euphonic alternate of ``і``, the two chosen by the surrounding vowel and consonant rather than by meaning (``"Олесь і Олена"`` but ``"Марія й Петро"``), so real Ukrainian data carries both spellings and shipping only ``і`` recognized just one of them. ``"Олесь й Олена Коваленки"`` now gives given ``"Олесь й Олена"`` where the ``й`` previously landed in ``middle``. Same treatment as the ``и``/``і`` entries added in 2.0.0, single-letter carve-out included: the conjunction joins only once the name has enough pieces, and a punctuated initial still wins, so ``"Й. Сліпий"`` is unaffected. Raised in a comment on #267 * 2.0.0 - July 27, 2026 diff --git a/nameparser/config/conjunctions.py b/nameparser/config/conjunctions.py index cc9a842..5e606ea 100644 --- a/nameparser/config/conjunctions.py +++ b/nameparser/config/conjunctions.py @@ -9,9 +9,13 @@ 'the', 'und', 'y', - # #269: Cyrillic (ru/uk/bg) "and": и, і, та. + # #269: Cyrillic (ru/uk/bg) "and": и, і, та. Ukrainian writes і and + # й for the same conjunction, alternating on the surrounding + # vowel/consonant for euphony ("Олесь і Олена", "Марія й Петро"), + # so real data carries both spellings and neither alone suffices. 'и', 'і', + 'й', 'та', # #269: Greek "and": και. 'και', diff --git a/tests/v2/test_locales.py b/tests/v2/test_locales.py index 98cf5aa..c3dcfaa 100644 --- a/tests/v2/test_locales.py +++ b/tests/v2/test_locales.py @@ -931,6 +931,16 @@ def test_non_interference_all_packs_combined() -> None: # Ukrainian "і" is single-character like "и", so the same # single-letter carve-out applies: pinned with a 5-piece name. ("проф і акад Тарас Григорович Шевченко", "title", "проф і акад"), + # Ukrainian "й" is the euphonic alternate of "і" (the two swap by + # surrounding vowel/consonant, so real data carries both) -- same + # single-letter carve-out, pinned with the same 5-piece shape. + ("проф й акад Тарас Григорович Шевченко", "title", "проф й акад"), + # ...and joining two given names, the shape #267's reporter raised: + # a bare Cyrillic letter between given names reads as the + # conjunction, not an initial (Cyrillic typography writes initials + # with periods -- see + # test_267_dotted_cyrillic_initial_wins_over_conjunction). + ("Олесь й Олена Коваленки", "given", "Олесь й Олена"), # Greek titles + conjunction (και has 3 letters, so the single-char # initial carve-out above never applies to it). Bare κ is NOT # shipped -- it collides with the initial+surname shape (see the @@ -1075,3 +1085,18 @@ def test_269_bare_greek_kappa_not_a_title() -> None: assert n.title == "" assert n.given == "Κ." assert n.family == "Παπαδόπουλος" + + +def test_267_dotted_cyrillic_initial_wins_over_conjunction() -> None: + # Blast-radius guard for the single-letter Cyrillic conjunctions + # (и/і/й): each is also the first letter of real given names + # (Игорь, Ірина, Йосип), so the shipped entries would be a trap if + # membership could outrank a punctuated initial. It cannot -- the + # initial regex's dotted branch is Unicode-aware -- and #267 settled + # the BARE letter in the conjunction's favor precisely because + # Cyrillic typography writes initials with the period. + for dotted, family in (("И.", "Иванов"), ("І.", "Франко"), + ("Й.", "Сліпий")): + n = parse(f"{dotted} {family}") + assert n.given == dotted + assert n.family == family