Repository: mozilla/pontoon

Issue:
Warn on empty Fluent rich editor fields
> In addition to the bug, I believe this is a UX regression? We should show a warning that one of the values is empty when trying to save such message.

The current practice is to consider any content in a Fluent value or attribute to qualify the whole message as not-empty. The check for this was added in #2675. Previously empty attributes may indeed have resulted in errors, due to stringifying as invalid Fluent.

_Originally posted by @eemeli in https://github.com/mozilla/pontoon/issues/2973#issuecomment-1750344569_
            


Discussion:
Any chance to prioritize this? I just had to fix ~50 strings in one batch for Armenian, and my hope is that a warning would prevent most of these.

If you search for "empty" in [this page](https://test.flod.org/checks/), you will notice how frequent the problem is.

Resolving patch:
diff --git a/pontoon/checks/libraries/pontoon_db.py b/pontoon/checks/libraries/pontoon_db.py
--- a/pontoon/checks/libraries/pontoon_db.py
+++ b/pontoon/checks/libraries/pontoon_db.py
@@ -28,20 +28,27 @@ def get_max_length(comment):
 
 class IsEmptyVisitor(Visitor):
     def __init__(self):
-        self.is_empty = True
+        self.is_empty = False
+        self.is_pattern_empty = True
+
+    def visit_Pattern(self, node):
+        self.is_pattern_empty = True
+        self.visit(node.elements)
+        if self.is_pattern_empty:
+            self.is_empty = True
 
     def visit_Placeable(self, node):
         if isinstance(node.expression, ast.Literal):
             if node.expression.parse()["value"]:
-                self.is_empty = False
+                self.is_pattern_empty = False
         elif isinstance(node.expression, ast.SelectExpression):
             self.generic_visit(node.expression)
         else:
-            self.is_empty = False
+            self.is_pattern_empty = False
 
     def visit_TextElement(self, node):
         if node.value:
-            self.is_empty = False
+            self.is_pattern_empty = False
 
 
 def run_checks(entity, original, string):


Test patch:
diff --git a/pontoon/checks/tests/test_pontoon_db.py b/pontoon/checks/tests/test_pontoon_db.py
--- a/pontoon/checks/tests/test_pontoon_db.py
+++ b/pontoon/checks/tests/test_pontoon_db.py
@@ -130,6 +130,11 @@ def test_empty_translations(get_entity_mock):
         get_entity_mock("ftl", string="key = value"), "", 'key = { "" }'
     ) == {"pndbWarnings": ["Empty translation"]}
 
+    assert (
+        run_checks(get_entity_mock("ftl", string="key = value"), "", 'key = { "x" }')
+        == {}
+    )
+
     assert (
         run_checks(
             get_entity_mock("ftl", string="key =\n  .attr = value"),
@@ -145,6 +150,51 @@ def test_empty_translations(get_entity_mock):
         == {"pndbWarnings": ["Empty translation"]}
     )
 
+    assert (
+        run_checks(
+            get_entity_mock("ftl", string="key =\n  .attr = value"),
+            "",
+            """key =
+              { $var ->
+                  [a] { "x" }
+                 *[b] { "y" }
+              }
+              .attr = { "" }
+            """,
+        )
+        == {"pndbWarnings": ["Empty translation"]}
+    )
+
+    assert (
+        run_checks(
+            get_entity_mock("ftl", string="key =\n  .attr = value"),
+            "",
+            """key =
+              { $var ->
+                  [a] { "x" }
+                 *[b] { "" }
+              }
+              .attr = { "y" }
+            """,
+        )
+        == {"pndbWarnings": ["Empty translation"]}
+    )
+
+    assert (
+        run_checks(
+            get_entity_mock("ftl", string="key =\n  .attr = value"),
+            "",
+            """key =
+              { $var ->
+                  [a] { "x" }
+                 *[b] { "y" }
+              }
+              .attr = { "z" }
+            """,
+        )
+        == {}
+    )
+
 
 def test_lang_newlines(get_entity_mock):
     """Newlines aren't allowed in lang files"""