Repository: WeblateOrg/weblate

Issue:
Add API for adding new monolingual strings
Currently, this is UI only feature and might be useful in the API as well.

It calls `Translation.new_unit` under the hood.

_Originally posted by @jornvl90 in https://github.com/WeblateOrg/weblate/issues/1145#issuecomment-555039651_


Discussion:
@nijel [moved the comment from 1145]
We also need this. In out project we can't connect our git to the weblate platform, and must use the API.
However, I don't understand why do this only for monolingual strings and not as a general way of adding terms. This seems like a very basic thing to do in a translation platform.
In any case, without this, I can't use weblate.
The reason is that with monolingual files, it's clear that the monolingual base file is the one to add it. With bilingual the strings are typically extracted from somewhere else (from source code, other documents, ...), so adding them manually doesn't make much sense. It might be eventually supported, but it needs an use case and I currently do not see one. Also, it might get tricky as the bilingual translation files do not have to be in sync - there is no requirement on all of them having same strings and it happens in some cases that they do not.

Resolving patch:
diff --git a/weblate/api/serializers.py b/weblate/api/serializers.py
--- a/weblate/api/serializers.py
+++ b/weblate/api/serializers.py
@@ -495,6 +495,11 @@ class Meta:
         }
 
 
+class MonolingualUnitSerializer(serializers.Serializer):
+    key = serializers.CharField()
+    value = serializers.CharField()
+
+
 class ReadOnlySerializer(serializers.Serializer):
     def update(self, instance, validated_data):
         return None
diff --git a/weblate/api/views.py b/weblate/api/views.py
--- a/weblate/api/views.py
+++ b/weblate/api/views.py
@@ -48,6 +48,7 @@
     LanguageSerializer,
     LockRequestSerializer,
     LockSerializer,
+    MonolingualUnitSerializer,
     ProjectSerializer,
     RepoRequestSerializer,
     RoleSerializer,
@@ -885,10 +886,32 @@ def changes(self, request, **kwargs):
 
         return self.get_paginated_response(serializer.data)
 
-    @action(detail=True, methods=["get"])
+    @action(detail=True, methods=["get", "post"])
     def units(self, request, **kwargs):
         obj = self.get_object()
 
+        if request.method == "POST":
+            if not request.user.has_perm("unit.add", obj):
+                self.permission_denied(request, message="Can not add unit")
+            serializer = MonolingualUnitSerializer(data=request.data)
+            serializer.is_valid(raise_exception=True)
+
+            key = serializer.validated_data["key"]
+            value = serializer.validated_data["value"]
+
+            if obj.unit_set.filter(context=key).exists():
+                return Response(
+                    data={
+                        "result": "Unsuccessful",
+                        "detail": "Translation with this key seem to already exist!",
+                    },
+                    status=status.HTTP_400_BAD_REQUEST,
+                )
+
+            obj.new_unit(request, key, value)
+            serializer = self.serializer_class(obj, context={"request": request})
+            return Response(serializer.data, status=status.HTTP_200_OK,)
+
         queryset = obj.unit_set.all().order_by("id")
         page = self.paginate_queryset(queryset)
 


Test patch:
diff --git a/weblate/api/tests.py b/weblate/api/tests.py
--- a/weblate/api/tests.py
+++ b/weblate/api/tests.py
@@ -1311,6 +1311,45 @@ def test_units(self):
         request = self.do_request("api:translation-units", self.translation_kwargs)
         self.assertEqual(request.data["count"], 4)
 
+    def test_add_monolingual(self):
+        self.create_acl()
+        self.do_request(
+            "api:translation-units",
+            {
+                "language__code": "cs",
+                "component__slug": "test",
+                "component__project__slug": "acl",
+            },
+            method="post",
+            superuser=True,
+            request={"key": "key", "value": "Source Language"},
+            code=403,
+        )
+        self.do_request(
+            "api:translation-units",
+            {
+                "language__code": "en",
+                "component__slug": "test",
+                "component__project__slug": "acl",
+            },
+            method="post",
+            superuser=True,
+            request={"key": "key", "value": "Source Language"},
+            code=200,
+        )
+        self.do_request(
+            "api:translation-units",
+            {
+                "language__code": "en",
+                "component__slug": "test",
+                "component__project__slug": "acl",
+            },
+            method="post",
+            superuser=True,
+            request={"key": "key", "value": "Source Language"},
+            code=400,
+        )
+
     def test_delete(self):
         self.assertEqual(Translation.objects.count(), 4)
         self.do_request(