A sort name folds differently from upstream's #59
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A sort name folds differently from upstream's for 11 artists.
Measured over the music library, 174 of 185 artist rows match upstream on every
column; the other 11 differ only on
SortName, and one also on its presentationkey and path.
Upstream transliterates CJK to pinyin, strips U+2010 and U+2019, and maps
Ø→Oand
®→rthrough the Diacritics library. The folding here does none of those.Note this is a different table from the one #56 fixed.
fold_diacriticsnowreproduces upstream's
CleanNameexactly over 56,266 rows; what remains is theseparate spelling a sort name is built from.
Present before the by-name rows were written and unchanged by them — the rows
merely make it visible in a second place.
Traced the rule and measured it against upstream's own
SortNamecolumn over both production databases.Upstream's rule
BaseItem.SortName→CreateSortName→GetSortName→ModifySortChunks(
MediaBrowser.Controller/Entities/BaseItem.cs:928-1025). The last lines ofModifySortChunksare the ones this port does not have:Transliterated()is ICU, with the id set insrc/Jellyfin.Extensions/StringExtensions.cs:14-16:That one ruleset accounts for every symptom in this issue.
Any-LatinromanizesHan and kana (七生報國 →
qi sheng bao guo, ロンググッドバイ →ronguguddobai)and Greek and Cyrillic (Δ →
d, Ш →s).Latin-Asciimaps ® to(R),U+2010 / U+2019 / U+2026 / U+2013 to ASCII punctuation, and U+00A0 to a space.
Lowerfolds the(R), and[:Punctuation:] Removethen deletes the bracketsand the hyphens — which is what turns
swedm®intoswedmrandBUCK‐TICKinto
bucktick. It also explains why™survives:Latin-Asciihas no rulefor it and it is a symbol rather than punctuation.
The gate matters. The transliteration runs only when the diacritic fold left
the string non-ASCII, so a pure-ASCII title keeps its brackets and its colons.
Any-Latinhas no Rust equivalenticu_experimental::transliterate) shipsund-t-und-latn-d0-ascii(Latin-ASCII) in its compiled data and nothing else;
und-t-und-latn,und-t-und-haniand the rest all fail withIdentifierNotFound. ICU4X doesnot model the
Any-*composites at all — they are ICU's per-script-rundispatch, not a CLDR rule file.
ICU4N's, which is a dependency
jellyfin-corecannot take.Implementing the tail of the pipeline without
Any-Latinwould be exact forevery name whose letters are all Latin —
Any-Latinis the identity there — andwould close 22 of the 30 music-library divergences. It would still leave the
eight CJK/Greek/Cyrillic ones wrong, and it needs Latin-ASCII, NFD and Unicode
category tests inside
jellyfin-core. That is an architecture decision ratherthan a bug fix, so it is not on
fix/59-sort-name.What is fixed
One divergence here is independent of the transliterator and exactly
reproducible:
char.IsDigitis the decimal-digit category alone, where Rust'sis_numericalso takes superscripts, fractions and numeral letters.R² Filmssorted here as
r000000000² filmsagainst upstream'sr² films.Measured
Recomputed
SortNamefor every row of both databases and compared it with thestored column — the method that validated the folding fix.
No row moved from matching to mismatching.
Five of the seven anchor rows left are stale rather than wrong: their
DateLastSavedis 2022 or 2023, before jellyfin#11172 added thetransliteration, so the stored value is what an older rule produced. The two
written in 2026 are the
Swedm®artists and are genuine transliteration cases.All 30 music rows were written by the pinned upstream in August 2026 and are all
genuine — 22 need only the Latin-ASCII and punctuation tail, 8 need
Any-Latin.Partly fixed, and the rest is blocked on a decision rather than on work.
The rule.
BaseItem.CreateSortName(BaseItem.cs:928-1025) ends withand
Transliterated()is ICU with the idAny-Latin; Latin-Ascii; Lower; NFD; [:Nonspacing Mark:] Remove; [:Punctuation:] Remove;(
Jellyfin.Extensions/StringExtensions.cs:14). That one ruleset explains everysymptom this issue listed, and the gate matters: it runs only where the fold
left the string non-ASCII, so an all-ASCII title keeps its colons and brackets.
The framing here was slightly wrong.
Ø→Ois already handled;®→ris notDiacritics.NET at all — it is
Latin-Asciiturning®into(R), thenLower,then
[:Punctuation:] Removedeleting the brackets.Any-Latincannot be reproduced in Rust.icu_experimental::transliterateships exactly one transform in compiled data,
und-t-und-latn-d0-ascii;und-t-und-latnand the Han variants all fail withIdentifierNotFound. ICU4Xdoes not model the
Any-*composites — they are ICU's per-script-run dispatchrather than a CLDR rule file. The faithful route is FFI to a libicu whose data
version matches ICU4N's, which
jellyfin-core's allowlist forbids.What was fixed. One divergence here is independent of the transliterator and
exactly reproducible: C#
char.IsDigitis the decimal-digit category alone,where Rust's
is_numericalso takes superscripts and fractions.R² Filmspadded the
²into a sort chunk.clean_nameis deliberately left onis_numeric, because upstream'sGetCleanValueuses\p{N}, which is everynumber category — the stored
CleanNamefor that row isr² films.Measured, recomputing
SortNamefor every row and comparing to the storedcolumn:
No row moved from matching to mismatching.
Of the 7 left in the production database, 5 are stale rather than wrong —
DateLastSavedis 2022/2023, predating the upstream change that addedtransliteration, so the stored value is what an older rule produced. The 2 fresh
ones are the
Swedm®artists.All 30 music rows are genuine. 22 need only the tail — Latin-ASCII, lower,
NFD, strip marks, strip punctuation — which is exact for any name whose letters
are Latin, since
Any-Latinis the identity there. 8 needAny-Latinitself: 七生報國, きのこ帝国, 竹村延和, デモ 一, ロンググッドバイ,
D E Δ T Hand two
M├ШLfolders.The decision. Shipping the tail alone would close 22 of 30 and regress
nothing, but it wants Latin-ASCII data, NFD and Unicode category tests inside
jellyfin-core— andunicode-normalizationwas just removed from that crate.That is an allowlist change, so it is not a bug fix to make unilaterally.
Closed by transliterating through ICU, as upstream does.
crates/jellyfin-core/src/text.rsgainedtransliterate, running upstream'sexact transform id through ICU4C via
rust_icu_utrans:and
sort_nameends with upstream's gate — a name the diacritic fold leftASCII keeps its colons and brackets; anything else goes through the
transliterator. The full compound id works through the C API;
uconv's refusalof
[:Nonspacing Mark:] Removeis its own CLI parser and nothing had to bereimplemented.
Recomputing
SortNamefor every named row and comparing to the stored column:The second number going up is the right answer, and the databases say so.
Upstream added this in
c161358feat(i18n): transliterate item name beforesorting (#11172), 2024-03-23. Every one of the 31 was last written between
2022-01-15 and 2023-05-17, and 27 have a twin in the music database — the same
name, rescanned by the pinned upstream in 2026:
きのこ帝国kinoko di guo竹村延和zhu cun yan hebuck‐tickbucktickThose rows record a rule upstream abandoned; matching them again is only
possible by not matching the version this project pins. Not one row saved after
that commit disagrees.
rust_icu_utransandrust_icu_sysare now onjellyfin-core's allowlist —the one dependency here that is not pure Rust, because no pure-Rust
transliterator implements
Any-Latin. ICU4X was checked first and its bakeddata carries no
hani/jpantransform at all. ARCHITECTURE.md records thereason, README the build requirement, and CI installs the headers.
libicu is a hard build dependency with no fallback. A binary built here links
version-suffixed symbols, so a box without a matching ICU refuses to start it —
loud, rather than a silently different sort name.
Not verified: only ICU 78 was available. It reproduces the pinned upstream's
stored values across 15,146 rows, but a CI container on a different ICU is
unmeasured.