Repository: WeblateOrg/weblate

Issue:
JSON Source context add a leading dot
**Describe the bug**
A leading dot is added to the context for JSON file format.

**To Reproduce**
Steps to reproduce the behavior:
1. Using a Monolingual base language file:
```
{
    "general_activity":"Activity",
    "general_report":"Report"
}
```
2. Create a component using the file format 'JSON file'
3. Go to the translation page of this component
4. See the context has an added leading dot: `.general_activity`


**Expected behavior**
No extra dot should be added

**Screenshots**
<img width="331" alt="screenshot 2019-01-30 at 09 59 21" src="https://user-images.githubusercontent.com/687942/51969929-ed266200-2475-11e9-8425-461c9eac1a8a.png">


**Server configuration and status**
Weblate: 3.3
Python: 3.5.2
Django: 2.1.4
translate-toolkit: 2.3.1

**Additional context**
Having identical context is important in order to have the translation propagation working between  components (ios in JSON & android in Android String Resource)



Discussion:
This is caused by way JSON files are currently parsed and indeed should be addressed. See also https://github.com/WeblateOrg/weblate/issues/2149.
Thanks for the quick response. I saw in the related ticket that you already have started some work and I would like to know if you could give more details about what is still missing and how we could help ?

Thank you for this great product.
The end goal is to store structured string identifiers instead of plain text. That is really the only reliable way to avoid conflicts with strings used in the identifiers (several examples with current approach are documented in https://github.com/WeblateOrg/weblate/issues/2149).

To get there, two steps are needed:

- Implement support for this in the translate-toolkit, see https://github.com/translate/translate/issues/3819 and https://github.com/translate/translate/pull/3835, where I drafted initial code. This is probably the most tricky part as it needs to keep backward compatiblity in the API while extending it for some use cases.

- Change Weblate to utilize this new API and store the structured data properly. This will have to cover migrating existing data as well.

Resolving patch:
diff --git a/weblate/formats/ttkit.py b/weblate/formats/ttkit.py
--- a/weblate/formats/ttkit.py
+++ b/weblate/formats/ttkit.py
@@ -669,7 +669,16 @@ def is_readonly(self):
         return False
 
 
-class WebExtensionJSONUnit(MonolingualSimpleUnit):
+class JSONUnit(MonolingualSimpleUnit):
+    @cached_property
+    def context(self):
+        context = super().context
+        if context.startswith("."):
+            return context[1:]
+        return context
+
+
+class WebExtensionJSONUnit(JSONUnit):
     @cached_property
     def flags(self):
         placeholders = self.mainunit.placeholders
@@ -683,7 +692,7 @@ def flags(self):
         )
 
 
-class ARBJSONUnit(MonolingualSimpleUnit):
+class ARBJSONUnit(JSONUnit):
     @cached_property
     def flags(self):
         placeholders = self.mainunit.placeholders
@@ -1081,7 +1090,7 @@ class JSONFormat(TTKitFormat):
     name = _("JSON file")
     format_id = "json"
     loader = ("jsonl10n", "JsonFile")
-    unit_class = MonolingualSimpleUnit
+    unit_class = JSONUnit
     autoload = ("*.json",)
     new_translation = "{}\n"
 
diff --git a/weblate/trans/migrations/0091_json_key.py b/weblate/trans/migrations/0091_json_key.py
new file mode 100644
--- /dev/null
+++ b/weblate/trans/migrations/0091_json_key.py
@@ -0,0 +1,45 @@
+# Generated by Django 3.0.7 on 2020-07-23 08:41
+
+from django.db import migrations
+
+
+def report_progress(previous, current):
+    if previous != current and current % 5 == 0:
+        print(f"Migrating JSON strings ... {current}%")
+    return current
+
+
+def migrate_componentlist(apps, schema_editor):
+    Unit = apps.get_model("trans", "Unit")
+    db_alias = schema_editor.connection.alias
+    units = Unit.objects.using(db_alias).filter(
+        translation__component__file_format__in=(
+            "json",
+            "arb",
+            "go-i18n-json",
+            "i18next",
+            "webextension",
+            "json-nested",
+        )
+    )
+    count = units.count()
+    progress = -1
+    for pos, unit in enumerate(units.iterator()):
+        if unit.context.startswith("."):
+            unit.context = unit.context[1:]
+            unit.save(update_fields=["context"])
+        progress = report_progress(progress, 100 * pos // count)
+    report_progress(progress, 100)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("trans", "0090_alert_updated"),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            migrate_componentlist, migrations.RunPython.noop, elidable=True
+        ),
+    ]


Test patch:
diff --git a/weblate/trans/tests/test_component.py b/weblate/trans/tests/test_component.py
--- a/weblate/trans/tests/test_component.py
+++ b/weblate/trans/tests/test_component.py
@@ -216,10 +216,10 @@ def test_create_json_intermediate(self):
         # Verify source strings are loaded from correct file
         translation = component.translation_set.get(language_code="cs")
         self.assertEqual(
-            translation.unit_set.get(context=".hello").source, "Hello, world!\n"
+            translation.unit_set.get(context="hello").source, "Hello, world!\n"
         )
         self.assertEqual(
-            translation.unit_set.get(context=".thanks").source,
+            translation.unit_set.get(context="thanks").source,
             "Thank you for using Weblate.",
         )
 
@@ -232,10 +232,10 @@ def test_switch_json_intermediate(self):
         )
         translation = component.translation_set.get(language_code="cs")
         self.assertEqual(
-            translation.unit_set.get(context=".hello").source, "Hello world!\n"
+            translation.unit_set.get(context="hello").source, "Hello world!\n"
         )
         self.assertEqual(
-            translation.unit_set.get(context=".thanks").source,
+            translation.unit_set.get(context="thanks").source,
             "Thanks for using Weblate.",
         )
         component.intermediate = "intermediate/dev.json"
@@ -243,10 +243,10 @@ def test_switch_json_intermediate(self):
         component.save()
         translation = component.translation_set.get(language_code="cs")
         self.assertEqual(
-            translation.unit_set.get(context=".hello").source, "Hello, world!\n"
+            translation.unit_set.get(context="hello").source, "Hello, world!\n"
         )
         self.assertEqual(
-            translation.unit_set.get(context=".thanks").source,
+            translation.unit_set.get(context="thanks").source,
             "Thank you for using Weblate.",
         )