mirror of
https://github.com/eternnoir/pyTelegramBotAPI.git
synced 2023-08-10 21:12:57 +03:00
Merge pull request #1511 from abdullaev388/master
Added sync i18n class based middleware
This commit is contained in:
commit
453df01f26
@ -0,0 +1,121 @@
|
|||||||
|
import gettext
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from telebot.handler_backends import BaseMiddleware
|
||||||
|
|
||||||
|
try:
|
||||||
|
from babel.support import LazyProxy
|
||||||
|
|
||||||
|
babel_imported = True
|
||||||
|
except ImportError:
|
||||||
|
babel_imported = False
|
||||||
|
|
||||||
|
|
||||||
|
class I18N(BaseMiddleware):
|
||||||
|
"""
|
||||||
|
This middleware provides high-level tool for internationalization
|
||||||
|
It is based on gettext util.
|
||||||
|
"""
|
||||||
|
|
||||||
|
context_lang = threading.local()
|
||||||
|
|
||||||
|
def __init__(self, translations_path, domain_name: str):
|
||||||
|
super().__init__()
|
||||||
|
self.update_types = self.process_update_types()
|
||||||
|
|
||||||
|
self.path = translations_path
|
||||||
|
self.domain = domain_name
|
||||||
|
self.translations = self.find_translations()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_translations(self):
|
||||||
|
return list(self.translations)
|
||||||
|
|
||||||
|
def gettext(self, text: str, lang: str = None):
|
||||||
|
"""
|
||||||
|
Singular translations
|
||||||
|
"""
|
||||||
|
|
||||||
|
if lang is None:
|
||||||
|
lang = self.context_lang.language
|
||||||
|
|
||||||
|
if lang not in self.translations:
|
||||||
|
return text
|
||||||
|
|
||||||
|
translator = self.translations[lang]
|
||||||
|
return translator.gettext(text)
|
||||||
|
|
||||||
|
def ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||||
|
"""
|
||||||
|
Plural translations
|
||||||
|
"""
|
||||||
|
if lang is None:
|
||||||
|
lang = self.context_lang.language
|
||||||
|
|
||||||
|
if lang not in self.translations:
|
||||||
|
if n == 1:
|
||||||
|
return singular
|
||||||
|
return plural
|
||||||
|
|
||||||
|
translator = self.translations[lang]
|
||||||
|
return translator.ngettext(singular, plural, n)
|
||||||
|
|
||||||
|
def lazy_gettext(self, text: str, lang: str = None):
|
||||||
|
if not babel_imported:
|
||||||
|
raise RuntimeError('babel module is not imported. Check that you installed it.')
|
||||||
|
return LazyProxy(self.gettext, text, lang, enable_cache=False)
|
||||||
|
|
||||||
|
def lazy_ngettext(self, singular: str, plural: str, lang: str = None, n=1):
|
||||||
|
if not babel_imported:
|
||||||
|
raise RuntimeError('babel module is not imported. Check that you installed it.')
|
||||||
|
return LazyProxy(self.ngettext, singular, plural, lang, n, enable_cache=False)
|
||||||
|
|
||||||
|
def get_user_language(self, obj):
|
||||||
|
"""
|
||||||
|
You need to override this method and return user language
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def process_update_types(self) -> list:
|
||||||
|
"""
|
||||||
|
You need to override this method and return any update types which you want to be processed
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def pre_process(self, message, data):
|
||||||
|
"""
|
||||||
|
context language variable will be set each time when update from 'process_update_types' comes
|
||||||
|
value is the result of 'get_user_language' method
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.context_lang.language = self.get_user_language(obj=message)
|
||||||
|
|
||||||
|
def post_process(self, message, data, exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def find_translations(self):
|
||||||
|
"""
|
||||||
|
Looks for translations with passed 'domain' in passed 'path'
|
||||||
|
"""
|
||||||
|
if not os.path.exists(self.path):
|
||||||
|
raise RuntimeError(f"Translations directory by path: {self.path!r} was not found")
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for name in os.listdir(self.path):
|
||||||
|
translations_path = os.path.join(self.path, name, 'LC_MESSAGES')
|
||||||
|
|
||||||
|
if not os.path.isdir(translations_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
po_file = os.path.join(translations_path, self.domain + '.po')
|
||||||
|
mo_file = po_file[:-2] + 'mo'
|
||||||
|
|
||||||
|
if os.path.isfile(po_file) and not os.path.isfile(mo_file):
|
||||||
|
raise FileNotFoundError(f"Translations for: {name!r} were not compiled!")
|
||||||
|
|
||||||
|
with open(mo_file, 'rb') as file:
|
||||||
|
result[name] = gettext.GNUTranslations(file)
|
||||||
|
|
||||||
|
return result
|
34
examples/middleware/class_based/i18n_middleware/keyboards.py
Normal file
34
examples/middleware/class_based/i18n_middleware/keyboards.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
|
||||||
|
def languages_keyboard():
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(text="English", callback_data='en'),
|
||||||
|
InlineKeyboardButton(text="Русский", callback_data='ru'),
|
||||||
|
InlineKeyboardButton(text="O'zbekcha", callback_data='uz_Latn')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def clicker_keyboard(_):
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(text=_("click"), callback_data='click'),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def menu_keyboard(_):
|
||||||
|
keyboard = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||||
|
keyboard.add(
|
||||||
|
KeyboardButton(text=_("My user id")),
|
||||||
|
KeyboardButton(text=_("My user name")),
|
||||||
|
KeyboardButton(text=_("My first name"))
|
||||||
|
)
|
||||||
|
|
||||||
|
return keyboard
|
@ -0,0 +1,81 @@
|
|||||||
|
# English translations for PROJECT.
|
||||||
|
# Copyright (C) 2022 ORGANIZATION
|
||||||
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
|
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||||
|
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"Language-Team: en <LL@li.org>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: Babel 2.9.1\n"
|
||||||
|
|
||||||
|
#: keyboards.py:20
|
||||||
|
msgid "click"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: keyboards.py:29
|
||||||
|
msgid "My user id"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: keyboards.py:30
|
||||||
|
msgid "My user name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: keyboards.py:31
|
||||||
|
msgid "My first name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: main.py:97
|
||||||
|
msgid ""
|
||||||
|
"Hello, {user_fist_name}!\n"
|
||||||
|
"This is the example of multilanguage bot.\n"
|
||||||
|
"Available commands:\n"
|
||||||
|
"\n"
|
||||||
|
"/lang - change your language\n"
|
||||||
|
"/plural - pluralization example\n"
|
||||||
|
"/menu - text menu example"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: main.py:121
|
||||||
|
msgid "Language has been changed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: main.py:130 main.py:150
|
||||||
|
#, fuzzy
|
||||||
|
msgid "You have {number} click"
|
||||||
|
msgid_plural "You have {number} clicks"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: main.py:135 main.py:155
|
||||||
|
msgid ""
|
||||||
|
"This is clicker.\n"
|
||||||
|
"\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: main.py:163
|
||||||
|
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: main.py:203
|
||||||
|
msgid "Seems you confused language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "Hello, {user_fist_name}!\n"
|
||||||
|
#~ "This is the example of multilanguage bot.\n"
|
||||||
|
#~ "Available commands:\n"
|
||||||
|
#~ "\n"
|
||||||
|
#~ "/lang - change your language\n"
|
||||||
|
#~ "/plural - pluralization example"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
@ -0,0 +1,82 @@
|
|||||||
|
# Russian translations for PROJECT.
|
||||||
|
# Copyright (C) 2022 ORGANIZATION
|
||||||
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
|
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||||
|
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"Language-Team: ru <LL@li.org>\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||||
|
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: Babel 2.9.1\n"
|
||||||
|
|
||||||
|
#: keyboards.py:20
|
||||||
|
msgid "click"
|
||||||
|
msgstr "Клик"
|
||||||
|
|
||||||
|
#: keyboards.py:29
|
||||||
|
msgid "My user id"
|
||||||
|
msgstr "Мой user id"
|
||||||
|
|
||||||
|
#: keyboards.py:30
|
||||||
|
msgid "My user name"
|
||||||
|
msgstr "Мой user name"
|
||||||
|
|
||||||
|
#: keyboards.py:31
|
||||||
|
msgid "My first name"
|
||||||
|
msgstr "Мой first name"
|
||||||
|
|
||||||
|
#: main.py:97
|
||||||
|
msgid ""
|
||||||
|
"Hello, {user_fist_name}!\n"
|
||||||
|
"This is the example of multilanguage bot.\n"
|
||||||
|
"Available commands:\n"
|
||||||
|
"\n"
|
||||||
|
"/lang - change your language\n"
|
||||||
|
"/plural - pluralization example\n"
|
||||||
|
"/menu - text menu example"
|
||||||
|
msgstr ""
|
||||||
|
"Привет, {user_fist_name}!\n"
|
||||||
|
"Это пример мультиязычного бота.\n"
|
||||||
|
"Доступные команды:\n"
|
||||||
|
"\n"
|
||||||
|
"/lang - изменить язык\n"
|
||||||
|
"/plural - пример плюрализации\n"
|
||||||
|
"/menu - Пример текстового меню"
|
||||||
|
|
||||||
|
#: main.py:121
|
||||||
|
msgid "Language has been changed"
|
||||||
|
msgstr "Язык был сменён"
|
||||||
|
|
||||||
|
#: main.py:130 main.py:150
|
||||||
|
msgid "You have {number} click"
|
||||||
|
msgid_plural "You have {number} clicks"
|
||||||
|
msgstr[0] "У вас {number} клик"
|
||||||
|
msgstr[1] "У вас {number} клика"
|
||||||
|
msgstr[2] "У вас {number} кликов"
|
||||||
|
|
||||||
|
#: main.py:135 main.py:155
|
||||||
|
msgid ""
|
||||||
|
"This is clicker.\n"
|
||||||
|
"\n"
|
||||||
|
msgstr ""
|
||||||
|
"Это кликер.\n"
|
||||||
|
"\n"
|
||||||
|
|
||||||
|
#: main.py:163
|
||||||
|
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||||
|
msgstr "Это пример ReplyKeyboardMarkup меню в мультиязычном боте."
|
||||||
|
|
||||||
|
#: main.py:203
|
||||||
|
msgid "Seems you confused language"
|
||||||
|
msgstr "Кажется, вы перепутали язык"
|
||||||
|
|
@ -0,0 +1,80 @@
|
|||||||
|
# Uzbek (Latin) translations for PROJECT.
|
||||||
|
# Copyright (C) 2022 ORGANIZATION
|
||||||
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
|
"POT-Creation-Date: 2022-02-19 18:37+0500\n"
|
||||||
|
"PO-Revision-Date: 2022-02-18 16:22+0500\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language: uz_Latn\n"
|
||||||
|
"Language-Team: uz_Latn <LL@li.org>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: Babel 2.9.1\n"
|
||||||
|
|
||||||
|
#: keyboards.py:20
|
||||||
|
msgid "click"
|
||||||
|
msgstr "clik"
|
||||||
|
|
||||||
|
#: keyboards.py:29
|
||||||
|
msgid "My user id"
|
||||||
|
msgstr "Mani user id"
|
||||||
|
|
||||||
|
#: keyboards.py:30
|
||||||
|
msgid "My user name"
|
||||||
|
msgstr "Mani user name"
|
||||||
|
|
||||||
|
#: keyboards.py:31
|
||||||
|
msgid "My first name"
|
||||||
|
msgstr "Mani first name"
|
||||||
|
|
||||||
|
#: main.py:97
|
||||||
|
msgid ""
|
||||||
|
"Hello, {user_fist_name}!\n"
|
||||||
|
"This is the example of multilanguage bot.\n"
|
||||||
|
"Available commands:\n"
|
||||||
|
"\n"
|
||||||
|
"/lang - change your language\n"
|
||||||
|
"/plural - pluralization example\n"
|
||||||
|
"/menu - text menu example"
|
||||||
|
msgstr ""
|
||||||
|
"Salom, {user_fist_name}!\n"
|
||||||
|
"Bu multilanguage bot misoli.\n"
|
||||||
|
"Mavjud buyruqlar:\n"
|
||||||
|
"\n"
|
||||||
|
"/lang - tilni ozgartirish\n"
|
||||||
|
"/plural - pluralizatsiya misoli\n"
|
||||||
|
"/menu - text menu misoli"
|
||||||
|
|
||||||
|
#: main.py:121
|
||||||
|
msgid "Language has been changed"
|
||||||
|
msgstr "Til ozgartirildi"
|
||||||
|
|
||||||
|
#: main.py:130 main.py:150
|
||||||
|
msgid "You have {number} click"
|
||||||
|
msgid_plural "You have {number} clicks"
|
||||||
|
msgstr[0] "Sizda {number}ta clik"
|
||||||
|
msgstr[1] "Sizda {number}ta clik"
|
||||||
|
|
||||||
|
#: main.py:135 main.py:155
|
||||||
|
msgid ""
|
||||||
|
"This is clicker.\n"
|
||||||
|
"\n"
|
||||||
|
msgstr ""
|
||||||
|
"Bu clicker.\n"
|
||||||
|
"\n"
|
||||||
|
|
||||||
|
#: main.py:163
|
||||||
|
msgid "This is ReplyKeyboardMarkup menu example in multilanguage bot."
|
||||||
|
msgstr "Bu multilanguage bot da replykeyboardmarkup menyu misoli."
|
||||||
|
|
||||||
|
#: main.py:203
|
||||||
|
msgid "Seems you confused language"
|
||||||
|
msgstr "Tilni adashtirdiz"
|
||||||
|
|
211
examples/middleware/class_based/i18n_middleware/main.py
Normal file
211
examples/middleware/class_based/i18n_middleware/main.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
"""
|
||||||
|
In this example you will learn how to adapt your bot to different languages
|
||||||
|
Using built-in middleware I18N.
|
||||||
|
|
||||||
|
You need to install babel package 'https://pypi.org/project/Babel/'
|
||||||
|
Babel provides a command-line interface for working with message catalogs
|
||||||
|
After installing babel package you have a script called 'pybabel'
|
||||||
|
Too see all the commands open terminal and type 'pybabel --help'
|
||||||
|
Full description for pybabel commands can be found here: 'https://babel.pocoo.org/en/latest/cmdline.html'
|
||||||
|
|
||||||
|
Create a directory 'locales' where our translations will be stored
|
||||||
|
|
||||||
|
First we need to extract texts:
|
||||||
|
pybabel extract -o locales/{domain_name}.pot --input-dirs .
|
||||||
|
{domain_name}.pot - is the file where all translations are saved
|
||||||
|
The name of this file should be the same as domain which you pass to I18N class
|
||||||
|
In this example domain_name will be 'messages'
|
||||||
|
|
||||||
|
For gettext (singular texts) we use '_' alias and it works perfect
|
||||||
|
You may also you some alias for ngettext (plural texts) but you can face with a problem that
|
||||||
|
your plural texts are not being extracted
|
||||||
|
That is because by default 'pybabel extract' recognizes the following keywords:
|
||||||
|
_, gettext, ngettext, ugettext, ungettext, dgettext, dngettext, N_
|
||||||
|
To add your own keyword you can use '-k' flag
|
||||||
|
In this example for 'ngettext' i will assign double underscore alias '__'
|
||||||
|
|
||||||
|
Full command with pluralization support will look so:
|
||||||
|
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||||
|
|
||||||
|
Then create directories with translations (get list of all locales: 'pybabel --list-locales'):
|
||||||
|
pybabel init -i locales/{domain_name}.pot -d locales -l en
|
||||||
|
pybabel init -i locales/{domain_name}.pot -d locales -l ru
|
||||||
|
pybabel init -i locales/{domain_name}.pot -d locales -l uz_Latn
|
||||||
|
|
||||||
|
Now you can translate the texts located in locales/{language}/LC_MESSAGES/{domain_name}.po
|
||||||
|
After you translated all the texts you need to compile .po files:
|
||||||
|
pybabel compile -d locales
|
||||||
|
|
||||||
|
When you delete/update your texts you also need to update them in .po files:
|
||||||
|
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||||
|
pybabel update -i locales/{domain_name}.pot -d locales
|
||||||
|
- translate
|
||||||
|
pybabel compile -d locales
|
||||||
|
|
||||||
|
If you have any exceptions check:
|
||||||
|
- you have installed babel
|
||||||
|
- translations are ready, so you just compiled it
|
||||||
|
- in the commands above you replaced {domain_name} to messages
|
||||||
|
- you are writing commands from correct path in terminal
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import keyboards
|
||||||
|
from i18n_base_midddleware import I18N
|
||||||
|
from telebot import TeleBot
|
||||||
|
from telebot import types, StateMemoryStorage
|
||||||
|
from telebot.custom_filters import TextMatchFilter, TextFilter
|
||||||
|
|
||||||
|
class I18NMiddleware(I18N):
|
||||||
|
|
||||||
|
def process_update_types(self) -> list:
|
||||||
|
"""
|
||||||
|
Here you need to return a list of update types which you want to be processed
|
||||||
|
"""
|
||||||
|
return ['message', 'callback_query']
|
||||||
|
|
||||||
|
def get_user_language(self, obj: Union[types.Message, types.CallbackQuery]):
|
||||||
|
"""
|
||||||
|
This method is called when new update comes (only updates which you return in 'process_update_types' method)
|
||||||
|
Returned language will be used in 'pre_process' method of parent class
|
||||||
|
Returned language will be set to context language variable.
|
||||||
|
If you need to get translation with user's actual language you don't have to pass it manually
|
||||||
|
It will be automatically passed from context language value.
|
||||||
|
However if you need some other language you can always pass it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
user_id = obj.from_user.id
|
||||||
|
|
||||||
|
if user_id not in users_lang:
|
||||||
|
users_lang[user_id] = 'en'
|
||||||
|
|
||||||
|
return users_lang[user_id]
|
||||||
|
|
||||||
|
|
||||||
|
storage = StateMemoryStorage()
|
||||||
|
bot = TeleBot("", state_storage=storage, use_class_middlewares=True)
|
||||||
|
|
||||||
|
i18n = I18NMiddleware(translations_path='locales', domain_name='messages')
|
||||||
|
_ = i18n.gettext # for singular translations
|
||||||
|
__ = i18n.ngettext # for plural translations
|
||||||
|
|
||||||
|
# These are example storages, do not use it in a production development
|
||||||
|
users_lang = {}
|
||||||
|
users_clicks = {}
|
||||||
|
|
||||||
|
@bot.message_handler(commands=['start'])
|
||||||
|
def start_handler(message: types.Message):
|
||||||
|
text = _("Hello, {user_fist_name}!\n"
|
||||||
|
"This is the example of multilanguage bot.\n"
|
||||||
|
"Available commands:\n\n"
|
||||||
|
"/lang - change your language\n"
|
||||||
|
"/plural - pluralization example\n"
|
||||||
|
"/menu - text menu example")
|
||||||
|
|
||||||
|
# remember don't use f string for interpolation, use .format method instead
|
||||||
|
text = text.format(user_fist_name=message.from_user.first_name)
|
||||||
|
bot.send_message(message.from_user.id, text)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(commands=['lang'])
|
||||||
|
def change_language_handler(message: types.Message):
|
||||||
|
bot.send_message(message.chat.id, "Choose language\nВыберите язык\nTilni tanlang",
|
||||||
|
reply_markup=keyboards.languages_keyboard())
|
||||||
|
|
||||||
|
|
||||||
|
@bot.callback_query_handler(func=None, text=TextFilter(contains=['en', 'ru', 'uz_Latn']))
|
||||||
|
def language_handler(call: types.CallbackQuery):
|
||||||
|
lang = call.data
|
||||||
|
users_lang[call.from_user.id] = lang
|
||||||
|
|
||||||
|
# When you changed user language, you have to pass it manually beacause it is not changed in context
|
||||||
|
bot.edit_message_text(_("Language has been changed", lang=lang), call.from_user.id, call.message.id)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(commands=['plural'])
|
||||||
|
def pluralization_handler(message: types.Message):
|
||||||
|
if not users_clicks.get(message.from_user.id):
|
||||||
|
users_clicks[message.from_user.id] = 0
|
||||||
|
clicks = users_clicks[message.from_user.id]
|
||||||
|
|
||||||
|
text = __(
|
||||||
|
singular="You have {number} click",
|
||||||
|
plural="You have {number} clicks",
|
||||||
|
n=clicks
|
||||||
|
)
|
||||||
|
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||||
|
|
||||||
|
bot.send_message(message.chat.id, text, reply_markup=keyboards.clicker_keyboard(_))
|
||||||
|
|
||||||
|
|
||||||
|
@bot.callback_query_handler(func=None, text=TextFilter(equals='click'))
|
||||||
|
def click_handler(call: types.CallbackQuery):
|
||||||
|
if not users_clicks.get(call.from_user.id):
|
||||||
|
users_clicks[call.from_user.id] = 1
|
||||||
|
else:
|
||||||
|
users_clicks[call.from_user.id] += 1
|
||||||
|
|
||||||
|
clicks = users_clicks[call.from_user.id]
|
||||||
|
|
||||||
|
text = __(
|
||||||
|
singular="You have {number} click",
|
||||||
|
plural="You have {number} clicks",
|
||||||
|
n=clicks
|
||||||
|
)
|
||||||
|
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||||
|
|
||||||
|
bot.edit_message_text(text, call.from_user.id, call.message.message_id,
|
||||||
|
reply_markup=keyboards.clicker_keyboard(_))
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(commands=['menu'])
|
||||||
|
def menu_handler(message: types.Message):
|
||||||
|
text = _("This is ReplyKeyboardMarkup menu example in multilanguage bot.")
|
||||||
|
bot.send_message(message.chat.id, text, reply_markup=keyboards.menu_keyboard(_))
|
||||||
|
|
||||||
|
|
||||||
|
# For lazy tranlations
|
||||||
|
# lazy gettext is used when you don't know user's locale
|
||||||
|
# It can be used for example to handle text buttons in multilanguage bot
|
||||||
|
# The actual translation will be delayed until update comes and context language is set
|
||||||
|
l_ = i18n.lazy_gettext
|
||||||
|
|
||||||
|
|
||||||
|
# Handlers below will handle text according to user's language
|
||||||
|
@bot.message_handler(text=l_("My user id"))
|
||||||
|
def return_user_id(message: types.Message):
|
||||||
|
bot.send_message(message.chat.id, str(message.from_user.id))
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(text=l_("My user name"))
|
||||||
|
def return_user_id(message: types.Message):
|
||||||
|
username = message.from_user.username
|
||||||
|
if not username:
|
||||||
|
username = '-'
|
||||||
|
bot.send_message(message.chat.id, username)
|
||||||
|
|
||||||
|
|
||||||
|
# You can make it case-insensitive
|
||||||
|
@bot.message_handler(text=TextFilter(equals=l_("My first name"), ignore_case=True))
|
||||||
|
def return_user_id(message: types.Message):
|
||||||
|
bot.send_message(message.chat.id, message.from_user.first_name)
|
||||||
|
|
||||||
|
|
||||||
|
all_menu_texts = []
|
||||||
|
for language in i18n.available_translations:
|
||||||
|
for menu_text in ("My user id", "My user name", "My first name"):
|
||||||
|
all_menu_texts.append(_(menu_text, language))
|
||||||
|
|
||||||
|
|
||||||
|
# When user confused language. (handles all menu buttons texts)
|
||||||
|
@bot.message_handler(text=TextFilter(contains=all_menu_texts, ignore_case=True))
|
||||||
|
def missed_message(message: types.Message):
|
||||||
|
bot.send_message(message.chat.id, _("Seems you confused language"), reply_markup=keyboards.menu_keyboard(_))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
bot.setup_middleware(i18n)
|
||||||
|
bot.add_custom_filter(TextMatchFilter())
|
||||||
|
asyncio.run(bot.infinity_polling())
|
@ -1,5 +1,6 @@
|
|||||||
import gettext
|
import gettext
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
class I18N:
|
class I18N:
|
||||||
@ -8,6 +9,8 @@ class I18N:
|
|||||||
It is based on gettext util.
|
It is based on gettext util.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
context_lang = threading.local()
|
||||||
|
|
||||||
def __init__(self, translations_path, domain_name: str):
|
def __init__(self, translations_path, domain_name: str):
|
||||||
self.path = translations_path
|
self.path = translations_path
|
||||||
self.domain = domain_name
|
self.domain = domain_name
|
||||||
@ -21,7 +24,11 @@ class I18N:
|
|||||||
"""
|
"""
|
||||||
Singular translations
|
Singular translations
|
||||||
"""
|
"""
|
||||||
if not lang or lang not in self.translations:
|
|
||||||
|
if lang is None:
|
||||||
|
lang = self.context_lang.language
|
||||||
|
|
||||||
|
if lang not in self.translations:
|
||||||
return text
|
return text
|
||||||
|
|
||||||
translator = self.translations[lang]
|
translator = self.translations[lang]
|
||||||
@ -31,7 +38,10 @@ class I18N:
|
|||||||
"""
|
"""
|
||||||
Plural translations
|
Plural translations
|
||||||
"""
|
"""
|
||||||
if not lang or lang not in self.translations:
|
if lang is None:
|
||||||
|
lang = self.context_lang.language
|
||||||
|
|
||||||
|
if lang not in self.translations:
|
||||||
if n == 1:
|
if n == 1:
|
||||||
return singular
|
return singular
|
||||||
return plural
|
return plural
|
||||||
@ -39,6 +49,7 @@ class I18N:
|
|||||||
translator = self.translations[lang]
|
translator = self.translations[lang]
|
||||||
return translator.ngettext(singular, plural, n)
|
return translator.ngettext(singular, plural, n)
|
||||||
|
|
||||||
|
|
||||||
def find_translations(self):
|
def find_translations(self):
|
||||||
"""
|
"""
|
||||||
Looks for translations with passed 'domain' in passed 'path'
|
Looks for translations with passed 'domain' in passed 'path'
|
@ -13,11 +13,11 @@ def languages_keyboard():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def clicker_keyboard(_, lang):
|
def clicker_keyboard(_):
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
keyboard=[
|
keyboard=[
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(text=_("click", lang=lang), callback_data='click'),
|
InlineKeyboardButton(text=_("click"), callback_data='click'),
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
@ -48,4 +48,3 @@ msgid ""
|
|||||||
"This is clicker.\n"
|
"This is clicker.\n"
|
||||||
"\n"
|
"\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -57,4 +57,3 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Это кликер.\n"
|
"Это кликер.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
@ -55,4 +55,3 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Bu clicker.\n"
|
"Bu clicker.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
@ -1,21 +1,17 @@
|
|||||||
"""
|
"""
|
||||||
In this example you will learn how to adapt your bot to different languages
|
In this example you will learn how to adapt your bot to different languages
|
||||||
Using built-in class I18N.
|
Using built-in class I18N.
|
||||||
|
|
||||||
You need to install babel package 'https://pypi.org/project/Babel/'
|
You need to install babel package 'https://pypi.org/project/Babel/'
|
||||||
Babel provides a command-line interface for working with message catalogs
|
Babel provides a command-line interface for working with message catalogs
|
||||||
After installing babel package you have a script called 'pybabel'
|
After installing babel package you have a script called 'pybabel'
|
||||||
Too see all the commands open terminal and type 'pybabel --help'
|
Too see all the commands open terminal and type 'pybabel --help'
|
||||||
Full description for pybabel commands can be found here: 'https://babel.pocoo.org/en/latest/cmdline.html'
|
Full description for pybabel commands can be found here: 'https://babel.pocoo.org/en/latest/cmdline.html'
|
||||||
|
|
||||||
Create a directory 'locales' where our translations will be stored
|
Create a directory 'locales' where our translations will be stored
|
||||||
|
|
||||||
First we need to extract texts:
|
First we need to extract texts:
|
||||||
pybabel extract -o locales/{domain_name}.pot --input-dirs .
|
pybabel extract -o locales/{domain_name}.pot --input-dirs .
|
||||||
{domain_name}.pot - is the file where all translations are saved
|
{domain_name}.pot - is the file where all translations are saved
|
||||||
The name of this file should be the same as domain which you pass to I18N class
|
The name of this file should be the same as domain which you pass to I18N class
|
||||||
In this example domain_name will be 'messages'
|
In this example domain_name will be 'messages'
|
||||||
|
|
||||||
For gettext (singular texts) we use '_' alias and it works perfect
|
For gettext (singular texts) we use '_' alias and it works perfect
|
||||||
You may also you some alias for ngettext (plural texts) but you can face with a problem that
|
You may also you some alias for ngettext (plural texts) but you can face with a problem that
|
||||||
your plural texts are not being extracted
|
your plural texts are not being extracted
|
||||||
@ -23,25 +19,20 @@ That is because by default 'pybabel extract' recognizes the following keywords:
|
|||||||
_, gettext, ngettext, ugettext, ungettext, dgettext, dngettext, N_
|
_, gettext, ngettext, ugettext, ungettext, dgettext, dngettext, N_
|
||||||
To add your own keyword you can use '-k' flag
|
To add your own keyword you can use '-k' flag
|
||||||
In this example for 'ngettext' i will assign double underscore alias '__'
|
In this example for 'ngettext' i will assign double underscore alias '__'
|
||||||
|
|
||||||
Full command with pluralization support will look so:
|
Full command with pluralization support will look so:
|
||||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||||
|
|
||||||
Then create directories with translations (get list of all locales: 'pybabel --list-locales'):
|
Then create directories with translations (get list of all locales: 'pybabel --list-locales'):
|
||||||
pybabel init -i locales/{domain_name}.pot -d locales -l en
|
pybabel init -i locales/{domain_name}.pot -d locales -l en
|
||||||
pybabel init -i locales/{domain_name}.pot -d locales -l ru
|
pybabel init -i locales/{domain_name}.pot -d locales -l ru
|
||||||
pybabel init -i locales/{domain_name}.pot -d locales -l uz_Latn
|
pybabel init -i locales/{domain_name}.pot -d locales -l uz_Latn
|
||||||
|
|
||||||
Now you can translate the texts located in locales/{language}/LC_MESSAGES/{domain_name}.po
|
Now you can translate the texts located in locales/{language}/LC_MESSAGES/{domain_name}.po
|
||||||
After you translated all the texts you need to compile .po files:
|
After you translated all the texts you need to compile .po files:
|
||||||
pybabel compile -d locales
|
pybabel compile -d locales
|
||||||
|
|
||||||
When you delete/update your texts you also need to update them in .po files:
|
When you delete/update your texts you also need to update them in .po files:
|
||||||
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
pybabel extract -o locales/{domain_name}.pot -k __:1,2 --input-dirs .
|
||||||
pybabel update -i locales/{domain_name}.pot -d locales
|
pybabel update -i locales/{domain_name}.pot -d locales
|
||||||
- translate
|
- translate
|
||||||
pybabel compile -d locales
|
pybabel compile -d locales
|
||||||
|
|
||||||
If you have any exceptions check:
|
If you have any exceptions check:
|
||||||
- you have installed babel
|
- you have installed babel
|
||||||
- translations are ready, so you just compiled it
|
- translations are ready, so you just compiled it
|
||||||
@ -49,14 +40,17 @@ If you have any exceptions check:
|
|||||||
- you are writing commands from correct path in terminal
|
- you are writing commands from correct path in terminal
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
import keyboards
|
|
||||||
from telebot import TeleBot, types, custom_filters
|
from telebot import TeleBot, types, custom_filters
|
||||||
|
from telebot import apihelper
|
||||||
from telebot.storage.memory_storage import StateMemoryStorage
|
from telebot.storage.memory_storage import StateMemoryStorage
|
||||||
|
|
||||||
|
import keyboards
|
||||||
from i18n_class import I18N
|
from i18n_class import I18N
|
||||||
|
|
||||||
|
apihelper.ENABLE_MIDDLEWARE = True
|
||||||
storage = StateMemoryStorage()
|
storage = StateMemoryStorage()
|
||||||
bot = TeleBot("", state_storage=storage)
|
# IMPORTANT! This example works only if polling is non-threaded.
|
||||||
|
bot = TeleBot("", state_storage=storage, threaded=False)
|
||||||
|
|
||||||
i18n = I18N(translations_path='locales', domain_name='messages')
|
i18n = I18N(translations_path='locales', domain_name='messages')
|
||||||
_ = i18n.gettext # for singular translations
|
_ = i18n.gettext # for singular translations
|
||||||
@ -67,35 +61,25 @@ users_lang = {}
|
|||||||
users_clicks = {}
|
users_clicks = {}
|
||||||
|
|
||||||
|
|
||||||
def get_user_language(func):
|
@bot.middleware_handler(update_types=['message', 'callback_query'])
|
||||||
"""
|
def set_contex_language(bot_instance, message):
|
||||||
This decorator will pass to your handler current user's language
|
i18n.context_lang.language = users_lang.get(message.from_user.id, 'en')
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
obj = args[0]
|
|
||||||
kwargs.update(lang=users_lang.get(obj.from_user.id, 'en'))
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
@bot.message_handler(commands='start')
|
@bot.message_handler(commands=['start'])
|
||||||
@get_user_language
|
def start_handler(message: types.Message):
|
||||||
def start_handler(message: types.Message, lang):
|
|
||||||
text = _("Hello, {user_fist_name}!\n"
|
text = _("Hello, {user_fist_name}!\n"
|
||||||
"This is the example of multilanguage bot.\n"
|
"This is the example of multilanguage bot.\n"
|
||||||
"Available commands:\n\n"
|
"Available commands:\n\n"
|
||||||
"/lang - change your language\n"
|
"/lang - change your language\n"
|
||||||
"/plural - pluralization example", lang=lang)
|
"/plural - pluralization example")
|
||||||
|
|
||||||
# remember don't use f string for interpolation, use .format method instead
|
# remember don't use f string for interpolation, use .format method instead
|
||||||
text = text.format(user_fist_name=message.from_user.first_name)
|
text = text.format(user_fist_name=message.from_user.first_name)
|
||||||
bot.send_message(message.from_user.id, text)
|
bot.send_message(message.from_user.id, text)
|
||||||
|
|
||||||
|
|
||||||
@bot.message_handler(commands='lang')
|
@bot.message_handler(commands=['lang'])
|
||||||
def change_language_handler(message: types.Message):
|
def change_language_handler(message: types.Message):
|
||||||
bot.send_message(message.chat.id, "Choose language\nВыберите язык\nTilni tanlang",
|
bot.send_message(message.chat.id, "Choose language\nВыберите язык\nTilni tanlang",
|
||||||
reply_markup=keyboards.languages_keyboard())
|
reply_markup=keyboards.languages_keyboard())
|
||||||
@ -106,13 +90,13 @@ def language_handler(call: types.CallbackQuery):
|
|||||||
lang = call.data
|
lang = call.data
|
||||||
users_lang[call.from_user.id] = lang
|
users_lang[call.from_user.id] = lang
|
||||||
|
|
||||||
|
# When you change user's language, pass language explicitly coz it's not changed in context
|
||||||
bot.edit_message_text(_("Language has been changed", lang=lang), call.from_user.id, call.message.id)
|
bot.edit_message_text(_("Language has been changed", lang=lang), call.from_user.id, call.message.id)
|
||||||
bot.delete_state(call.from_user.id)
|
bot.delete_state(call.from_user.id)
|
||||||
|
|
||||||
|
|
||||||
@bot.message_handler(commands='plural')
|
@bot.message_handler(commands=['plural'])
|
||||||
@get_user_language
|
def pluralization_handler(message: types.Message):
|
||||||
def pluralization_handler(message: types.Message, lang):
|
|
||||||
if not users_clicks.get(message.from_user.id):
|
if not users_clicks.get(message.from_user.id):
|
||||||
users_clicks[message.from_user.id] = 0
|
users_clicks[message.from_user.id] = 0
|
||||||
clicks = users_clicks[message.from_user.id]
|
clicks = users_clicks[message.from_user.id]
|
||||||
@ -121,15 +105,13 @@ def pluralization_handler(message: types.Message, lang):
|
|||||||
singular="You have {number} click",
|
singular="You have {number} click",
|
||||||
plural="You have {number} clicks",
|
plural="You have {number} clicks",
|
||||||
n=clicks,
|
n=clicks,
|
||||||
lang=lang
|
|
||||||
)
|
)
|
||||||
text = _("This is clicker.\n\n", lang=lang) + text.format(number=clicks)
|
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||||
bot.send_message(message.chat.id, text, reply_markup=keyboards.clicker_keyboard(_, lang))
|
bot.send_message(message.chat.id, text, reply_markup=keyboards.clicker_keyboard(_))
|
||||||
|
|
||||||
|
|
||||||
@bot.callback_query_handler(func=None, text=custom_filters.TextFilter(equals='click'))
|
@bot.callback_query_handler(func=None, text=custom_filters.TextFilter(equals='click'))
|
||||||
@get_user_language
|
def click_handler(call: types.CallbackQuery):
|
||||||
def click_handler(call: types.CallbackQuery, lang):
|
|
||||||
if not users_clicks.get(call.from_user.id):
|
if not users_clicks.get(call.from_user.id):
|
||||||
users_clicks[call.from_user.id] = 1
|
users_clicks[call.from_user.id] = 1
|
||||||
else:
|
else:
|
||||||
@ -140,12 +122,11 @@ def click_handler(call: types.CallbackQuery, lang):
|
|||||||
text = __(
|
text = __(
|
||||||
singular="You have {number} click",
|
singular="You have {number} click",
|
||||||
plural="You have {number} clicks",
|
plural="You have {number} clicks",
|
||||||
n=clicks,
|
n=clicks
|
||||||
lang=lang
|
|
||||||
)
|
)
|
||||||
text = _("This is clicker.\n\n", lang=lang) + text.format(number=clicks)
|
text = _("This is clicker.\n\n") + text.format(number=clicks)
|
||||||
bot.edit_message_text(text, call.from_user.id, call.message.message_id,
|
bot.edit_message_text(text, call.from_user.id, call.message.message_id,
|
||||||
reply_markup=keyboards.clicker_keyboard(_, lang))
|
reply_markup=keyboards.clicker_keyboard(_))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
@ -39,7 +39,7 @@ class TextFilter:
|
|||||||
"""
|
"""
|
||||||
Advanced text filter to check (types.Message, types.CallbackQuery, types.InlineQuery, types.Poll)
|
Advanced text filter to check (types.Message, types.CallbackQuery, types.InlineQuery, types.Poll)
|
||||||
|
|
||||||
example of usage is in examples/custom_filters/advanced_text_filter.py
|
example of usage is in examples/asynchronous_telebot/custom_filters/advanced_text_filter.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
@ -69,7 +69,7 @@ class TextFilter:
|
|||||||
self.ends_with = self._check_iterable(ends_with, filter_name='ends_with')
|
self.ends_with = self._check_iterable(ends_with, filter_name='ends_with')
|
||||||
self.ignore_case = ignore_case
|
self.ignore_case = ignore_case
|
||||||
|
|
||||||
def _check_iterable(self, iterable, filter_name: str):
|
def _check_iterable(self, iterable, filter_name):
|
||||||
if not iterable:
|
if not iterable:
|
||||||
pass
|
pass
|
||||||
elif not isinstance(iterable, str) and not isinstance(iterable, list) and not isinstance(iterable, tuple):
|
elif not isinstance(iterable, str) and not isinstance(iterable, list) and not isinstance(iterable, tuple):
|
||||||
@ -95,39 +95,33 @@ class TextFilter:
|
|||||||
|
|
||||||
if self.ignore_case:
|
if self.ignore_case:
|
||||||
text = text.lower()
|
text = text.lower()
|
||||||
|
prepare_func = lambda string: str(string).lower()
|
||||||
|
else:
|
||||||
|
prepare_func = str
|
||||||
|
|
||||||
if self.equals:
|
if self.equals:
|
||||||
self.equals = self.equals.lower()
|
result = prepare_func(self.equals) == text
|
||||||
elif self.contains:
|
|
||||||
self.contains = tuple(map(str.lower, self.contains))
|
|
||||||
elif self.starts_with:
|
|
||||||
self.starts_with = tuple(map(str.lower, self.starts_with))
|
|
||||||
elif self.ends_with:
|
|
||||||
self.ends_with = tuple(map(str.lower, self.ends_with))
|
|
||||||
|
|
||||||
if self.equals:
|
|
||||||
result = self.equals == text
|
|
||||||
if result:
|
if result:
|
||||||
return True
|
return True
|
||||||
elif not result and not any((self.contains, self.starts_with, self.ends_with)):
|
elif not result and not any((self.contains, self.starts_with, self.ends_with)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.contains:
|
if self.contains:
|
||||||
result = any([i in text for i in self.contains])
|
result = any([prepare_func(i) in text for i in self.contains])
|
||||||
if result:
|
if result:
|
||||||
return True
|
return True
|
||||||
elif not result and not any((self.starts_with, self.ends_with)):
|
elif not result and not any((self.starts_with, self.ends_with)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.starts_with:
|
if self.starts_with:
|
||||||
result = any([text.startswith(i) for i in self.starts_with])
|
result = any([text.startswith(prepare_func(i)) for i in self.starts_with])
|
||||||
if result:
|
if result:
|
||||||
return True
|
return True
|
||||||
elif not result and not self.ends_with:
|
elif not result and not self.ends_with:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.ends_with:
|
if self.ends_with:
|
||||||
return any([text.endswith(i) for i in self.ends_with])
|
return any([text.endswith(prepare_func(i)) for i in self.ends_with])
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user