From 853cfbd3f894bfa14aeedf29234d0c49d06cc815 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 30 Jul 2025 13:25:24 +0300 Subject: [PATCH 1/6] gh-87281: Fix support for locales with modifiers --- Lib/locale.py | 34 ++++++-- Lib/test/test_locale.py | 152 +++++++++++++++++++++++++++++----- Tools/i18n/makelocalealias.py | 9 +- 3 files changed, 162 insertions(+), 33 deletions(-) diff --git a/Lib/locale.py b/Lib/locale.py index 0bde7ed51c66c1..ab98dbd4be0a4b 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -375,12 +375,12 @@ def _replace_encoding(code, encoding): def _append_modifier(code, modifier): if modifier == 'euro': if '.' not in code: - return code + '.ISO8859-15' + return code + '.ISO8859-15@euro' _, _, encoding = code.partition('.') - if encoding in ('ISO8859-15', 'UTF-8'): + if encoding == 'UTF-8': return code if encoding == 'ISO8859-1': - return _replace_encoding(code, 'ISO8859-15') + code = _replace_encoding(code, 'ISO8859-15') return code + '@' + modifier def normalize(localename): @@ -487,11 +487,16 @@ def _parse_localename(localename): if modifier == 'euro' and '.' not in code: # Assume Latin-9 for @euro locales. This is bogus, # since some systems may use other encodings for these - # locales. Also, we ignore other modifiers. - return code, 'iso-8859-15' + # locales. + return code + '@euro', 'ISO8859-15' + else: + modifier = '' if '.' in code: - return tuple(code.split('.')[:2]) + code, encoding = code.split('.')[:2] + if modifier: + code += '@' + modifier + return code, encoding elif code == 'C': return None, None elif code == 'UTF-8': @@ -516,7 +521,14 @@ def _build_localename(localetuple): if encoding is None: return language else: - return language + '.' + encoding + if '@' in language: + language, modifier = language.split('@', 1) + else: + modifier = '' + localename = language + '.' + encoding + if modifier: + localename += '@' + modifier + return localename except (TypeError, ValueError): raise TypeError('Locale must be None, a string, or an iterable of ' 'two strings -- language code, encoding.') from None @@ -888,6 +900,12 @@ def getpreferredencoding(do_setlocale=True): # SS 2025-06-10: # Remove 'c.utf8' -> 'en_US.UTF-8' because 'en_US.UTF-8' does not exist # on all platforms. +# +# SS 2025-07-30: +# Remove conflicts with GNU libc. +# +# removed 'el_gr@euro' +# removed 'uz_uz@cyrillic' locale_alias = { 'a3': 'az_AZ.KOI8-C', @@ -1021,7 +1039,6 @@ def getpreferredencoding(do_setlocale=True): 'el': 'el_GR.ISO8859-7', 'el_cy': 'el_CY.ISO8859-7', 'el_gr': 'el_GR.ISO8859-7', - 'el_gr@euro': 'el_GR.ISO8859-15', 'en': 'en_US.ISO8859-1', 'en_ag': 'en_AG.UTF-8', 'en_au': 'en_AU.ISO8859-1', @@ -1456,7 +1473,6 @@ def getpreferredencoding(do_setlocale=True): 'ur_pk': 'ur_PK.CP1256', 'uz': 'uz_UZ.UTF-8', 'uz_uz': 'uz_UZ.UTF-8', - 'uz_uz@cyrillic': 'uz_UZ.UTF-8', 've': 've_ZA.UTF-8', 've_za': 've_ZA.UTF-8', 'vi': 'vi_VN.TCVN', diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 55b502e52ca454..4b728a7354fcfa 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -1,4 +1,5 @@ from decimal import Decimal +from test import support from test.support import cpython_only, verbose, is_android, linked_to_musl, os_helper from test.support.warnings_helper import check_warnings from test.support.import_helper import ensure_lazy_imports, import_fresh_module @@ -424,8 +425,8 @@ def test_hyphenated_encoding(self): self.check('cs_CZ.ISO8859-2', 'cs_CZ.ISO8859-2') def test_euro_modifier(self): - self.check('de_DE@euro', 'de_DE.ISO8859-15') - self.check('en_US.ISO8859-15@euro', 'en_US.ISO8859-15') + self.check('de_DE@euro', 'de_DE.ISO8859-15@euro') + self.check('en_US.ISO8859-15@euro', 'en_US.ISO8859-15@euro') self.check('de_DE.utf8@euro', 'de_DE.UTF-8') def test_latin_modifier(self): @@ -486,6 +487,132 @@ def test_japanese(self): self.check('jp_jp', 'ja_JP.eucJP') +class TestRealLocales(unittest.TestCase): + locale_type = locale.LC_CTYPE + + def setUp(self): + oldlocale = locale.setlocale(locale.LC_CTYPE) + self.addCleanup(locale.setlocale, locale.LC_CTYPE, oldlocale) + + def test_getsetlocale_issue1813(self): + # Issue #1813: setting and getting the locale under a Turkish locale + try: + locale.setlocale(locale.LC_CTYPE, 'tr_TR') + except locale.Error: + # Unsupported locale on this system + self.skipTest('test needs Turkish locale') + loc = locale.getlocale(locale.LC_CTYPE) + if verbose: + print('testing with %a' % (loc,), end=' ', flush=True) + try: + locale.setlocale(locale.LC_CTYPE, loc) + except locale.Error as exc: + # bpo-37945: setlocale(LC_CTYPE) fails with getlocale(LC_CTYPE) + # and the tr_TR locale on Windows. getlocale() builds a locale + # which is not recognize by setlocale(). + self.skipTest(f"setlocale(LC_CTYPE, {loc!r}) failed: {exc!r}") + self.assertEqual(loc, locale.getlocale(locale.LC_CTYPE)) + + @support.subTests('localename,localetuple', [ + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'iso885915')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'iso88591')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'ISO8859-15')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'ISO8859-1')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', None)), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'iso885915')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'iso88591')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'ISO8859-15')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'ISO8859-1')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', None)), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', 'iso88597')), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', 'ISO8859-7')), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', None)), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'iso885915')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'iso88591')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'ISO8859-1')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', None)), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', 'utf8')), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', None)), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', 'utf8')), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', None)), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', 'utf8')), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', None)), + ('be_BY.UTF-8@latin', ('be_BY@latin', 'utf8')), + ('be_BY.UTF-8@latin', ('be_BY@latin', 'UTF-8')), + ('be_BY.UTF-8@latin', ('be_BY@latin', None)), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', 'utf8')), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', 'UTF-8')), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', None)), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', 'utf8')), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', 'UTF-8')), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', None)), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', 'utf8')), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', None)), + ]) + def test_setlocale_with_modifier(self, localename, localetuple): + try: + locale.setlocale(locale.LC_CTYPE, localename) + except locale.Error as exc: + self.skipTest(str(exc)) + loc = locale.setlocale(locale.LC_CTYPE, localetuple) + self.assertEqual(loc, localename) + + loctuple = locale.getlocale(locale.LC_CTYPE) + loc = locale.setlocale(locale.LC_CTYPE, loctuple) + self.assertEqual(loc, localename) + + @support.subTests('localename,localetuple', [ + ('fr_FR.iso885915@euro', ('fr_FR@euro', 'ISO8859-15')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'ISO8859-15')), + ('fr_FR@euro', ('fr_FR@euro', 'ISO8859-15')), + ('de_DE.iso885915@euro', ('de_DE@euro', 'ISO8859-15')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'ISO8859-15')), + ('de_DE@euro', ('de_DE@euro', 'ISO8859-15')), + ('el_GR.iso88597@euro', ('el_GR@euro', 'ISO8859-7')), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', 'ISO8859-7')), + ('el_GR@euro', ('el_GR@euro', 'ISO8859-7')), + ('ca_ES.iso885915@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES.utf8@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ca_ES@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ks_IN.utf8@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('ks_IN@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('sd_IN.utf8@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('sd_IN@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('be_BY.utf8@latin', ('be_BY@latin', 'UTF-8')), + ('be_BY.UTF-8@latin', ('be_BY@latin', 'UTF-8')), + ('be_BY@latin', ('be_BY@latin', 'UTF-8')), + ('sr_RS.utf8@latin', ('sr_RS@latin', 'UTF-8')), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', 'UTF-8')), + ('sr_RS@latin', ('sr_RS@latin', 'UTF-8')), + ('ug_CN.utf8@latin', ('ug_CN@latin', 'UTF-8')), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', 'UTF-8')), + ('ug_CN@latin', ('ug_CN@latin', 'UTF-8')), + ('uz_UZ.utf8@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ('uz_UZ@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ]) + def test_getlocale_with_modifier(self, localename, localetuple): + try: + locale.setlocale(locale.LC_CTYPE, localename) + except locale.Error as exc: + self.skipTest(str(exc)) + loctuple = locale.getlocale(locale.LC_CTYPE) + self.assertEqual(loctuple, localetuple) + + locale.setlocale(locale.LC_CTYPE, loctuple) + self.assertEqual(locale.getlocale(locale.LC_CTYPE), localetuple) + + class TestMiscellaneous(unittest.TestCase): def test_defaults_UTF8(self): # Issue #18378: on (at least) macOS setting LC_CTYPE to "UTF-8" is @@ -552,27 +679,6 @@ def test_setlocale_category(self): # crasher from bug #7419 self.assertRaises(locale.Error, locale.setlocale, 12345) - def test_getsetlocale_issue1813(self): - # Issue #1813: setting and getting the locale under a Turkish locale - oldlocale = locale.setlocale(locale.LC_CTYPE) - self.addCleanup(locale.setlocale, locale.LC_CTYPE, oldlocale) - try: - locale.setlocale(locale.LC_CTYPE, 'tr_TR') - except locale.Error: - # Unsupported locale on this system - self.skipTest('test needs Turkish locale') - loc = locale.getlocale(locale.LC_CTYPE) - if verbose: - print('testing with %a' % (loc,), end=' ', flush=True) - try: - locale.setlocale(locale.LC_CTYPE, loc) - except locale.Error as exc: - # bpo-37945: setlocale(LC_CTYPE) fails with getlocale(LC_CTYPE) - # and the tr_TR locale on Windows. getlocale() builds a locale - # which is not recognize by setlocale(). - self.skipTest(f"setlocale(LC_CTYPE, {loc!r}) failed: {exc!r}") - self.assertEqual(loc, locale.getlocale(locale.LC_CTYPE)) - def test_invalid_locale_format_in_localetuple(self): with self.assertRaises(TypeError): locale.setlocale(locale.LC_ALL, b'fi_FI') diff --git a/Tools/i18n/makelocalealias.py b/Tools/i18n/makelocalealias.py index 02af1caff7d499..72682d9a1001d0 100755 --- a/Tools/i18n/makelocalealias.py +++ b/Tools/i18n/makelocalealias.py @@ -44,6 +44,9 @@ def parse(filename): # Ignore one letter locale mappings (except for 'c') if len(locale) == 1 and locale != 'c': continue + if '@' in locale and '@' not in alias: + if locale.endswith('@euro') and not locale.endswith('.utf-8@euro'): + alias += '@euro' # Normalize encoding, if given if '.' in locale: lang, encoding = locale.split('.')[:2] @@ -51,6 +54,10 @@ def parse(filename): encoding = encoding.replace('_', '') locale = lang + '.' + encoding data[locale] = alias + # Conflict with GNU libc + data.pop('el_gr@euro', None) + data.pop('uz_uz@cyrillic', None) + data.pop('uz_uz.utf8@cyrillic', None) return data def parse_glibc_supported(filename): @@ -81,7 +88,7 @@ def parse_glibc_supported(filename): # Add an encoding to alias alias, _, modifier = alias.partition('@') alias = _locale._replace_encoding(alias, alias_encoding) - if modifier and not (modifier == 'euro' and alias_encoding == 'ISO-8859-15'): + if modifier: alias += '@' + modifier data[locale] = alias return data From abcbb93ec4d4b91bd06db406a5139b6a091d120a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 31 Jul 2025 19:01:34 +0300 Subject: [PATCH 2/6] Skip the crashing tests on Windows. --- Lib/test/test_locale.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 4b728a7354fcfa..ef3d10d35d8c17 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -6,6 +6,7 @@ from unittest import mock import unittest import locale +import os import sys import codecs @@ -555,6 +556,9 @@ def test_getsetlocale_issue1813(self): ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', None)), ]) def test_setlocale_with_modifier(self, localename, localetuple): + if os.name == 'nt' and localename in ('ks_IN.UTF-8@devanagari', + 'sd_IN.UTF-8@devanagari'): + self.skipTest('gh-137273: crashes on Windows') try: locale.setlocale(locale.LC_CTYPE, localename) except locale.Error as exc: @@ -602,6 +606,9 @@ def test_setlocale_with_modifier(self, localename, localetuple): ('uz_UZ@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), ]) def test_getlocale_with_modifier(self, localename, localetuple): + if os.name == 'nt' and localename in ('ks_IN.UTF-8@devanagari', + 'sd_IN.UTF-8@devanagari'): + self.skipTest('gh-137273: crashes on Windows') try: locale.setlocale(locale.LC_CTYPE, localename) except locale.Error as exc: From e0df7e0a3180395a2d0fddb7438c64300e3418fa Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 13 Aug 2025 23:43:50 +0300 Subject: [PATCH 3/6] Revert "Skip the crashing tests on Windows." This reverts commit abcbb93ec4d4b91bd06db406a5139b6a091d120a. --- Lib/test/test_locale.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index ef3d10d35d8c17..4b728a7354fcfa 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -6,7 +6,6 @@ from unittest import mock import unittest import locale -import os import sys import codecs @@ -556,9 +555,6 @@ def test_getsetlocale_issue1813(self): ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', None)), ]) def test_setlocale_with_modifier(self, localename, localetuple): - if os.name == 'nt' and localename in ('ks_IN.UTF-8@devanagari', - 'sd_IN.UTF-8@devanagari'): - self.skipTest('gh-137273: crashes on Windows') try: locale.setlocale(locale.LC_CTYPE, localename) except locale.Error as exc: @@ -606,9 +602,6 @@ def test_setlocale_with_modifier(self, localename, localetuple): ('uz_UZ@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), ]) def test_getlocale_with_modifier(self, localename, localetuple): - if os.name == 'nt' and localename in ('ks_IN.UTF-8@devanagari', - 'sd_IN.UTF-8@devanagari'): - self.skipTest('gh-137273: crashes on Windows') try: locale.setlocale(locale.LC_CTYPE, localename) except locale.Error as exc: From 4954f97c09461eccd18d59df1cd2c95bf485dba2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Aug 2025 00:00:44 +0300 Subject: [PATCH 4/6] Add cocumentation. --- Doc/library/locale.rst | 10 ++++++++-- Doc/whatsnew/3.15.rst | 9 +++++++++ .../2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst | 3 +++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index d48ea04077f366..5e77f1fb433f08 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -42,7 +42,7 @@ The :mod:`locale` module defines the following exception and functions: If *locale* is a pair, it is converted to a locale name using the locale aliasing engine. The language code has the same format as a :ref:`locale name `, - but without encoding and ``@``-modifier. + but without encoding. The language code and encoding can be ``None``. If *locale* is omitted or ``None``, the current setting for *category* is @@ -58,6 +58,9 @@ The :mod:`locale` module defines the following exception and functions: specified in the :envvar:`LANG` environment variable). If the locale is not changed thereafter, using multithreading should not cause problems. + .. verssionchanged:: next + Support language codes with ``@``-modifiers. + .. function:: localeconv() @@ -366,11 +369,14 @@ The :mod:`locale` module defines the following exception and functions: values except :const:`LC_ALL`. It defaults to :const:`LC_CTYPE`. The language code has the same format as a :ref:`locale name `, - but without encoding and ``@``-modifier. + but without encoding. The language code and encoding may be ``None`` if their values cannot be determined. The "C" locale is represented as ``(None, None)``. + .. verssionchanged:: next + The language code can now contain a ``@``-modifier. + .. function:: getpreferredencoding(do_setlocale=True) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9f01b52f1aff3b..266d204506a217 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -263,6 +263,15 @@ http.client (Contributed by Alexander Enrique Urieles Nieto in :gh:`131724`.) +locale +------ + +* :func:`~locale.setlocale` now supports language codes with ``@``-modifiers. + ``@``-modifier are no longer silently removed in :func:`~locale.getlocale`, + but included in the language code. + (Contributed by Serhiy Storchaka in :gh:`137729`.) + + math ---- diff --git a/Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst b/Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst new file mode 100644 index 00000000000000..b324a42c7f869e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst @@ -0,0 +1,3 @@ +:func:`locale.setlocale` now supports language codes with ``@``-modifiers. +``@``-modifier are no longer silently removed in :func:`locale.getlocale`, +but included in the language code. From 1096f32a42db80d72f1b8820aa724e146cf29618 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Aug 2025 00:17:57 +0300 Subject: [PATCH 5/6] Add comments. --- Lib/locale.py | 8 +++++--- Tools/i18n/makelocalealias.py | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/locale.py b/Lib/locale.py index ab98dbd4be0a4b..37cafb4a601b3c 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -375,6 +375,8 @@ def _replace_encoding(code, encoding): def _append_modifier(code, modifier): if modifier == 'euro': if '.' not in code: + # Linux appears to require keeping the "@euro" modifier in place, + # even when using the ".ISO8859-15" encoding. return code + '.ISO8859-15@euro' _, _, encoding = code.partition('.') if encoding == 'UTF-8': @@ -485,9 +487,9 @@ def _parse_localename(localename): # Deal with locale modifiers code, modifier = code.split('@', 1) if modifier == 'euro' and '.' not in code: - # Assume Latin-9 for @euro locales. This is bogus, - # since some systems may use other encodings for these - # locales. + # Assume ISO8859-15 for @euro locales. Do note that some systems + # may use other encodings for these locales, so this may not always + # be correct. return code + '@euro', 'ISO8859-15' else: modifier = '' diff --git a/Tools/i18n/makelocalealias.py b/Tools/i18n/makelocalealias.py index 72682d9a1001d0..7f001abc09745d 100755 --- a/Tools/i18n/makelocalealias.py +++ b/Tools/i18n/makelocalealias.py @@ -45,6 +45,10 @@ def parse(filename): if len(locale) == 1 and locale != 'c': continue if '@' in locale and '@' not in alias: + # Do not simply remove the "@euro" modifier. + # Glibc generates separate locales with the "@euro" modifier, and + # not always generates a locale without it with the same encoding. + # It can also affect collation. if locale.endswith('@euro') and not locale.endswith('.utf-8@euro'): alias += '@euro' # Normalize encoding, if given @@ -54,7 +58,7 @@ def parse(filename): encoding = encoding.replace('_', '') locale = lang + '.' + encoding data[locale] = alias - # Conflict with GNU libc + # Conflict with glibc. data.pop('el_gr@euro', None) data.pop('uz_uz@cyrillic', None) data.pop('uz_uz.utf8@cyrillic', None) From 073572e83efbefefe0c29e212757559ea3b379c4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Aug 2025 08:40:29 +0300 Subject: [PATCH 6/6] Fix typos. --- Doc/library/locale.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index 5e77f1fb433f08..0800b3e5677c93 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -58,7 +58,7 @@ The :mod:`locale` module defines the following exception and functions: specified in the :envvar:`LANG` environment variable). If the locale is not changed thereafter, using multithreading should not cause problems. - .. verssionchanged:: next + .. versionchanged:: next Support language codes with ``@``-modifiers. @@ -374,8 +374,9 @@ The :mod:`locale` module defines the following exception and functions: determined. The "C" locale is represented as ``(None, None)``. - .. verssionchanged:: next - The language code can now contain a ``@``-modifier. + .. versionchanged:: next + ``@``-modifier are no longer silently removed, but included in + the language code. .. function:: getpreferredencoding(do_setlocale=True)