From fcce7bd887d9d39f45456009f2f5ad48e6a4c476 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 19 Mar 2025 17:33:35 +0000 Subject: [PATCH 01/11] Initial --- Doc/library/gettext.rst | 10 +++++++--- Lib/gettext.py | 16 +++++++++++----- Lib/test/test_gettext.py | 10 +++++++++- ...2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst | 2 ++ 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst diff --git a/Doc/library/gettext.rst b/Doc/library/gettext.rst index d0de83907eb297..4179dc24c0d265 100644 --- a/Doc/library/gettext.rst +++ b/Doc/library/gettext.rst @@ -130,9 +130,10 @@ install themselves in the built-in namespace as the function :func:`!_`. strings, where each string is a language code. If *localedir* is not given, then the default system locale directory is used. - [#]_ If *languages* is not given, then the following environment variables are - searched: :envvar:`LANGUAGE`, :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, and - :envvar:`LANG`. The first one returning a non-empty value is used for the + [#]_ If *languages* is not given, then the environment variable :envvar:`LANGUAGE` + is searched, it falls back to :func:`locale.getlocale`, which in turn falls + back to the environment variables :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, and + :envvar:`LANG` where the first one returning a non-empty value is used for the *languages* variable. The environment variables should contain a colon separated list of languages, which will be split on the colon to produce the expected list of language code strings. @@ -147,6 +148,9 @@ install themselves in the built-in namespace as the function :func:`!_`. of all file names, in the order in which they appear in the languages list or the environment variables. + .. versionchanged:: next + :func:`locale.getlocale` is used to generate *languages* if *languages* is + not provided. .. function:: translation(domain, localedir=None, languages=None, class_=None, fallback=False) diff --git a/Lib/gettext.py b/Lib/gettext.py index 6c11ab2b1eb570..3565977f068136 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -46,6 +46,7 @@ import operator import os import sys +import locale __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog', @@ -491,11 +492,16 @@ def find(domain, localedir=None, languages=None, all=False): localedir = _default_localedir if languages is None: languages = [] - for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): - val = os.environ.get(envar) - if val: - languages = val.split(':') - break + if os.environ.get('LANGUAGE'): + languages = os.environ.get('LANGUAGE').split(',') + elif locale.getlocale() != (None, None): + languages.append(".".join(filter(None, locale.getlocale()))) + else: + for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): + val = os.environ.get(envar) + if val: + languages = val.split(':') + break if 'C' not in languages: languages.append('C') # now normalize and expand the languages diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 0fbd90dcb485f8..cb8f375facb3fc 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -1,3 +1,4 @@ +import locale import os import base64 import gettext @@ -736,7 +737,8 @@ def create_mo_file(self, lang): f.write(GNU_MO_DATA) return mo_file - def test_find_with_env_vars(self): + @unittest.mock.patch("locale.getlocale", return_value=(None, None)) + def test_find_with_env_vars(self, patch_getlocale): # test that find correctly finds the environment variables # when languages are not supplied mo_file = self.create_mo_file("ga_IE") @@ -747,6 +749,12 @@ def test_find_with_env_vars(self): self.assertEqual(result, mo_file) self.env.unset(var) + @unittest.mock.patch("locale.getlocale", return_value=('ga_IE', 'UTF-8')) + def test_process_vars_override(self, patch_getlocale): + mo_file = self.create_mo_file("ga_IE") + result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) + self.assertEqual(result, mo_file) + def test_find_with_languages(self): # test that passed languages are used self.env.set('LANGUAGE', 'pt_BR') diff --git a/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst b/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst new file mode 100644 index 00000000000000..84fead5f88fe00 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst @@ -0,0 +1,2 @@ +Implement a fall back to :func:`locale.getlocale` in :func:`gettext.find` if +*languages* is not provided and :envvar:`LANGUAGES` is not set. From 03ca427c5561b5ec6005520f2015da9d16af70d6 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 19 Mar 2025 18:48:02 +0000 Subject: [PATCH 02/11] Correct NEWS --- .../next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst b/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst index 84fead5f88fe00..8e301340c4fa48 100644 --- a/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst +++ b/Misc/NEWS.d/next/Library/2025-03-19-17-30-00.gh-issue-64243.fuheq3.rst @@ -1,2 +1,2 @@ Implement a fall back to :func:`locale.getlocale` in :func:`gettext.find` if -*languages* is not provided and :envvar:`LANGUAGES` is not set. +*languages* is not provided and :envvar:`LANGUAGE` is not set. From f9851e12a99d29bfbc21ab2a0bba0b413afbfe35 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 31 Mar 2025 18:14:00 +0100 Subject: [PATCH 03/11] Tomas's feedback --- Lib/gettext.py | 8 ++++---- Lib/test/test_gettext.py | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Lib/gettext.py b/Lib/gettext.py index 3565977f068136..672a54c82f1c0f 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -492,10 +492,10 @@ def find(domain, localedir=None, languages=None, all=False): localedir = _default_localedir if languages is None: languages = [] - if os.environ.get('LANGUAGE'): - languages = os.environ.get('LANGUAGE').split(',') - elif locale.getlocale() != (None, None): - languages.append(".".join(filter(None, locale.getlocale()))) + if val := os.environ.get('LANGUAGE'): + languages = val.split(':') + elif (loc := locale.getlocale()) != (None, None): + languages.append(".".join(filter(None, loc))) else: for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): val = os.environ.get(envar) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index cb8f375facb3fc..09e5c861566d6c 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -748,6 +748,13 @@ def test_find_with_env_vars(self, patch_getlocale): localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(result, mo_file) self.env.unset(var) + # test fallbacks + for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): + self.env.set(var, 'es_ES:ga_IE:fr_FR') + result = gettext.find("mofile", + localedir=os.path.join(self.tempdir, "locale")) + self.assertEqual(result, mo_file) + self.env.unset(var) @unittest.mock.patch("locale.getlocale", return_value=('ga_IE', 'UTF-8')) def test_process_vars_override(self, patch_getlocale): From 4f3734269d4e547363ad6fc17e569433340fe808 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 5 Apr 2025 13:52:48 +0100 Subject: [PATCH 04/11] Tomas's suggestion + typo spotted while testing --- Lib/gettext.py | 2 +- Lib/test/test_gettext.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/gettext.py b/Lib/gettext.py index 672a54c82f1c0f..cf34771e64a296 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -495,7 +495,7 @@ def find(domain, localedir=None, languages=None, all=False): if val := os.environ.get('LANGUAGE'): languages = val.split(':') elif (loc := locale.getlocale()) != (None, None): - languages.append(".".join(filter(None, loc))) + languages = [".".join(filter(None, loc))] else: for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): val = os.environ.get(envar) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 09e5c861566d6c..4900b8b2b62ea3 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -813,7 +813,7 @@ def test__all__(self): unittest.main() -# For reference, here's the .po file used to created the GNU_MO_DATA above. +# For reference, here's the .po file used to create the GNU_MO_DATA above. # # The original version was automatically generated from the sources with # pygettext. Later it was manually modified to add plural forms support. From c85d0c26adb8eb494e2671e9e383e83b1ea8c332 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 30 Apr 2025 21:04:23 +0100 Subject: [PATCH 05/11] Serhiy's review --- Doc/library/gettext.rst | 4 ++-- Lib/gettext.py | 3 +-- Lib/test/test_gettext.py | 39 ++++++++++++++++++++------------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Doc/library/gettext.rst b/Doc/library/gettext.rst index 4179dc24c0d265..6a8385426ad744 100644 --- a/Doc/library/gettext.rst +++ b/Doc/library/gettext.rst @@ -131,7 +131,7 @@ install themselves in the built-in namespace as the function :func:`!_`. If *localedir* is not given, then the default system locale directory is used. [#]_ If *languages* is not given, then the environment variable :envvar:`LANGUAGE` - is searched, it falls back to :func:`locale.getlocale`, which in turn falls + is searched, it falls back to :func:`locale.setlocale`, which in turn falls back to the environment variables :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, and :envvar:`LANG` where the first one returning a non-empty value is used for the *languages* variable. The environment variables should contain a colon separated @@ -149,7 +149,7 @@ install themselves in the built-in namespace as the function :func:`!_`. the environment variables. .. versionchanged:: next - :func:`locale.getlocale` is used to generate *languages* if *languages* is + :func:`locale.setlocale` is used to generate *languages* if *languages* is not provided. .. function:: translation(domain, localedir=None, languages=None, class_=None, fallback=False) diff --git a/Lib/gettext.py b/Lib/gettext.py index cf34771e64a296..13d49aa618ef5c 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -230,7 +230,6 @@ def func(n): def _expand_lang(loc): - import locale loc = locale.normalize(loc) COMPONENT_CODESET = 1 << 0 COMPONENT_TERRITORY = 1 << 1 @@ -494,7 +493,7 @@ def find(domain, localedir=None, languages=None, all=False): languages = [] if val := os.environ.get('LANGUAGE'): languages = val.split(':') - elif (loc := locale.getlocale()) != (None, None): + elif (loc := locale.setlocale(locale.LC_MESSAGES)) != (None, None): languages = [".".join(filter(None, loc))] else: for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 4900b8b2b62ea3..fc44c89f410caf 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -1,4 +1,3 @@ -import locale import os import base64 import gettext @@ -737,30 +736,32 @@ def create_mo_file(self, lang): f.write(GNU_MO_DATA) return mo_file - @unittest.mock.patch("locale.getlocale", return_value=(None, None)) - def test_find_with_env_vars(self, patch_getlocale): - # test that find correctly finds the environment variables - # when languages are not supplied - mo_file = self.create_mo_file("ga_IE") - for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): - self.env.set(var, 'ga_IE') - result = gettext.find("mofile", - localedir=os.path.join(self.tempdir, "locale")) - self.assertEqual(result, mo_file) - self.env.unset(var) - # test fallbacks + def _for_all_vars(self, mo_file, locale): for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): - self.env.set(var, 'es_ES:ga_IE:fr_FR') + self.env.set(var, locale) result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) - self.assertEqual(result, mo_file) + self.assertEqual(mo_file, result) self.env.unset(var) - @unittest.mock.patch("locale.getlocale", return_value=('ga_IE', 'UTF-8')) - def test_process_vars_override(self, patch_getlocale): + @unittest.mock.patch("locale.setlocale", return_value=(None, None)) + def test_find_with_env_vars(self, patch_getlocale): + # test that find correctly finds the environment variables + # when languages are not supplied mo_file = self.create_mo_file("ga_IE") - result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) - self.assertEqual(result, mo_file) + self._for_all_vars(mo_file, "ga_IE") + self._for_all_vars(mo_file, "ga_IE.UTF-8") + self._for_all_vars(mo_file, "es_ES:ga_IE:fr_FR") + self._for_all_vars(mo_file, "ga_IE@euro") + + def test_process_vars_override(self): + mo_file = self.create_mo_file("ga_IE") + with unittest.mock.patch("locale.setlocale", return_value=('ga_IE', 'UTF-8')): + result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) + self.assertEqual(mo_file, result) + with unittest.mock.patch("locale.setlocale", return_value=('ga_IE', None)): + result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) + self.assertEqual(mo_file, result) def test_find_with_languages(self): # test that passed languages are used From c40039fa3dbc4a120da243d1e79e8fcbe8d27aa7 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 19 Jun 2025 15:20:29 +0100 Subject: [PATCH 06/11] Review --- Doc/library/gettext.rst | 4 +-- Lib/gettext.py | 4 +-- Lib/test/test_gettext.py | 59 +++++++++++++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/Doc/library/gettext.rst b/Doc/library/gettext.rst index 6a8385426ad744..3a352ccea6f582 100644 --- a/Doc/library/gettext.rst +++ b/Doc/library/gettext.rst @@ -131,8 +131,8 @@ install themselves in the built-in namespace as the function :func:`!_`. If *localedir* is not given, then the default system locale directory is used. [#]_ If *languages* is not given, then the environment variable :envvar:`LANGUAGE` - is searched, it falls back to :func:`locale.setlocale`, which in turn falls - back to the environment variables :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, and + is searched, it falls back to the current locale or to the environment + variables :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, and :envvar:`LANG` where the first one returning a non-empty value is used for the *languages* variable. The environment variables should contain a colon separated list of languages, which will be split on the colon to produce the expected list diff --git a/Lib/gettext.py b/Lib/gettext.py index 13d49aa618ef5c..f26bd67d2a4a4c 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -493,8 +493,8 @@ def find(domain, localedir=None, languages=None, all=False): languages = [] if val := os.environ.get('LANGUAGE'): languages = val.split(':') - elif (loc := locale.setlocale(locale.LC_MESSAGES)) != (None, None): - languages = [".".join(filter(None, loc))] + elif loc := locale.setlocale(locale.LC_MESSAGES): + languages = loc.split(':') else: for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): val = os.environ.get(envar) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index fc44c89f410caf..4f994f6a22e22f 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -1,3 +1,6 @@ +import sys + +import locale import os import base64 import gettext @@ -6,8 +9,7 @@ from functools import partial from test import support -from test.support import os_helper - +from test.support import os_helper, run_with_locale # TODO: # - Add new tests, for example for "dgettext" @@ -736,32 +738,63 @@ def create_mo_file(self, lang): f.write(GNU_MO_DATA) return mo_file - def _for_all_vars(self, mo_file, locale): + def _for_all_vars(self, mo_file, locale, expected=True): for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): self.env.set(var, locale) result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) - self.assertEqual(mo_file, result) + if expected: + self.assertEqual(mo_file, result) + else: + self.assertIsNone(result) self.env.unset(var) - @unittest.mock.patch("locale.setlocale", return_value=(None, None)) + @unittest.mock.patch("locale.setlocale", return_value='') def test_find_with_env_vars(self, patch_getlocale): # test that find correctly finds the environment variables # when languages are not supplied + mo_file = self.create_mo_file("ca_ES") + self._for_all_vars(mo_file, "ca_ES") + self._for_all_vars(mo_file, "ca_ES.UTF-8") + self._for_all_vars(mo_file, "ca_ES.UTF-8.mo") + self._for_all_vars(mo_file, "es_ES:ca_ES:fr_FR") + self._for_all_vars(mo_file, "ca_ES@euro") + self._for_all_vars(mo_file, "ca_ES.UTF-8@euro") + self._for_all_vars(mo_file, "ca_ES@valencia") + self._for_all_vars(mo_file, "C", expected=False) + self._for_all_vars(mo_file, "C.UTF-8", expected=False) + + @unittest.mock.patch('gettext._expand_lang') + def test_encoding_not_ignored(self, patch_expand_lang): + self.env.set('LANGUAGE', 'ga_IE.UTF-8') + gettext.find("mofile") + patch_expand_lang.assert_any_call('ga_IE.UTF-8') + self.env.unset('LANGUAGE') + + def test_find_LANGUAGE_priority(self): + orig = locale.setlocale(locale.LC_MESSAGES) + self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) + self.env.set('LANGUAGE', 'ga_IE') + self.env.set('LC_ALL', 'pt_BR') + locale.setlocale(locale.LC_MESSAGES, 'pt_BR') mo_file = self.create_mo_file("ga_IE") - self._for_all_vars(mo_file, "ga_IE") - self._for_all_vars(mo_file, "ga_IE.UTF-8") - self._for_all_vars(mo_file, "es_ES:ga_IE:fr_FR") - self._for_all_vars(mo_file, "ga_IE@euro") + + result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) + self.assertEqual(result, mo_file) + locale.setlocale(locale.LC_MESSAGES, orig) def test_process_vars_override(self): - mo_file = self.create_mo_file("ga_IE") - with unittest.mock.patch("locale.setlocale", return_value=('ga_IE', 'UTF-8')): + orig = locale.setlocale(locale.LC_MESSAGES) + self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) + mo_file = self.create_mo_file("ca_ES") + for loc in ("ca_ES", "ca_ES.UTF-8", "ca_ES@euro", "ca_ES@valencia"): + locale.setlocale(locale.LC_MESSAGES, loc) result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(mo_file, result) - with unittest.mock.patch("locale.setlocale", return_value=('ga_IE', None)): + for loc in ("C", "C.UTF-8"): + locale.setlocale(locale.LC_MESSAGES, loc) result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) - self.assertEqual(mo_file, result) + self.assertIsNone(result) def test_find_with_languages(self): # test that passed languages are used From d75292d33ed8a8369d789a3f614e893d8183eee6 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 19 Jun 2025 15:37:57 +0100 Subject: [PATCH 07/11] Who even uses this "Windows" thing...? --- Lib/gettext.py | 2 +- Lib/test/test_gettext.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Lib/gettext.py b/Lib/gettext.py index f26bd67d2a4a4c..40087b1cee2060 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -493,7 +493,7 @@ def find(domain, localedir=None, languages=None, all=False): languages = [] if val := os.environ.get('LANGUAGE'): languages = val.split(':') - elif loc := locale.setlocale(locale.LC_MESSAGES): + elif os.name == 'posix' and (loc := locale.setlocale(locale.LC_MESSAGES)): languages = loc.split(':') else: for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 4f994f6a22e22f..e442d4b60d4826 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -771,18 +771,20 @@ def test_encoding_not_ignored(self, patch_expand_lang): patch_expand_lang.assert_any_call('ga_IE.UTF-8') self.env.unset('LANGUAGE') + @unittest.skipIf(os.name != "posix", "LC_MESSAGES is posix only") def test_find_LANGUAGE_priority(self): - orig = locale.setlocale(locale.LC_MESSAGES) - self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) self.env.set('LANGUAGE', 'ga_IE') self.env.set('LC_ALL', 'pt_BR') - locale.setlocale(locale.LC_MESSAGES, 'pt_BR') + if os.name != "posix": + orig = locale.setlocale(locale.LC_MESSAGES) + self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) + locale.setlocale(locale.LC_MESSAGES, 'pt_BR') mo_file = self.create_mo_file("ga_IE") result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(result, mo_file) - locale.setlocale(locale.LC_MESSAGES, orig) + @unittest.skipIf(os.name != "posix", "LC_MESSAGES is posix only") def test_process_vars_override(self): orig = locale.setlocale(locale.LC_MESSAGES) self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) From 7e2a5645d063e1fecab2dd3577e71e59b3fd93fa Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 19 Jun 2025 15:39:34 +0100 Subject: [PATCH 08/11] Clean up --- Lib/test/test_gettext.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index e442d4b60d4826..3210f37325f9ca 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -1,5 +1,3 @@ -import sys - import locale import os import base64 @@ -9,7 +7,7 @@ from functools import partial from test import support -from test.support import os_helper, run_with_locale +from test.support import os_helper # TODO: # - Add new tests, for example for "dgettext" From ff7d10a36a27417f6356d0453603c324f85f49ea Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 19 Jun 2025 16:01:00 +0100 Subject: [PATCH 09/11] Clean up confusion --- Lib/gettext.py | 4 ++-- Lib/test/test_gettext.py | 20 ++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Lib/gettext.py b/Lib/gettext.py index 40087b1cee2060..55397026ba3f08 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -493,8 +493,8 @@ def find(domain, localedir=None, languages=None, all=False): languages = [] if val := os.environ.get('LANGUAGE'): languages = val.split(':') - elif os.name == 'posix' and (loc := locale.setlocale(locale.LC_MESSAGES)): - languages = loc.split(':') + elif (loc := locale.getlocale()) != (None, None): + languages = [".".join(filter(None, loc))] else: for envar in ('LC_ALL', 'LC_MESSAGES', 'LANG'): val = os.environ.get(envar) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 3210f37325f9ca..7daad14edbf7c8 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -747,7 +747,7 @@ def _for_all_vars(self, mo_file, locale, expected=True): self.assertIsNone(result) self.env.unset(var) - @unittest.mock.patch("locale.setlocale", return_value='') + @unittest.mock.patch("locale.getlocale", return_value=(None, None)) def test_find_with_env_vars(self, patch_getlocale): # test that find correctly finds the environment variables # when languages are not supplied @@ -769,30 +769,26 @@ def test_encoding_not_ignored(self, patch_expand_lang): patch_expand_lang.assert_any_call('ga_IE.UTF-8') self.env.unset('LANGUAGE') - @unittest.skipIf(os.name != "posix", "LC_MESSAGES is posix only") def test_find_LANGUAGE_priority(self): self.env.set('LANGUAGE', 'ga_IE') self.env.set('LC_ALL', 'pt_BR') - if os.name != "posix": - orig = locale.setlocale(locale.LC_MESSAGES) - self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) - locale.setlocale(locale.LC_MESSAGES, 'pt_BR') + orig = locale.setlocale(locale.LC_ALL) + self.addCleanup(lambda: locale.setlocale(locale.LC_ALL, orig)) + locale.setlocale(locale.LC_ALL, 'pt_BR') mo_file = self.create_mo_file("ga_IE") - result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(result, mo_file) - @unittest.skipIf(os.name != "posix", "LC_MESSAGES is posix only") def test_process_vars_override(self): - orig = locale.setlocale(locale.LC_MESSAGES) - self.addCleanup(lambda: locale.setlocale(locale.LC_MESSAGES, orig)) + orig = locale.setlocale(locale.LC_ALL) + self.addCleanup(lambda: locale.setlocale(locale.LC_ALL, orig)) mo_file = self.create_mo_file("ca_ES") for loc in ("ca_ES", "ca_ES.UTF-8", "ca_ES@euro", "ca_ES@valencia"): - locale.setlocale(locale.LC_MESSAGES, loc) + locale.setlocale(locale.LC_ALL, loc) result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(mo_file, result) for loc in ("C", "C.UTF-8"): - locale.setlocale(locale.LC_MESSAGES, loc) + locale.setlocale(locale.LC_ALL, loc) result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertIsNone(result) From 307c7f33d033e825bfcae88384ae25c0b1b3e6b6 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 19 Jun 2025 17:09:53 +0100 Subject: [PATCH 10/11] Skip unsupported locales --- Lib/test/test_gettext.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 7daad14edbf7c8..2542910f289b31 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -771,10 +771,10 @@ def test_encoding_not_ignored(self, patch_expand_lang): def test_find_LANGUAGE_priority(self): self.env.set('LANGUAGE', 'ga_IE') - self.env.set('LC_ALL', 'pt_BR') + self.env.set('LC_ALL', 'C') orig = locale.setlocale(locale.LC_ALL) self.addCleanup(lambda: locale.setlocale(locale.LC_ALL, orig)) - locale.setlocale(locale.LC_ALL, 'pt_BR') + locale.setlocale(locale.LC_ALL, 'C') mo_file = self.create_mo_file("ga_IE") result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(result, mo_file) @@ -784,7 +784,10 @@ def test_process_vars_override(self): self.addCleanup(lambda: locale.setlocale(locale.LC_ALL, orig)) mo_file = self.create_mo_file("ca_ES") for loc in ("ca_ES", "ca_ES.UTF-8", "ca_ES@euro", "ca_ES@valencia"): - locale.setlocale(locale.LC_ALL, loc) + try: + locale.setlocale(locale.LC_ALL, loc) + except locale.Error: + self.skipTest('platform does not support locale') result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale")) self.assertEqual(mo_file, result) for loc in ("C", "C.UTF-8"): From fed61fbb8bb54c5944decc0b03b9e28cd98c32e7 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 19 Jun 2025 17:24:50 +0100 Subject: [PATCH 11/11] Fix new test --- Lib/test/test_gettext.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 28cc9846705ce5..0a976fce084c4b 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -10,6 +10,7 @@ from test.support import cpython_only, os_helper from test.support.import_helper import ensure_lazy_imports + # TODO: # - Add new tests, for example for "dgettext" # - Tests should have only one assert. @@ -982,7 +983,7 @@ def test__all__(self): @cpython_only def test_lazy_import(self): - ensure_lazy_imports("gettext", {"re", "warnings", "locale"}) + ensure_lazy_imports("gettext", {"re", "warnings"}) if __name__ == '__main__':