Queer European MD passionate about IT
Browse Source

Multilanguage support

Support /language command (and buttons) to make user choose a language.
Davte 5 years ago
parent
commit
c43c0be4fa
2 changed files with 218 additions and 8 deletions
  1. 1 1
      davtelepot/__init__.py
  2. 217 7
      davtelepot/languages.py

+ 1 - 1
davtelepot/__init__.py

@@ -14,7 +14,7 @@ __author__ = "Davide Testa"
 __email__ = "davide@davte.it"
 __credits__ = ["Marco Origlia", "Nick Lee @Nickoala"]
 __license__ = "GNU General Public License v3.0"
-__version__ = "2.1.17"
+__version__ = "2.1.18"
 __maintainer__ = "Davide Testa"
 __contact__ = "t.me/davte"
 

+ 217 - 7
davtelepot/languages.py

@@ -1,23 +1,43 @@
 """Bot support for multiple languages."""
 
 # Standard library modules
+from collections import OrderedDict
 import logging
 
+# Third party modules
+from .utilities import extract, make_button, make_inline_keyboard
+
 
 class MultiLanguageObject(object):
     """Make bot inherit from this class to make it support multiple languages.
 
-    Call MultiLanguage().get_message(
+    Call MultiLanguageObject().get_message(
         field1, field2, ...,
         update, user_record, language,
         format_kwarg1, format_kwarg2, ...
     ) to get the corresponding message in the selected language.
     """
 
-    def __init__(self):
-        """Instantiate MultiLanguage object, setting self.messages."""
-        self.messages = dict()
-        self._default_language = 'en'
+    def __init__(self, *args,
+                 messages=dict(),
+                 default_language='en',
+                 missing_message="Invalid message!",
+                 supported_languages=None,
+                 **kwargs):
+        """Instantiate MultiLanguageObject, setting its attributes."""
+        self.messages = messages
+        self._default_language = default_language
+        self._missing_message = missing_message
+        if supported_languages is None:
+            supported_languages = OrderedDict(
+                {
+                    self.default_language: OrderedDict(
+                        name=self.default_language,
+                        flag=''
+                    )
+                }
+            )
+        self._supported_languages = supported_languages
 
     @property
     def default_language(self):
@@ -28,6 +48,47 @@ class MultiLanguageObject(object):
         """Set default language."""
         self._default_language = language
 
