Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions picard/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,25 @@ def _digits_replace(matchobj):

def _sort_key_qt(string, numeric=False):
collator = _qcollator_numeric if numeric else _qcollator

# Null bytes can cause crashes in OS collation functions.
string = string.replace('\0', '')

# On macOS / Windows the numeric sorting does not work reliable with non-latin
# scripts. Replace numbers in the sort string with their latin equivalent.
if numeric and (IS_MACOS or IS_WIN):
string = RE_NUMBER.sub(_digits_replace, string)

# On macOS numeric sorting of strings entirely consisting of numeric characters fails
# and always sorts alphabetically (002 < 1). Always prefix with an alphabetic character
# to work around that.
return collator.sortKey('a' + string.replace('\0', ''))
if IS_MACOS:
# macOS does not sort the empty string before other values correctly
if not string:
string = ' '
# On macOS numeric sorting of strings entirely consisting of numeric
# characters fails and always sorts alphabetically (002 < 1). Always
# prefix with an alphabetic character to work around that.
string = 'a' + string

return collator.sortKey(string)


def _sort_key_strxfrm(string, numeric=False):
Expand Down
29 changes: 16 additions & 13 deletions test/test_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,28 @@ def test_gettext_handles_empty_string(self):

def test_sort_key(self):
setup_gettext(localedir, 'de')
self.assertTrue(sort_key('äb') < sort_key('ac'))
self.assertTrue(sort_key('foo002') < sort_key('foo1'))
self.assertTrue(sort_key('002 foo') < sort_key('1 foo'))
self.assertTrue(sort_key('1') < sort_key('C'))
self.assertTrue(sort_key('foo1', numeric=True) < sort_key('foo002', numeric=True))
self.assertTrue(sort_key('004', numeric=True) < sort_key('5', numeric=True))
self.assertTrue(sort_key('0042', numeric=True) < sort_key('50', numeric=True))
self.assertTrue(sort_key('5', numeric=True) < sort_key('0042', numeric=True))
self.assertTrue(sort_key('99', numeric=True) < sort_key('100', numeric=True))
self.assertLess(sort_key('äb'), sort_key('ac'))
self.assertLess(sort_key('foo002'), sort_key('foo1'))
self.assertLess(sort_key('002 foo'), sort_key('1 foo'))
self.assertLess(sort_key('1'), sort_key('C'))
self.assertLess(sort_key(''), sort_key('0'))
self.assertLess(sort_key('\0'), sort_key('0'))
self.assertLess(sort_key('0'), sort_key('00'))
self.assertLess(sort_key('foo1', numeric=True), sort_key('foo002', numeric=True))
self.assertLess(sort_key('004', numeric=True), sort_key('5', numeric=True))
self.assertLess(sort_key('0042', numeric=True), sort_key('50', numeric=True))
self.assertLess(sort_key('5', numeric=True), sort_key('0042', numeric=True))
self.assertLess(sort_key('99', numeric=True), sort_key('100', numeric=True))

def test_sort_key_numbers_different_scripts(self):
setup_gettext(localedir, 'en')
for four in ('4', '𝟜', '٤', '๔'):
self.assertTrue(
sort_key('3', numeric=True) < sort_key(four, numeric=True),
self.assertLess(
sort_key('3', numeric=True), sort_key(four, numeric=True),
msg=f'3 < {four}'
)
self.assertTrue(
sort_key(four, numeric=True) < sort_key('5', numeric=True),
self.assertLess(
sort_key(four, numeric=True), sort_key('5', numeric=True),
msg=f'{four} < 5'
)

Expand Down
Loading