We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Make /$locale/search.json redirect
**Summary**
We get a lot of `NoReverseMatch` on URLs like https://developer.mozilla.org/en-US/search.json
That endpoint disappeared when we switched to the new search API. 
Let's make it redirect. 


**Additional context**
https://sentry.prod.mozaws.net/operations/mdn-prod/issues/10482841/


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kuma/search/views.py`
Content:
```
1 from urllib.parse import parse_qs, urlencode
2 
3 from django.shortcuts import render
4 from django.urls import reverse_lazy
5 from django.views.decorators.cache import never_cache
6 from django.views.decorators.http import require_GET
7 from django.views.generic import RedirectView
8 from ratelimit.decorators import ratelimit
9 
10 from kuma.api.v1.search import search as search_api
11 from kuma.core.decorators import shared_cache_control
12 
13 
14 # Since the search endpoint accepts user input (via query parameters) and its
15 # response is compressed, use rate limiting to mitigate the BREACH attack
16 # (see http://breachattack.com/). It still needs to allow a user to click
17 # the filter switches (bug 1426968).
18 # Alternate: forbid gzip by setting Content-Encoding: identity
19 @never_cache
20 @require_GET
21 @ratelimit(key="user_or_ip", rate="25/m", block=True)
22 def search(request, *args, **kwargs):
23     """
24     The search view.
25 
26     --2021-- THIS VIEW IS A HACK! --2021--
27     This Django view exists to server-side render the search results page.
28     But we're moving the search result page to Yari and that one will use a XHR
29     request (to /api/v1/search) from a skeleton page (aka. SPA).
30     But as a way to get to that, we need to transition from the old to the new.
31     So, this page uses the Django view in kuma.api.v1.search.search, which
32     returns a special `JsonResponse` instance whose data we can pluck out
33     to our needs for this old view.
34     Once we've fully moved to the Yari (static + XHR to v1 API) site-search,
35     we can comfortably delete this view.
36     """
37     # The underlying v1 API supports searching without a 'q' but the web
38     # UI doesn't. For example, the search input field requires a value.
39     # So we match that here too.
40     if not request.GET.get("q", "").strip():
41         status = 400
42         context = {"results": {}}
43     else:
44         # TODO consider, if the current locale is *not* en-US, that we force
45         # it to do a search in both locales.
46         # This might come in handy for people searching in a locale where
47         # there's very little results but they'd be happy to get the en-US ones.
48         response = search_api(request, *args, **kwargs)
49         results = response.data
50 
51         error = None
52         status = response.status_code
53 
54         # Determine if there were validation errors
55         if status == 400:
56             error = ""
57             for key, messages in results["errors"].items():
58                 for message in messages:
59                     error += f"{key}: {message['message']}\n"
60         else:
61             # Have to rearrange the 'results' in a way the old search expects it.
62             # ...which is as follows:
63             #  - `count`: integer number of matched documents
64             #  - `previous`: a URL or empty string
65             #  - `next`: a URL or empty string
66             #  - `query`: string
67             #  - `start`: pagination number
68             #  - `end`: pagination number
69             #  - `documents`:
70             #      - `title`
71             #      - `locale`
72             #      - `slug`
73             #      - `excerpt`: string of safe HTML
74             next_url = ""
75             previous_url = ""
76             page = results["metadata"]["page"]
77             size = results["metadata"]["size"]
78             count = results["metadata"]["total"]["value"]
79             query_string = request.META.get("QUERY_STRING")
80             query_string_parsed = parse_qs(query_string)
81             if (page + 1) * size < count:
82                 query_string_parsed["page"] = f"{page + 1}"
83                 next_url = f"?{urlencode(query_string_parsed, True)}"
84             if page > 1:
85                 if page == 2:
86                     del query_string_parsed["page"]
87                 else:
88                     query_string_parsed["page"] = f"{page - 1}"
89                 previous_url = f"?{urlencode(query_string_parsed, True)}"
90 
91             results = {
92                 "count": count,
93                 "next": next_url,
94                 "previous": previous_url,
95                 "query": request.GET.get("q"),
96                 "start": (page - 1) * size + 1,
97                 "end": page * size,
98                 "documents": [
99                     {
100                         "title": x["title"],
101                         "slug": x["slug"],
102                         "locale": x["locale"],
103                         "excerpt": "<br>".join(x["highlight"].get("body", [])),
104                     }
105                     for x in results["documents"]
106                 ],
107             }
108 
109         context = {"results": {"results": None if error else results, "error": error}}
110     return render(request, "search/react.html", context, status=status)
111 
112 
113 class SearchRedirectView(RedirectView):
114     permanent = True
115 
116     def get_redirect_url(self, *args, **kwargs):
117         query_string = self.request.META.get("QUERY_STRING")
118         url = reverse_lazy(
119             "api.v1.search", kwargs={"locale": self.request.LANGUAGE_CODE}
120         )
121         if query_string:
122             url += "?" + query_string
123         return url
124 
125 
126 @shared_cache_control(s_maxage=60 * 60 * 24 * 7)
127 def plugin(request):
128     """Render an OpenSearch Plugin."""
129     return render(
130         request,
131         "search/plugin.html",
132         {"locale": request.LANGUAGE_CODE},
133         content_type="application/opensearchdescription+xml",
134     )
135
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kuma/search/views.py b/kuma/search/views.py
--- a/kuma/search/views.py
+++ b/kuma/search/views.py
@@ -1,5 +1,6 @@
 from urllib.parse import parse_qs, urlencode
 
