Repository: WeblateOrg/weblate

Issue:
Exporters for monolingual files
**Is your feature request related to a problem? Please describe.**
In some cases users want to have single translation project and export the files into other monolingual formats (for example to be used in both Android or iOS apps).

**Describe the solution you'd like**
Additional exporters for monolingual files to cover these formats.

**Describe alternatives you've considered**
Alternative approach is to let Weblate edit these files directly, but it seems undesired in some use cases.


Discussion:
This issue has been added to backlog. It is not scheduled on our road map, but it might be eventually implemented. In case you desperately need this feature, please consider helping or [funding the development](https://weblate.org/support/).
Related to https://github.com/WeblateOrg/weblate/issues/4024, we will need JSON exporter there.

Resolving patch:
diff --git a/weblate/formats/exporters.py b/weblate/formats/exporters.py
--- a/weblate/formats/exporters.py
+++ b/weblate/formats/exporters.py
@@ -23,10 +23,13 @@
 from django.utils.translation import gettext_lazy as _
 from lxml.etree import XMLSyntaxError
 from translate.misc.multistring import multistring
+from translate.storage.aresource import AndroidResourceFile
 from translate.storage.csvl10n import csvfile
+from translate.storage.jsonl10n import JsonFile
 from translate.storage.mo import mofile
 from translate.storage.po import pofile
 from translate.storage.poxliff import PoXliffFile
+from translate.storage.properties import stringsfile
 from translate.storage.tbx import tbxfile
 from translate.storage.tmx import tmxfile
 from translate.storage.xliff import xlifffile
@@ -387,3 +390,52 @@ class XlsxExporter(CVSBaseExporter):
     def serialize(self):
         """Return storage content."""
         return XlsxFormat.serialize(self.storage)
+
+
+class MonolingualExporter(BaseExporter):
+    """Base class for monolingual exports."""
+
+    @staticmethod
+    def supports(translation):
+        return translation.component.has_template()
+
+    def build_unit(self, unit):
+        output = self.storage.UnitClass(unit.context)
+        output.setid(unit.context)
+        self.add(output, self.handle_plurals(unit.get_target_plurals()))
+        return output
+
+
+@register_exporter
+class JSONExporter(MonolingualExporter):
+    storage_class = JsonFile
+    name = "json"
+    content_type = "application/json"
+    extension = "json"
+    verbose = _("JSON")
+
+
+@register_exporter
+class AndroidResourceExporter(MonolingualExporter):
+    storage_class = AndroidResourceFile
+    name = "aresource"
+    content_type = "application/xml"
+    extension = "xml"
+    verbose = _("Android String Resource")
+
+    def add(self, unit, word):
+        # Need to have storage to handle plurals
+        unit._store = self.storage
+        super().add(unit, word)
+
+    def string_filter(self, text):
+        return text.translate(_CHARMAP)
+
+
+@register_exporter
+class StringsExporter(MonolingualExporter):
+    storage_class = stringsfile
+    name = "strings"
+    content_type = "text/plain"
+    extension = "strings"
+    verbose = _("iOS strings")


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
@@ -19,10 +19,13 @@
 
 from weblate.formats.base import EmptyFormat
 from weblate.formats.exporters import (
+    AndroidResourceExporter,
     CSVExporter,
+    JSONExporter,
     MoExporter,
     PoExporter,
     PoXliffExporter,
+    StringsExporter,
     TBXExporter,
     XliffExporter,
     XlsxExporter,
@@ -115,6 +118,9 @@ def test_unit(self):
     def test_unit_mono(self):
         self.check_unit(source="xxx", target="yyy", template="template")
 
+    def _encode(self, string):
+        return string.encode("utf-8")
+
     def test_unit_plural(self):
         result = self.check_unit(
             source="xxx\x1e\x1efff",
@@ -138,9 +144,9 @@ def test_context(self):
             source="foo", target="bar", context="context", state=STATE_TRANSLATED
         )
         if self._has_context:
-            self.assertIn(b"context", result)
+            self.assertIn(self._encode("context"), result)
         elif self._has_context is not None:
-            self.assertNotIn(b"context", result)
+            self.assertNotIn(self._encode("context"), result)
 
     def test_extra_info(self):
         result = self.check_unit(
@@ -154,14 +160,14 @@ def test_extra_info(self):
             },
         )
         if self._has_context:
-            self.assertIn(b"context", result)
+            self.assertIn(self._encode("context"), result)
         elif self._has_context is not None:
-            self.assertNotIn(b"context", result)
+            self.assertNotIn(self._encode("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)
+            self.assertIn(self._encode("Context in Weblate"), result)
+            self.assertIn(self._encode("Weblate translator comment"), result)
+            self.assertIn(self._encode("Suggested in Weblate"), result)
+            self.assertIn(self._encode("Weblate translator suggestion"), result)
 
     def setUp(self):
         self.exporter = self.get_exporter()
@@ -268,3 +274,33 @@ class XlsxExporterTest(PoExporterTest):
     def check_plurals(self, result):
         # Doesn't support plurals
         pass
+
+
+class AndroidResourceExporterTest(PoExporterTest):
+    _class = AndroidResourceExporter
+    _has_comments = False
+
+    def check_plurals(self, result):
+        self.assertIn(b"<plural", result)
+
+
+class JSONExporterTest(PoExporterTest):
+    _class = JSONExporter
+    _has_comments = False
+
+    def check_plurals(self, result):
+        # Doesn't support plurals
+        pass
+
+
+class StringsExporterTest(PoExporterTest):
+    _class = StringsExporter
+    _has_comments = False
+
+    def _encode(self, string):
+        # Skip BOM
+        return string.encode("utf-16")[2:]
+
+    def check_plurals(self, result):
+        # Doesn't support plurals
+        pass