We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[V3 Streams] issue with `[p]streamset`
# Command bugs

<!-- 
Did you find a bug with a command? Fill out the following:
-->

#### Command name

`streamset`

#### What cog is this command from?

`Streams`

#### What were you expecting to happen?

Should output help

#### What actually happened?

```py
Exception in command 'streamset'
Traceback (most recent call last):
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py", line 62, in wrapped
    ret = yield from coro(*args, **kwargs)
  File "/home/palm/.pyenv/versions/3.6.5/envs/redbot/lib/python3.6/site-packages/redbot/cogs/streams/streams.py", line 283, in streamset
    await ctx.send_help()
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/context.py", line 33, in send_help
    embeds = await self.bot.formatter.format_help_for(self, command)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py", line 233, in format_help_for
    emb = await self.format()
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py", line 156, in format
    if self.command.help:
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/commands.py", line 41, in help
    return inspect.cleandoc(translator(self.callback.__doc__))
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py", line 181, in __call__
    normalized_untranslated = _normalize(untranslated, True)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py", line 127, in _normalize
    string = string.replace('\\n\\n', '\n\n')
AttributeError: 'NoneType' object has no attribute 'replace'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/bot.py", line 886, in invoke
    yield from ctx.command.invoke(ctx)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py", line 923, in invoke
    yield from injected(*ctx.args, **ctx.kwargs)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py", line 71, in wrapped
    raise CommandInvokeError(e) from e
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'replace'
```
NOTE: This is an issue occurring on `V3/develop`. I'm in the process of trying to confirm if this is also occurring on the beta 13 release

#### How can we reproduce this issue?

1. Do `[p]streamset`
2. See error

[V3 Streams] issue with `[p]streamset`
# Command bugs

<!-- 
Did you find a bug with a command? Fill out the following:
-->

#### Command name

`streamset`

#### What cog is this command from?

`Streams`

#### What were you expecting to happen?

Should output help

#### What actually happened?

```py
Exception in command 'streamset'
Traceback (most recent call last):
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py", line 62, in wrapped
    ret = yield from coro(*args, **kwargs)
  File "/home/palm/.pyenv/versions/3.6.5/envs/redbot/lib/python3.6/site-packages/redbot/cogs/streams/streams.py", line 283, in streamset
    await ctx.send_help()
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/context.py", line 33, in send_help
    embeds = await self.bot.formatter.format_help_for(self, command)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py", line 233, in format_help_for
    emb = await self.format()
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py", line 156, in format
    if self.command.help:
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/commands.py", line 41, in help
    return inspect.cleandoc(translator(self.callback.__doc__))
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py", line 181, in __call__
    normalized_untranslated = _normalize(untranslated, True)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py", line 127, in _normalize
    string = string.replace('\\n\\n', '\n\n')
AttributeError: 'NoneType' object has no attribute 'replace'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/bot.py", line 886, in invoke
    yield from ctx.command.invoke(ctx)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py", line 923, in invoke
    yield from injected(*ctx.args, **ctx.kwargs)
  File "/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py", line 71, in wrapped
    raise CommandInvokeError(e) from e
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'replace'
```
NOTE: This is an issue occurring on `V3/develop`. I'm in the process of trying to confirm if this is also occurring on the beta 13 release

#### How can we reproduce this issue?

