Repository: mozilla/pontoon

Issue:
Fluent pretranslations not serialized in canonical form
Fluent pretranslations are not stored in a canonical form. For example, `key = {example}` should actually be stored as `key = { example }` (mind the whitespace).

In the exported files in the repository, the strings appear as they should, which results in sync trying to import pretranslations, because they aren't the same as in Pontoon DB. That also triggers #2284. 

In the UI (editor, string list) the strings are also rendered properly, which only makes the problem more difficult to spot.

We should make sure that pretranslations are always stored them in the canonical form.


Discussion:

Resolving patch:
diff --git a/pontoon/pretranslation/pretranslate.py b/pontoon/pretranslation/pretranslate.py
--- a/pontoon/pretranslation/pretranslate.py
+++ b/pontoon/pretranslation/pretranslate.py
@@ -58,6 +58,10 @@ def get_pretranslations(entity, locale, preserve_placeables=False):
 
         pretranslation = serializer.serialize_entry(entry)
 
+        # Parse and serialize pretranslation again in order to assure cannonical style
+        parsed_pretranslation = parser.parse_entry(pretranslation)
+        pretranslation = serializer.serialize_entry(parsed_pretranslation)
+
         authors = [services[service] for service in pretranslate.services]
         author = max(set(authors), key=authors.count) if authors else services["tm"]
 
@@ -82,7 +86,7 @@ def get_pretranslations(entity, locale, preserve_placeables=False):
 
 
 def get_pretranslated_data(source, locale, preserve_placeables):
-    # Empty strings do not need translation
+    # Empty strings and strings containing whitespace only do not need translation
     if re.search("^\\s*$", source):
         return source, "tm"
 


Test patch:
diff --git a/pontoon/pretranslation/tests/test_pretranslate.py b/pontoon/pretranslation/tests/test_pretranslate.py
--- a/pontoon/pretranslation/tests/test_pretranslate.py
+++ b/pontoon/pretranslation/tests/test_pretranslate.py
@@ -47,6 +47,22 @@ def test_get_pretranslations_empty_string(entity_a, locale_b, tm_user):
     assert response == [("", None, tm_user)]
 
 
+@pytest.mark.django_db
+def test_get_pretranslations_whitespace(entity_a, locale_b, tm_user):
+    # Entity.string is an empty string
+    entity_a.string = " "
+    response = get_pretranslations(entity_a, locale_b)
+    assert response == [(" ", None, tm_user)]
+
+    entity_a.string = "\t"
+    response = get_pretranslations(entity_a, locale_b)
+    assert response == [("\t", None, tm_user)]
+
+    entity_a.string = "\n"
+    response = get_pretranslations(entity_a, locale_b)
+    assert response == [("\n", None, tm_user)]
+
+
 @pytest.mark.django_db
 def test_get_pretranslations_tm_match(entity_a, entity_b, locale_b, tm_user):
     # 100% TM match exists
@@ -145,32 +161,6 @@ def test_get_pretranslations_fluent_empty(
     assert response == [(pretranslated_string, None, gt_user)]
 
 
-@pytest.mark.django_db
-def test_get_pretranslations_fluent_whitespace(
-    fluent_resource, google_translate_locale, tm_user
-):
-    # Various types of whitespace should be preserved
-    input_string = dedent(
-        """
-        whitespace =
-            { $count ->
-                [0] { "" }
-                [1] { " " }
-                *[other] { "\t" } { "\n" }
-            }
-    """
-    )
-    fluent_entity = EntityFactory(resource=fluent_resource, string=input_string)
-
-    output_string = input_string
-
-    # Re-serialize to match whitespace
-    pretranslated_string = serializer.serialize_entry(parser.parse_entry(output_string))
-
-    response = get_pretranslations(fluent_entity, google_translate_locale)
-    assert response == [(pretranslated_string, None, tm_user)]
-
-
 @patch("pontoon.pretranslation.pretranslate.get_google_translate_data")
 @pytest.mark.django_db
 def test_get_pretranslations_fluent_accesskeys_no_attribute_source(