Fixed #9988 -- Added support for translation contexts. Thanks, Claude Paroz.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14450 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-11-04 10:48:27 +00:00
parent 0659391baf
commit 83aeb3c768
13 changed files with 137 additions and 4 deletions

View File

@ -190,7 +190,7 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
f.write(src)
finally:
f.close()
cmd = 'xgettext -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (domain, os.path.join(dirpath, thefile))
cmd = 'xgettext -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 --from-code UTF-8 -o - "%s"' % (domain, os.path.join(dirpath, thefile))
msgs, errors = _popen(cmd)
if errors:
raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
@ -225,7 +225,7 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
raise SyntaxError(msg)
if verbosity > 1:
sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 --keyword=pgettext_lazy:1c,2 --keyword=npgettext_lazy:1c,2,3 --from-code UTF-8 -o - "%s"' % (
domain, os.path.join(dirpath, thefile))
msgs, errors = _popen(cmd)
if errors:

View File

@ -10,7 +10,8 @@ __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
'get_language', 'get_language_bidi', 'get_date_formats',
'get_partial_date_formats', 'check_for_language', 'to_locale',
'get_language_from_request', 'templatize', 'ugettext', 'ugettext_lazy',
'ungettext', 'deactivate_all']
'ungettext', 'ungettext_lazy', 'pgettext', 'pgettext_lazy',
'npgettext', 'npgettext_lazy', 'deactivate_all']
# Here be dragons, so a short explanation of the logic won't hurt:
# We are trying to solve two problems: (1) access settings, in particular
@ -63,10 +64,18 @@ def ugettext(message):
def ungettext(singular, plural, number):
return _trans.ungettext(singular, plural, number)
def pgettext(context, message):
return _trans.pgettext(context, message)
def npgettext(context, singular, plural, number):
return _trans.npgettext(context, singular, plural, number)
ngettext_lazy = lazy(ngettext, str)
gettext_lazy = lazy(gettext, str)
ungettext_lazy = lazy(ungettext, unicode)
ugettext_lazy = lazy(ugettext, unicode)
pgettext_lazy = lazy(pgettext, unicode)
npgettext_lazy = lazy(npgettext, unicode)
def activate(language):
return _trans.activate(language)

View File

@ -15,6 +15,12 @@ ngettext_lazy = ngettext
def ungettext(singular, plural, number):
return force_unicode(ngettext(singular, plural, number))
def pgettext(context, message):
return ugettext(message)
def npgettext(context, singular, plural, number):
return ungettext(singular, plural, number)
activate = lambda x: None
deactivate = deactivate_all = lambda: None
get_language = lambda: settings.LANGUAGE_CODE

View File

@ -24,6 +24,9 @@ _default = None
# file lookups when checking the same locale on repeated requests.
_accepted = {}
# magic gettext number to separate context from message
CONTEXT_SEPARATOR = u"\x04"
# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
accept_language_re = re.compile(r'''
([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
@ -279,6 +282,14 @@ def gettext(message):
def ugettext(message):
return do_translate(message, 'ugettext')
def pgettext(context, message):
result = do_translate(
u"%s%s%s" % (context, CONTEXT_SEPARATOR, message), 'ugettext')
if CONTEXT_SEPARATOR in result:
# Translation not found
result = message
return result
def gettext_noop(message):
"""
Marks strings for translation but doesn't translate them now. This can be
@ -313,6 +324,15 @@ def ungettext(singular, plural, number):
"""
return do_ntranslate(singular, plural, number, 'ungettext')
def npgettext(context, singular, plural, number):
result = do_ntranslate(u"%s%s%s" % (context, CONTEXT_SEPARATOR, singular),
u"%s%s%s" % (context, CONTEXT_SEPARATOR, plural),
number, 'ungettext')
if CONTEXT_SEPARATOR in result:
# Translation not found
result = do_ntranslate(singular, plural, number, 'ungettext')
return result
def check_for_language(lang_code):
"""
Checks whether there is a global language file for the given language

View File

@ -68,6 +68,8 @@ NullSource = """
function gettext(msgid) { return msgid; }
function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
function gettext_noop(msgid) { return msgid; }
function pgettext(context, msgid) { return msgid; }
function npgettext(context, singular, plural, count) { return (count == 1) ? singular : plural; }
"""
LibHead = """
@ -98,6 +100,21 @@ function ngettext(singular, plural, count) {
function gettext_noop(msgid) { return msgid; }
function pgettext(context, msgid) {
var value = gettext(context + '\x04' + msgid);
if (value.indexOf('\x04') != -1) {
value = msgid;
}
return value;
}
function npgettext(context, singular, plural, count) {
var value = ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
if (value.indexOf('\x04') != -1) {
value = ngettext(singular, plural, count);
}
return value;
}
"""
LibFormatHead = """

View File

@ -86,6 +86,14 @@ Users of Python 2.5 and above may now use :ref:`transaction management functions
For more information, see :ref:`transaction-management-functions`.
Contextual markers in translatable strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For translation strings with ambiguous meaning, you can now
use the ``pgettext`` function to specify the context of the string.
For more information, see :ref:`contextual-markers`
Everything else
~~~~~~~~~~~~~~~

View File

@ -193,6 +193,39 @@ cardinality of the elements at play.
``django-admin.py compilemessages`` or a ``KeyError`` Python exception at
runtime.
.. _contextual-markers:
Contextual markers
------------------
.. versionadded:: 1.3
Sometimes words have several meanings, such as ``"May"`` in English, which
refers to a month name and to a verb. To enable translators to translate
these words correctly in different contexts, you can use the
``django.utils.translation.pgettext()`` function, or the
``django.utils.translation.npgettext()`` function if the string needs
pluralization. Both take a context string as the first variable.
In the resulting .po file, the string will then appear as often as there are
different contextual markers for the same string (the context will appear on
the ``msgctxt`` line), allowing the translator to give a different translation
for each of them.
For example::
from django.utils.translation import pgettext
month = pgettext("month name", "May")
will appear in the .po file as:
.. code-block:: po
msgctxt "month name"
msgid "May"
msgstr ""
.. _lazy-translations:
Lazy translation

View File

@ -20,3 +20,20 @@ msgstr ""
#: models.py:3
msgid "Date/time"
msgstr "Datum/Zeit (LOCALE_PATHS)"
#: models.py:5
msgctxt "month name"
msgid "May"
msgstr "Mai"
#: models.py:7
msgctxt "verb"
msgid "May"
msgstr "Kann"
#: models.py:9
msgctxt "search"
msgid "%d result"
msgid_plural "%d results"
msgstr[0] "%d Resultat"
msgstr[1] "%d Resultate"

View File

@ -11,7 +11,7 @@ from django.test import TestCase
from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules
from django.utils.numberformat import format as nformat
from django.utils.safestring import mark_safe, SafeString, SafeUnicode
from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale
from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, pgettext, npgettext, to_locale
from django.utils.importlib import import_module
@ -54,6 +54,22 @@ class TranslationTests(TestCase):
s2 = pickle.loads(pickle.dumps(s1))
self.assertEqual(unicode(s2), "test")
def test_pgettext(self):
# Reset translation catalog to include other/locale/de
self.old_locale_paths = settings.LOCALE_PATHS
settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),)
from django.utils.translation import trans_real
trans_real._active = {}
trans_real._translations = {}
activate('de')
self.assertEqual(pgettext("unexisting", "May"), u"May")
self.assertEqual(pgettext("month name", "May"), u"Mai")
self.assertEqual(pgettext("verb", "May"), u"Kann")
self.assertEqual(npgettext("search", "%d result", "%d results", 4) % 4, u"4 Resultate")
settings.LOCALE_PATHS = self.old_locale_paths
def test_string_concat(self):
"""
unicode(string_concat(...)) should not raise a TypeError - #4796

View File

@ -22,3 +22,7 @@ msgstr "il faut le traduire"
msgid "Choose a time"
msgstr "Choisir une heure"
msgctxt "month name"
msgid "May"
msgstr "mai"

View File

@ -30,6 +30,9 @@ class I18NTests(TestCase):
# catalog['this is to be translated'] = 'same_that_trans_txt'
# javascript_quote is used to be able to check unicode strings
self.assertContains(response, javascript_quote(trans_txt), 1)
if lang_code == 'fr':
# Message with context (msgctxt)
self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
class JsI18NTests(TestCase):