+from django.conf import settings
 from django.shortcuts import render
 from django.urls import reverse_lazy
 from django.views.decorators.cache import never_cache
@@ -115,11 +116,14 @@
 
     def get_redirect_url(self, *args, **kwargs):
         query_string = self.request.META.get("QUERY_STRING")
-        url = reverse_lazy(
-            "api.v1.search", kwargs={"locale": self.request.LANGUAGE_CODE}
-        )
-        if query_string:
-            url += "?" + query_string
+        url = reverse_lazy("api.v1.search")
+        qs = parse_qs(query_string)
+        # If you used `/en-Us/search.json` you can skip the `?locale=`
+        # because the default locale in `/api/v1/search` is `en-US`.
+        if self.request.LANGUAGE_CODE.lower() != settings.LANGUAGE_CODE.lower():
+            qs["locale"] = self.request.LANGUAGE_CODE
+        if qs:
+            url += "?" + urlencode(qs, True)
         return url

{"golden_diff": "diff --git a/kuma/search/views.py b/kuma/search/views.py\n--- a/kuma/search/views.py\n+++ b/kuma/search/views.py\n@@ -1,5 +1,6 @@\n from urllib.parse import parse_qs, urlencode\n \n+from django.conf import settings\n from django.shortcuts import render\n from django.urls import reverse_lazy\n from django.views.decorators.cache import never_cache\n@@ -115,11 +116,14 @@\n \n     def get_redirect_url(self, *args, **kwargs):\n         query_string = self.request.META.get(\"QUERY_STRING\")\n-        url = reverse_lazy(\n-            \"api.v1.search\", kwargs={\"locale\": self.request.LANGUAGE_CODE}\n-        )\n-        if query_string:\n-            url += \"?\" + query_string\n+        url = reverse_lazy(\"api.v1.search\")\n+        qs = parse_qs(query_string)\n+        # If you used `/en-Us/search.json` you can skip the `?locale=`\n+        # because the default locale in `/api/v1/search` is `en-US`.\n+        if self.request.LANGUAGE_CODE.lower() != settings.LANGUAGE_CODE.lower():\n+            qs[\"locale\"] = self.request.LANGUAGE_CODE\n+        if qs:\n+            url += \"?\" + urlencode(qs, True)\n         return url\n", "issue": "Make /$locale/search.json redirect\n**Summary**\r\nWe get a lot of `NoReverseMatch` on URLs like https://developer.mozilla.org/en-US/search.json\r\nThat endpoint disappeared when we switched to the new search API. \r\nLet's make it redirect. \r\n\r\n\r\n**Additional context**\r\nhttps://sentry.prod.mozaws.net/operations/mdn-prod/issues/10482841/\r\n\n", "before_files": [{"content": "from urllib.parse import parse_qs, urlencode\n\nfrom django.shortcuts import render\nfrom django.urls import reverse_lazy\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.http import require_GET\nfrom django.views.generic import RedirectView\nfrom ratelimit.decorators import ratelimit\n\nfrom kuma.api.v1.search import search as search_api\nfrom kuma.core.decorators import shared_cache_control\n\n\n# Since the search endpoint accepts user input (via query parameters) and its\n# response is compressed, use rate limiting to mitigate the BREACH attack\n# (see http://breachattack.com/). It still needs to allow a user to click\n# the filter switches (bug 1426968).\n# Alternate: forbid gzip by setting Content-Encoding: identity\n@never_cache\n@require_GET\n@ratelimit(key=\"user_or_ip\", rate=\"25/m\", block=True)\ndef search(request, *args, **kwargs):\n    \"\"\"\n    The search view.\n\n    --2021-- THIS VIEW IS A HACK! --2021--\n    This Django view exists to server-side render the search results page.\n    But we're moving the search result page to Yari and that one will use a XHR\n    request (to /api/v1/search) from a skeleton page (aka. SPA).\n    But as a way to get to that, we need to transition from the old to the new.\n    So, this page uses the Django view in kuma.api.v1.search.search, which\n    returns a special `JsonResponse` instance whose data we can pluck out\n    to our needs for this old view.\n    Once we've fully moved to the Yari (static + XHR to v1 API) site-search,\n    we can comfortably delete this view.\n    \"\"\"\n    # The underlying v1 API supports searching without a 'q' but the web\n    # UI doesn't. For example, the search input field requires a value.\n    # So we match that here too.\n    if not request.GET.get(\"q\", \"\").strip():\n        status = 400\n        context = {\"results\": {}}\n    else:\n        # TODO consider, if the current locale is *not* en-US, that we force\n        # it to do a search in both locales.\n        # This might come in handy for people searching in a locale where\n        # there's very little results but they'd be happy to get the en-US ones.\n        response = search_api(request, *args, **kwargs)\n        results = response.data\n\n        error = None\n        status = response.status_code\n\n        # Determine if there were validation errors\n        if status == 400:\n            error = \"\"\n            for key, messages in results[\"errors\"].items():\n                for message in messages:\n                    error += f\"{key}: {message['message']}\\n\"\n        else:\n            # Have to rearrange the 'results' in a way the old search expects it.\n            # ...which is as follows:\n            #  - `count`: integer number of matched documents\n            #  - `previous`: a URL or empty string\n            #  - `next`: a URL or empty string\n            #  - `query`: string\n            #  - `start`: pagination number\n            #  - `end`: pagination number\n            #  - `documents`:\n            #      - `title`\n            #      - `locale`\n            #      - `slug`\n            #      - `excerpt`: string of safe HTML\n            next_url = \"\"\n            previous_url = \"\"\n            page = results[\"metadata\"][\"page\"]\n            size = results[\"metadata\"][\"size\"]\n            count = results[\"metadata\"][\"total\"][\"value\"]\n            query_string = request.META.get(\"QUERY_STRING\")\n            query_string_parsed = parse_qs(query_string)\n            if (page + 1) * size < count:\n                query_string_parsed[\"page\"] = f\"{page + 1}\"\n                next_url = f\"?{urlencode(query_string_parsed, True)}\"\n            if page > 1:\n                if page == 2:\n                    del query_string_parsed[\"page\"]\n                else:\n                    query_string_parsed[\"page\"] = f\"{page - 1}\"\n                previous_url = f\"?{urlencode(query_string_parsed, True)}\"\n\n            results = {\n                \"count\": count,\n                \"next\": next_url,\n                \"previous\": previous_url,\n                \"query\": request.GET.get(\"q\"),\n                \"start\": (page - 1) * size + 1,\n                \"end\": page * size,\n                \"documents\": [\n                    {\n                        \"title\": x[\"title\"],\n                        \"slug\": x[\"slug\"],\n                        \"locale\": x[\"locale\"],\n                        \"excerpt\": \"<br>\".join(x[\"highlight\"].get(\"body\", [])),\n                    }\n                    for x in results[\"documents\"]\n                ],\n            }\n\n        context = {\"results\": {\"results\": None if error else results, \"error\": error}}\n    return render(request, \"search/react.html\", context, status=status)\n\n\nclass SearchRedirectView(RedirectView):\n    permanent = True\n\n    def get_redirect_url(self, *args, **kwargs):\n        query_string = self.request.META.get(\"QUERY_STRING\")\n        url = reverse_lazy(\n            \"api.v1.search\", kwargs={\"locale\": self.request.LANGUAGE_CODE}\n        )\n        if query_string:\n            url += \"?\" + query_string\n        return url\n\n\n@shared_cache_control(s_maxage=60 * 60 * 24 * 7)\ndef plugin(request):\n    \"\"\"Render an OpenSearch Plugin.\"\"\"\n    return render(\n        request,\n        \"search/plugin.html\",\n        {\"locale\": request.LANGUAGE_CODE},\n        content_type=\"application/opensearchdescription+xml\",\n    )\n", "path": "kuma/search/views.py"}], "after_files": [{"content": "from urllib.parse import parse_qs, urlencode\n\nfrom django.conf import settings\nfrom django.shortcuts import render\nfrom django.urls import reverse_lazy\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.http import require_GET\nfrom django.views.generic import RedirectView\nfrom ratelimit.decorators import ratelimit\n\nfrom kuma.api.v1.search import search as search_api\nfrom kuma.core.decorators import shared_cache_control\n\n\n# Since the search endpoint accepts user input (via query parameters) and its\n# response is compressed, use rate limiting to mitigate the BREACH attack\n# (see http://breachattack.com/). It still needs to allow a user to click\n# the filter switches (bug 1426968).\n# Alternate: forbid gzip by setting Content-Encoding: identity\n@never_cache\n@require_GET\n@ratelimit(key=\"user_or_ip\", rate=\"25/m\", block=True)\ndef search(request, *args, **kwargs):\n    \"\"\"\n    The search view.\n\n    --2021-- THIS VIEW IS A HACK! --2021--\n    This Django view exists to server-side render the search results page.\n    But we're moving the search result page to Yari and that one will use a XHR\n    request (to /api/v1/search) from a skeleton page (aka. SPA).\n    But as a way to get to that, we need to transition from the old to the new.\n    So, this page uses the Django view in kuma.api.v1.search.search, which\n    returns a special `JsonResponse` instance whose data we can pluck out\n    to our needs for this old view.\n    Once we've fully moved to the Yari (static + XHR to v1 API) site-search,\n    we can comfortably delete this view.\n    \"\"\"\n    # The underlying v1 API supports searching without a 'q' but the web\n    # UI doesn't. For example, the search input field requires a value.\n    # So we match that here too.\n    if not request.GET.get(\"q\", \"\").strip():\n        status = 400\n        context = {\"results\": {}}\n    else:\n        # TODO consider, if the current locale is *not* en-US, that we force\n        # it to do a search in both locales.\n        # This might come in handy for people searching in a locale where\n        # there's very little results but they'd be happy to get the en-US ones.\n        response = search_api(request, *args, **kwargs)\n        results = response.data\n\n        error = None\n        status = response.status_code\n\n        # Determine if there were validation errors\n        if status == 400:\n            error = \"\"\n            for key, messages in results[\"errors\"].items():\n                for message in messages:\n                    error += f\"{key}: {message['message']}\\n\"\n        else:\n            # Have to rearrange the 'results' in a way the old search expects it.\n            # ...which is as follows:\n            #  - `count`: integer number of matched documents\n            #  - `previous`: a URL or empty string\n            #  - `next`: a URL or empty string\n            #  - `query`: string\n            #  - `start`: pagination number\n            #  - `end`: pagination number\n            #  - `documents`:\n            #      - `title`\n            #      - `locale`\n            #      - `slug`\n            #      - `excerpt`: string of safe HTML\n            next_url = \"\"\n            previous_url = \"\"\n            page = results[\"metadata\"][\"page\"]\n            size = results[\"metadata\"][\"size\"]\n            count = results[\"metadata\"][\"total\"][\"value\"]\n            query_string = request.META.get(\"QUERY_STRING\")\n            query_string_parsed = parse_qs(query_string)\n            if (page + 1) * size < count:\n                query_string_parsed[\"page\"] = f\"{page + 1}\"\n                next_url = f\"?{urlencode(query_string_parsed, True)}\"\n            if page > 1:\n                if page == 2:\n                    del query_string_parsed[\"page\"]\n                else:\n                    query_string_parsed[\"page\"] = f\"{page - 1}\"\n                previous_url = f\"?{urlencode(query_string_parsed, True)}\"\n\n            results = {\n                \"count\": count,\n                \"next\": next_url,\n                \"previous\": previous_url,\n                \"query\": request.GET.get(\"q\"),\n                \"start\": (page - 1) * size + 1,\n                \"end\": page * size,\n                \"documents\": [\n                    {\n                        \"title\": x[\"title\"],\n                        \"slug\": x[\"slug\"],\n                        \"locale\": x[\"locale\"],\n                        \"excerpt\": \"<br>\".join(x[\"highlight\"].get(\"body\", [])),\n                    }\n                    for x in results[\"documents\"]\n                ],\n            }\n\n        context = {\"results\": {\"results\": None if error else results, \"error\": error}}\n    return render(request, \"search/react.html\", context, status=status)\n\n\nclass SearchRedirectView(RedirectView):\n    permanent = True\n\n    def get_redirect_url(self, *args, **kwargs):\n        query_string = self.request.META.get(\"QUERY_STRING\")\n        url = reverse_lazy(\"api.v1.search\")\n        qs = parse_qs(query_string)\n        # If you used `/en-Us/search.json` you can skip the `?locale=`\n        # because the default locale in `/api/v1/search` is `en-US`.\n        if self.request.LANGUAGE_CODE.lower() != settings.LANGUAGE_CODE.lower():\n            qs[\"locale\"] = self.request.LANGUAGE_CODE\n        if qs:\n            url += \"?\" + urlencode(qs, True)\n        return url\n\n\n@shared_cache_control(s_maxage=60 * 60 * 24 * 7)\ndef plugin(request):\n    \"\"\"Render an OpenSearch Plugin.\"\"\"\n    return render(\n        request,\n        \"search/plugin.html\",\n        {\"locale\": request.LANGUAGE_CODE},\n        content_type=\"application/opensearchdescription+xml\",\n    )\n", "path": "kuma/search/views.py"}]}