Fixed #30439 -- Added support for different plural forms for a language.
Thanks to Michal Čihař for review.
This commit is contained in:
parent
591e2270dc
commit
e3e48b0012
|
@ -58,6 +58,63 @@ def reset_cache(**kwargs):
|
|||
get_supported_language_variant.cache_clear()
|
||||
|
||||
|
||||
class TranslationCatalog:
|
||||
"""
|
||||
Simulate a dict for DjangoTranslation._catalog so as multiple catalogs
|
||||
with different plural equations are kept separate.
|
||||
"""
|
||||
def __init__(self, trans=None):
|
||||
self._catalogs = [trans._catalog.copy()] if trans else [{}]
|
||||
self._plurals = [trans.plural] if trans else [lambda n: int(n != 1)]
|
||||
|
||||
def __getitem__(self, key):
|
||||
for cat in self._catalogs:
|
||||
try:
|
||||
return cat[key]
|
||||
except KeyError:
|
||||
pass
|
||||
raise KeyError(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._catalogs[0][key] = value
|
||||
|
||||
def __contains__(self, key):
|
||||
return any(key in cat for cat in self._catalogs)
|
||||
|
||||
def items(self):
|
||||
for cat in self._catalogs:
|
||||
yield from cat.items()
|
||||
|
||||
def keys(self):
|
||||
for cat in self._catalogs:
|
||||
yield from cat.keys()
|
||||
|
||||
def update(self, trans):
|
||||
# Merge if plural function is the same, else prepend.
|
||||
for cat, plural in zip(self._catalogs, self._plurals):
|
||||
if trans.plural.__code__ == plural.__code__:
|
||||
cat.update(trans._catalog)
|
||||
break
|
||||
else:
|
||||
self._catalogs.insert(0, trans._catalog)
|
||||
self._plurals.insert(0, trans.plural)
|
||||
|
||||
def get(self, key, default=None):
|
||||
missing = object()
|
||||
for cat in self._catalogs:
|
||||
result = cat.get(key, missing)
|
||||
if result is not missing:
|
||||
return result
|
||||
return default
|
||||
|
||||
def plural(self, msgid, num):
|
||||
for cat, plural in zip(self._catalogs, self._plurals):
|
||||
tmsg = cat.get((msgid, plural(num)))
|
||||
if tmsg is not None:
|
||||
return tmsg
|
||||
raise KeyError
|
||||
|
||||
|
||||
class DjangoTranslation(gettext_module.GNUTranslations):
|
||||
"""
|
||||
Set up the GNUTranslations context with regard to output charset.
|
||||
|
@ -104,7 +161,7 @@ class DjangoTranslation(gettext_module.GNUTranslations):
|
|||
self._add_fallback(localedirs)
|
||||
if self._catalog is None:
|
||||
# No catalogs found for this language, set an empty catalog.
|
||||
self._catalog = {}
|
||||
self._catalog = TranslationCatalog()
|
||||
|
||||
def __repr__(self):
|
||||
return "<DjangoTranslation lang:%s>" % self.__language
|
||||
|
@ -175,9 +232,9 @@ class DjangoTranslation(gettext_module.GNUTranslations):
|
|||
# Take plural and _info from first catalog found (generally Django's).
|
||||
self.plural = other.plural
|
||||
self._info = other._info.copy()
|
||||
self._catalog = other._catalog.copy()
|
||||
self._catalog = TranslationCatalog(other)
|
||||
else:
|
||||
self._catalog.update(other._catalog)
|
||||
self._catalog.update(other)
|
||||
if other._fallback:
|
||||
self.add_fallback(other._fallback)
|
||||
|
||||
|
@ -189,6 +246,18 @@ class DjangoTranslation(gettext_module.GNUTranslations):
|
|||
"""Return the translation language name."""
|
||||
return self.__to_language
|
||||
|
||||
def ngettext(self, msgid1, msgid2, n):
|
||||
try:
|
||||
tmsg = self._catalog.plural(msgid1, n)
|
||||
except KeyError:
|
||||
if self._fallback:
|
||||
return self._fallback.ngettext(msgid1, msgid2, n)
|
||||
if n == 1:
|
||||
tmsg = msgid1
|
||||
else:
|
||||
tmsg = msgid2
|
||||
return tmsg
|
||||
|
||||
|
||||
def translation(language):
|
||||
"""
|
||||
|
|
|
@ -4,9 +4,10 @@ Django 2.2.12 release notes
|
|||
|
||||
*Expected April 1, 2020*
|
||||
|
||||
Django 2.2.12 fixes several bugs in 2.2.11.
|
||||
Django 2.2.12 fixes a bug in 2.2.11.
|
||||
|
||||
Bugfixes
|
||||
========
|
||||
|
||||
* ...
|
||||
* Added the ability to handle ``.po`` files containing different plural
|
||||
equations for the same language (:ticket:`30439`).
|
||||
|
|
|
@ -9,4 +9,5 @@ Django 3.0.5 fixes several bugs in 3.0.4.
|
|||
Bugfixes
|
||||
========
|
||||
|
||||
* ...
|
||||
* Added the ability to handle ``.po`` files containing different plural
|
||||
equations for the same language (:ticket:`30439`).
|
||||
|
|
|
@ -277,14 +277,9 @@ In a case like this, consider something like the following::
|
|||
|
||||
a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid'
|
||||
|
||||
.. note:: Plural form and po files
|
||||
.. versionchanged: 2.2.12
|
||||
|
||||
Django does not support custom plural equations in po files. As all
|
||||
translation catalogs are merged, only the plural form for the main Django po
|
||||
file (in ``django/conf/locale/<lang_code>/LC_MESSAGES/django.po``) is
|
||||
considered. Plural forms in all other po files are ignored. Therefore, you
|
||||
should not use different plural equations in your project or application po
|
||||
files.
|
||||
Added support for different plural equations in ``.po`` files.
|
||||
|
||||
.. _contextual-markers:
|
||||
|
||||
|
|
Binary file not shown.
|
@ -14,7 +14,10 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 ? 1 : 2);\n"
|
||||
|
||||
# Plural form is purposefully different from the normal French plural to test
|
||||
# multiple plural forms for one language.
|
||||
|
||||
#: template.html:3
|
||||
# Note: Intentional: variable name is translated.
|
||||
|
@ -24,4 +27,10 @@ msgstr "Mon nom est %(personne)s."
|
|||
#: template.html:3
|
||||
# Note: Intentional: the variable name is badly formatted (missing 's' at the end)
|
||||
msgid "My other name is %(person)s."
|
||||
msgstr "Mon autre nom est %(person)."
|
||||
msgstr "Mon autre nom est %(person)."
|
||||
|
||||
msgid "%d singular"
|
||||
msgid_plural "%d plural"
|
||||
msgstr[0] "%d singulier"
|
||||
msgstr[1] "%d pluriel1"
|
||||
msgstr[2] "%d pluriel2"
|
||||
|
|
|
@ -125,6 +125,22 @@ class TranslationTests(SimpleTestCase):
|
|||
self.assertEqual(g('%d year', '%d years', 1) % 1, '1 year')
|
||||
self.assertEqual(g('%d year', '%d years', 2) % 2, '2 years')
|
||||
|
||||
@override_settings(LOCALE_PATHS=extended_locale_paths)
|
||||
@translation.override('fr')
|
||||
def test_multiple_plurals_per_language(self):
|
||||
"""
|
||||
Normally, French has 2 plurals. As other/locale/fr/LC_MESSAGES/django.po
|
||||
has a different plural equation with 3 plurals, this tests if those
|
||||
plural are honored.
|
||||
"""
|
||||
self.assertEqual(ngettext("%d singular", "%d plural", 0) % 0, "0 pluriel1")
|
||||
self.assertEqual(ngettext("%d singular", "%d plural", 1) % 1, "1 singulier")
|
||||
self.assertEqual(ngettext("%d singular", "%d plural", 2) % 2, "2 pluriel2")
|
||||
french = trans_real.catalog()
|
||||
# Internal _catalog can query subcatalogs (from different po files).
|
||||
self.assertEqual(french._catalog[('%d singular', 0)], '%d singulier')
|
||||
self.assertEqual(french._catalog[('%d hour', 0)], '%d heure')
|
||||
|
||||
def test_override(self):
|
||||
activate('de')
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue