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:
parent
0659391baf
commit
83aeb3c768
|
@ -190,7 +190,7 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
|
||||||
f.write(src)
|
f.write(src)
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
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)
|
msgs, errors = _popen(cmd)
|
||||||
if errors:
|
if errors:
|
||||||
raise CommandError("errors happened while running xgettext on %s\n%s" % (file, 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)
|
raise SyntaxError(msg)
|
||||||
if verbosity > 1:
|
if verbosity > 1:
|
||||||
sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
|
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))
|
domain, os.path.join(dirpath, thefile))
|
||||||
msgs, errors = _popen(cmd)
|
msgs, errors = _popen(cmd)
|
||||||
if errors:
|
if errors:
|
||||||
|
|
|
@ -10,7 +10,8 @@ __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
|
||||||
'get_language', 'get_language_bidi', 'get_date_formats',
|
'get_language', 'get_language_bidi', 'get_date_formats',
|
||||||
'get_partial_date_formats', 'check_for_language', 'to_locale',
|
'get_partial_date_formats', 'check_for_language', 'to_locale',
|
||||||
'get_language_from_request', 'templatize', 'ugettext', 'ugettext_lazy',
|
'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:
|
# 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
|
# We are trying to solve two problems: (1) access settings, in particular
|
||||||
|
@ -63,10 +64,18 @@ def ugettext(message):
|
||||||
def ungettext(singular, plural, number):
|
def ungettext(singular, plural, number):
|
||||||
return _trans.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)
|
ngettext_lazy = lazy(ngettext, str)
|
||||||
gettext_lazy = lazy(gettext, str)
|
gettext_lazy = lazy(gettext, str)
|
||||||
ungettext_lazy = lazy(ungettext, unicode)
|
ungettext_lazy = lazy(ungettext, unicode)
|
||||||
ugettext_lazy = lazy(ugettext, unicode)
|
ugettext_lazy = lazy(ugettext, unicode)
|
||||||
|
pgettext_lazy = lazy(pgettext, unicode)
|
||||||
|
npgettext_lazy = lazy(npgettext, unicode)
|
||||||
|
|
||||||
def activate(language):
|
def activate(language):
|
||||||
return _trans.activate(language)
|
return _trans.activate(language)
|
||||||
|
|
|
@ -15,6 +15,12 @@ ngettext_lazy = ngettext
|
||||||
def ungettext(singular, plural, number):
|
def ungettext(singular, plural, number):
|
||||||
return force_unicode(ngettext(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
|
activate = lambda x: None
|
||||||
deactivate = deactivate_all = lambda: None
|
deactivate = deactivate_all = lambda: None
|
||||||
get_language = lambda: settings.LANGUAGE_CODE
|
get_language = lambda: settings.LANGUAGE_CODE
|
||||||
|
|
|
@ -24,6 +24,9 @@ _default = None
|
||||||
# file lookups when checking the same locale on repeated requests.
|
# file lookups when checking the same locale on repeated requests.
|
||||||
_accepted = {}
|
_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.
|
# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
|
||||||
accept_language_re = re.compile(r'''
|
accept_language_re = re.compile(r'''
|
||||||
([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
|
([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):
|
def ugettext(message):
|
||||||
return do_translate(message, 'ugettext')
|
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):
|
def gettext_noop(message):
|
||||||
"""
|
"""
|
||||||
Marks strings for translation but doesn't translate them now. This can be
|
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')
|
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):
|
def check_for_language(lang_code):
|
||||||
"""
|
"""
|
||||||
Checks whether there is a global language file for the given language
|
Checks whether there is a global language file for the given language
|
||||||
|
|
|
@ -68,6 +68,8 @@ NullSource = """
|
||||||
function gettext(msgid) { return msgid; }
|
function gettext(msgid) { return msgid; }
|
||||||
function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
|
function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
|
||||||
function gettext_noop(msgid) { return msgid; }
|
function gettext_noop(msgid) { return msgid; }
|
||||||
|
function pgettext(context, msgid) { return msgid; }
|
||||||
|
function npgettext(context, singular, plural, count) { return (count == 1) ? singular : plural; }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LibHead = """
|
LibHead = """
|
||||||
|
@ -98,6 +100,21 @@ function ngettext(singular, plural, count) {
|
||||||
|
|
||||||
function gettext_noop(msgid) { return msgid; }
|
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 = """
|
LibFormatHead = """
|
||||||
|
|
|
@ -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`.
|
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
|
Everything else
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -193,6 +193,39 @@ cardinality of the elements at play.
|
||||||
``django-admin.py compilemessages`` or a ``KeyError`` Python exception at
|
``django-admin.py compilemessages`` or a ``KeyError`` Python exception at
|
||||||
runtime.
|
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-translations:
|
||||||
|
|
||||||
Lazy translation
|
Lazy translation
|
||||||
|
|
Binary file not shown.
|
@ -20,3 +20,20 @@ msgstr ""
|
||||||
#: models.py:3
|
#: models.py:3
|
||||||
msgid "Date/time"
|
msgid "Date/time"
|
||||||
msgstr "Datum/Zeit (LOCALE_PATHS)"
|
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"
|
||||||
|
|
|
@ -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.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.numberformat import format as nformat
|
||||||
from django.utils.safestring import mark_safe, SafeString, SafeUnicode
|
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
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,6 +54,22 @@ class TranslationTests(TestCase):
|
||||||
s2 = pickle.loads(pickle.dumps(s1))
|
s2 = pickle.loads(pickle.dumps(s1))
|
||||||
self.assertEqual(unicode(s2), "test")
|
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):
|
def test_string_concat(self):
|
||||||
"""
|
"""
|
||||||
unicode(string_concat(...)) should not raise a TypeError - #4796
|
unicode(string_concat(...)) should not raise a TypeError - #4796
|
||||||
|
|
Binary file not shown.
|
@ -22,3 +22,7 @@ msgstr "il faut le traduire"
|
||||||
|
|
||||||
msgid "Choose a time"
|
msgid "Choose a time"
|
||||||
msgstr "Choisir une heure"
|
msgstr "Choisir une heure"
|
||||||
|
|
||||||
|
msgctxt "month name"
|
||||||
|
msgid "May"
|
||||||
|
msgstr "mai"
|
||||||
|
|
|
@ -30,6 +30,9 @@ class I18NTests(TestCase):
|
||||||
# catalog['this is to be translated'] = 'same_that_trans_txt'
|
# catalog['this is to be translated'] = 'same_that_trans_txt'
|
||||||
# javascript_quote is used to be able to check unicode strings
|
# javascript_quote is used to be able to check unicode strings
|
||||||
self.assertContains(response, javascript_quote(trans_txt), 1)
|
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):
|
class JsI18NTests(TestCase):
|
||||||
|
|
Loading…
Reference in New Issue