+    @property
+    def missing_message(self):
+        """Return this message when a proper message can not be found."""
+        return self._missing_message
+
+    def set_missing_message(self, message):
+        """Set message to be returned where a proper one can not be found."""
+        self._missing_message = message
+
+    @property
+    def supported_languages(self):
+        """Return dict of supported languages.
+
+        If it is not set, return default language only without flag.
+        """
+        return self._supported_languages
+
+    def add_supported_languages(self, languages):
+        """Add some `languages` to supported languages.
+
+        Example
+        ```python
+        languages = {
+            'en': {
+                'flag': '🇬🇧',
+                'name': 'English'
+            },
+            'it': {
+                'flag': '🇮🇹',
+                'name': 'Italiano'
+            }
+        }
+        ```
+        """
+        assert type(languages) is dict, "Supported languages must be in a dict"
+        if len(languages) == 0:
+            return
+        if self._supported_languages is None:
+            self._supported_languages = dict()
+        self._supported_languages.update(languages)
+
     def get_language(self, update=dict(), user_record=dict(), language=None):
         """Get language.
 
@@ -77,7 +138,7 @@ class MultiLanguageObject(object):
                         )
                     )
                 )
-                return "Invalid message!"
+                return self.missing_message
             result = result[field]
         if language not in result:
             # For specific languages, try generic ones
@@ -95,7 +156,156 @@ class MultiLanguageObject(object):
                             )
                         )
                     )
-                    return "Invalid message!"
+                    return self.missing_message
         return result[language].format(
             **format_kwargs
         )
+
+
+async def _language_command(bot, update, user_record):
+    text, reply_markup = get_language_panel(bot, user_record)
+    return dict(
+        text=text,
+        reply_markup=reply_markup
+    )
+
+
+def get_language_panel(bot, user_record):
+    """Get language panel for user.
+
+    Return text and reply_markup of the message about user's language
+        preferences.
+    """
+    text = bot.get_message(
+        'language', 'language_panel', 'text',
+        user_record=user_record,
+    )
+    text += "\n"
+    if 'selected_language_code' in user_record:
+        current_code = user_record['selected_language_code']
+    else:
+        current_code = None
+    for code, language in bot.supported_languages.items():
+        text += (f"\n{'✅' if code == current_code else '☑️'} "
+                 f"{language['name']} {language['flag']}")
+    reply_markup = make_inline_keyboard(
+        [
+            make_button(
+                text=(
+                    f"{'✅' if code == current_code else '☑️'} "
+                    f"{language['name']} {language['flag']}"
+                ),
+                prefix='lang:///',
+                delimiter='|',
+                data=['set', code]
+            )
+            for code, language in bot.supported_languages.items()
+        ],
+        3
+    )
+    return text, reply_markup
+
+
+async def _language_button(bot, update, user_record, data):
+    result, text, reply_markup = '', '', None
+    if len(data) > 1 and data[0] == 'set':
+        # If message is already updated, do not update it
+        if (
+            'selected_language_code' in user_record
+            and data[1] == user_record['selected_language_code']
+            and data[1] in bot.supported_languages
+            and bot.supported_languages[data[1]]['flag'] in extract(
+                update['message']['text'],
+                starter='✅',
+                ender='\n'
+            )
+        ):
+            return
+        # If database-stored information is not updated, update it
+        if (
+            'selected_language_code' in user_record
+            and data[1] != user_record['selected_language_code']
+        ):
+            with bot.db as db:
+                db['users'].update(
+                    dict(
+                        selected_language_code=data[1],
+                        id=user_record['id']
+                    ),
+                    ['id']
+                )
+                user_record['selected_language_code'] = data[1]
+    if len(data) == 0 or data[0] in ('show', 'set'):
+        text, reply_markup = get_language_panel(bot, user_record)
+    if text:
+        return dict(
+            text=result,
+            edit=dict(
+                text=text,
+                reply_markup=reply_markup
+            )
+        )
+    return result
+
+
+def init(
+    bot, language=None, language_messages=dict(), show_in_keyboard=True,
+    supported_languages={}
+):
+    """Set language support to `bot`."""
+    assert isinstance(bot, MultiLanguageObject), (
+        "Bot must be a MultiLanguageObject subclass in order to support "
+        "multiple languages."
+    )
+    bot.messages['language'] = language_messages
+    if language is None:
+        language = bot.default_language
+    bot.add_supported_languages(supported_languages)
+
+    language_command_name = bot.get_message(
+        'language', 'language_command', 'name',
+        language=language
+    )
+    if language_command_name == bot.missing_message:
+        language_command_name = '/language'
+
+    language_command_alias = bot.get_message(
+        'language', 'language_command', 'alias',
+        language=language
+    )
+    if language_command_alias == bot.missing_message:
+        aliases = []
+    else:
+        aliases = [language_command_alias]
+
+    language_command_description = bot.get_message(
+        'language', 'language_command', 'description',
+        language=language
+    )
+    if language_command_description == bot.missing_message:
+        language_command_description = ''
+
+    language_button_description = bot.get_message(
+        'language', 'language_button', 'description',
+        language=language
+    )
+    if language_button_description == bot.missing_message:
+        language_button_description = ''
+
+    @bot.command(
+        command=language_command_name, aliases=aliases,
+        show_in_keyboard=show_in_keyboard,
+        description=language_command_description,
+        authorization_level='everybody'
+    )
+    async def language_command(bot, update, user_record):
+        return await _language_command(bot, update, user_record)
+
+    @bot.button(
+        prefix='lang:///',
+        separator='|',
+        description=language_button_description,
+        authorization_level='everybody'
+    )
+    async def language_button(bot, update, user_record, data):
+        return await _language_button(bot, update, user_record, data)