prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  No warnings when trying to submit empty translations
  I've noticed an increase in the number of empty strings in Firefox, where I have [special checks](https://test.flod.org/checks/).
  
  Apparently, we don't warn anymore when someone tries to submit an empty translation.
  
  </issue>
  <code>
  [start of pontoon/checks/libraries/pontoon_db.py]
  1 import html
  2 import re
  3 
  4 import bleach
  5 
  6 from collections import defaultdict
  7 from fluent.syntax import FluentParser, ast
  8 
  9 from pontoon.sync.formats.ftl import localizable_entries
  10 
  11 
  12 MAX_LENGTH_RE = re.compile(r"MAX_LENGTH:( *)(\d+)", re.MULTILINE)
  13 parser = FluentParser()
  14 
  15 
  16 def get_max_length(comment):
  17     """
  18     Return max length value for an entity with MAX_LENTH.
  19     """
  20     max_length = re.findall(MAX_LENGTH_RE, comment or "")
  21 
  22     if max_length:
  23         return int(max_length[0][1])
  24 
  25     return None
  26 
  27 
  28 def run_checks(entity, original, string):
  29     """
  30     Group all checks related to the base UI that get stored in the DB
  31     :arg pontoon.base.models.Entity entity: Source entity
  32     :arg basestring original: an original string
  33     :arg basestring string: a translation
  34     """
  35     checks = defaultdict(list)
  36     resource_ext = entity.resource.format
  37 
  38     if resource_ext == "lang":
  39         # Newlines are not allowed in .lang files (bug 1190754)
  40         if "\n" in string:
  41             checks["pErrors"].append("Newline characters are not allowed")
  42 
  43         # Prevent translations exceeding the given length limit
  44         max_length = get_max_length(entity.comment)
  45 
  46         if max_length:
  47             string_length = len(
  48                 html.unescape(bleach.clean(string, strip=True, tags=()))
  49             )
  50 
  51             if string_length > max_length:
  52                 checks["pErrors"].append("Translation too long")
  53 
  54     # Bug 1599056: Original and translation must either both end in a newline,
  55     # or none of them should.
  56     if resource_ext == "po":
  57         if original.endswith("\n") != string.endswith("\n"):
  58             checks["pErrors"].append("Ending newline mismatch")
  59 
  60     # Prevent empty translation submissions if not supported
  61     if string == "" and not entity.resource.allows_empty_translations:
  62         checks["pErrors"].append("Empty translations are not allowed")
  63 
  64     # FTL checks
  65     if resource_ext == "ftl" and string != "":
  66         translation_ast = parser.parse_entry(string)
  67         entity_ast = parser.parse_entry(entity.string)
  68 
  69         # Parse error
  70         if isinstance(translation_ast, ast.Junk):
  71             checks["pErrors"].append(translation_ast.annotations[0].message)
  72 
  73         # Not a localizable entry
  74         elif not isinstance(translation_ast, localizable_entries):
  75             checks["pErrors"].append(
  76                 "Translation needs to be a valid localizable entry"
  77             )
  78 
  79         # Message ID mismatch
  80         elif entity_ast.id.name != translation_ast.id.name:
  81             checks["pErrors"].append("Translation key needs to match source string key")
  82 
  83     return checks
  84
  [end of pontoon/checks/libraries/pontoon_db.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: 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
  @@ -5,6 +5,7 @@
   
   from collections import defaultdict
   from fluent.syntax import FluentParser, ast
  +from fluent.syntax.visitor import Visitor
   
   from pontoon.sync.formats.ftl import localizable_entries
   
  @@ -25,6 +26,24 @@
       return None
   
   
  +class IsEmptyVisitor(Visitor):
  +    def __init__(self):
  +        self.is_empty = True
  +
  +    def visit_Placeable(self, node):
  +        if isinstance(node.expression, ast.Literal):
  +            if node.expression.parse()["value"]:
  +                self.is_empty = False
  +        elif isinstance(node.expression, ast.SelectExpression):
  +            self.generic_visit(node.expression)
  +        else:
  +            self.is_empty = False
  +
  +    def visit_TextElement(self, node):
  +        if node.value:
  +            self.is_empty = False
  +
  +
   def run_checks(entity, original, string):
       """
       Group all checks related to the base UI that get stored in the DB
  @@ -80,4 +99,12 @@
           elif entity_ast.id.name != translation_ast.id.name:
               checks["pErrors"].append("Translation key needs to match source string key")
   
  +        # Empty translation entry warning; set here rather than pontoon_non_db.py
  +        # to avoid needing to parse the Fluent message twice.
  +        else:
  +            visitor = IsEmptyVisitor()
  +            visitor.visit(translation_ast)
  +            if visitor.is_empty:
  +                checks["pndbWarnings"].append("Empty translation")
  +
       return checks
  
verification_info: {"golden_diff": "diff --git a/pontoon/checks/libraries/pontoon_db.py b/pontoon/checks/libraries/pontoon_db.py\n--- a/pontoon/checks/libraries/pontoon_db.py\n+++ b/pontoon/checks/libraries/pontoon_db.py\n@@ -5,6 +5,7 @@\n \n from collections import defaultdict\n from fluent.syntax import FluentParser, ast\n+from fluent.syntax.visitor import Visitor\n \n from pontoon.sync.formats.ftl import localizable_entries\n \n@@ -25,6 +26,24 @@\n     return None\n \n \n+class IsEmptyVisitor(Visitor):\n+    def __init__(self):\n+        self.is_empty = True\n+\n+    def visit_Placeable(self, node):\n+        if isinstance(node.expression, ast.Literal):\n+            if node.expression.parse()[\"value\"]:\n+                self.is_empty = False\n+        elif isinstance(node.expression, ast.SelectExpression):\n+            self.generic_visit(node.expression)\n+        else:\n+            self.is_empty = False\n+\n+    def visit_TextElement(self, node):\n+        if node.value:\n+            self.is_empty = False\n+\n+\n def run_checks(entity, original, string):\n     \"\"\"\n     Group all checks related to the base UI that get stored in the DB\n@@ -80,4 +99,12 @@\n         elif entity_ast.id.name != translation_ast.id.name:\n             checks[\"pErrors\"].append(\"Translation key needs to match source string key\")\n \n+        # Empty translation entry warning; set here rather than pontoon_non_db.py\n+        # to avoid needing to parse the Fluent message twice.\n+        else:\n+            visitor = IsEmptyVisitor()\n+            visitor.visit(translation_ast)\n+            if visitor.is_empty:\n+                checks[\"pndbWarnings\"].append(\"Empty translation\")\n+\n     return checks\n", "issue": "No warnings when trying to submit empty translations\nI've noticed an increase in the number of empty strings in Firefox, where I have [special checks](https://test.flod.org/checks/).\r\n\r\nApparently, we don't warn anymore when someone tries to submit an empty translation.\n", "before_files": [{"content": "import html\nimport re\n\nimport bleach\n\nfrom collections import defaultdict\nfrom fluent.syntax import FluentParser, ast\n\nfrom pontoon.sync.formats.ftl import localizable_entries\n\n\nMAX_LENGTH_RE = re.compile(r\"MAX_LENGTH:( *)(\\d+)\", re.MULTILINE)\nparser = FluentParser()\n\n\ndef get_max_length(comment):\n    \"\"\"\n    Return max length value for an entity with MAX_LENTH.\n    \"\"\"\n    max_length = re.findall(MAX_LENGTH_RE, comment or \"\")\n\n    if max_length:\n        return int(max_length[0][1])\n\n    return None\n\n\ndef run_checks(entity, original, string):\n    \"\"\"\n    Group all checks related to the base UI that get stored in the DB\n    :arg pontoon.base.models.Entity entity: Source entity\n    :arg basestring original: an original string\n    :arg basestring string: a translation\n    \"\"\"\n    checks = defaultdict(list)\n    resource_ext = entity.resource.format\n\n    if resource_ext == \"lang\":\n        # Newlines are not allowed in .lang files (bug 1190754)\n        if \"\\n\" in string:\n            checks[\"pErrors\"].append(\"Newline characters are not allowed\")\n\n        # Prevent translations exceeding the given length limit\n        max_length = get_max_length(entity.comment)\n\n        if max_length:\n            string_length = len(\n                html.unescape(bleach.clean(string, strip=True, tags=()))\n            )\n\n            if string_length > max_length:\n                checks[\"pErrors\"].append(\"Translation too long\")\n\n    # Bug 1599056: Original and translation must either both end in a newline,\n    # or none of them should.\n    if resource_ext == \"po\":\n        if original.endswith(\"\\n\") != string.endswith(\"\\n\"):\n            checks[\"pErrors\"].append(\"Ending newline mismatch\")\n\n    # Prevent empty translation submissions if not supported\n    if string == \"\" and not entity.resource.allows_empty_translations:\n        checks[\"pErrors\"].append(\"Empty translations are not allowed\")\n\n    # FTL checks\n    if resource_ext == \"ftl\" and string != \"\":\n        translation_ast = parser.parse_entry(string)\n        entity_ast = parser.parse_entry(entity.string)\n\n        # Parse error\n        if isinstance(translation_ast, ast.Junk):\n            checks[\"pErrors\"].append(translation_ast.annotations[0].message)\n\n        # Not a localizable entry\n        elif not isinstance(translation_ast, localizable_entries):\n            checks[\"pErrors\"].append(\n                \"Translation needs to be a valid localizable entry\"\n            )\n\n        # Message ID mismatch\n        elif entity_ast.id.name != translation_ast.id.name:\n            checks[\"pErrors\"].append(\"Translation key needs to match source string key\")\n\n    return checks\n", "path": "pontoon/checks/libraries/pontoon_db.py"}]}