Repository: python-babel/babel (python)

Issue:
Option --sort-output only uses msgid to sort translations
When you have several identical `msgid` with different `msgctxt`, using `--sort-ouput` will only sort the translations according to `msgid` leading to a random order in the output.

This leads to useless diff in the VCS:

```
-msgctxt "payment mode"
+msgctxt "subscription"
 msgid "Expired"
 msgstr "Expired"

-msgctxt "subscription"
+msgctxt "payment mode"
 msgid "Expired"
 msgstr "Expired"
```

I'd like Babel to also use `msgctxt` to sort the output.

Pull request description:
Sort translations according to msgid and msgctxt
Fixes #481

Patch:
diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py
index 018094c..3145fa3 100644
--- a/babel/messages/catalog.py
+++ b/babel/messages/catalog.py
@@ -116,21 +116,13 @@ class Message(object):
         return '<%s %r (flags: %r)>' % (type(self).__name__, self.id,
                                         list(self.flags))
 
-    def __cmp__(self, obj):
+    def __cmp__(self, other):
         """Compare Messages, taking into account plural ids"""
-        def values_to_compare():
-            if isinstance(obj, Message):
-                plural = self.pluralizable
-                obj_plural = obj.pluralizable
-                if plural and obj_plural:
-                    return self.id[0], obj.id[0]
-                elif plural:
-                    return self.id[0], obj.id
-                elif obj_plural:
-                    return self.id, obj.id[0]
-            return self.id, obj.id
-        this, other = values_to_compare()
-        return cmp(this, other)
+        def values_to_compare(obj):
+            if isinstance(obj, Message) and obj.pluralizable:
+                return (obj.id[0], obj.context or '')
+            return (obj.id, obj.context or '')
+        return cmp(values_to_compare(self), values_to_compare(other))
 
     def __gt__(self, other):
         return self.__cmp__(other) > 0


Test patch:
diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py
index cef6913..ebf9702 100644
--- a/tests/messages/test_pofile.py
+++ b/tests/messages/test_pofile.py
@@ -680,6 +680,41 @@ msgstr[0] "Voh"
 msgstr[1] "Voeh"''' in value
         assert value.find(b'msgid ""') < value.find(b'msgid "bar"') < value.find(b'msgid "foo"')
 
+    def test_sorted_po_context(self):
+        catalog = Catalog()
+        catalog.add((u'foo', u'foos'), (u'Voh', u'Voeh'),
+                    locations=[('main.py', 1)],
+                    context='there')
+        catalog.add((u'foo', u'foos'), (u'Voh', u'Voeh'),
+                    locations=[('main.py', 1)])
+        catalog.add((u'foo', u'foos'), (u'Voh', u'Voeh'),
+                    locations=[('main.py', 1)],
+                    context='here')
+        buf = BytesIO()
+        pofile.write_po(buf, catalog, sort_output=True)
+        value = buf.getvalue().strip()
+        # We expect the foo without ctx, followed by "here" foo and "there" foo
+        assert b'''\
+#: main.py:1
+msgid "foo"
+msgid_plural "foos"
+msgstr[0] "Voh"
+msgstr[1] "Voeh"
+
+#: main.py:1
+msgctxt "here"
+msgid "foo"
+msgid_plural "foos"
+msgstr[0] "Voh"
+msgstr[1] "Voeh"
+
+#: main.py:1
+msgctxt "there"
+msgid "foo"
+msgid_plural "foos"
+msgstr[0] "Voh"
+msgstr[1] "Voeh"''' in value
+
     def test_file_sorted_po(self):
         catalog = Catalog()
         catalog.add(u'bar', locations=[('utils.py', 3)])