We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Internationalization of "Browse 1 more stream" and two more strings
<!-- Describe what you were expecting to see, what you saw instead, and steps to take in order to reproduce the buggy behavior. Screenshots can be helpful. -->
I am missing "Browse 1 more stream" https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/subscribe_to_more_streams.hbs#L4
and "Browse # more streams" https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/subscribe_to_more_streams.hbs#L9
in the internationalization files (translation.json or django.po).

![image](https://github.com/zulip/zulip/assets/38357406/a8049e56-0fc1-42c9-82e7-869d54d18205)

Syntax above differs from syntax in
https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/subscribe_to_more_streams.hbs#L14
which has an entry in translation.json.

I am also missing a translation of "Direct messages" in the first column of recent conversations. May be it is generated by the following code and missing internationalization. https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/recent_topic_row.hbs#L7

<!-- Check the box for the version of Zulip you are using (see https://zulip.com/help/view-zulip-version).-->

**Zulip Server and web app version:**

- [ ] Zulip Cloud (`*.zulipchat.com`)
- [x] Zulip Server 7.0+
- [ ] Zulip Server 6.0+
- [ ] Zulip Server 5.0 or older
- [ ] Other or not sure


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/management/commands/makemessages.py`
Content:
```
1 """
2 See https://zulip.readthedocs.io/en/latest/translating/internationalization.html
3 for background.
4 
5 The contents of this file are taken from
6 https://github.com/niwinz/django-jinja/blob/master/django_jinja/management/commands/makemessages.py
7 
8 Jinja2's i18n functionality is not exactly the same as Django's.
9 In particular, the tags names and their syntax are different:
10 
11   1. The Django ``trans`` tag is replaced by a _() global.
12   2. The Django ``blocktrans`` tag is called ``trans``.
13 
14 (1) isn't an issue, since the whole ``makemessages`` process is based on
15 converting the template tags to ``_()`` calls. However, (2) means that
16 those Jinja2 ``trans`` tags will not be picked up by Django's
17 ``makemessages`` command.
18 
19 There aren't any nice solutions here. While Jinja2's i18n extension does
20 come with extraction capabilities built in, the code behind ``makemessages``
21 unfortunately isn't extensible, so we can:
22 
23   * Duplicate the command + code behind it.
24   * Offer a separate command for Jinja2 extraction.
25   * Try to get Django to offer hooks into makemessages().
26   * Monkey-patch.
27 
28 We are currently doing that last thing. It turns out there we are lucky
29 for once: It's simply a matter of extending two regular expressions.
30 Credit for the approach goes to:
31 https://stackoverflow.com/questions/2090717
32 
33 """
34 import glob
35 import itertools
36 import json
37 import os
38 import re
39 import subprocess
40 from typing import Any, Collection, Dict, Iterator, List, Mapping
41 
42 from django.core.management.base import CommandParser
43 from django.core.management.commands import makemessages
44 from django.template.base import BLOCK_TAG_END, BLOCK_TAG_START
45 from django.utils.translation import template
46 
47 strip_whitespace_right = re.compile(
48     f"({BLOCK_TAG_START}-?\\s*(trans|pluralize).*?-{BLOCK_TAG_END})\\s+"
49 )
50 strip_whitespace_left = re.compile(
51     f"\\s+({BLOCK_TAG_START}-\\s*(endtrans|pluralize).*?-?{BLOCK_TAG_END})"
52 )
53 
54 regexes = [
55     r"{{#tr}}([\s\S]*?)(?:{{/tr}}|{{#\*inline )",  # '.' doesn't match '\n' by default
56     r'{{\s*t "([\s\S]*?)"\W*}}',
57     r"{{\s*t '([\s\S]*?)'\W*}}",
58     r'\(t "([\s\S]*?)"\)',
59     r'=\(t "([\s\S]*?)"\)(?=[^{]*}})',
60     r"=\(t '([\s\S]*?)'\)(?=[^{]*}})",
61 ]
62 tags = [
63     ("err_", "error"),
64 ]
65 
66 frontend_compiled_regexes = [re.compile(regex) for regex in regexes]
67 multiline_js_comment = re.compile(r"/\*.*?\*/", re.DOTALL)
68 singleline_js_comment = re.compile("//.*?\n")
69 
70 
71 def strip_whitespaces(src: str) -> str:
72     src = strip_whitespace_left.sub("\\1", src)
73     src = strip_whitespace_right.sub("\\1", src)
74     return src
75 
76 
77 class Command(makemessages.Command):
78     xgettext_options = makemessages.Command.xgettext_options
79     for func, tag in tags:
80         xgettext_options += [f'--keyword={func}:1,"{tag}"']
81 
82     def add_arguments(self, parser: CommandParser) -> None:
83         super().add_arguments(parser)
84         parser.add_argument(
85             "--frontend-source",
86             default="web/templates",
87             help="Name of the Handlebars template directory",
88         )
89         parser.add_argument(
90             "--frontend-output",
91             default="locale",
92             help="Name of the frontend messages output directory",
93         )
94         parser.add_argument(
95             "--frontend-namespace",
96             default="translations.json",
97             help="Namespace of the frontend locale file",
98         )
99 
100     def handle(self, *args: Any, **options: Any) -> None:
101         self.handle_django_locales(*args, **options)
102         self.handle_frontend_locales(**options)
103 
104     def handle_frontend_locales(
105         self,
106         *,
107         frontend_source: str,
108         frontend_output: str,
109         frontend_namespace: str,
110         locale: List[str],
111         exclude: List[str],
112         all: bool,
113         **options: Any,
114     ) -> None:
115         self.frontend_source = frontend_source
116         self.frontend_output = frontend_output
117         self.frontend_namespace = frontend_namespace
118         self.frontend_locale = locale
119         self.frontend_exclude = exclude
120         self.frontend_all = all
121 
122         translation_strings = self.get_translation_strings()
123         self.write_translation_strings(translation_strings)
124 
125     def handle_django_locales(self, *args: Any, **options: Any) -> None:
126         old_endblock_re = template.endblock_re
127         old_block_re = template.block_re
128         old_constant_re = template.constant_re
129 
130         old_templatize = template.templatize
131         # Extend the regular expressions that are used to detect
132         # translation blocks with an "OR jinja-syntax" clause.
133         template.endblock_re = re.compile(
134             template.endblock_re.pattern + "|" + r"""^-?\s*endtrans\s*-?$"""
135         )
136         template.block_re = re.compile(
137             template.block_re.pattern + "|" + r"""^-?\s*trans(?:\s+(?!'|")(?=.*?=.*?)|\s*-?$)"""
138         )
139         template.plural_re = re.compile(
140             template.plural_re.pattern + "|" + r"""^-?\s*pluralize(?:\s+.+|-?$)"""
141         )
142         template.constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?')).*\)""")
143 
144         def my_templatize(src: str, *args: Any, **kwargs: Any) -> str:
145             new_src = strip_whitespaces(src)
146             return old_templatize(new_src, *args, **kwargs)
147 
148         template.templatize = my_templatize
149 
150         try:
151             ignore_patterns = options.get("ignore_patterns", [])
152             ignore_patterns.append("docs/*")
153             ignore_patterns.append("templates/zerver/emails/custom/*")
154             ignore_patterns.append("var/*")
155             options["ignore_patterns"] = ignore_patterns
156             super().handle(*args, **options)
157         finally:
158             template.endblock_re = old_endblock_re
159             template.block_re = old_block_re
160             template.templatize = old_templatize
161             template.constant_re = old_constant_re
162 
163     def extract_strings(self, data: str) -> List[str]:
164         translation_strings: List[str] = []
165         for regex in frontend_compiled_regexes:
166             for match in regex.findall(data):
167                 match = match.strip()
168                 match = " ".join(line.strip() for line in match.splitlines())
169                 translation_strings.append(match)
170 
171         return translation_strings
172 
173     def ignore_javascript_comments(self, data: str) -> str:
174         # Removes multi line comments.
175         data = multiline_js_comment.sub("", data)
176         # Removes single line (//) comments.
177         data = singleline_js_comment.sub("", data)
178         return data
179 
180     def get_translation_strings(self) -> List[str]:
181         translation_strings: List[str] = []
182         dirname = self.get_template_dir()
183 
184         for dirpath, dirnames, filenames in os.walk(dirname):
185             for filename in [f for f in filenames if f.endswith(".hbs")]:
186                 if filename.startswith("."):
187                     continue
188                 with open(os.path.join(dirpath, filename)) as reader:
189                     data = reader.read()
190                     translation_strings.extend(self.extract_strings(data))
191         for dirpath, dirnames, filenames in itertools.chain(
192             os.walk("web/src"), os.walk("web/shared/src")
193         ):
194             for filename in [f for f in filenames if f.endswith((".js", ".ts"))]:
195                 if filename.startswith("."):
196                     continue
197                 with open(os.path.join(dirpath, filename)) as reader:
198                     data = reader.read()
199                     data = self.ignore_javascript_comments(data)
200                     translation_strings.extend(self.extract_strings(data))
201 
202         extracted = subprocess.check_output(
203             [
204                 "node_modules/.bin/formatjs",
205                 "extract",
206                 "--additional-function-names=$t,$t_html",
207                 "--format=simple",
208                 "--ignore=**/*.d.ts",
209                 "web/src/**/*.js",
210                 "web/src/**/*.ts",
211             ]
212         )
213         translation_strings.extend(json.loads(extracted).values())
214 
215         return list(set(translation_strings))
216 
217     def get_template_dir(self) -> str:
218         return self.frontend_source
219 
220     def get_namespace(self) -> str:
221         return self.frontend_namespace
222 
223     def get_locales(self) -> Collection[str]:
224         locale = self.frontend_locale
225         exclude = self.frontend_exclude
226         process_all = self.frontend_all
227 
228         # After calling super().handle(), default_locale_path gets set on self
229         # so that we can reuse it here.
230         default_locale_path = self.default_locale_path  # type: ignore[attr-defined] # not in stubs
231         paths = glob.glob(f"{default_locale_path}/*")
232         all_locales = [os.path.basename(path) for path in paths if os.path.isdir(path)]
233 
234         # Account for excluded locales
235         if process_all:
236             return all_locales
237         else:
238             locales = locale or all_locales
239             return set(locales) - set(exclude)
240 
241     def get_base_path(self) -> str:
242         return self.frontend_output
243 
244     def get_output_paths(self) -> Iterator[str]:
245         base_path = self.get_base_path()
246         locales = self.get_locales()
247         for path in [os.path.join(base_path, locale) for locale in locales]:
248             if not os.path.exists(path):
249                 os.makedirs(path)
250 
251             yield os.path.join(path, self.get_namespace())
252 
253     def get_new_strings(
254         self, old_strings: Mapping[str, str], translation_strings: List[str], locale: str
255     ) -> Dict[str, str]:
256         """
257         Missing strings are removed, new strings are added and already
258         translated strings are not touched.
259         """
260         new_strings = {}  # Dict[str, str]
261         for k in translation_strings:
262             if locale == "en":
263                 # For English language, translation is equal to the key.
264                 new_strings[k] = old_strings.get(k, k)
265             else:
266                 new_strings[k] = old_strings.get(k, "")
267 
268         return new_strings
269 
270     def write_translation_strings(self, translation_strings: List[str]) -> None:
271         for locale, output_path in zip(self.get_locales(), self.get_output_paths()):
272             self.stdout.write(f"[frontend] processing locale {locale}")
273             try:
274                 with open(output_path) as reader:
275                     old_strings = json.load(reader)
276             except (OSError, ValueError):
277                 old_strings = {}
278 
279             new_strings = self.get_new_strings(old_strings, translation_strings, locale)
280             with open(output_path, "w") as writer:
281                 json.dump(new_strings, writer, indent=2, sort_keys=True)
282
```
--- 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/zerver/management/commands/makemessages.py b/zerver/management/commands/makemessages.py
--- a/zerver/management/commands/makemessages.py
+++ b/zerver/management/commands/makemessages.py
@@ -52,9 +52,9 @@
 )
 
 regexes = [
-    r"{{#tr}}([\s\S]*?)(?:{{/tr}}|{{#\*inline )",  # '.' doesn't match '\n' by default
-    r'{{\s*t "([\s\S]*?)"\W*}}',
-    r"{{\s*t '([\s\S]*?)'\W*}}",
+    r"{{~?#tr}}([\s\S]*?)(?:~?{{/tr}}|{{#\*inline )",  # '.' doesn't match '\n' by default
+    r'{{~?\s*t "([\s\S]*?)"\W*~?}}',
+    r"{{~?\s*t '([\s\S]*?)'\W*~?}}",
     r'\(t "([\s\S]*?)"\)',
     r'=\(t "([\s\S]*?)"\)(?=[^{]*}})',
     r"=\(t '([\s\S]*?)'\)(?=[^{]*}})",

{"golden_diff": "diff --git a/zerver/management/commands/makemessages.py b/zerver/management/commands/makemessages.py\n--- a/zerver/management/commands/makemessages.py\n+++ b/zerver/management/commands/makemessages.py\n@@ -52,9 +52,9 @@\n )\n \n regexes = [\n-    r\"{{#tr}}([\\s\\S]*?)(?:{{/tr}}|{{#\\*inline )\",  # '.' doesn't match '\\n' by default\n-    r'{{\\s*t \"([\\s\\S]*?)\"\\W*}}',\n-    r\"{{\\s*t '([\\s\\S]*?)'\\W*}}\",\n+    r\"{{~?#tr}}([\\s\\S]*?)(?:~?{{/tr}}|{{#\\*inline )\",  # '.' doesn't match '\\n' by default\n+    r'{{~?\\s*t \"([\\s\\S]*?)\"\\W*~?}}',\n+    r\"{{~?\\s*t '([\\s\\S]*?)'\\W*~?}}\",\n     r'\\(t \"([\\s\\S]*?)\"\\)',\n     r'=\\(t \"([\\s\\S]*?)\"\\)(?=[^{]*}})',\n     r\"=\\(t '([\\s\\S]*?)'\\)(?=[^{]*}})\",\n", "issue": "Internationalization of \"Browse 1 more stream\" and two more strings\n<!-- Describe what you were expecting to see, what you saw instead, and steps to take in order to reproduce the buggy behavior. Screenshots can be helpful. -->\r\nI am missing \"Browse 1 more stream\" https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/subscribe_to_more_streams.hbs#L4\r\nand \"Browse # more streams\" https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/subscribe_to_more_streams.hbs#L9\r\nin the internationalization files (translation.json or django.po).\r\n\r\n![image](https://github.com/zulip/zulip/assets/38357406/a8049e56-0fc1-42c9-82e7-869d54d18205)\r\n\r\nSyntax above differs from syntax in\r\nhttps://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/subscribe_to_more_streams.hbs#L14\r\nwhich has an entry in translation.json.\r\n\r\nI am also missing a translation of \"Direct messages\" in the first column of recent conversations. May be it is generated by the following code and missing internationalization. https://github.com/zulip/zulip/blob/81bd63cb46273b8c94ef9e92c00893ed97110119/web/templates/recent_topic_row.hbs#L7\r\n\r\n<!-- Check the box for the version of Zulip you are using (see https://zulip.com/help/view-zulip-version).-->\r\n\r\n**Zulip Server and web app version:**\r\n\r\n- [ ] Zulip Cloud (`*.zulipchat.com`)\r\n- [x] Zulip Server 7.0+\r\n- [ ] Zulip Server 6.0+\r\n- [ ] Zulip Server 5.0 or older\r\n- [ ] Other or not sure\r\n\n", "before_files": [{"content": "\"\"\"\nSee https://zulip.readthedocs.io/en/latest/translating/internationalization.html\nfor background.\n\nThe contents of this file are taken from\nhttps://github.com/niwinz/django-jinja/blob/master/django_jinja/management/commands/makemessages.py\n\nJinja2's i18n functionality is not exactly the same as Django's.\nIn particular, the tags names and their syntax are different:\n\n  1. The Django ``trans`` tag is replaced by a _() global.\n  2. The Django ``blocktrans`` tag is called ``trans``.\n\n(1) isn't an issue, since the whole ``makemessages`` process is based on\nconverting the template tags to ``_()`` calls. However, (2) means that\nthose Jinja2 ``trans`` tags will not be picked up by Django's\n``makemessages`` command.\n\nThere aren't any nice solutions here. While Jinja2's i18n extension does\ncome with extraction capabilities built in, the code behind ``makemessages``\nunfortunately isn't extensible, so we can:\n\n  * Duplicate the command + code behind it.\n  * Offer a separate command for Jinja2 extraction.\n  * Try to get Django to offer hooks into makemessages().\n  * Monkey-patch.\n\nWe are currently doing that last thing. It turns out there we are lucky\nfor once: It's simply a matter of extending two regular expressions.\nCredit for the approach goes to:\nhttps://stackoverflow.com/questions/2090717\n\n\"\"\"\nimport glob\nimport itertools\nimport json\nimport os\nimport re\nimport subprocess\nfrom typing import Any, Collection, Dict, Iterator, List, Mapping\n\nfrom django.core.management.base import CommandParser\nfrom django.core.management.commands import makemessages\nfrom django.template.base import BLOCK_TAG_END, BLOCK_TAG_START\nfrom django.utils.translation import template\n\nstrip_whitespace_right = re.compile(\n    f\"({BLOCK_TAG_START}-?\\\\s*(trans|pluralize).*?-{BLOCK_TAG_END})\\\\s+\"\n)\nstrip_whitespace_left = re.compile(\n    f\"\\\\s+({BLOCK_TAG_START}-\\\\s*(endtrans|pluralize).*?-?{BLOCK_TAG_END})\"\n)\n\nregexes = [\n    r\"{{#tr}}([\\s\\S]*?)(?:{{/tr}}|{{#\\*inline )\",  # '.' doesn't match '\\n' by default\n    r'{{\\s*t \"([\\s\\S]*?)\"\\W*}}',\n    r\"{{\\s*t '([\\s\\S]*?)'\\W*}}\",\n    r'\\(t \"([\\s\\S]*?)\"\\)',\n    r'=\\(t \"([\\s\\S]*?)\"\\)(?=[^{]*}})',\n    r\"=\\(t '([\\s\\S]*?)'\\)(?=[^{]*}})\",\n]\ntags = [\n    (\"err_\", \"error\"),\n]\n\nfrontend_compiled_regexes = [re.compile(regex) for regex in regexes]\nmultiline_js_comment = re.compile(r\"/\\*.*?\\*/\", re.DOTALL)\nsingleline_js_comment = re.compile(\"//.*?\\n\")\n\n\ndef strip_whitespaces(src: str) -> str:\n    src = strip_whitespace_left.sub(\"\\\\1\", src)\n    src = strip_whitespace_right.sub(\"\\\\1\", src)\n    return src\n\n\nclass Command(makemessages.Command):\n    xgettext_options = makemessages.Command.xgettext_options\n    for func, tag in tags:\n        xgettext_options += [f'--keyword={func}:1,\"{tag}\"']\n\n    def add_arguments(self, parser: CommandParser) -> None:\n        super().add_arguments(parser)\n        parser.add_argument(\n            \"--frontend-source\",\n            default=\"web/templates\",\n            help=\"Name of the Handlebars template directory\",\n        )\n        parser.add_argument(\n            \"--frontend-output\",\n            default=\"locale\",\n            help=\"Name of the frontend messages output directory\",\n        )\n        parser.add_argument(\n            \"--frontend-namespace\",\n            default=\"translations.json\",\n            help=\"Namespace of the frontend locale file\",\n        )\n\n    def handle(self, *args: Any, **options: Any) -> None:\n        self.handle_django_locales(*args, **options)\n        self.handle_frontend_locales(**options)\n\n    def handle_frontend_locales(\n        self,\n        *,\n        frontend_source: str,\n        frontend_output: str,\n        frontend_namespace: str,\n        locale: List[str],\n        exclude: List[str],\n        all: bool,\n        **options: Any,\n    ) -> None:\n        self.frontend_source = frontend_source\n        self.frontend_output = frontend_output\n        self.frontend_namespace = frontend_namespace\n        self.frontend_locale = locale\n        self.frontend_exclude = exclude\n        self.frontend_all = all\n\n        translation_strings = self.get_translation_strings()\n        self.write_translation_strings(translation_strings)\n\n    def handle_django_locales(self, *args: Any, **options: Any) -> None:\n        old_endblock_re = template.endblock_re\n        old_block_re = template.block_re\n        old_constant_re = template.constant_re\n\n        old_templatize = template.templatize\n        # Extend the regular expressions that are used to detect\n        # translation blocks with an \"OR jinja-syntax\" clause.\n        template.endblock_re = re.compile(\n            template.endblock_re.pattern + \"|\" + r\"\"\"^-?\\s*endtrans\\s*-?$\"\"\"\n        )\n        template.block_re = re.compile(\n            template.block_re.pattern + \"|\" + r\"\"\"^-?\\s*trans(?:\\s+(?!'|\")(?=.*?=.*?)|\\s*-?$)\"\"\"\n        )\n        template.plural_re = re.compile(\n            template.plural_re.pattern + \"|\" + r\"\"\"^-?\\s*pluralize(?:\\s+.+|-?$)\"\"\"\n        )\n        template.constant_re = re.compile(r\"\"\"_\\(((?:\".*?\")|(?:'.*?')).*\\)\"\"\")\n\n        def my_templatize(src: str, *args: Any, **kwargs: Any) -> str:\n            new_src = strip_whitespaces(src)\n            return old_templatize(new_src, *args, **kwargs)\n\n        template.templatize = my_templatize\n\n        try:\n            ignore_patterns = options.get(\"ignore_patterns\", [])\n            ignore_patterns.append(\"docs/*\")\n            ignore_patterns.append(\"templates/zerver/emails/custom/*\")\n            ignore_patterns.append(\"var/*\")\n            options[\"ignore_patterns\"] = ignore_patterns\n            super().handle(*args, **options)\n        finally:\n            template.endblock_re = old_endblock_re\n            template.block_re = old_block_re\n            template.templatize = old_templatize\n            template.constant_re = old_constant_re\n\n    def extract_strings(self, data: str) -> List[str]:\n        translation_strings: List[str] = []\n        for regex in frontend_compiled_regexes:\n            for match in regex.findall(data):\n                match = match.strip()\n                match = \" \".join(line.strip() for line in match.splitlines())\n                translation_strings.append(match)\n\n        return translation_strings\n\n    def ignore_javascript_comments(self, data: str) -> str:\n        # Removes multi line comments.\n        data = multiline_js_comment.sub(\"\", data)\n        # Removes single line (//) comments.\n        data = singleline_js_comment.sub(\"\", data)\n        return data\n\n    def get_translation_strings(self) -> List[str]:\n        translation_strings: List[str] = []\n        dirname = self.get_template_dir()\n\n        for dirpath, dirnames, filenames in os.walk(dirname):\n            for filename in [f for f in filenames if f.endswith(\".hbs\")]:\n                if filename.startswith(\".\"):\n                    continue\n                with open(os.path.join(dirpath, filename)) as reader:\n                    data = reader.read()\n                    translation_strings.extend(self.extract_strings(data))\n        for dirpath, dirnames, filenames in itertools.chain(\n            os.walk(\"web/src\"), os.walk(\"web/shared/src\")\n        ):\n            for filename in [f for f in filenames if f.endswith((\".js\", \".ts\"))]:\n                if filename.startswith(\".\"):\n                    continue\n                with open(os.path.join(dirpath, filename)) as reader:\n                    data = reader.read()\n                    data = self.ignore_javascript_comments(data)\n                    translation_strings.extend(self.extract_strings(data))\n\n        extracted = subprocess.check_output(\n            [\n                \"node_modules/.bin/formatjs\",\n                \"extract\",\n                \"--additional-function-names=$t,$t_html\",\n                \"--format=simple\",\n                \"--ignore=**/*.d.ts\",\n                \"web/src/**/*.js\",\n                \"web/src/**/*.ts\",\n            ]\n        )\n        translation_strings.extend(json.loads(extracted).values())\n\n        return list(set(translation_strings))\n\n    def get_template_dir(self) -> str:\n        return self.frontend_source\n\n    def get_namespace(self) -> str:\n        return self.frontend_namespace\n\n    def get_locales(self) -> Collection[str]:\n        locale = self.frontend_locale\n        exclude = self.frontend_exclude\n        process_all = self.frontend_all\n\n        # After calling super().handle(), default_locale_path gets set on self\n        # so that we can reuse it here.\n        default_locale_path = self.default_locale_path  # type: ignore[attr-defined] # not in stubs\n        paths = glob.glob(f\"{default_locale_path}/*\")\n        all_locales = [os.path.basename(path) for path in paths if os.path.isdir(path)]\n\n        # Account for excluded locales\n        if process_all:\n            return all_locales\n        else:\n            locales = locale or all_locales\n            return set(locales) - set(exclude)\n\n    def get_base_path(self) -> str:\n        return self.frontend_output\n\n    def get_output_paths(self) -> Iterator[str]:\n        base_path = self.get_base_path()\n        locales = self.get_locales()\n        for path in [os.path.join(base_path, locale) for locale in locales]:\n            if not os.path.exists(path):\n                os.makedirs(path)\n\n            yield os.path.join(path, self.get_namespace())\n\n    def get_new_strings(\n        self, old_strings: Mapping[str, str], translation_strings: List[str], locale: str\n    ) -> Dict[str, str]:\n        \"\"\"\n        Missing strings are removed, new strings are added and already\n        translated strings are not touched.\n        \"\"\"\n        new_strings = {}  # Dict[str, str]\n        for k in translation_strings:\n            if locale == \"en\":\n                # For English language, translation is equal to the key.\n                new_strings[k] = old_strings.get(k, k)\n            else:\n                new_strings[k] = old_strings.get(k, \"\")\n\n        return new_strings\n\n    def write_translation_strings(self, translation_strings: List[str]) -> None:\n        for locale, output_path in zip(self.get_locales(), self.get_output_paths()):\n            self.stdout.write(f\"[frontend] processing locale {locale}\")\n            try:\n                with open(output_path) as reader:\n                    old_strings = json.load(reader)\n            except (OSError, ValueError):\n                old_strings = {}\n\n            new_strings = self.get_new_strings(old_strings, translation_strings, locale)\n            with open(output_path, \"w\") as writer:\n                json.dump(new_strings, writer, indent=2, sort_keys=True)\n", "path": "zerver/management/commands/makemessages.py"}], "after_files": [{"content": "\"\"\"\nSee https://zulip.readthedocs.io/en/latest/translating/internationalization.html\nfor background.\n\nThe contents of this file are taken from\nhttps://github.com/niwinz/django-jinja/blob/master/django_jinja/management/commands/makemessages.py\n\nJinja2's i18n functionality is not exactly the same as Django's.\nIn particular, the tags names and their syntax are different:\n\n  1. The Django ``trans`` tag is replaced by a _() global.\n  2. The Django ``blocktrans`` tag is called ``trans``.\n\n(1) isn't an issue, since the whole ``makemessages`` process is based on\nconverting the template tags to ``_()`` calls. However, (2) means that\nthose Jinja2 ``trans`` tags will not be picked up by Django's\n``makemessages`` command.\n\nThere aren't any nice solutions here. While Jinja2's i18n extension does\ncome with extraction capabilities built in, the code behind ``makemessages``\nunfortunately isn't extensible, so we can:\n\n  * Duplicate the command + code behind it.\n  * Offer a separate command for Jinja2 extraction.\n  * Try to get Django to offer hooks into makemessages().\n  * Monkey-patch.\n\nWe are currently doing that last thing. It turns out there we are lucky\nfor once: It's simply a matter of extending two regular expressions.\nCredit for the approach goes to:\nhttps://stackoverflow.com/questions/2090717\n\n\"\"\"\nimport glob\nimport itertools\nimport json\nimport os\nimport re\nimport subprocess\nfrom typing import Any, Collection, Dict, Iterator, List, Mapping\n\nfrom django.core.management.base import CommandParser\nfrom django.core.management.commands import makemessages\nfrom django.template.base import BLOCK_TAG_END, BLOCK_TAG_START\nfrom django.utils.translation import template\n\nstrip_whitespace_right = re.compile(\n    f\"({BLOCK_TAG_START}-?\\\\s*(trans|pluralize).*?-{BLOCK_TAG_END})\\\\s+\"\n)\nstrip_whitespace_left = re.compile(\n    f\"\\\\s+({BLOCK_TAG_START}-\\\\s*(endtrans|pluralize).*?-?{BLOCK_TAG_END})\"\n)\n\nregexes = [\n    r\"{{~?#tr}}([\\s\\S]*?)(?:~?{{/tr}}|{{#\\*inline )\",  # '.' doesn't match '\\n' by default\n    r'{{~?\\s*t \"([\\s\\S]*?)\"\\W*~?}}',\n    r\"{{~?\\s*t '([\\s\\S]*?)'\\W*~?}}\",\n    r'\\(t \"([\\s\\S]*?)\"\\)',\n    r'=\\(t \"([\\s\\S]*?)\"\\)(?=[^{]*}})',\n    r\"=\\(t '([\\s\\S]*?)'\\)(?=[^{]*}})\",\n]\ntags = [\n    (\"err_\", \"error\"),\n]\n\nfrontend_compiled_regexes = [re.compile(regex) for regex in regexes]\nmultiline_js_comment = re.compile(r\"/\\*.*?\\*/\", re.DOTALL)\nsingleline_js_comment = re.compile(\"//.*?\\n\")\n\n\ndef strip_whitespaces(src: str) -> str:\n    src = strip_whitespace_left.sub(\"\\\\1\", src)\n    src = strip_whitespace_right.sub(\"\\\\1\", src)\n    return src\n\n\nclass Command(makemessages.Command):\n    xgettext_options = makemessages.Command.xgettext_options\n    for func, tag in tags:\n        xgettext_options += [f'--keyword={func}:1,\"{tag}\"']\n\n    def add_arguments(self, parser: CommandParser) -> None:\n        super().add_arguments(parser)\n        parser.add_argument(\n            \"--frontend-source\",\n            default=\"web/templates\",\n            help=\"Name of the Handlebars template directory\",\n        )\n        parser.add_argument(\n            \"--frontend-output\",\n            default=\"locale\",\n            help=\"Name of the frontend messages output directory\",\n        )\n        parser.add_argument(\n            \"--frontend-namespace\",\n            default=\"translations.json\",\n            help=\"Namespace of the frontend locale file\",\n        )\n\n    def handle(self, *args: Any, **options: Any) -> None:\n        self.handle_django_locales(*args, **options)\n        self.handle_frontend_locales(**options)\n\n    def handle_frontend_locales(\n        self,\n        *,\n        frontend_source: str,\n        frontend_output: str,\n        frontend_namespace: str,\n        locale: List[str],\n        exclude: List[str],\n        all: bool,\n        **options: Any,\n    ) -> None:\n        self.frontend_source = frontend_source\n        self.frontend_output = frontend_output\n        self.frontend_namespace = frontend_namespace\n        self.frontend_locale = locale\n        self.frontend_exclude = exclude\n        self.frontend_all = all\n\n        translation_strings = self.get_translation_strings()\n        self.write_translation_strings(translation_strings)\n\n    def handle_django_locales(self, *args: Any, **options: Any) -> None:\n        old_endblock_re = template.endblock_re\n        old_block_re = template.block_re\n        old_constant_re = template.constant_re\n\n        old_templatize = template.templatize\n        # Extend the regular expressions that are used to detect\n        # translation blocks with an \"OR jinja-syntax\" clause.\n        template.endblock_re = re.compile(\n            template.endblock_re.pattern + \"|\" + r\"\"\"^-?\\s*endtrans\\s*-?$\"\"\"\n        )\n        template.block_re = re.compile(\n            template.block_re.pattern + \"|\" + r\"\"\"^-?\\s*trans(?:\\s+(?!'|\")(?=.*?=.*?)|\\s*-?$)\"\"\"\n        )\n        template.plural_re = re.compile(\n            template.plural_re.pattern + \"|\" + r\"\"\"^-?\\s*pluralize(?:\\s+.+|-?$)\"\"\"\n        )\n        template.constant_re = re.compile(r\"\"\"_\\(((?:\".*?\")|(?:'.*?')).*\\)\"\"\")\n\n        def my_templatize(src: str, *args: Any, **kwargs: Any) -> str:\n            new_src = strip_whitespaces(src)\n            return old_templatize(new_src, *args, **kwargs)\n\n        template.templatize = my_templatize\n\n        try:\n            ignore_patterns = options.get(\"ignore_patterns\", [])\n            ignore_patterns.append(\"docs/*\")\n            ignore_patterns.append(\"templates/zerver/emails/custom/*\")\n            ignore_patterns.append(\"var/*\")\n            options[\"ignore_patterns\"] = ignore_patterns\n            super().handle(*args, **options)\n        finally:\n            template.endblock_re = old_endblock_re\n            template.block_re = old_block_re\n            template.templatize = old_templatize\n            template.constant_re = old_constant_re\n\n    def extract_strings(self, data: str) -> List[str]:\n        translation_strings: List[str] = []\n        for regex in frontend_compiled_regexes:\n            for match in regex.findall(data):\n                match = match.strip()\n                match = \" \".join(line.strip() for line in match.splitlines())\n                translation_strings.append(match)\n\n        return translation_strings\n\n    def ignore_javascript_comments(self, data: str) -> str:\n        # Removes multi line comments.\n        data = multiline_js_comment.sub(\"\", data)\n        # Removes single line (//) comments.\n        data = singleline_js_comment.sub(\"\", data)\n        return data\n\n    def get_translation_strings(self) -> List[str]:\n        translation_strings: List[str] = []\n        dirname = self.get_template_dir()\n\n        for dirpath, dirnames, filenames in os.walk(dirname):\n            for filename in [f for f in filenames if f.endswith(\".hbs\")]:\n                if filename.startswith(\".\"):\n                    continue\n                with open(os.path.join(dirpath, filename)) as reader:\n                    data = reader.read()\n                    translation_strings.extend(self.extract_strings(data))\n        for dirpath, dirnames, filenames in itertools.chain(\n            os.walk(\"web/src\"), os.walk(\"web/shared/src\")\n        ):\n            for filename in [f for f in filenames if f.endswith((\".js\", \".ts\"))]:\n                if filename.startswith(\".\"):\n                    continue\n                with open(os.path.join(dirpath, filename)) as reader:\n                    data = reader.read()\n                    data = self.ignore_javascript_comments(data)\n                    translation_strings.extend(self.extract_strings(data))\n\n        extracted = subprocess.check_output(\n            [\n                \"node_modules/.bin/formatjs\",\n                \"extract\",\n                \"--additional-function-names=$t,$t_html\",\n                \"--format=simple\",\n                \"--ignore=**/*.d.ts\",\n                \"web/src/**/*.js\",\n                \"web/src/**/*.ts\",\n            ]\n        )\n        translation_strings.extend(json.loads(extracted).values())\n\n        return list(set(translation_strings))\n\n    def get_template_dir(self) -> str:\n        return self.frontend_source\n\n    def get_namespace(self) -> str:\n        return self.frontend_namespace\n\n    def get_locales(self) -> Collection[str]:\n        locale = self.frontend_locale\n        exclude = self.frontend_exclude\n        process_all = self.frontend_all\n\n        # After calling super().handle(), default_locale_path gets set on self\n        # so that we can reuse it here.\n        default_locale_path = self.default_locale_path  # type: ignore[attr-defined] # not in stubs\n        paths = glob.glob(f\"{default_locale_path}/*\")\n        all_locales = [os.path.basename(path) for path in paths if os.path.isdir(path)]\n\n        # Account for excluded locales\n        if process_all:\n            return all_locales\n        else:\n            locales = locale or all_locales\n            return set(locales) - set(exclude)\n\n    def get_base_path(self) -> str:\n        return self.frontend_output\n\n    def get_output_paths(self) -> Iterator[str]:\n        base_path = self.get_base_path()\n        locales = self.get_locales()\n        for path in [os.path.join(base_path, locale) for locale in locales]:\n            if not os.path.exists(path):\n                os.makedirs(path)\n\n            yield os.path.join(path, self.get_namespace())\n\n    def get_new_strings(\n        self, old_strings: Mapping[str, str], translation_strings: List[str], locale: str\n    ) -> Dict[str, str]:\n        \"\"\"\n        Missing strings are removed, new strings are added and already\n        translated strings are not touched.\n        \"\"\"\n        new_strings = {}  # Dict[str, str]\n        for k in translation_strings:\n            if locale == \"en\":\n                # For English language, translation is equal to the key.\n                new_strings[k] = old_strings.get(k, k)\n            else:\n                new_strings[k] = old_strings.get(k, \"\")\n\n        return new_strings\n\n    def write_translation_strings(self, translation_strings: List[str]) -> None:\n        for locale, output_path in zip(self.get_locales(), self.get_output_paths()):\n            self.stdout.write(f\"[frontend] processing locale {locale}\")\n            try:\n                with open(output_path) as reader:\n                    old_strings = json.load(reader)\n            except (OSError, ValueError):\n                old_strings = {}\n\n            new_strings = self.get_new_strings(old_strings, translation_strings, locale)\n            with open(output_path, \"w\") as writer:\n                json.dump(new_strings, writer, indent=2, sort_keys=True)\n", "path": "zerver/management/commands/makemessages.py"}]}