Skip to content
10 changes: 7 additions & 3 deletions Doc/library/gettext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
of language code strings.
Expand All @@ -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.setlocale` is used to generate *languages* if *languages* is
not provided.

.. function:: translation(domain, localedir=None, languages=None, class_=None, fallback=False)

Expand Down
17 changes: 11 additions & 6 deletions Lib/gettext.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import operator
import os
import sys
import locale


__all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
Expand Down Expand Up @@ -229,7 +230,6 @@ def func(n):


def _expand_lang(loc):
import locale
loc = locale.normalize(loc)
COMPONENT_CODESET = 1 << 0
COMPONENT_TERRITORY = 1 << 1
Expand Down Expand Up @@ -491,11 +491,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 val := os.environ.get('LANGUAGE'):
languages = val.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)
if val:
languages = val.split(':')
break
if 'C' not in languages:
languages.append('C')
# now normalize and expand the languages
Expand Down
65 changes: 57 additions & 8 deletions Lib/test/test_gettext.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import locale
import os
import base64
import gettext
Expand Down Expand Up @@ -875,17 +876,65 @@ def create_mo_file(self, lang):
f.write(GNU_MO_DATA)
return mo_file

def test_find_with_env_vars(self):
# test that find correctly finds the environment variables
# when languages are not supplied
mo_file = self.create_mo_file("ga_IE")
def _for_all_vars(self, mo_file, locale, expected=True):
for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
self.env.set(var, 'ga_IE')
self.env.set(var, locale)
result = gettext.find("mofile",
localedir=os.path.join(self.tempdir, "locale"))
self.assertEqual(result, mo_file)
if expected:
self.assertEqual(mo_file, result)
else:
self.assertIsNone(result)
self.env.unset(var)

@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("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):
self.env.set('LANGUAGE', 'ga_IE')
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, '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)

def test_process_vars_override(self):
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"):
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"):
locale.setlocale(locale.LC_ALL, loc)
result = gettext.find("mofile", localedir=os.path.join(self.tempdir, "locale"))
self.assertIsNone(result)

def test_find_with_languages(self):
# test that passed languages are used
self.env.set('LANGUAGE', 'pt_BR')
Expand Down Expand Up @@ -934,14 +983,14 @@ 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__':
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implement a fall back to :func:`locale.getlocale` in :func:`gettext.find` if
*languages* is not provided and :envvar:`LANGUAGE` is not set.
Loading