Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 83aeb3c768173f48dc295c3194fecd705d1c05ac 1 parent 0659391
Jannis Leidel authored November 04, 2010
4  django/core/management/commands/makemessages.py
@@ -190,7 +190,7 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
190 190
                     f.write(src)
191 191
                 finally:
192 192
                     f.close()
193  
-                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))
  193
+                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))
194 194
                 msgs, errors = _popen(cmd)
195 195
                 if errors:
196 196
                     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,
225 225
                         raise SyntaxError(msg)
226 226
                 if verbosity > 1:
227 227
                     sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
228  
-                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"' % (
  228
+                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"' % (
229 229
                     domain, os.path.join(dirpath, thefile))
230 230
                 msgs, errors = _popen(cmd)
231 231
                 if errors:
11  django/utils/translation/__init__.py
@@ -10,7 +10,8 @@
10 10
         'get_language', 'get_language_bidi', 'get_date_formats',
11 11
         'get_partial_date_formats', 'check_for_language', 'to_locale',
12 12
         'get_language_from_request', 'templatize', 'ugettext', 'ugettext_lazy',
13  
-        'ungettext', 'deactivate_all']
  13
+        'ungettext', 'ungettext_lazy', 'pgettext', 'pgettext_lazy',
  14
+        'npgettext', 'npgettext_lazy', 'deactivate_all']
14 15
 
15 16
 # Here be dragons, so a short explanation of the logic won't hurt:
16 17
 # We are trying to solve two problems: (1) access settings, in particular
@@ -63,10 +64,18 @@ def ugettext(message):
63 64
 def ungettext(singular, plural, number):
64 65
     return _trans.ungettext(singular, plural, number)
65 66
 
  67
+def pgettext(context, message):
  68
+    return _trans.pgettext(context, message)
  69
+
  70
+def npgettext(context, singular, plural, number):
  71
+    return _trans.npgettext(context, singular, plural, number)
  72
+
66 73
 ngettext_lazy = lazy(ngettext, str)
67 74
 gettext_lazy = lazy(gettext, str)
68 75
 ungettext_lazy = lazy(ungettext, unicode)
69 76
 ugettext_lazy = lazy(ugettext, unicode)
  77
+pgettext_lazy = lazy(pgettext, unicode)
  78
+npgettext_lazy = lazy(npgettext, unicode)
70 79
 
71 80
 def activate(language):
72 81
     return _trans.activate(language)
6  django/utils/translation/trans_null.py
@@ -15,6 +15,12 @@ def ngettext(singular, plural, number):
15 15
 def ungettext(singular, plural, number):
16 16
     return force_unicode(ngettext(singular, plural, number))
17 17
 
  18
+def pgettext(context, message):
  19
+    return ugettext(message)
  20
+
  21
+def npgettext(context, singular, plural, number):
  22
+    return ungettext(singular, plural, number)
  23
+
18 24
 activate = lambda x: None
19 25
 deactivate = deactivate_all = lambda: None
20 26
 get_language = lambda: settings.LANGUAGE_CODE
20  django/utils/translation/trans_real.py
@@ -24,6 +24,9 @@
24 24
 # file lookups when checking the same locale on repeated requests.
25 25
 _accepted = {}
26 26
 
  27
+# magic gettext number to separate context from message
  28
+CONTEXT_SEPARATOR = u"\x04"
  29
+
27 30
 # Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
