Repository: mdn/kuma

Issue:
T - Code snippets gets translated by Google Translate
This should not happen. 
<img width="598" alt="Screen Shot 2020-05-18 at 2 28 21 PM" src="https://user-images.githubusercontent.com/26739/82250854-3bd16700-991a-11ea-932c-6b394f7980ad.png">

From: https://translate.google.com/translate?sl=auto&tl=sv&u=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FLearn%2FCSS%2FFirst_steps%2FWhat_is_CSS



Discussion:
I'm editing the title because the issue should be about the problem and not the solution. 
And I'm bumping the estimate because I need to do some research to better understand it and it's hard to test things with Google Translate without doing a prod release and waiting a week.
Interestingly, there's an HTML5 attribute [`translate=no`](https://html.spec.whatwg.org/multipage/dom.html#the-translate-attribute) but it didn't work. 
However, using this tip worked: https://stackoverflow.com/a/9629628/205832
When I turn make it this:
```html
<pre class="brush: css notranslate">h1 {
    color: red;
    font-size: 5em;
}</pre>
```
it now worked:
<img width="888" alt="Screen Shot 2020-06-02 at 2 47 48 PM" src="https://user-images.githubusercontent.com/26739/83557901-6ad20600-a4e0-11ea-9caf-ca2ec72d76ad.png">

And it works with the Chrome extension too:
<img width="892" alt="Screen Shot 2020-06-02 at 2 53 21 PM" src="https://user-images.githubusercontent.com/26739/83558333-dfa54000-a4e0-11ea-8452-2dc6a956d67b.png">

Now, in my site, that I used for preliminary testing, I don't have `prism` installed to mutate the DOM. My next task is to try this whole thing using actual Kuma. Let's see!
Interestingly, I can't reproduce the original bug with the Chrome extension:
<img width="1173" alt="Screen Shot 2020-06-02 at 3 04 20 PM" src="https://user-images.githubusercontent.com/26739/83559339-69a1d880-a4e2-11ea-9d50-2eeae250acac.png">

But I can definitely reproduce it with their web service:
<img width="1091" alt="Screen Shot 2020-06-02 at 3 05 01 PM" src="https://user-images.githubusercontent.com/26739/83559386-7f170280-a4e2-11ea-9a73-ee7daa07da76.png">


Resolving patch:
diff --git a/kuma/wiki/content.py b/kuma/wiki/content.py
--- a/kuma/wiki/content.py
+++ b/kuma/wiki/content.py
@@ -193,6 +193,7 @@ def clean_content(content):
     parsed.injectSectionIDs()
     if not settings.ALLOW_ALL_IFRAMES:
         parsed.filterIframeHosts(settings.ALLOWED_IFRAME_PATTERNS)
+    parsed.annotatePreTags()
     return parsed.serialize()
 
 
@@ -367,6 +368,11 @@ def annotateLinks(self, base_url):
         self.stream = LinkAnnotationFilter(self.stream, base_url)
         return self
 
+    @newrelic.agent.function_trace()
+    def annotatePreTags(self):
+        self.stream = PreNotranslateClassFilter(self.stream)
+        return self
+
     @newrelic.agent.function_trace()
     def filterIframeHosts(self, patterns):
         self.stream = IframeHostFilter(self.stream, patterns)
@@ -559,6 +565,27 @@ def add_to_attr(attr_name, add_list):
             yield token
 
 
