We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
translate: mangle is a bit useless due to rate limit
<!-- Before reporting a bug, please search both open *and closed* issues to
see if it has already been reported. If you can, try to reproduce the problem
on an unmodified copy of the `master` branch first, as sometimes bugs are found
and fixed without a report. If the problem is unreported and persists in
`master`, please help us fix it quickly by filling out as much of this
information as you can. Thanks! -->

### Description
Mangle ends up just saying 'None' a lot after using it more than once or twice within a minute or so due to what seems to be a rate limit

### Reproduction steps
1. Start a basic, stock sopel up
2. Use mangle a few times within a minute or so
3. It returns None

### Expected behavior
It either back off and respond when it is successful or error nicely. @rate (I think) might be useful here

### Logs
```
If applicable, add logs to help us figure out what's happening. Raw logs are
super helpful! Logs are usually found in ~/.sopel/logs, depending on your
configuration.
```

### Environment
- Sopel `.version`: lastest on PyPi
- Sopel installed via: pip 
- Python version: 3.7
- Operating system: Debian Buster
- IRCd `/version`: freenode's
- Relevant plugins: mangle

### Notes
https://cloud.google.com/translate/quotas


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sopel/modules/translate.py`
Content:
```
1 """
2 translate.py - Sopel Translation Plugin
3 Copyright 2008, Sean B. Palmer, inamidst.com
4 Copyright 2013-2014, Elad Alfassa <elad@fedoraproject.org>
5 Licensed under the Eiffel Forum License 2.
6 
7 https://sopel.chat
8 """
9 from __future__ import generator_stop
10 
11 import json
12 import logging
13 import random
14 
15 import requests
16 
17 from sopel import plugin, tools
18 from sopel.tools import web
19 
20 
21 LOGGER = logging.getLogger(__name__)
22 PLUGIN_OUTPUT_PREFIX = '[translate] '
23 
24 
25 def setup(bot):
26     if 'mangle_lines' not in bot.memory:
27         bot.memory['mangle_lines'] = tools.SopelIdentifierMemory()
28 
29 
30 def shutdown(bot):
31     try:
32         del bot.memory['mangle_lines']
33     except KeyError:
34         pass
35 
36 
37 def translate(text, in_lang='auto', out_lang='en'):
38     raw = False
39     if str(out_lang).endswith('-raw'):
40         out_lang = out_lang[:-4]
41         raw = True
42 
43     headers = {
44         'User-Agent': 'Mozilla/5.0' +
45         '(X11; U; Linux i686)' +
46         'Gecko/20071127 Firefox/2.0.0.11'
47     }
48 
49     query = {
50         "client": "gtx",
51         "sl": in_lang,
52         "tl": out_lang,
53         "dt": "t",
54         "q": text,
55     }
56     url = "https://translate.googleapis.com/translate_a/single"
57     result = requests.get(url, params=query, timeout=40, headers=headers).text
58 
59     if result == '[,,""]':
60         return None, in_lang
61 
62     while ',,' in result:
63         result = result.replace(',,', ',null,')
64         result = result.replace('[,', '[null,')
65 
66     try:
67         data = json.loads(result)
68     except ValueError:
69         LOGGER.error(
70             'Error parsing JSON response from translate API (%s to %s: "%s")',
71             in_lang, out_lang, text)
72         return None, None
73 
74     if raw:
75         return str(data), 'en-raw'
76 
77     try:
78         language = data[2]  # -2][0][0]
79     except IndexError:
80         language = '?'
81 
82     return ''.join(x[0] for x in data[0]), language
83 
84 
85 @plugin.rule(r'$nickname[,:]\s+(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?["“](.+?)["”]\? *$')
86 @plugin.example('$nickname: "mon chien"? or $nickname: fr "mon chien"?')
87 @plugin.priority('low')
88 @plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
89 def tr(bot, trigger):
90     """Translates a phrase, with an optional language hint."""
91     in_lang, out_lang, phrase = trigger.groups()
92 
93     if (len(phrase) > 350) and (not trigger.admin):
94         bot.reply('Phrase must be under 350 characters.')
95         return
96 
97     if phrase.strip() == '':
98         bot.reply('You need to specify a string for me to translate!')
99         return
100 
101     in_lang = in_lang or 'auto'
102     out_lang = out_lang or 'en'
103 
104     if in_lang == out_lang:
105         bot.reply('Language guessing failed, so try suggesting one!')
106         return
107 
108     try:
109         msg, in_lang = translate(phrase, in_lang, out_lang)
110     except requests.Timeout:
111         bot.reply("Translation service unavailable (timeout).")
112         LOGGER.error(
113             'Translate API error (%s to %s: "%s"): timeout.',
114             in_lang, out_lang, phrase)
115         return
116     except requests.RequestException as http_error:
117         bot.reply("Translation request failed.")
118         LOGGER.exception(
119             'Translate API error (%s to %s: "%s"): %s.',
120             in_lang, out_lang, phrase, http_error)
121         return
122 
123     if not in_lang:
124         bot.reply("Translation failed, probably because of a rate-limit.")
125         return
126 
127     if not msg:
128         bot.reply(
129             'The %s to %s translation failed; are you sure you specified '
130             'valid language abbreviations?' % (in_lang, out_lang)
131         )
132         return
133 
134     msg = web.decode(msg)
135     msg = '"%s" (%s to %s, translate.google.com)' % (msg, in_lang, out_lang)
136     bot.say(msg)
137 
138 
139 @plugin.command('translate', 'tr')
140 @plugin.example('.tr :en :fr my dog',
141                 '"mon chien" (en to fr, translate.google.com)',
142                 online=True, vcr=True)
143 @plugin.example('.tr מחשב',
144                 '"computer" (iw to en, translate.google.com)',
145                 online=True, vcr=True)
146 @plugin.example('.tr mon chien',
147                 '"my dog" (fr to en, translate.google.com)',
148                 online=True, vcr=True)
149 @plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
150 def tr2(bot, trigger):
151     """Translates a phrase, with an optional language hint."""
152     command = trigger.group(2)
153 
154     if not command:
155         bot.reply('You did not give me anything to translate.')
156         return
157 
158     def langcode(p):
159         return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()
160 
161     args = ['auto', 'en']
162 
163     for i in range(2):
164         if ' ' not in command:
165             break
166         prefix, cmd = command.split(' ', 1)
167         if langcode(prefix):
168             args[i] = prefix[1:]
169             command = cmd
170 
171     phrase = command
172     if (len(phrase) > 350) and (not trigger.admin):
173         bot.reply('Phrase must be under 350 characters.')
174         return
175 
176     if phrase.strip() == '':
177         bot.reply('You need to specify a string for me to translate!')
178         return
179 
180     src, dest = args
181 
182     if src == dest:
183         bot.reply('Language guessing failed, so try suggesting one!')
184         return
185 
186     try:
187         msg, src = translate(phrase, src, dest)
188     except requests.Timeout:
189         bot.reply("Translation service unavailable (timeout).")
190         LOGGER.error(
191             'Translate API error (%s to %s: "%s"): timeout.',
192             src, dest, phrase)
193         return
194     except requests.RequestException as http_error:
195         bot.reply("Translation request failed.")
196         LOGGER.exception(
197             'Translate API error (%s to %s: "%s"): %s.',
198             src, dest, phrase, http_error)
199         return
200 
201     if not src:
202         return bot.say("Translation failed, probably because of a rate-limit.")
203 
204     if not msg:
205         bot.reply(
206             'The %s to %s translation failed; '
207             'are you sure you specified valid language abbreviations?'
208             % (src, dest))
209         return
210 
211     msg = web.decode(msg)  # msg.replace('&#39;', "'")
212     msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest)
213 
214     bot.say(msg)
215 
216 
217 def get_random_lang(long_list, short_list):
218     random_index = random.randint(0, len(long_list) - 1)
219     random_lang = long_list[random_index]
220     if random_lang not in short_list:
221         short_list.append(random_lang)
222     else:
223         return get_random_lang(long_list, short_list)
224     return short_list
225 
226 
227 @plugin.command('mangle', 'mangle2')
228 @plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)
229 def mangle(bot, trigger):
230     """Repeatedly translate the input until it makes absolutely no sense."""
231     long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']
232     lang_list = []
233     for __ in range(0, 8):
234         lang_list = get_random_lang(long_lang_list, lang_list)
235     random.shuffle(lang_list)
236     if trigger.group(2) is None:
237         try:
238             phrase = (bot.memory['mangle_lines'][trigger.sender], '')
239         except KeyError:
240             bot.reply("What do you want me to mangle?")
241             return
242     else:
243         phrase = (trigger.group(2).strip(), '')
244     if phrase[0] == '':
245         bot.reply("What do you want me to mangle?")
246         return
247     for lang in lang_list:
248         backup = phrase
249         try:
250             phrase = translate(phrase[0], 'en', lang)
251         except Exception:  # TODO: Be specific
252             phrase = False
253         if not phrase:
254             phrase = backup
255             break
256 
257         try:
258             phrase = translate(phrase[0], lang, 'en')
259         except Exception:  # TODO: Be specific
260             phrase = backup
261             continue
262 
263         if not phrase:
264             phrase = backup
265             break
266 
267     bot.say(phrase[0])
268 
269 
270 @plugin.rule('(.*)')
271 @plugin.priority('low')
272 @plugin.unblockable
273 def collect_mangle_lines(bot, trigger):
274     bot.memory['mangle_lines'][trigger.sender] = "%s said '%s'" % (
275         trigger.nick,
276         trigger.group(0).strip(),
277     )
278
```
--- 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/sopel/modules/translate.py b/sopel/modules/translate.py
--- a/sopel/modules/translate.py
+++ b/sopel/modules/translate.py
@@ -230,9 +230,11 @@
     """Repeatedly translate the input until it makes absolutely no sense."""
     long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']
     lang_list = []
+
     for __ in range(0, 8):
         lang_list = get_random_lang(long_lang_list, lang_list)
     random.shuffle(lang_list)
+
     if trigger.group(2) is None:
         try:
             phrase = (bot.memory['mangle_lines'][trigger.sender], '')
@@ -241,9 +243,11 @@
             return
     else:
         phrase = (trigger.group(2).strip(), '')
+
     if phrase[0] == '':
         bot.reply("What do you want me to mangle?")
         return
+
     for lang in lang_list:
         backup = phrase
         try:
@@ -264,6 +268,12 @@
             phrase = backup
             break
 
+    if phrase[0] is None:
+        # translate() returns (None, None) if an error happens,
+        # usually because the bot has exceeded a rate limit
+        bot.reply("Translation rate limit reached. Try again later.")
+        return
+
     bot.say(phrase[0])

{"golden_diff": "diff --git a/sopel/modules/translate.py b/sopel/modules/translate.py\n--- a/sopel/modules/translate.py\n+++ b/sopel/modules/translate.py\n@@ -230,9 +230,11 @@\n     \"\"\"Repeatedly translate the input until it makes absolutely no sense.\"\"\"\n     long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']\n     lang_list = []\n+\n     for __ in range(0, 8):\n         lang_list = get_random_lang(long_lang_list, lang_list)\n     random.shuffle(lang_list)\n+\n     if trigger.group(2) is None:\n         try:\n             phrase = (bot.memory['mangle_lines'][trigger.sender], '')\n@@ -241,9 +243,11 @@\n             return\n     else:\n         phrase = (trigger.group(2).strip(), '')\n+\n     if phrase[0] == '':\n         bot.reply(\"What do you want me to mangle?\")\n         return\n+\n     for lang in lang_list:\n         backup = phrase\n         try:\n@@ -264,6 +268,12 @@\n             phrase = backup\n             break\n \n+    if phrase[0] is None:\n+        # translate() returns (None, None) if an error happens,\n+        # usually because the bot has exceeded a rate limit\n+        bot.reply(\"Translation rate limit reached. Try again later.\")\n+        return\n+\n     bot.say(phrase[0])\n", "issue": "translate: mangle is a bit useless due to rate limit\n<!-- Before reporting a bug, please search both open *and closed* issues to\r\nsee if it has already been reported. If you can, try to reproduce the problem\r\non an unmodified copy of the `master` branch first, as sometimes bugs are found\r\nand fixed without a report. If the problem is unreported and persists in\r\n`master`, please help us fix it quickly by filling out as much of this\r\ninformation as you can. Thanks! -->\r\n\r\n### Description\r\nMangle ends up just saying 'None' a lot after using it more than once or twice within a minute or so due to what seems to be a rate limit\r\n\r\n### Reproduction steps\r\n1. Start a basic, stock sopel up\r\n2. Use mangle a few times within a minute or so\r\n3. It returns None\r\n\r\n### Expected behavior\r\nIt either back off and respond when it is successful or error nicely. @rate (I think) might be useful here\r\n\r\n### Logs\r\n```\r\nIf applicable, add logs to help us figure out what's happening. Raw logs are\r\nsuper helpful! Logs are usually found in ~/.sopel/logs, depending on your\r\nconfiguration.\r\n```\r\n\r\n### Environment\r\n- Sopel `.version`: lastest on PyPi\r\n- Sopel installed via: pip \r\n- Python version: 3.7\r\n- Operating system: Debian Buster\r\n- IRCd `/version`: freenode's\r\n- Relevant plugins: mangle\r\n\r\n### Notes\r\nhttps://cloud.google.com/translate/quotas\r\n\n", "before_files": [{"content": "\"\"\"\ntranslate.py - Sopel Translation Plugin\nCopyright 2008, Sean B. Palmer, inamidst.com\nCopyright 2013-2014, Elad Alfassa <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import generator_stop\n\nimport json\nimport logging\nimport random\n\nimport requests\n\nfrom sopel import plugin, tools\nfrom sopel.tools import web\n\n\nLOGGER = logging.getLogger(__name__)\nPLUGIN_OUTPUT_PREFIX = '[translate] '\n\n\ndef setup(bot):\n    if 'mangle_lines' not in bot.memory:\n        bot.memory['mangle_lines'] = tools.SopelIdentifierMemory()\n\n\ndef shutdown(bot):\n    try:\n        del bot.memory['mangle_lines']\n    except KeyError:\n        pass\n\n\ndef translate(text, in_lang='auto', out_lang='en'):\n    raw = False\n    if str(out_lang).endswith('-raw'):\n        out_lang = out_lang[:-4]\n        raw = True\n\n    headers = {\n        'User-Agent': 'Mozilla/5.0' +\n        '(X11; U; Linux i686)' +\n        'Gecko/20071127 Firefox/2.0.0.11'\n    }\n\n    query = {\n        \"client\": \"gtx\",\n        \"sl\": in_lang,\n        \"tl\": out_lang,\n        \"dt\": \"t\",\n        \"q\": text,\n    }\n    url = \"https://translate.googleapis.com/translate_a/single\"\n    result = requests.get(url, params=query, timeout=40, headers=headers).text\n\n    if result == '[,,\"\"]':\n        return None, in_lang\n\n    while ',,' in result:\n        result = result.replace(',,', ',null,')\n        result = result.replace('[,', '[null,')\n\n    try:\n        data = json.loads(result)\n    except ValueError:\n        LOGGER.error(\n            'Error parsing JSON response from translate API (%s to %s: \"%s\")',\n            in_lang, out_lang, text)\n        return None, None\n\n    if raw:\n        return str(data), 'en-raw'\n\n    try:\n        language = data[2]  # -2][0][0]\n    except IndexError:\n        language = '?'\n\n    return ''.join(x[0] for x in data[0]), language\n\n\n@plugin.rule(r'$nickname[,:]\\s+(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?[\"\u201c](.+?)[\"\u201d]\\? *$')\n@plugin.example('$nickname: \"mon chien\"? or $nickname: fr \"mon chien\"?')\n@plugin.priority('low')\n@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)\ndef tr(bot, trigger):\n    \"\"\"Translates a phrase, with an optional language hint.\"\"\"\n    in_lang, out_lang, phrase = trigger.groups()\n\n    if (len(phrase) > 350) and (not trigger.admin):\n        bot.reply('Phrase must be under 350 characters.')\n        return\n\n    if phrase.strip() == '':\n        bot.reply('You need to specify a string for me to translate!')\n        return\n\n    in_lang = in_lang or 'auto'\n    out_lang = out_lang or 'en'\n\n    if in_lang == out_lang:\n        bot.reply('Language guessing failed, so try suggesting one!')\n        return\n\n    try:\n        msg, in_lang = translate(phrase, in_lang, out_lang)\n    except requests.Timeout:\n        bot.reply(\"Translation service unavailable (timeout).\")\n        LOGGER.error(\n            'Translate API error (%s to %s: \"%s\"): timeout.',\n            in_lang, out_lang, phrase)\n        return\n    except requests.RequestException as http_error:\n        bot.reply(\"Translation request failed.\")\n        LOGGER.exception(\n            'Translate API error (%s to %s: \"%s\"): %s.',\n            in_lang, out_lang, phrase, http_error)\n        return\n\n    if not in_lang:\n        bot.reply(\"Translation failed, probably because of a rate-limit.\")\n        return\n\n    if not msg:\n        bot.reply(\n            'The %s to %s translation failed; are you sure you specified '\n            'valid language abbreviations?' % (in_lang, out_lang)\n        )\n        return\n\n    msg = web.decode(msg)\n    msg = '\"%s\" (%s to %s, translate.google.com)' % (msg, in_lang, out_lang)\n    bot.say(msg)\n\n\n@plugin.command('translate', 'tr')\n@plugin.example('.tr :en :fr my dog',\n                '\"mon chien\" (en to fr, translate.google.com)',\n                online=True, vcr=True)\n@plugin.example('.tr \u05de\u05d7\u05e9\u05d1',\n                '\"computer\" (iw to en, translate.google.com)',\n                online=True, vcr=True)\n@plugin.example('.tr mon chien',\n                '\"my dog\" (fr to en, translate.google.com)',\n                online=True, vcr=True)\n@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)\ndef tr2(bot, trigger):\n    \"\"\"Translates a phrase, with an optional language hint.\"\"\"\n    command = trigger.group(2)\n\n    if not command:\n        bot.reply('You did not give me anything to translate.')\n        return\n\n    def langcode(p):\n        return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()\n\n    args = ['auto', 'en']\n\n    for i in range(2):\n        if ' ' not in command:\n            break\n        prefix, cmd = command.split(' ', 1)\n        if langcode(prefix):\n            args[i] = prefix[1:]\n            command = cmd\n\n    phrase = command\n    if (len(phrase) > 350) and (not trigger.admin):\n        bot.reply('Phrase must be under 350 characters.')\n        return\n\n    if phrase.strip() == '':\n        bot.reply('You need to specify a string for me to translate!')\n        return\n\n    src, dest = args\n\n    if src == dest:\n        bot.reply('Language guessing failed, so try suggesting one!')\n        return\n\n    try:\n        msg, src = translate(phrase, src, dest)\n    except requests.Timeout:\n        bot.reply(\"Translation service unavailable (timeout).\")\n        LOGGER.error(\n            'Translate API error (%s to %s: \"%s\"): timeout.',\n            src, dest, phrase)\n        return\n    except requests.RequestException as http_error:\n        bot.reply(\"Translation request failed.\")\n        LOGGER.exception(\n            'Translate API error (%s to %s: \"%s\"): %s.',\n            src, dest, phrase, http_error)\n        return\n\n    if not src:\n        return bot.say(\"Translation failed, probably because of a rate-limit.\")\n\n    if not msg:\n        bot.reply(\n            'The %s to %s translation failed; '\n            'are you sure you specified valid language abbreviations?'\n            % (src, dest))\n        return\n\n    msg = web.decode(msg)  # msg.replace('&#39;', \"'\")\n    msg = '\"%s\" (%s to %s, translate.google.com)' % (msg, src, dest)\n\n    bot.say(msg)\n\n\ndef get_random_lang(long_list, short_list):\n    random_index = random.randint(0, len(long_list) - 1)\n    random_lang = long_list[random_index]\n    if random_lang not in short_list:\n        short_list.append(random_lang)\n    else:\n        return get_random_lang(long_list, short_list)\n    return short_list\n\n\n@plugin.command('mangle', 'mangle2')\n@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)\ndef mangle(bot, trigger):\n    \"\"\"Repeatedly translate the input until it makes absolutely no sense.\"\"\"\n    long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']\n    lang_list = []\n    for __ in range(0, 8):\n        lang_list = get_random_lang(long_lang_list, lang_list)\n    random.shuffle(lang_list)\n    if trigger.group(2) is None:\n        try:\n            phrase = (bot.memory['mangle_lines'][trigger.sender], '')\n        except KeyError:\n            bot.reply(\"What do you want me to mangle?\")\n            return\n    else:\n        phrase = (trigger.group(2).strip(), '')\n    if phrase[0] == '':\n        bot.reply(\"What do you want me to mangle?\")\n        return\n    for lang in lang_list:\n        backup = phrase\n        try:\n            phrase = translate(phrase[0], 'en', lang)\n        except Exception:  # TODO: Be specific\n            phrase = False\n        if not phrase:\n            phrase = backup\n            break\n\n        try:\n            phrase = translate(phrase[0], lang, 'en')\n        except Exception:  # TODO: Be specific\n            phrase = backup\n            continue\n\n        if not phrase:\n            phrase = backup\n            break\n\n    bot.say(phrase[0])\n\n\n@plugin.rule('(.*)')\n@plugin.priority('low')\n@plugin.unblockable\ndef collect_mangle_lines(bot, trigger):\n    bot.memory['mangle_lines'][trigger.sender] = \"%s said '%s'\" % (\n        trigger.nick,\n        trigger.group(0).strip(),\n    )\n", "path": "sopel/modules/translate.py"}], "after_files": [{"content": "\"\"\"\ntranslate.py - Sopel Translation Plugin\nCopyright 2008, Sean B. Palmer, inamidst.com\nCopyright 2013-2014, Elad Alfassa <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import generator_stop\n\nimport json\nimport logging\nimport random\n\nimport requests\n\nfrom sopel import plugin, tools\nfrom sopel.tools import web\n\n\nLOGGER = logging.getLogger(__name__)\nPLUGIN_OUTPUT_PREFIX = '[translate] '\n\n\ndef setup(bot):\n    if 'mangle_lines' not in bot.memory:\n        bot.memory['mangle_lines'] = tools.SopelIdentifierMemory()\n\n\ndef shutdown(bot):\n    try:\n        del bot.memory['mangle_lines']\n    except KeyError:\n        pass\n\n\ndef translate(text, in_lang='auto', out_lang='en'):\n    raw = False\n    if str(out_lang).endswith('-raw'):\n        out_lang = out_lang[:-4]\n        raw = True\n\n    headers = {\n        'User-Agent': 'Mozilla/5.0' +\n        '(X11; U; Linux i686)' +\n        'Gecko/20071127 Firefox/2.0.0.11'\n    }\n\n    query = {\n        \"client\": \"gtx\",\n        \"sl\": in_lang,\n        \"tl\": out_lang,\n        \"dt\": \"t\",\n        \"q\": text,\n    }\n    url = \"https://translate.googleapis.com/translate_a/single\"\n    result = requests.get(url, params=query, timeout=40, headers=headers).text\n\n    if result == '[,,\"\"]':\n        return None, in_lang\n\n    while ',,' in result:\n        result = result.replace(',,', ',null,')\n        result = result.replace('[,', '[null,')\n\n    try:\n        data = json.loads(result)\n    except ValueError:\n        LOGGER.error(\n            'Error parsing JSON response from translate API (%s to %s: \"%s\")',\n            in_lang, out_lang, text)\n        return None, None\n\n    if raw:\n        return str(data), 'en-raw'\n\n    try:\n        language = data[2]  # -2][0][0]\n    except IndexError:\n        language = '?'\n\n    return ''.join(x[0] for x in data[0]), language\n\n\n@plugin.rule(r'$nickname[,:]\\s+(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?[\"\u201c](.+?)[\"\u201d]\\? *$')\n@plugin.example('$nickname: \"mon chien\"? or $nickname: fr \"mon chien\"?')\n@plugin.priority('low')\n@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)\ndef tr(bot, trigger):\n    \"\"\"Translates a phrase, with an optional language hint.\"\"\"\n    in_lang, out_lang, phrase = trigger.groups()\n\n    if (len(phrase) > 350) and (not trigger.admin):\n        bot.reply('Phrase must be under 350 characters.')\n        return\n\n    if phrase.strip() == '':\n        bot.reply('You need to specify a string for me to translate!')\n        return\n\n    in_lang = in_lang or 'auto'\n    out_lang = out_lang or 'en'\n\n    if in_lang == out_lang:\n        bot.reply('Language guessing failed, so try suggesting one!')\n        return\n\n    try:\n        msg, in_lang = translate(phrase, in_lang, out_lang)\n    except requests.Timeout:\n        bot.reply(\"Translation service unavailable (timeout).\")\n        LOGGER.error(\n            'Translate API error (%s to %s: \"%s\"): timeout.',\n            in_lang, out_lang, phrase)\n        return\n    except requests.RequestException as http_error:\n        bot.reply(\"Translation request failed.\")\n        LOGGER.exception(\n            'Translate API error (%s to %s: \"%s\"): %s.',\n            in_lang, out_lang, phrase, http_error)\n        return\n\n    if not in_lang:\n        bot.reply(\"Translation failed, probably because of a rate-limit.\")\n        return\n\n    if not msg:\n        bot.reply(\n            'The %s to %s translation failed; are you sure you specified '\n            'valid language abbreviations?' % (in_lang, out_lang)\n        )\n        return\n\n    msg = web.decode(msg)\n    msg = '\"%s\" (%s to %s, translate.google.com)' % (msg, in_lang, out_lang)\n    bot.say(msg)\n\n\n@plugin.command('translate', 'tr')\n@plugin.example('.tr :en :fr my dog',\n                '\"mon chien\" (en to fr, translate.google.com)',\n                online=True, vcr=True)\n@plugin.example('.tr \u05de\u05d7\u05e9\u05d1',\n                '\"computer\" (iw to en, translate.google.com)',\n                online=True, vcr=True)\n@plugin.example('.tr mon chien',\n                '\"my dog\" (fr to en, translate.google.com)',\n                online=True, vcr=True)\n@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)\ndef tr2(bot, trigger):\n    \"\"\"Translates a phrase, with an optional language hint.\"\"\"\n    command = trigger.group(2)\n\n    if not command:\n        bot.reply('You did not give me anything to translate.')\n        return\n\n    def langcode(p):\n        return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()\n\n    args = ['auto', 'en']\n\n    for i in range(2):\n        if ' ' not in command:\n            break\n        prefix, cmd = command.split(' ', 1)\n        if langcode(prefix):\n            args[i] = prefix[1:]\n            command = cmd\n\n    phrase = command\n    if (len(phrase) > 350) and (not trigger.admin):\n        bot.reply('Phrase must be under 350 characters.')\n        return\n\n    if phrase.strip() == '':\n        bot.reply('You need to specify a string for me to translate!')\n        return\n\n    src, dest = args\n\n    if src == dest:\n        bot.reply('Language guessing failed, so try suggesting one!')\n        return\n\n    try:\n        msg, src = translate(phrase, src, dest)\n    except requests.Timeout:\n        bot.reply(\"Translation service unavailable (timeout).\")\n        LOGGER.error(\n            'Translate API error (%s to %s: \"%s\"): timeout.',\n            src, dest, phrase)\n        return\n    except requests.RequestException as http_error:\n        bot.reply(\"Translation request failed.\")\n        LOGGER.exception(\n            'Translate API error (%s to %s: \"%s\"): %s.',\n            src, dest, phrase, http_error)\n        return\n\n    if not src:\n        return bot.say(\"Translation failed, probably because of a rate-limit.\")\n\n    if not msg:\n        bot.reply(\n            'The %s to %s translation failed; '\n            'are you sure you specified valid language abbreviations?'\n            % (src, dest))\n        return\n\n    msg = web.decode(msg)  # msg.replace('&#39;', \"'\")\n    msg = '\"%s\" (%s to %s, translate.google.com)' % (msg, src, dest)\n\n    bot.say(msg)\n\n\ndef get_random_lang(long_list, short_list):\n    random_index = random.randint(0, len(long_list) - 1)\n    random_lang = long_list[random_index]\n    if random_lang not in short_list:\n        short_list.append(random_lang)\n    else:\n        return get_random_lang(long_list, short_list)\n    return short_list\n\n\n@plugin.command('mangle', 'mangle2')\n@plugin.output_prefix(PLUGIN_OUTPUT_PREFIX)\ndef mangle(bot, trigger):\n    \"\"\"Repeatedly translate the input until it makes absolutely no sense.\"\"\"\n    long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']\n    lang_list = []\n\n    for __ in range(0, 8):\n        lang_list = get_random_lang(long_lang_list, lang_list)\n    random.shuffle(lang_list)\n\n    if trigger.group(2) is None:\n        try:\n            phrase = (bot.memory['mangle_lines'][trigger.sender], '')\n        except KeyError:\n            bot.reply(\"What do you want me to mangle?\")\n            return\n    else:\n        phrase = (trigger.group(2).strip(), '')\n\n    if phrase[0] == '':\n        bot.reply(\"What do you want me to mangle?\")\n        return\n\n    for lang in lang_list:\n        backup = phrase\n        try:\n            phrase = translate(phrase[0], 'en', lang)\n        except Exception:  # TODO: Be specific\n            phrase = False\n        if not phrase:\n            phrase = backup\n            break\n\n        try:\n            phrase = translate(phrase[0], lang, 'en')\n        except Exception:  # TODO: Be specific\n            phrase = backup\n            continue\n\n        if not phrase:\n            phrase = backup\n            break\n\n    if phrase[0] is None:\n        # translate() returns (None, None) if an error happens,\n        # usually because the bot has exceeded a rate limit\n        bot.reply(\"Translation rate limit reached. Try again later.\")\n        return\n\n    bot.say(phrase[0])\n\n\n@plugin.rule('(.*)')\n@plugin.priority('low')\n@plugin.unblockable\ndef collect_mangle_lines(bot, trigger):\n    bot.memory['mangle_lines'][trigger.sender] = \"%s said '%s'\" % (\n        trigger.nick,\n        trigger.group(0).strip(),\n    )\n", "path": "sopel/modules/translate.py"}]}