Repository: WeblateOrg/weblate

Issue:
Download translation & Weblate-specific data
Hello,
When downloading for offline translation, you get the store data but you never get Weblate-specific data such as Suggestions, Weblate Comments, or Weblate source string tags like check-length etc.
Do you think it would be appropriate to get this data at download\* so that offline translator has a level of information closer to online translation?
Thanks!

*) I guess all formats have a comment zone where all of this could be fed. 
For .po, we could be a bit more specific, as Weblate comments and suggestions could qualify as translator comments (#) (rather than extracted comments #.), and for Weblate flags, maybe flags? (#,)
(Or maybe you'd envision that as a separate downloaded file??)
## 



Discussion:
Something like this is definitely possible, though it really needs good specification to make it usable.

For example using flags for anything non standard in Gettext is quite tricky, many of the tools do silently strip non standard flags...

OK then, the less risky approach would be to keep everything as comments 
(e.g. # [Weblate data] { json data... })
You might also get problems with the "Merge translation comments" option at upload, but then again, I think there is already a problem with that option, as I tend to get duplicated comments if I keep this box checked.

Flags could be indeed exported to Xliff and gettext PO files (the latter is blocked by https://github.com/translate/translate/pull/3913 as they will otherwise be completely messed up). That's something I will definitely do for 3.9 release.

Comments could be turned into xliff [notes](http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#note) and gettext translator comments.

I'm not really sure about suggestions as those really do not translate well into anything existing.

 

Resolving patch:
diff --git a/weblate/formats/exporters.py b/weblate/formats/exporters.py
--- a/weblate/formats/exporters.py
+++ b/weblate/formats/exporters.py
@@ -151,12 +151,23 @@ def add_unit(self, unit):
         note = self.string_filter(unit.comment)
         if note:
             output.addnote(note, origin='developer')
+        # In Weblate context
+        note = self.string_filter(unit.source_info.context)
+        if context:
+            output.addnote(note, origin='developer')
+        # Comments
+        for comment in unit.get_comments():
+            output.addnote(comment.comment, origin='translator')
+        # Suggestions
+        for suggestion in unit.suggestions:
+            output.addnote(
+                'Suggested in Weblate: {}'.format(suggestion.target),
+                origin='translator'
+            )
 
-        # Set type comment (for Gettext)
-        if hasattr(output, 'settypecomment'):
-            for flag in unit.flags.split(','):
-                if flag:
-                    output.settypecomment(flag)
+        # Store flags
+        if unit.all_flags:
+            self.store_flags(output, unit.all_flags)
 
         # Store fuzzy flag
         if unit.fuzzy:
@@ -187,6 +198,9 @@ def serialize(self):
         """Return storage content"""
         return TTKitFormat.serialize(self.storage)
 
+    def store_flags(self, output, flags):
+        return
+
 
 @register_exporter
 class PoExporter(BaseExporter):
@@ -196,6 +210,10 @@ class PoExporter(BaseExporter):
     verbose = _('gettext PO')
     _storage = pofile
 
+    def store_flags(self, output, flags):
+        for flag in flags.items():
+            output.settypecomment(flag)
+
     def get_storage(self):
         store = self._storage()
         plural = self.plural
@@ -241,9 +259,15 @@ class PoXliffExporter(XMLExporter):
     def get_storage(self):
         return PoXliffFile()
 
+    def store_flags(self, output, flags):
+        if flags.has_value('max-length'):
+            output.xmlelement.set("maxwidth", str(flags.get_value('max-length')))
+
+        output.xmlelement.set("weblate-flags", flags.format())
+
 
 @register_exporter
-class XliffExporter(XMLExporter):
+class XliffExporter(PoXliffExporter):
     name = 'xliff11'
     content_type = 'application/x-xliff+xml'
     extension = 'xlf'
@@ -298,6 +322,9 @@ def __init__(self, project=None, language=None, url=None,
                 unit = next(translation.store.translatable_units, None)
                 self.use_context = unit is not None and not unit.template.source
 
+    def store_flags(self, output, flags):
+        return
+
     def add_unit(self, unit):
         # We do not store not translated units
         if not unit.translated:


Test patch:
diff --git a/weblate/formats/tests/test_exporters.py b/weblate/formats/tests/test_exporters.py
--- a/weblate/formats/tests/test_exporters.py
+++ b/weblate/formats/tests/test_exporters.py
@@ -33,13 +33,27 @@
 )
 from weblate.formats.helpers import BytesIOMode
 from weblate.lang.models import Language, Plural
-from weblate.trans.models import Component, Dictionary, Project, Translation, Unit
+from weblate.trans.models import (
+    Comment,
+    Component,
+    Dictionary,
+    Project,
+    Source,
+    Suggestion,
+    Translation,
+    Unit,
+)
 from weblate.utils.state import STATE_EMPTY, STATE_TRANSLATED
 
 
+def fake_get_comments():
+    return [Comment(comment='Weblate translator comment')]
+
+
 class PoExporterTest(TestCase):
     _class = PoExporter
     _has_context = True
+    _has_comments = True
 
     def get_exporter(self, lang=None, **kwargs):
         if lang is None:
@@ -75,7 +89,9 @@ def test_dictionary_markup(self):
     def test_dictionary_special(self):
         self.check_dict(Dictionary(source='bar\x1e\x1efoo', target='br\x1eff'))
 
-    def check_unit(self, nplurals=3, template=None, **kwargs):
+    def check_unit(self, nplurals=3, template=None, source_info=None, **kwargs):
+        if source_info is None:
+            source_info = {}
         if nplurals == 3:
             equation = 'n==0 ? 0 : n==1 ? 1 : 2'
         else:
@@ -106,6 +122,12 @@ def check_unit(self, nplurals=3, template=None, **kwargs):
         # Fake file format to avoid need for actual files
         translation.store = EmptyFormat(BytesIOMode('', b''))
         unit = Unit(translation=translation, id_hash=-1, **kwargs)
+        unit.__dict__['source_info'] = Source(**source_info)
+        if source_info:
+            unit.get_comments = fake_get_comments
+            unit.__dict__['suggestions'] = [
+                Suggestion(target='Weblate translator suggestion')
+            ]
         exporter = self.get_exporter(lang, translation=translation)
         exporter.add_unit(unit)
         return self.check_export(exporter)
@@ -152,6 +174,27 @@ def test_context(self):
         elif self._has_context is not None:
             self.assertNotIn(b'context', result)
 
+    def test_extra_info(self):
+        result = self.check_unit(
+            source='foo',
+            target='bar',
+            context='context',
+            state=STATE_TRANSLATED,
+            source_info={
+                'check_flags': 'max-length:200',
+                'context': 'Context in Weblate',
+            },
+        )
+        if self._has_context:
+            self.assertIn(b'context', result)
+        elif self._has_context is not None:
+            self.assertNotIn(b'context', result)
+        if self._has_comments:
+            self.assertIn(b'Context in Weblate', result)
+            self.assertIn(b'Weblate translator comment', result)
+            self.assertIn(b'Suggested in Weblate', result)
+            self.assertIn(b'Weblate translator suggestion', result)
+
     def setUp(self):
         self.exporter = self.get_exporter()
 
@@ -200,6 +243,7 @@ def check_plurals(self, result):
 class MoExporterTest(PoExporterTest):
     _class = MoExporter
     _has_context = True
+    _has_comments = False
 
     def check_plurals(self, result):
         self.assertIn(b'www', result)
@@ -224,6 +268,7 @@ def test_escaping(self):
 class XlsxExporterTest(PoExporterTest):
     _class = XlsxExporter
     _has_context = False
+    _has_comments = False
 
     def check_plurals(self, result):
         # Doesn't support plurals