28 31
 accept_language_re = re.compile(r'''
29 32
         ([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*)   # "en", "en-au", "x-y-z", "*"
@@ -279,6 +282,14 @@ def gettext(message):
279 282
 def ugettext(message):
280 283
     return do_translate(message, 'ugettext')
281 284
 
  285
+def pgettext(context, message):
  286
+    result = do_translate(
  287
+        u"%s%s%s" % (context, CONTEXT_SEPARATOR, message), 'ugettext')
  288
+    if CONTEXT_SEPARATOR in result:
  289
+        # Translation not found
  290
+        result = message
  291
+    return result
  292
+
282 293
 def gettext_noop(message):
283 294
     """
284 295
     Marks strings for translation but doesn't translate them now. This can be
@@ -313,6 +324,15 @@ def ungettext(singular, plural, number):
313 324
     """
314 325
     return do_ntranslate(singular, plural, number, 'ungettext')
315 326
 
  327
+def npgettext(context, singular, plural, number):
  328
+    result = do_ntranslate(u"%s%s%s" % (context, CONTEXT_SEPARATOR, singular),
  329
+                           u"%s%s%s" % (context, CONTEXT_SEPARATOR, plural),
  330
+                           number, 'ungettext')
  331
+    if CONTEXT_SEPARATOR in result:
  332
+        # Translation not found
  333
+        result = do_ntranslate(singular, plural, number, 'ungettext')
  334
+    return result
  335
+
316 336
 def check_for_language(lang_code):
317 337
     """
318 338
     Checks whether there is a global language file for the given language
17  django/views/i18n.py
@@ -68,6 +68,8 @@ def get_formats():
68 68
 function gettext(msgid) { return msgid; }
69 69
 function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
70 70
 function gettext_noop(msgid) { return msgid; }
  71
+function pgettext(context, msgid) { return msgid; }
  72
+function npgettext(context, singular, plural, count) { return (count == 1) ? singular : plural; }
71 73
 """
72 74
 
73 75
 LibHead = """
@@ -98,6 +100,21 @@ def get_formats():
98 100
 
99 101
 function gettext_noop(msgid) { return msgid; }
100 102
 
  103
+function pgettext(context, msgid) {
  104
+  var value = gettext(context + '\x04' + msgid);
  105
+  if (value.indexOf('\x04') != -1) {
  106
+    value = msgid;
  107
+  }
  108
+  return value;
  109
+}
  110
+
  111
+function npgettext(context, singular, plural, count) {
  112
+  var value = ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
  113
+  if (value.indexOf('\x04') != -1) {
  114
+    value = ngettext(singular, plural, count);
  115
+  }
  116
+  return value;
  117
+}
101 118
 """
102 119
 
103 120
 LibFormatHead = """
8  docs/releases/1.3.txt
@@ -86,6 +86,14 @@ Users of Python 2.5 and above may now use :ref:`transaction management functions
86 86
 
87 87
 For more information, see :ref:`transaction-management-functions`.
88 88
 
  89
+Contextual markers in translatable strings
  90
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  91
+
  92
+For translation strings with ambiguous meaning, you can now
  93
+use the ``pgettext`` function to specify the context of the string.
  94
+
  95
+For more information, see :ref:`contextual-markers`
  96
+
89 97
 Everything else
90 98
 ~~~~~~~~~~~~~~~
91 99
 
33  docs/topics/i18n/internationalization.txt
@@ -193,6 +193,39 @@ cardinality of the elements at play.
193 193
     ``django-admin.py compilemessages`` or a ``KeyError`` Python exception at
194 194
     runtime.
195 195
 
  196
+.. _contextual-markers:
  197
+
  198
+Contextual markers
  199
+------------------
  200
+
  201
+.. versionadded:: 1.3
  202
+
  203
+Sometimes words have several meanings, such as ``"May"`` in English, which
  204
+refers to a month name and to a verb. To enable translators to translate
  205
+these words correctly in different contexts, you can use the
  206
+``django.utils.translation.pgettext()`` function, or the
  207
+``django.utils.translation.npgettext()`` function if the string needs
  208
+pluralization. Both take a context string as the first variable.
  209
+
  210
+In the resulting .po file, the string will then appear as often as there are
  211
+different contextual markers for the same string (the context will appear on
  212
+the ``msgctxt`` line), allowing the translator to give a different translation
  213
+for each of them.
  214
+
  215
+For example::
  216
+
  217
+    from django.utils.translation import pgettext
  218
+
  219
+    month = pgettext("month name", "May")
  220
+
  221
+will appear in the .po file as:
  222
+
  223
+.. code-block:: po
  224
+
  225
+    msgctxt "month name"
  226
+    msgid "May"
  227
+    msgstr ""
  228
+
196 229
 .. _lazy-translations:
197 230
 
198 231
 Lazy translation
BIN  tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
Binary file not shown
17  tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
@@ -20,3 +20,20 @@ msgstr ""
20 20
 #: models.py:3
21 21
 msgid "Date/time"
22 22
 msgstr "Datum/Zeit (LOCALE_PATHS)"
  23
+
  24
+#: models.py:5
  25
+msgctxt "month name"
  26
+msgid "May"
  27
+msgstr "Mai"
  28
+
  29
+#: models.py:7
  30
+msgctxt "verb"
  31
+msgid "May"
  32
+msgstr "Kann"
  33
+
  34
+#: models.py:9
  35
+msgctxt "search"
  36
+msgid "%d result"
  37
+msgid_plural "%d results"
  38
+msgstr[0] "%d Resultat"
  39
+msgstr[1] "%d Resultate"
18  tests/regressiontests/i18n/tests.py
@@ -11,7 +11,7 @@
11 11
 from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules
12 12
 from django.utils.numberformat import format as nformat
13 13
 from django.utils.safestring import mark_safe, SafeString, SafeUnicode
14  
-from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale
  14
+from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, pgettext, npgettext, to_locale
15 15
 from django.utils.importlib import import_module
16 16
 
17 17
 
@@ -54,6 +54,22 @@ def test_lazy_pickle(self):
54 54
         s2 = pickle.loads(pickle.dumps(s1))
55 55
         self.assertEqual(unicode(s2), "test")
56 56
 
  57
+    def test_pgettext(self):
  58
+        # Reset translation catalog to include other/locale/de
  59
+        self.old_locale_paths = settings.LOCALE_PATHS
  60
+        settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),)
  61
+        from django.utils.translation import trans_real
  62
+        trans_real._active = {}
  63
+        trans_real._translations = {}
  64
+        activate('de')
  65
+
  66
+        self.assertEqual(pgettext("unexisting", "May"), u"May")
  67
+        self.assertEqual(pgettext("month name", "May"), u"Mai")
  68
+        self.assertEqual(pgettext("verb", "May"), u"Kann")
  69
+        self.assertEqual(npgettext("search", "%d result", "%d results", 4) % 4, u"4 Resultate")
  70
+
  71
+        settings.LOCALE_PATHS = self.old_locale_paths
  72
+
57 73
     def test_string_concat(self):
58 74
         """
59 75
         unicode(string_concat(...)) should not raise a TypeError - #4796
BIN  tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo
Binary file not shown
4  tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po
@@ -22,3 +22,7 @@ msgstr "il faut le traduire"
22 22
 
23 23
 msgid "Choose a time"
24 24
 msgstr "Choisir une heure"
  25
+
  26
+msgctxt "month name"
  27
+msgid "May"
  28
+msgstr "mai"
3  tests/regressiontests/views/tests/i18n.py
@@ -30,6 +30,9 @@ def test_jsi18n(self):
30 30
             # catalog['this is to be translated'] = 'same_that_trans_txt'
31 31
             # javascript_quote is used to be able to check unicode strings
32 32
             self.assertContains(response, javascript_quote(trans_txt), 1)
  33
+            if lang_code == 'fr':
  34
+                # Message with context (msgctxt)
  35
+                self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
33 36
 
34 37
 
35 38
 class JsI18NTests(TestCase):

0 notes on commit 83aeb3c

Please sign in to comment.
Something went wrong with that request. Please try again.