1. Do `[p]streamset`
2. See error


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redbot/core/i18n.py`
Content:
```
1 import re
2 from pathlib import Path
3 
4 from . import commands
5 
6 __all__ = ['get_locale', 'set_locale', 'reload_locales', 'cog_i18n',
7            'Translator']
8 
9 _current_locale = 'en_us'
10 
11 WAITING_FOR_MSGID = 1
12 IN_MSGID = 2
13 WAITING_FOR_MSGSTR = 3
14 IN_MSGSTR = 4
15 
16 MSGID = 'msgid "'
17 MSGSTR = 'msgstr "'
18 
19 _translators = []
20 
21 
22 def get_locale():
23     return _current_locale
24 
25 
26 def set_locale(locale):
27     global _current_locale
28     _current_locale = locale
29     reload_locales()
30 
31 
32 def reload_locales():
33     for translator in _translators:
34         translator.load_translations()
35 
36 
37 def _parse(translation_file):
38     """
39     Custom gettext parsing of translation files. All credit for this code goes
40     to ProgVal/Valentin Lorentz and the Limnoria project.
41 
42     https://github.com/ProgVal/Limnoria/blob/master/src/i18n.py
43 
44     :param translation_file:
45         An open file-like object containing translations.
46     :return:
47         A set of 2-tuples containing the original string and the translated version.
48     """
49     step = WAITING_FOR_MSGID
50     translations = set()
51     for line in translation_file:
52         line = line[0:-1]  # Remove the ending \n
53         line = line
54 
55         if line.startswith(MSGID):
56             # Don't check if step is WAITING_FOR_MSGID
57             untranslated = ''
58             translated = ''
59             data = line[len(MSGID):-1]
60             if len(data) == 0:  # Multiline mode
61                 step = IN_MSGID
62             else:
63                 untranslated += data
64                 step = WAITING_FOR_MSGSTR
65 
66         elif step is IN_MSGID and line.startswith('"') and \
67                 line.endswith('"'):
68             untranslated += line[1:-1]
69         elif step is IN_MSGID and untranslated == '':  # Empty MSGID
70             step = WAITING_FOR_MSGID
71         elif step is IN_MSGID:  # the MSGID is finished
72             step = WAITING_FOR_MSGSTR
73 
74         if step is WAITING_FOR_MSGSTR and line.startswith(MSGSTR):
75             data = line[len(MSGSTR):-1]
76             if len(data) == 0:  # Multiline mode
77                 step = IN_MSGSTR
78             else:
79                 translations |= {(untranslated, data)}
80                 step = WAITING_FOR_MSGID
81 
82         elif step is IN_MSGSTR and line.startswith('"') and \
83                 line.endswith('"'):
84             translated += line[1:-1]
85         elif step is IN_MSGSTR:  # the MSGSTR is finished
86             step = WAITING_FOR_MSGID
87             if translated == '':
88                 translated = untranslated
89             translations |= {(untranslated, translated)}
90     if step is IN_MSGSTR:
91         if translated == '':
92             translated = untranslated
93         translations |= {(untranslated, translated)}
94     return translations
95 
96 
97 def _normalize(string, remove_newline=False):
98     """
99     String normalization.
100 
101     All credit for this code goes
102     to ProgVal/Valentin Lorentz and the Limnoria project.
103 
104     https://github.com/ProgVal/Limnoria/blob/master/src/i18n.py
105 
106     :param string:
107     :param remove_newline:
108     :return:
109     """
110     def normalize_whitespace(s):
111         """Normalizes the whitespace in a string; \s+ becomes one space."""
112         if not s:
113             return str(s)  # not the same reference
114         starts_with_space = (s[0] in ' \n\t\r')
115         ends_with_space = (s[-1] in ' \n\t\r')
116         if remove_newline:
117             newline_re = re.compile('[\r\n]+')
118             s = ' '.join(filter(bool, newline_re.split(s)))
119         s = ' '.join(filter(bool, s.split('\t')))
120         s = ' '.join(filter(bool, s.split(' ')))
121         if starts_with_space:
122             s = ' ' + s
123         if ends_with_space:
124             s += ' '
125         return s
126 
127     string = string.replace('\\n\\n', '\n\n')
128     string = string.replace('\\n', ' ')
129     string = string.replace('\\"', '"')
130     string = string.replace("\'", "'")
131     string = normalize_whitespace(string)
132     string = string.strip('\n')
133     string = string.strip('\t')
134     return string
135 
136 
137 def get_locale_path(cog_folder: Path, extension: str) -> Path:
138     """
139     Gets the folder path containing localization files.
140 
141     :param Path cog_folder:
142         The cog folder that we want localizations for.
143     :param str extension:
144         Extension of localization files.
145     :return:
146         Path of possible localization file, it may not exist.
147     """
148     return cog_folder / 'locales' / "{}.{}".format(get_locale(), extension)
149 
150 
151 class Translator:
152     """Function to get translated strings at runtime."""
153 
154     def __init__(self, name, file_location):
155         """
156         Initializes an internationalization object.
157 
158         Parameters
159         ----------
160         name : str
161             Your cog name.
162         file_location : `str` or `pathlib.Path`
163             This should always be ``__file__`` otherwise your localizations
164             will not load.
165 
166         """
167         self.cog_folder = Path(file_location).resolve().parent
168         self.cog_name = name
169         self.translations = {}
170 
171         _translators.append(self)
172 
173         self.load_translations()
174 
175     def __call__(self, untranslated: str):
176         """Translate the given string.
177 
178         This will look for the string in the translator's :code:`.pot` file,
179         with respect to the current locale.
180         """
181         normalized_untranslated = _normalize(untranslated, True)
182         try:
183             return self.translations[normalized_untranslated]
184         except KeyError:
185             return untranslated
186 
187     def load_translations(self):
188         """
189         Loads the current translations.
190         """
191         self.translations = {}
192         translation_file = None
193         locale_path = get_locale_path(self.cog_folder, 'po')
194         try:
195 
196             try:
197                 translation_file = locale_path.open('ru', encoding='utf-8')
198             except ValueError:  # We are using Windows
199                 translation_file = locale_path.open('r', encoding='utf-8')
200             self._parse(translation_file)
201         except (IOError, FileNotFoundError):  # The translation is unavailable
202             pass
203         finally:
204             if translation_file is not None:
205                 translation_file.close()
206 
207     def _parse(self, translation_file):
208         self.translations = {}
209         for translation in _parse(translation_file):
210             self._add_translation(*translation)
211 
212     def _add_translation(self, untranslated, translated):
213         untranslated = _normalize(untranslated, True)
214         translated = _normalize(translated)
215         if translated:
216             self.translations.update({untranslated: translated})
217 
218 
219 def cog_i18n(translator: Translator):
220     """Get a class decorator to link the translator to this cog."""
221     def decorator(cog_class: type):
222         cog_class.__translator__ = translator
223         for name, attr in cog_class.__dict__.items():
224             if isinstance(attr, (commands.Group, commands.Command)):
225                 attr.translator = translator
226                 setattr(cog_class, name, attr)
227         return cog_class
228     return decorator
229
```
--- 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/redbot/core/i18n.py b/redbot/core/i18n.py
--- a/redbot/core/i18n.py
+++ b/redbot/core/i18n.py
@@ -124,6 +124,9 @@
             s += ' '
         return s
 
+    if string is None:
+        return ""
+    
     string = string.replace('\\n\\n', '\n\n')
     string = string.replace('\\n', ' ')
     string = string.replace('\\"', '"')

{"golden_diff": "diff --git a/redbot/core/i18n.py b/redbot/core/i18n.py\n--- a/redbot/core/i18n.py\n+++ b/redbot/core/i18n.py\n@@ -124,6 +124,9 @@\n             s += ' '\n         return s\n \n+    if string is None:\n+        return \"\"\n+    \n     string = string.replace('\\\\n\\\\n', '\\n\\n')\n     string = string.replace('\\\\n', ' ')\n     string = string.replace('\\\\\"', '\"')\n", "issue": "[V3 Streams] issue with `[p]streamset`\n# Command bugs\r\n\r\n<!-- \r\nDid you find a bug with a command? Fill out the following:\r\n-->\r\n\r\n#### Command name\r\n\r\n`streamset`\r\n\r\n#### What cog is this command from?\r\n\r\n`Streams`\r\n\r\n#### What were you expecting to happen?\r\n\r\nShould output help\r\n\r\n#### What actually happened?\r\n\r\n```py\r\nException in command 'streamset'\r\nTraceback (most recent call last):\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py\", line 62, in wrapped\r\n    ret = yield from coro(*args, **kwargs)\r\n  File \"/home/palm/.pyenv/versions/3.6.5/envs/redbot/lib/python3.6/site-packages/redbot/cogs/streams/streams.py\", line 283, in streamset\r\n    await ctx.send_help()\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/context.py\", line 33, in send_help\r\n    embeds = await self.bot.formatter.format_help_for(self, command)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py\", line 233, in format_help_for\r\n    emb = await self.format()\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py\", line 156, in format\r\n    if self.command.help:\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/commands.py\", line 41, in help\r\n    return inspect.cleandoc(translator(self.callback.__doc__))\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py\", line 181, in __call__\r\n    normalized_untranslated = _normalize(untranslated, True)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py\", line 127, in _normalize\r\n    string = string.replace('\\\\n\\\\n', '\\n\\n')\r\nAttributeError: 'NoneType' object has no attribute 'replace'\r\n\r\nThe above exception was the direct cause of the following exception:\r\n\r\nTraceback (most recent call last):\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/bot.py\", line 886, in invoke\r\n    yield from ctx.command.invoke(ctx)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py\", line 923, in invoke\r\n    yield from injected(*ctx.args, **ctx.kwargs)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py\", line 71, in wrapped\r\n    raise CommandInvokeError(e) from e\r\ndiscord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'replace'\r\n```\r\nNOTE: This is an issue occurring on `V3/develop`. I'm in the process of trying to confirm if this is also occurring on the beta 13 release\r\n\r\n#### How can we reproduce this issue?\r\n\r\n1. Do `[p]streamset`\r\n2. See error\r\n\n[V3 Streams] issue with `[p]streamset`\n# Command bugs\r\n\r\n<!-- \r\nDid you find a bug with a command? Fill out the following:\r\n-->\r\n\r\n#### Command name\r\n\r\n`streamset`\r\n\r\n#### What cog is this command from?\r\n\r\n`Streams`\r\n\r\n#### What were you expecting to happen?\r\n\r\nShould output help\r\n\r\n#### What actually happened?\r\n\r\n```py\r\nException in command 'streamset'\r\nTraceback (most recent call last):\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py\", line 62, in wrapped\r\n    ret = yield from coro(*args, **kwargs)\r\n  File \"/home/palm/.pyenv/versions/3.6.5/envs/redbot/lib/python3.6/site-packages/redbot/cogs/streams/streams.py\", line 283, in streamset\r\n    await ctx.send_help()\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/context.py\", line 33, in send_help\r\n    embeds = await self.bot.formatter.format_help_for(self, command)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py\", line 233, in format_help_for\r\n    emb = await self.format()\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/help_formatter.py\", line 156, in format\r\n    if self.command.help:\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/commands/commands.py\", line 41, in help\r\n    return inspect.cleandoc(translator(self.callback.__doc__))\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py\", line 181, in __call__\r\n    normalized_untranslated = _normalize(untranslated, True)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/redbot/core/i18n.py\", line 127, in _normalize\r\n    string = string.replace('\\\\n\\\\n', '\\n\\n')\r\nAttributeError: 'NoneType' object has no attribute 'replace'\r\n\r\nThe above exception was the direct cause of the following exception:\r\n\r\nTraceback (most recent call last):\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/bot.py\", line 886, in invoke\r\n    yield from ctx.command.invoke(ctx)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py\", line 923, in invoke\r\n    yield from injected(*ctx.args, **ctx.kwargs)\r\n  File \"/home/palm/.pyenv/versions/redbot/lib/python3.6/site-packages/discord/ext/commands/core.py\", line 71, in wrapped\r\n    raise CommandInvokeError(e) from e\r\ndiscord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'replace'\r\n```\r\nNOTE: This is an issue occurring on `V3/develop`. I'm in the process of trying to confirm if this is also occurring on the beta 13 release\r\n\r\n#### How can we reproduce this issue?\r\n\r\n1. Do `[p]streamset`\r\n2. See error\r\n\n", "before_files": [{"content": "import re\nfrom pathlib import Path\n\nfrom . import commands\n\n__all__ = ['get_locale', 'set_locale', 'reload_locales', 'cog_i18n',\n           'Translator']\n\n_current_locale = 'en_us'\n\nWAITING_FOR_MSGID = 1\nIN_MSGID = 2\nWAITING_FOR_MSGSTR = 3\nIN_MSGSTR = 4\n\nMSGID = 'msgid \"'\nMSGSTR = 'msgstr \"'\n\n_translators = []\n\n\ndef get_locale():\n    return _current_locale\n\n\ndef set_locale(locale):\n    global _current_locale\n    _current_locale = locale\n    reload_locales()\n\n\ndef reload_locales():\n    for translator in _translators:\n        translator.load_translations()\n\n\ndef _parse(translation_file):\n    \"\"\"\n    Custom gettext parsing of translation files. All credit for this code goes\n    to ProgVal/Valentin Lorentz and the Limnoria project.\n\n    https://github.com/ProgVal/Limnoria/blob/master/src/i18n.py\n\n    :param translation_file:\n        An open file-like object containing translations.\n    :return:\n        A set of 2-tuples containing the original string and the translated version.\n    \"\"\"\n    step = WAITING_FOR_MSGID\n    translations = set()\n    for line in translation_file:\n        line = line[0:-1]  # Remove the ending \\n\n        line = line\n\n        if line.startswith(MSGID):\n            # Don't check if step is WAITING_FOR_MSGID\n            untranslated = ''\n            translated = ''\n            data = line[len(MSGID):-1]\n            if len(data) == 0:  # Multiline mode\n                step = IN_MSGID\n            else:\n                untranslated += data\n                step = WAITING_FOR_MSGSTR\n\n        elif step is IN_MSGID and line.startswith('\"') and \\\n                line.endswith('\"'):\n            untranslated += line[1:-1]\n        elif step is IN_MSGID and untranslated == '':  # Empty MSGID\n            step = WAITING_FOR_MSGID\n        elif step is IN_MSGID:  # the MSGID is finished\n            step = WAITING_FOR_MSGSTR\n\n        if step is WAITING_FOR_MSGSTR and line.startswith(MSGSTR):\n            data = line[len(MSGSTR):-1]\n            if len(data) == 0:  # Multiline mode\n                step = IN_MSGSTR\n            else:\n                translations |= {(untranslated, data)}\n                step = WAITING_FOR_MSGID\n\n        elif step is IN_MSGSTR and line.startswith('\"') and \\\n                line.endswith('\"'):\n            translated += line[1:-1]\n        elif step is IN_MSGSTR:  # the MSGSTR is finished\n            step = WAITING_FOR_MSGID\n            if translated == '':\n                translated = untranslated\n            translations |= {(untranslated, translated)}\n    if step is IN_MSGSTR:\n        if translated == '':\n            translated = untranslated\n        translations |= {(untranslated, translated)}\n    return translations\n\n\ndef _normalize(string, remove_newline=False):\n    \"\"\"\n    String normalization.\n\n    All credit for this code goes\n    to ProgVal/Valentin Lorentz and the Limnoria project.\n\n    https://github.com/ProgVal/Limnoria/blob/master/src/i18n.py\n\n    :param string:\n    :param remove_newline:\n    :return:\n    \"\"\"\n    def normalize_whitespace(s):\n        \"\"\"Normalizes the whitespace in a string; \\s+ becomes one space.\"\"\"\n        if not s:\n            return str(s)  # not the same reference\n        starts_with_space = (s[0] in ' \\n\\t\\r')\n        ends_with_space = (s[-1] in ' \\n\\t\\r')\n        if remove_newline:\n            newline_re = re.compile('[\\r\\n]+')\n            s = ' '.join(filter(bool, newline_re.split(s)))\n        s = ' '.join(filter(bool, s.split('\\t')))\n        s = ' '.join(filter(bool, s.split(' ')))\n        if starts_with_space:\n            s = ' ' + s\n        if ends_with_space:\n            s += ' '\n        return s\n\n    string = string.replace('\\\\n\\\\n', '\\n\\n')\n    string = string.replace('\\\\n', ' ')\n    string = string.replace('\\\\\"', '\"')\n    string = string.replace(\"\\'\", \"'\")\n    string = normalize_whitespace(string)\n    string = string.strip('\\n')\n    string = string.strip('\\t')\n    return string\n\n\ndef get_locale_path(cog_folder: Path, extension: str) -> Path:\n    \"\"\"\n    Gets the folder path containing localization files.\n\n    :param Path cog_folder:\n        The cog folder that we want localizations for.\n    :param str extension:\n        Extension of localization files.\n    :return:\n        Path of possible localization file, it may not exist.\n    \"\"\"\n    return cog_folder / 'locales' / \"{}.{}\".format(get_locale(), extension)\n\n\nclass Translator:\n    \"\"\"Function to get translated strings at runtime.\"\"\"\n\n    def __init__(self, name, file_location):\n        \"\"\"\n        Initializes an internationalization object.\n\n        Parameters\n        ----------\n        name : str\n            Your cog name.\n        file_location : `str` or `pathlib.Path`\n            This should always be ``__file__`` otherwise your localizations\n            will not load.\n\n        \"\"\"\n        self.cog_folder = Path(file_location).resolve().parent\n        self.cog_name = name\n        self.translations = {}\n\n        _translators.append(self)\n\n        self.load_translations()\n\n    def __call__(self, untranslated: str):\n        \"\"\"Translate the given string.\n\n        This will look for the string in the translator's :code:`.pot` file,\n        with respect to the current locale.\n        \"\"\"\n        normalized_untranslated = _normalize(untranslated, True)\n        try:\n            return self.translations[normalized_untranslated]\n        except KeyError:\n            return untranslated\n\n    def load_translations(self):\n        \"\"\"\n        Loads the current translations.\n        \"\"\"\n        self.translations = {}\n        translation_file = None\n        locale_path = get_locale_path(self.cog_folder, 'po')\n        try:\n\n            try:\n                translation_file = locale_path.open('ru', encoding='utf-8')\n            except ValueError:  # We are using Windows\n                translation_file = locale_path.open('r', encoding='utf-8')\n            self._parse(translation_file)\n        except (IOError, FileNotFoundError):  # The translation is unavailable\n            pass\n        finally:\n            if translation_file is not None:\n                translation_file.close()\n\n    def _parse(self, translation_file):\n        self.translations = {}\n        for translation in _parse(translation_file):\n            self._add_translation(*translation)\n\n    def _add_translation(self, untranslated, translated):\n        untranslated = _normalize(untranslated, True)\n        translated = _normalize(translated)\n        if translated:\n            self.translations.update({untranslated: translated})\n\n\ndef cog_i18n(translator: Translator):\n    \"\"\"Get a class decorator to link the translator to this cog.\"\"\"\n    def decorator(cog_class: type):\n        cog_class.__translator__ = translator\n        for name, attr in cog_class.__dict__.items():\n            if isinstance(attr, (commands.Group, commands.Command)):\n                attr.translator = translator\n                setattr(cog_class, name, attr)\n        return cog_class\n    return decorator\n", "path": "redbot/core/i18n.py"}], "after_files": [{"content": "import re\nfrom pathlib import Path\n\nfrom . import commands\n\n__all__ = ['get_locale', 'set_locale', 'reload_locales', 'cog_i18n',\n           'Translator']\n\n_current_locale = 'en_us'\n\nWAITING_FOR_MSGID = 1\nIN_MSGID = 2\nWAITING_FOR_MSGSTR = 3\nIN_MSGSTR = 4\n\nMSGID = 'msgid \"'\nMSGSTR = 'msgstr \"'\n\n_translators = []\n\n\ndef get_locale():\n    return _current_locale\n\n\ndef set_locale(locale):\n    global _current_locale\n    _current_locale = locale\n    reload_locales()\n\n\ndef reload_locales():\n    for translator in _translators:\n        translator.load_translations()\n\n\ndef _parse(translation_file):\n    \"\"\"\n    Custom gettext parsing of translation files. All credit for this code goes\n    to ProgVal/Valentin Lorentz and the Limnoria project.\n\n    https://github.com/ProgVal/Limnoria/blob/master/src/i18n.py\n\n    :param translation_file:\n        An open file-like object containing translations.\n    :return:\n        A set of 2-tuples containing the original string and the translated version.\n    \"\"\"\n    step = WAITING_FOR_MSGID\n    translations = set()\n    for line in translation_file:\n        line = line[0:-1]  # Remove the ending \\n\n        line = line\n\n        if line.startswith(MSGID):\n            # Don't check if step is WAITING_FOR_MSGID\n            untranslated = ''\n            translated = ''\n            data = line[len(MSGID):-1]\n            if len(data) == 0:  # Multiline mode\n                step = IN_MSGID\n            else:\n                untranslated += data\n                step = WAITING_FOR_MSGSTR\n\n        elif step is IN_MSGID and line.startswith('\"') and \\\n                line.endswith('\"'):\n            untranslated += line[1:-1]\n        elif step is IN_MSGID and untranslated == '':  # Empty MSGID\n            step = WAITING_FOR_MSGID\n        elif step is IN_MSGID:  # the MSGID is finished\n            step = WAITING_FOR_MSGSTR\n\n        if step is WAITING_FOR_MSGSTR and line.startswith(MSGSTR):\n            data = line[len(MSGSTR):-1]\n            if len(data) == 0:  # Multiline mode\n                step = IN_MSGSTR\n            else:\n                translations |= {(untranslated, data)}\n                step = WAITING_FOR_MSGID\n\n        elif step is IN_MSGSTR and line.startswith('\"') and \\\n                line.endswith('\"'):\n            translated += line[1:-1]\n        elif step is IN_MSGSTR:  # the MSGSTR is finished\n            step = WAITING_FOR_MSGID\n            if translated == '':\n                translated = untranslated\n            translations |= {(untranslated, translated)}\n    if step is IN_MSGSTR:\n        if translated == '':\n            translated = untranslated\n        translations |= {(untranslated, translated)}\n    return translations\n\n\ndef _normalize(string, remove_newline=False):\n    \"\"\"\n    String normalization.\n\n    All credit for this code goes\n    to ProgVal/Valentin Lorentz and the Limnoria project.\n\n    https://github.com/ProgVal/Limnoria/blob/master/src/i18n.py\n\n    :param string:\n    :param remove_newline:\n    :return:\n    \"\"\"\n    def normalize_whitespace(s):\n        \"\"\"Normalizes the whitespace in a string; \\s+ becomes one space.\"\"\"\n        if not s:\n            return str(s)  # not the same reference\n        starts_with_space = (s[0] in ' \\n\\t\\r')\n        ends_with_space = (s[-1] in ' \\n\\t\\r')\n        if remove_newline:\n            newline_re = re.compile('[\\r\\n]+')\n            s = ' '.join(filter(bool, newline_re.split(s)))\n        s = ' '.join(filter(bool, s.split('\\t')))\n        s = ' '.join(filter(bool, s.split(' ')))\n        if starts_with_space:\n            s = ' ' + s\n        if ends_with_space:\n            s += ' '\n        return s\n\n    if string is None:\n        return \"\"\n    \n    string = string.replace('\\\\n\\\\n', '\\n\\n')\n    string = string.replace('\\\\n', ' ')\n    string = string.replace('\\\\\"', '\"')\n    string = string.replace(\"\\'\", \"'\")\n    string = normalize_whitespace(string)\n    string = string.strip('\\n')\n    string = string.strip('\\t')\n    return string\n\n\ndef get_locale_path(cog_folder: Path, extension: str) -> Path:\n    \"\"\"\n    Gets the folder path containing localization files.\n\n    :param Path cog_folder:\n        The cog folder that we want localizations for.\n    :param str extension:\n        Extension of localization files.\n    :return:\n        Path of possible localization file, it may not exist.\n    \"\"\"\n    return cog_folder / 'locales' / \"{}.{}\".format(get_locale(), extension)\n\n\nclass Translator:\n    \"\"\"Function to get translated strings at runtime.\"\"\"\n\n    def __init__(self, name, file_location):\n        \"\"\"\n        Initializes an internationalization object.\n\n        Parameters\n        ----------\n        name : str\n            Your cog name.\n        file_location : `str` or `pathlib.Path`\n            This should always be ``__file__`` otherwise your localizations\n            will not load.\n\n        \"\"\"\n        self.cog_folder = Path(file_location).resolve().parent\n        self.cog_name = name\n        self.translations = {}\n\n        _translators.append(self)\n\n        self.load_translations()\n\n    def __call__(self, untranslated: str):\n        \"\"\"Translate the given string.\n\n        This will look for the string in the translator's :code:`.pot` file,\n        with respect to the current locale.\n        \"\"\"\n        normalized_untranslated = _normalize(untranslated, True)\n        try:\n            return self.translations[normalized_untranslated]\n        except KeyError:\n            return untranslated\n\n    def load_translations(self):\n        \"\"\"\n        Loads the current translations.\n        \"\"\"\n        self.translations = {}\n        translation_file = None\n        locale_path = get_locale_path(self.cog_folder, 'po')\n        try:\n\n            try:\n                translation_file = locale_path.open('ru', encoding='utf-8')\n            except ValueError:  # We are using Windows\n                translation_file = locale_path.open('r', encoding='utf-8')\n            self._parse(translation_file)\n        except (IOError, FileNotFoundError):  # The translation is unavailable\n            pass\n        finally:\n            if translation_file is not None:\n                translation_file.close()\n\n    def _parse(self, translation_file):\n        self.translations = {}\n        for translation in _parse(translation_file):\n            self._add_translation(*translation)\n\n    def _add_translation(self, untranslated, translated):\n        untranslated = _normalize(untranslated, True)\n        translated = _normalize(translated)\n        if translated:\n            self.translations.update({untranslated: translated})\n\n\ndef cog_i18n(translator: Translator):\n    \"\"\"Get a class decorator to link the translator to this cog.\"\"\"\n    def decorator(cog_class: type):\n        cog_class.__translator__ = translator\n        for name, attr in cog_class.__dict__.items():\n            if isinstance(attr, (commands.Group, commands.Command)):\n                attr.translator = translator\n                setattr(cog_class, name, attr)\n        return cog_class\n    return decorator\n", "path": "redbot/core/i18n.py"}]}