+class PreNotranslateClassFilter(html5lib_Filter):
+    """
+    Filter which annotates <pre> tags with the 'notranslate' class.
+    """
+
+    def __iter__(self):
+        input = html5lib_Filter.__iter__(self)
+        for token in input:
+            if token["type"] == "StartTag" and token["name"] == "pre":
+                attrs = dict(token["data"]) or {(None, "class"): ""}
+                for (namespace, name), value in attrs.copy().items():
+                    if name == "class" and "notranslate" not in value.split():
+                        before = attrs.get((namespace, "class")) or ""
+                        after = f"{before} notranslate".strip()
+                        attrs[(namespace, "class")] = after
+
+                token["data"] = attrs
+
+            yield token
+
+
 class SectionIDFilter(html5lib_Filter):
     """
     Filter which ensures section-related elements have unique IDs


Test patch:
diff --git a/kuma/wiki/tests/test_content.py b/kuma/wiki/tests/test_content.py
--- a/kuma/wiki/tests/test_content.py
+++ b/kuma/wiki/tests/test_content.py
@@ -994,6 +994,45 @@ def test_annotate_links_external_link():
     assert normalize_html(actual_raw) == expected
 
 
+@pytest.mark.parametrize(
+    "code",
+    (
+        # Simple existing class value
+        [
+            '<pre class="syntaxbox">h1 { color:brown }</pre>',
+            '<pre class="syntaxbox notranslate">h1 { color:brown }</pre>',
+        ],
+        # No class attribute at all
+        [
+            "<pre>h1 { color:pink }</pre>",
+            '<pre class="notranslate">h1 { color:pink }</pre>',
+        ],
+        # 'notranslate' already set
+        [
+            '<pre class="notranslate brush:js">h1 { color:brown }</pre>',
+            # Note, no difference!
+            '<pre class="notranslate brush:js">h1 { color:brown }</pre>',
+        ],
+        [
+            '<pre class="NOTranslate">h1 { color:brown }</pre>',
+            '<pre class="NOTranslate notranslate">h1 { color:brown }</pre>',
+        ],
+        # Present as a string but not really right
+        [
+            '<pre class="syntaxbox">h1 { color:brown }</pre>',
+            '<pre class="syntaxbox notranslate">h1 { color:brown }</pre>',
+        ],
+    ),
+    ids=("simple", "empty", "already", "already-case-insensitive", "tricky"),
+)
+def test_annotate_pre_tags(code):
+    """pre tags should get a 'notranslate' class added"""
+    html, expected_html = code
+    actual_raw = parse(html).annotatePreTags().serialize()
+    expected = normalize_html(expected_html)
+    assert normalize_html(actual_raw) == expected
+
+
 class FilterEditorSafetyTests(TestCase):
     def test_editor_safety_filter(self):
         """Markup that's hazardous for editing should be stripped"""
diff --git a/kuma/wiki/tests/test_forms.py b/kuma/wiki/tests/test_forms.py
--- a/kuma/wiki/tests/test_forms.py
+++ b/kuma/wiki/tests/test_forms.py
@@ -359,7 +359,7 @@ class RevisionFormEditTests(RevisionFormViewTests):
             " specifies the type of rendering box used for an element.</p>\n"
             "<p>{{cssinfo}}</p>\n"
             '<h2 id="Syntax">Syntax</h2>\n'
-            '<pre class="brush:css">'
+            '<pre class="brush:css notranslate">'
             "display: none;\n"
             "</pre>"
         ),
@@ -377,7 +377,7 @@ class RevisionFormEditTests(RevisionFormViewTests):
             "<p>{{cssinfo}} and my changes.</p>\n"
             '<h2 id="Syntax">Syntax</h2>\n'
             '<p><a href="http://spam.example.com">Buy my product!</a></p>\n'
-            '<pre class="brush:css">display: none;</pre>\n'
+            '<pre class="brush:css notranslate">display: none;</pre>\n'
         ),
         "comment": "Comment",
         "days": "0",
@@ -447,7 +447,7 @@ def test_standard_edit(self, mock_requests):
         expected_content = (
             "<p>{{cssinfo}} and my changes.</p>\n"
             '<p><a href="http://spam.example.com">Buy my product!</a></p>\n'
-            '<pre class="brush:css">display: none;</pre>\n'
+            '<pre class="brush:css notranslate">display: none;</pre>\n'
             "Comment"
         )
         assert parameters["comment_content"] == expected_content
@@ -476,7 +476,7 @@ def test_change_tags_edit(self, mock_requests):
         expected_content = (
             "<p>{{cssinfo}} and my changes.</p>\n"
             '<p><a href="http://spam.example.com">Buy my product!</a></p>\n'
-            '<pre class="brush:css">display: none;</pre>\n'
+            '<pre class="brush:css notranslate">display: none;</pre>\n'
             "Comment\n"
             "CSS Positioning"
         )
@@ -507,7 +507,7 @@ def test_legacy_edit(self, mock_requests):
         expected_content = (
             "The CSS property display\n" + "<p>{{cssinfo}} and my changes.</p>\n"
             '<p><a href="http://spam.example.com">Buy my product!</a></p>\n'
-            '<pre class="brush:css">display: none;</pre>\n'
+            '<pre class="brush:css notranslate">display: none;</pre>\n'
             "Updated\n"
             "CSS display, hidden"
         )
@@ -528,7 +528,7 @@ def test_quoteless_tags(self, mock_requests):
         expected_content = (
             "<p>{{cssinfo}} and my changes.</p>\n"
             '<p><a href="http://spam.example.com">Buy my product!</a></p>\n'
-            '<pre class="brush:css">display: none;</pre>\n'
+            '<pre class="brush:css notranslate">display: none;</pre>\n'
             "Comment\n"
             "CSS\n"
             "CSS Property\n"