Chinese language support (suddenly!)

This commit is contained in:
mihalin 2022-03-22 05:43:10 +03:00
parent db54473e0f
commit 1a9646d607
18 changed files with 740 additions and 126 deletions

View File

@ -4,6 +4,11 @@ ENV PYTHONUNBUFFERED=1 \
POETRY_VERSION=1.1.2 \ POETRY_VERSION=1.1.2 \
POETRY_VIRTUALENVS_CREATE="false" POETRY_VIRTUALENVS_CREATE="false"
RUN apt-get update && \
apt-get install -y gettext build-essential && \
apt-get clean && rm -rf /var/cache/apt/* && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/*
RUN pip install "poetry==$POETRY_VERSION" RUN pip install "poetry==$POETRY_VERSION"
WORKDIR /app WORKDIR /app
@ -13,6 +18,8 @@ RUN poetry install --no-interaction --no-ansi --no-dev
COPY . /app COPY . /app
RUN msgfmt locales/zh/LC_MESSAGES/olgram.po -o locales/zh/LC_MESSAGES/olgram.mo --use-fuzzy
EXPOSE 80 EXPOSE 80
ENTRYPOINT ["./docker-entrypoint.sh"] ENTRYPOINT ["./docker-entrypoint.sh"]

View File

@ -3,7 +3,7 @@
version: '3' version: '3'
services: services:
postgres: postgres:
image: postgres:13.4 image: postgres:14
environment: environment:
- POSTGRES_USER=test_user - POSTGRES_USER=test_user
- POSTGRES_PASSWORD=test_passwd - POSTGRES_PASSWORD=test_passwd

12
locales/locale.py Normal file
View File

@ -0,0 +1,12 @@
import gettext
from olgram.settings import BotSettings
from os.path import dirname
locales_dir = dirname(__file__)
lang = BotSettings.language()
if lang == "ru":
_ = lambda x: x
else:
t = gettext.translation("olgram", localedir=locales_dir, languages=[lang])
_ = t.gettext

View File

@ -0,0 +1,564 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2022-03-22 04:36+0300\n"
"PO-Revision-Date: 2022-03-22 04:55+0300\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.0\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Language: zh_CN\n"
#: olgram/commands/bot_actions.py:21
msgid "Бот удалён"
msgstr "移除机器人"
#: olgram/commands/bot_actions.py:37 olgram/commands/bot_actions.py:49
msgid "Текст сброшен"
msgstr "倾倒的文本"
#: olgram/commands/bot_actions.py:63
msgid "Выбран личный чат"
msgstr "选择了私聊"
#: olgram/commands/bot_actions.py:68
msgid "Нельзя привязать бота к этому чату"
msgstr "你不能将机器人链接到这个聊天室"
#: olgram/commands/bot_actions.py:72
msgid "Выбран чат {0}"
msgstr "聊天选择 {0}"
#: olgram/commands/bots.py:42
msgid "У вас уже слишком много ботов."
msgstr "你已经有太多的机器人了。"
#: olgram/commands/bots.py:45
msgid ""
"\n"
" Чтобы подключить бот, вам нужно выполнить три действия:\n"
"\n"
" 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /"
"newbot\n"
" 2. Введите название бота, а потом username бота.\n"
" 3. После создания бота перешлите ответное сообщение в этот бот или "
"скопируйте и пришлите token бота.\n"
"\n"
" Важно: не подключайте боты, которые используются в других сервисах "
"(Manybot, Chatfuel, Livegram и других).\n"
" "
msgstr ""
"\n"
" 要连接机器人,你需要遵循三个步骤。\n"
"\n"
" 1. 转到机器人@BotFather按START键并发送/newbot\n"
" 2. 输入机器人的名字,然后输入机器人的用户名。\n"
" 3. 一旦创建了机器人,就向这个机器人转发一条回复信息,或者复制并发送机器人"
"的令牌。\n"
"\n"
" 重要不要连接用于其他服务的机器人Manybot、Chatfuel、Livegram和其"
"他)。\n"
" "
#: olgram/commands/bots.py:65
msgid ""
"\n"
" Это не токен бота.\n"
"\n"
" Токен выглядит вот так: 123456789:AAAA-"
"abc123_AbcdEFghijKLMnopqrstu12\n"
" "
msgstr ""
"\n"
" 这不是一个机器人令牌。\n"
"\n"
" 该令牌看起来像这样123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12\n"
" "
#: olgram/commands/bots.py:72
msgid ""
"\n"
" Не удалось запустить этого бота: неверный токен\n"
" "
msgstr ""
"\n"
" 运行此机器人失败:错误的令牌\n"
" "
#: olgram/commands/bots.py:77
msgid ""
"\n"
" Не удалось запустить этого бота: непредвиденная ошибка\n"
" "
msgstr ""
"\n"
" 该机器人无法启动:意外错误\n"
" "
#: olgram/commands/bots.py:82
msgid ""
"\n"
" Такой бот уже есть в базе данных\n"
" "
msgstr ""
"\n"
" 这样的机器人已经在数据库中出现了\n"
" "
#: olgram/commands/bots.py:114
msgid "Бот добавлен! Список ваших ботов: /mybots"
msgstr "机器人已加入! 你的机器人列表:/mybots"
#: olgram/commands/info.py:21
msgid "Недостаточно прав"
msgstr "没有足够的权利"
#: olgram/commands/info.py:32
msgid "Количество ботов: {0}\n"
msgstr "机器人的数量。{0}\n"
#: olgram/commands/info.py:33
msgid "Количество пользователей (у конструктора): {0}\n"
msgstr "用户的数量(在构造器处)。{0}\n"
#: olgram/commands/info.py:34
msgid "Шаблонов ответов: {0}\n"
msgstr "答案模板。{0}\n"
#: olgram/commands/info.py:35
msgid "Входящих сообщений у всех ботов: {0}\n"
msgstr "所有的机器人都有传入的信息。{0}\n"
#: olgram/commands/info.py:36
msgid "Исходящих сообщений у всех ботов: {0}\n"
msgstr "所有的机器人都有外发信息。{0}\n"
#: olgram/commands/menu.py:31
msgid ""
"\n"
" У вас нет добавленных ботов.\n"
"\n"
" Отправьте команду /addbot, чтобы добавить бот.\n"
" "
msgstr ""
"\n"
" 你没有添加任何机器人。\n"
"\n"
" 发送命令/addbot来添加一个机器人。\n"
" "
#: olgram/commands/menu.py:46
msgid "Ваши боты"
msgstr "你的机器人"
#: olgram/commands/menu.py:67
msgid "Личные сообщения"
msgstr "个人留言"
#: olgram/commands/menu.py:72 olgram/commands/menu.py:117
#: olgram/commands/menu.py:143 olgram/commands/menu.py:166
#: olgram/commands/menu.py:222
msgid "<< Назад"
msgstr "<< 返回"
#: olgram/commands/menu.py:78
msgid ""
"\n"
" Этот бот не добавлен в чаты, поэтому все сообщения будут приходить "
"вам в бот.\n"
" Чтобы подключить чат — добавьте бот @{0} в чат, откройте это меню "
"ещё раз и выберите добавленный чат.\n"
" Если ваш бот состоял в групповом чате до того, как его добавили в "
"Olgram - удалите бота из чата и добавьте\n"
" снова.\n"
" "
msgstr ""
"\n"
" 这个机器人没有被添加到聊天记录中,所以所有的信息都会在机器人中找到"
"你。\n"
" 要连接聊天--将机器人@{0}添加到聊天中,再次打开此菜单并选择添加的聊"
"天。\n"
" 如果你的机器人在添加到Olgram之前是在群组聊天中请将其从聊天室中删"
"除,然后添加到群组中。\n"
" 再次。\n"
" "
#: olgram/commands/menu.py:85
msgid ""
"\n"
" В этом разделе вы можете привязать бота @{0} к чату.\n"
" Выберите чат, куда бот будет пересылать сообщения.\n"
" "
msgstr ""
"\n"
" 在本节中,您可以将@{0}机器人绑定到一个聊天室。\n"
" 选择机器人将转发消息的聊天室。\n"
" "
#: olgram/commands/menu.py:97
msgid "Текст"
msgstr "文本"
#: olgram/commands/menu.py:102
msgid "Чат"
msgstr "聊天"
#: olgram/commands/menu.py:107
msgid "Удалить бот"
msgstr "删除机器人"
#: olgram/commands/menu.py:112
msgid "Статистика"
msgstr "统计数据"
#: olgram/commands/menu.py:121
msgid "Опции"
msgstr "选择"
#: olgram/commands/menu.py:126
msgid ""
"\n"
" Управление ботом @{0}.\n"
"\n"
" Если у вас возникли вопросы по настройке бота, то посмотрите нашу "
"справку /help или напишите нам\n"
" @civsocit_feedback_bot\n"
" "
msgstr ""
"\n"
" 机器人管理@{0}。\n"
"\n"
" 如果你有任何关于机器人配置的问题,请参阅我们的帮助/help或给我们发电子邮"
"件\n"
" @civsocit_feedback_bot\n"
" "
#: olgram/commands/menu.py:138
msgid "Да, удалить бот"
msgstr "是的,删除该机器人"
#: olgram/commands/menu.py:147
msgid ""
"\n"
" Вы уверены, что хотите удалить бота @{0}?\n"
" "
msgstr ""
"\n"
" 你确定要删除机器人@{0}吗?\n"
" "
#: olgram/commands/menu.py:156
msgid "Потоки сообщений"
msgstr "信息流"
#: olgram/commands/menu.py:161
msgid "Данные пользователя"
msgstr "用户数据"
#: olgram/commands/menu.py:171 olgram/commands/menu.py:172
msgid "включены"
msgstr "包括"
#: olgram/commands/menu.py:171 olgram/commands/menu.py:172
msgid "выключены"
msgstr "关闭"
#: olgram/commands/menu.py:173
msgid ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#threads"
"\">Потоки сообщений</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-info"
"\">Данные пользователя</a>: <b>{1}</b>\n"
" "
msgstr ""
"\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#threads\">"
"信息流</a>: <b>{0}</b>\n"
" <a href=\"https://olgram.readthedocs.io/ru/latest/options.html#user-info"
"\">用户数据</a>: <b>{1}</b>\n"
" "
#: olgram/commands/menu.py:185 olgram/commands/menu.py:247
#: olgram/commands/menu.py:289
msgid "<< Завершить редактирование"
msgstr "<< 完成编辑"
#: olgram/commands/menu.py:189
msgid "Автоответчик"
msgstr "答录机"
#: olgram/commands/menu.py:194 olgram/commands/menu.py:261
msgid "Сбросить текст"
msgstr "重置文本"
#: olgram/commands/menu.py:199
msgid ""
"\n"
" Сейчас вы редактируете текст, который отправляется после того, как "
"пользователь отправит вашему боту @{0}\n"
" команду /start\n"
"\n"
" Текущий текст:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n"
" "
msgstr ""
"\n"
" 你现在正在编辑用户向你的机器人发送@{0}之后的文本。\n"
" /启动命令\n"
"\n"
" 目前的文本。\n"
" <pre>\n"
" {1}\n"
" </pre>。\n"
" 发送消息,改变文本。\n"
" "
#: olgram/commands/menu.py:226
msgid ""
"\n"
" Статистика по боту @{0}\n"
"\n"
" Входящих сообщений: <b>{1}</b>\n"
" Ответных сообщений: <b>{2}</b>\n"
" Шаблоны ответов: <b>{3}</b>\n"
" Забанено пользователей: <b>{4}</b>\n"
" "
msgstr ""
"\n"
" 机器人统计 @{0}\n"
"\n"
" 收到的信息: <b>{1}</b>\n"
" 回复信息: <b>{2}</b>\n"
" 答案模板: <b>{3}</b>\n"
" 被禁止的用户: <b>{4}</b>\n"
" "
#: olgram/commands/menu.py:251
msgid "Предыдущий текст"
msgstr ""
#: olgram/commands/menu.py:256
msgid "Шаблоны ответов..."
msgstr "回答模板..."
#: olgram/commands/menu.py:266
msgid ""
"\n"
" Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в "
"ответ на все входящие сообщения @{0} автоматически. По умолчанию оно "
"отключено.\n"
"\n"
" Текущий текст:\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте сообщение, чтобы изменить текст.\n"
" "
msgstr ""
"\n"
" 你现在正在编辑自动回复的文本。该信息会自动响应所有收到的@{0}信息而发送。"
"默认情况下,它是禁用的。\n"
"\n"
" 目前的文本。\n"
" <pre>\n"
" {1}\n"
" </pre>。\n"
" 发送消息,改变文本。\n"
" "
#: olgram/commands/menu.py:276
msgid "(отключено)"
msgstr "(关闭)"
#: olgram/commands/menu.py:293
msgid ""
"\n"
" Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:\n"
"\n"
" <pre>\n"
" {1}\n"
" </pre>\n"
" Отправьте какую-нибудь фразу (например: \"Ваш заказ готов, ожидайте!\"), "
"чтобы добавить её в шаблон.\n"
" Чтобы удалить шаблон из списка, отправьте его номер в списке (например, "
"4)\n"
" "
msgstr ""
"\n"
" 你现在正在编辑@{0}的答案模板。目前的模板。\n"
"\n"
" <pre>\n"
" {1}\n"
" </pre>。\n"
" 发送一个短语(例如:\"您的订单已准备好,请等待!\"),将其添加到模板"
"中。\n"
" 要从列表中删除一个模板请发送它在列表中的编号如4。\n"
" "
#: olgram/commands/menu.py:312
msgid "(нет шаблонов)"
msgstr "(没有模板)"
#: olgram/commands/menu.py:351
msgid "У вас нет шаблонов, чтобы их удалять"
msgstr "你没有模板来删除它们"
#: olgram/commands/menu.py:353
msgid "Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}"
msgstr "不正确的数字。要删除一个模式请在0和{0}之间输入一个数字。"
#: olgram/commands/menu.py:361
msgid "У вашего бота уже слишком много шаблонов"
msgstr "你的机器人已经有太多的模式了"
#: olgram/commands/menu.py:365
msgid "Такой текст уже есть в списке шаблонов"
msgstr "此文本已在模板列表中"
#: olgram/commands/menu.py:383
msgid "У вас нет прав на этого бота"
msgstr "你对这个机器人没有任何权利"
#: olgram/commands/start.py:23
msgid ""
"\n"
" Olgram Bot — это конструктор ботов обратной связи в Telegram. Подробнее "
"<a href=\"https://olgram.readthedocs.io\">читайте здесь</a>.\n"
"\n"
" Используйте эти команды, чтобы управлять этим ботом:\n"
"\n"
" /addbot - добавить бот\n"
" /mybots - управление ботами\n"
"\n"
" /help - помощь\n"
" "
msgstr ""
"\n"
" Olgram Bot — 是一个Telegram反馈机器人的构建者。阅读更多 <a href="
"\"https://olgram.readthedocs.io\">在此阅读</a>.\n"
"\n"
" 使用这些命令来控制这个机器人:\n"
"\n"
" /addbot - 捆绑\n"
" /mybots - 机器人控制\n"
"\n"
" /help - 帮助\n"
" "
#: olgram/commands/start.py:42
msgid ""
"\n"
" Читайте инструкции на нашем сайте https://olgram.readthedocs.io\n"
" Техническая поддержка: @civsocit_feedback_bot\n"
" Версия {0}\n"
" "
msgstr ""
"\n"
" 请阅读我们网站上的说明 https://olgram.readthedocs.io\n"
" 技术支持。@civsocit_feedback_bot\n"
" 版本{0}\n"
" "
#: olgram/models/models.py:30
msgid ""
"\n"
" Здравствуйте!\n"
" Напишите ваш вопрос и мы ответим вам в ближайшее время.\n"
" "
msgstr ""
"\n"
" 你好!\n"
" 请写下您的问题,我们将很快给您答复。\n"
" "
#: olgram/utils/permissions.py:40
msgid "Владелец бота ограничил доступ к этому функционалу 😞"
msgstr "机器人所有者已经限制了对该功能的访问 😞"
#: olgram/utils/permissions.py:52
msgid "Владелец бота ограничил доступ к этому функционалу😞"
msgstr "机器人主人限制了对该功能的访问😞。"
#: server/custom.py:40
msgid ""
"<b>Политика конфиденциальности</b>\n"
"\n"
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При "
"отправке сообщения (кроме команд /start и /security_policy) ваш "
"идентификатор пользователя записывается в кеш на некоторое время и потом "
"удаляется из кеша. Этот идентификатор используется только для общения с "
"оператором; боты Olgram не делают массовых рассылок.\n"
"\n"
msgstr ""
"<b>隐私政策</b\n"
"\n"
"这个机器人不存储你的信息、用户名或@用户名。当你发送消息时(除/start和/"
"security_policy外你的用户名会被缓存一段时间然后从缓存中删除。这个ID只用"
"于与运营商沟通Olgram机器人不做批量信息发送。\n"
"\n"
#: server/custom.py:46
msgid ""
"При отправке сообщения (кроме команд /start и /security_policy) оператор "
"<b>видит</b> ваши имя пользователя, @username и идентификатор пользователя в "
"силу настроек, которые оператор указал при создании бота."
msgstr ""
"当发送消息时(除了/start和/security_policy操作者<b>看到</b>你的用户名、@"
"用户名和用户ID凭借的是操作者在创建机器人时指定的设置。"
#: server/custom.py:50
msgid ""
"В зависимости от ваших настроек конфиденциальности Telegram, оператор может "
"видеть ваш username, имя пользователя и другую информацию."
msgstr ""
"根据你的Telegram隐私设置运营商可能会看到你的用户名用户名和其他信息。"
#: server/custom.py:61
msgid "Сообщение от пользователя "
msgstr "用户的信息 "
#: server/custom.py:88
msgid "Вы заблокированы в этом боте"
msgstr "你在这个机器人中被封锁了"
#: server/custom.py:128
msgid ""
"<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"старое?)</i>"
msgstr "无法转发信息:找不到作者(信息太旧?)"
#: server/custom.py:136
msgid "Пользователь заблокирован"
msgstr "用户被封锁了"
#: server/custom.py:141
msgid "Пользователь не был забанен"
msgstr "该用户没有被禁止"
#: server/custom.py:144
msgid "Пользователь разбанен"
msgstr "解禁的用户"
#: server/custom.py:149
msgid "<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"
msgstr "无法转发该信息(作者已经屏蔽了机器人?)"
#: server/server.py:41
msgid "(Пере)запустить бота"
msgstr "(重新)启动机器人"
#: server/server.py:42
msgid "Политика конфиденциальности"
msgstr "隐私政策"

View File

@ -12,6 +12,7 @@ import olgram.commands.start # noqa: F401
import olgram.commands.menu # noqa: F401 import olgram.commands.menu # noqa: F401
import olgram.commands.bot_actions # noqa: F401 import olgram.commands.bot_actions # noqa: F401
import olgram.commands.info # noqa: F401 import olgram.commands.info # noqa: F401
from locales.locale import _
from server.server import main as server_main from server.server import main as server_main
@ -26,10 +27,10 @@ async def init_olgram():
from aiogram.types import BotCommand from aiogram.types import BotCommand
await bot.set_my_commands( await bot.set_my_commands(
[ [
BotCommand("start", "Запустить бота"), BotCommand("start", _("Запустить бота")),
BotCommand("addbot", "Добавить бот"), BotCommand("addbot", _("Добавить бот")),
BotCommand("mybots", "Управление ботами"), BotCommand("mybots", _("Управление ботами")),
BotCommand("help", "Справка") BotCommand("help", _("Справка"))
] ]
) )

View File

@ -5,6 +5,7 @@ from aiogram import types
from aiogram.utils.exceptions import TelegramAPIError, Unauthorized from aiogram.utils.exceptions import TelegramAPIError, Unauthorized
from olgram.models.models import Bot from olgram.models.models import Bot
from server.server import unregister_token from server.server import unregister_token
from locales.locale import _
async def delete_bot(bot: Bot, call: types.CallbackQuery): async def delete_bot(bot: Bot, call: types.CallbackQuery):
@ -17,7 +18,7 @@ async def delete_bot(bot: Bot, call: types.CallbackQuery):
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы # Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
pass pass
await bot.delete() await bot.delete()
await call.answer("Бот удалён") await call.answer(_("Бот удалён"))
try: try:
await call.message.delete() await call.message.delete()
except TelegramAPIError: except TelegramAPIError:
@ -33,7 +34,7 @@ async def reset_bot_text(bot: Bot, call: types.CallbackQuery):
""" """
bot.start_text = bot._meta.fields_map['start_text'].default bot.start_text = bot._meta.fields_map['start_text'].default
await bot.save() await bot.save()
await call.answer("Текст сброшен") await call.answer(_("Текст сброшен"))
async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery): async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery):
@ -45,7 +46,7 @@ async def reset_bot_second_text(bot: Bot, call: types.CallbackQuery):
""" """
bot.second_text = bot._meta.fields_map['second_text'].default bot.second_text = bot._meta.fields_map['second_text'].default
await bot.save() await bot.save()
await call.answer("Текст сброшен") await call.answer(_("Текст сброшен"))
async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str): async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
@ -59,16 +60,16 @@ async def select_chat(bot: Bot, call: types.CallbackQuery, chat: str):
if chat == "personal": if chat == "personal":
bot.group_chat = None bot.group_chat = None
await bot.save() await bot.save()
await call.answer("Выбран личный чат") await call.answer(_("Выбран личный чат"))
return return
chat_obj = await bot.group_chats.filter(id=chat).first() chat_obj = await bot.group_chats.filter(id=chat).first()
if not chat_obj: if not chat_obj:
await call.answer("Нельзя привязать бота к этому чату") await call.answer(_("Нельзя привязать бота к этому чату"))
return return
bot.group_chat = chat_obj bot.group_chat = chat_obj
await bot.save() await bot.save()
await call.answer(f"Выбран чат {chat_obj.name}") await call.answer(_("Выбран чат {0}").format(chat_obj.name))
async def threads(bot: Bot, call: types.CallbackQuery): async def threads(bot: Bot, call: types.CallbackQuery):

View File

@ -12,6 +12,7 @@ from olgram.models.models import Bot, User
from olgram.settings import OlgramSettings from olgram.settings import OlgramSettings
from olgram.commands.menu import send_bots_menu from olgram.commands.menu import send_bots_menu
from server.server import register_token from server.server import register_token
from locales.locale import _
from olgram.router import dp from olgram.router import dp
@ -38,10 +39,10 @@ async def add_bot(message: types.Message, state: FSMContext):
""" """
bot_count = await Bot.filter(owner__telegram_id=message.from_user.id).count() bot_count = await Bot.filter(owner__telegram_id=message.from_user.id).count()
if bot_count >= OlgramSettings.max_bots_per_user(): if bot_count >= OlgramSettings.max_bots_per_user():
await message.answer("У вас уже слишком много ботов.") await message.answer(_("У вас уже слишком много ботов."))
return return
await message.answer(dedent(""" await message.answer(dedent(_("""
Чтобы подключить бот, вам нужно выполнить три действия: Чтобы подключить бот, вам нужно выполнить три действия:
1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot 1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot
@ -49,7 +50,7 @@ async def add_bot(message: types.Message, state: FSMContext):
3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота. 3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота.
Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других). Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других).
""")) """)))
await state.set_state("add_bot") await state.set_state("add_bot")
@ -61,26 +62,26 @@ async def bot_added(message: types.Message, state: FSMContext):
token = re.findall(token_pattern, message.text) token = re.findall(token_pattern, message.text)
async def on_invalid_token(): async def on_invalid_token():
await message.answer(dedent(""" await message.answer(dedent(_("""
Это не токен бота. Это не токен бота.
Токен выглядит вот так: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12 Токен выглядит вот так: 123456789:AAAA-abc123_AbcdEFghijKLMnopqrstu12
""")) """)))
async def on_dummy_token(): async def on_dummy_token():
await message.answer(dedent(""" await message.answer(dedent(_("""
Не удалось запустить этого бота: неверный токен Не удалось запустить этого бота: неверный токен
""")) """)))
async def on_unknown_error(): async def on_unknown_error():
await message.answer(dedent(""" await message.answer(dedent(_("""
Не удалось запустить этого бота: непредвиденная ошибка Не удалось запустить этого бота: непредвиденная ошибка
""")) """)))
async def on_duplication_bot(): async def on_duplication_bot():
await message.answer(dedent(""" await message.answer(dedent(_("""
Такой бот уже есть в базе данных Такой бот уже есть в базе данных
""")) """)))
if not token: if not token:
return await on_invalid_token() return await on_invalid_token()
@ -98,7 +99,7 @@ async def bot_added(message: types.Message, state: FSMContext):
except TelegramAPIError: except TelegramAPIError:
return await on_unknown_error() return await on_unknown_error()
user, _ = await User.get_or_create(telegram_id=message.from_user.id) user, created = await User.get_or_create(telegram_id=message.from_user.id)
bot = Bot(token=Bot.encrypted_token(token), owner=user, name=test_bot_info.username, bot = Bot(token=Bot.encrypted_token(token), owner=user, name=test_bot_info.username,
super_chat_id=message.from_user.id) super_chat_id=message.from_user.id)
try: try:
@ -110,5 +111,5 @@ async def bot_added(message: types.Message, state: FSMContext):
await bot.delete() await bot.delete()
return await on_unknown_error() return await on_unknown_error()
await message.answer("Бот добавлен! Список ваших ботов: /mybots") await message.answer(_("Бот добавлен! Список ваших ботов: /mybots"))
await state.reset_state() await state.reset_state()

View File

@ -8,6 +8,7 @@ from olgram.models import models
from olgram.router import dp from olgram.router import dp
from olgram.settings import OlgramSettings from olgram.settings import OlgramSettings
from locales.locale import _
@dp.message_handler(commands=["info"], state="*") @dp.message_handler(commands=["info"], state="*")
@ -17,7 +18,7 @@ async def info(message: types.Message, state: FSMContext):
""" """
if message.chat.id != OlgramSettings.supervisor_id(): if message.chat.id != OlgramSettings.supervisor_id():
await message.answer("Недостаточно прав") await message.answer(_("Недостаточно прав"))
return return
bots = await models.Bot.all() bots = await models.Bot.all()
@ -28,8 +29,8 @@ async def info(message: types.Message, state: FSMContext):
income_messages = sum([bot.incoming_messages_count for bot in bots]) income_messages = sum([bot.incoming_messages_count for bot in bots])
outgoing_messages = sum([bot.outgoing_messages_count for bot in bots]) outgoing_messages = sum([bot.outgoing_messages_count for bot in bots])
await message.answer(f"Количество ботов: {bots_count}\n" await message.answer(_("Количество ботов: {0}\n").format(bots_count) +
f"Количество пользователей (у конструктора): {user_count}\n" _("Количество пользователей (у конструктора): {0}\n").format(user_count) +
f"Шаблонов ответов: {templates_count}\n" _("Шаблонов ответов: {0}\n").format(templates_count) +
f"Входящих сообщений у всех ботов: {income_messages}\n" _("Входящих сообщений у всех ботов: {0}\n").format(income_messages) +
f"Исходящих сообщений у всех ботов: {outgoing_messages}\n") _("Исходящих сообщений у всех ботов: {0}\n").format(outgoing_messages))

View File

@ -7,6 +7,7 @@ from aiogram.utils.callback_data import CallbackData
from textwrap import dedent from textwrap import dedent
from olgram.utils.mix import edit_or_create, button_text_limit, wrap from olgram.utils.mix import edit_or_create, button_text_limit, wrap
from olgram.commands import bot_actions from olgram.commands import bot_actions
from locales.locale import _
import typing as ty import typing as ty
@ -27,11 +28,11 @@ async def send_bots_menu(chat_id: int, user_id: int, call=None):
user = await User.get_or_none(telegram_id=user_id) user = await User.get_or_none(telegram_id=user_id)
bots = await Bot.filter(owner=user) bots = await Bot.filter(owner=user)
if not bots: if not bots:
await AioBot.get_current().send_message(chat_id, dedent(""" await AioBot.get_current().send_message(chat_id, dedent(_("""
У вас нет добавленных ботов. У вас нет добавленных ботов.
Отправьте команду /addbot, чтобы добавить бот. Отправьте команду /addbot, чтобы добавить бот.
""")) """)))
return return
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
@ -42,7 +43,7 @@ async def send_bots_menu(chat_id: int, user_id: int, call=None):
chat=empty)) chat=empty))
) )
text = "Ваши боты" text = _("Ваши боты")
if call: if call:
await edit_or_create(call, text, keyboard) await edit_or_create(call, text, keyboard)
else: else:
@ -63,28 +64,28 @@ async def send_chats_menu(bot: Bot, call: types.CallbackQuery):
) )
if chats: if chats:
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Личные сообщения", types.InlineKeyboardButton(text=_("Личные сообщения"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="chat",
chat="personal")) chat="personal"))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Назад", types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
chat=empty)) chat=empty))
) )
if not chats: if not chats:
text = dedent(f""" text = dedent(_("""
Этот бот не добавлен в чаты, поэтому все сообщения будут приходить вам в бот. Этот бот не добавлен в чаты, поэтому все сообщения будут приходить вам в бот.
Чтобы подключить чат добавьте бот @{bot.name} в чат, откройте это меню ещё раз и выберите добавленный чат. Чтобы подключить чат добавьте бот @{0} в чат, откройте это меню ещё раз и выберите добавленный чат.
Если ваш бот состоял в групповом чате до того, как его добавили в Olgram - удалите бота из чата и добавьте Если ваш бот состоял в групповом чате до того, как его добавили в Olgram - удалите бота из чата и добавьте
снова. снова.
""") """)).format(bot.name)
else: else:
text = dedent(f""" text = dedent(_("""
В этом разделе вы можете привязать бота @{bot.name} к чату. В этом разделе вы можете привязать бота @{0} к чату.
Выберите чат, куда бот будет пересылать сообщения. Выберите чат, куда бот будет пересылать сообщения.
""") """)).format(bot.name)
await edit_or_create(call, text, keyboard) await edit_or_create(call, text, keyboard)
@ -93,86 +94,86 @@ async def send_bot_menu(bot: Bot, call: types.CallbackQuery):
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Текст", types.InlineKeyboardButton(text=_("Текст"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Чат", types.InlineKeyboardButton(text=_("Чат"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="chat", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="chat",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Удалить бот", types.InlineKeyboardButton(text=_("Удалить бот"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="delete", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="delete",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Статистика", types.InlineKeyboardButton(text=_("Статистика"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="stat", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="stat",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Назад", types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=0, bot_id=empty, operation=empty, chat=empty)) callback_data=menu_callback.new(level=0, bot_id=empty, operation=empty, chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Опции", types.InlineKeyboardButton(text=_("Опции"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="settings",
chat=empty)) chat=empty))
) )
await edit_or_create(call, dedent(f""" await edit_or_create(call, dedent(_("""
Управление ботом @{bot.name}. Управление ботом @{0}.
Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help или напишите нам Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help или напишите нам
@civsocit_feedback_bot @civsocit_feedback_bot
"""), reply_markup=keyboard) """)).format(bot.name), reply_markup=keyboard)
async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery): async def send_bot_delete_menu(bot: Bot, call: types.CallbackQuery):
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Да, удалить бот", types.InlineKeyboardButton(text=_("Да, удалить бот"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="delete_yes", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="delete_yes",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Назад", types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
) )
await edit_or_create(call, dedent(f""" await edit_or_create(call, dedent(_("""
Вы уверены, что хотите удалить бота @{bot.name}? Вы уверены, что хотите удалить бота @{0}?
"""), reply_markup=keyboard) """)).format(bot.name), reply_markup=keyboard)
async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery): async def send_bot_settings_menu(bot: Bot, call: types.CallbackQuery):
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=1) keyboard = types.InlineKeyboardMarkup(row_width=1)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Потоки сообщений", types.InlineKeyboardButton(text=_("Потоки сообщений"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="threads", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="threads",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Данные пользователя", types.InlineKeyboardButton(text=_("Данные пользователя"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="additional_info", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="additional_info",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Назад", types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty,
chat=empty)) chat=empty))
) )
thread_turn = "включены" if bot.enable_threads else "выключены" thread_turn = _("включены") if bot.enable_threads else _("выключены")
info_turn = "включены" if bot.enable_additional_info else "выключены" info_turn = _("включены") if bot.enable_additional_info else _("выключены")
text = dedent(f""" text = dedent(_("""
<a href="https://olgram.readthedocs.io/ru/latest/options.html#threads">Потоки сообщений</a>: <b>{thread_turn}</b> <a href="https://olgram.readthedocs.io/ru/latest/options.html#threads">Потоки сообщений</a>: <b>{0}</b>
<a href="https://olgram.readthedocs.io/ru/latest/options.html#user-info">Данные пользователя</a>: <b>{info_turn}</b> <a href="https://olgram.readthedocs.io/ru/latest/options.html#user-info">Данные пользователя</a>: <b>{1}</b>
""") """)).format(thread_turn, info_turn)
await edit_or_create(call, text, reply_markup=keyboard, parse_mode="HTML") await edit_or_create(call, text, reply_markup=keyboard, parse_mode="HTML")
@ -181,21 +182,21 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Завершить редактирование", types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Автоответчик", types.InlineKeyboardButton(text=_("Автоответчик"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="next_text", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="next_text",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Сбросить текст", types.InlineKeyboardButton(text=_("Сбросить текст"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="reset_text",
chat=empty)) chat=empty))
) )
text = dedent(""" text = dedent(_("""
Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту @{0} Сейчас вы редактируете текст, который отправляется после того, как пользователь отправит вашему боту @{0}
команду /start команду /start
@ -204,7 +205,7 @@ async def send_bot_text_menu(bot: Bot, call: ty.Optional[types.CallbackQuery] =
{1} {1}
</pre> </pre>
Отправьте сообщение, чтобы изменить текст. Отправьте сообщение, чтобы изменить текст.
""") """))
text = text.format(bot.name, bot.start_text) text = text.format(bot.name, bot.start_text)
if call: if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML") await edit_or_create(call, text, keyboard, parse_mode="HTML")
@ -218,18 +219,19 @@ async def send_bot_statistic_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Назад", types.InlineKeyboardButton(text=_("<< Назад"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
) )
text = dedent(f""" text = dedent(_("""
Статистика по боту @{bot.name} Статистика по боту @{0}
Входящих сообщений: <b>{bot.incoming_messages_count}</b> Входящих сообщений: <b>{1}</b>
Ответных сообщений: <b>{bot.outgoing_messages_count}</b> Ответных сообщений: <b>{2}</b>
Шаблоны ответов: <b>{len(await bot.answers)}</b> Шаблоны ответов: <b>{3}</b>
Забанено пользователей: <b>{len(await bot.banned_users)}</b> Забанено пользователей: <b>{4}</b>
""") """)).format(bot.name, bot.incoming_messages_count, bot.outgoing_messages_count, len(await bot.answers),
len(await bot.banned_users))
if call: if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML") await edit_or_create(call, text, keyboard, parse_mode="HTML")
else: else:
@ -242,26 +244,26 @@ async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQu
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Завершить редактирование", types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Предыдущий текст", types.InlineKeyboardButton(text=_("Предыдущий текст"),
callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text", callback_data=menu_callback.new(level=2, bot_id=bot.id, operation="text",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Шаблоны ответов...", types.InlineKeyboardButton(text=_("Шаблоны ответов..."),
callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="templates", callback_data=menu_callback.new(level=3, bot_id=bot.id, operation="templates",
chat=empty)) chat=empty))
) )
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="Сбросить текст", types.InlineKeyboardButton(text=_("Сбросить текст"),
callback_data=menu_callback.new(level=3, bot_id=bot.id, callback_data=menu_callback.new(level=3, bot_id=bot.id,
operation="reset_second_text", chat=empty)) operation="reset_second_text", chat=empty))
) )
text = dedent(""" text = dedent(_("""
Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \ Сейчас вы редактируете текст автоответчика. Это сообщение отправляется в ответ на все входящие сообщения @{0} \
автоматически. По умолчанию оно отключено. автоматически. По умолчанию оно отключено.
@ -270,8 +272,8 @@ async def send_bot_second_text_menu(bot: Bot, call: ty.Optional[types.CallbackQu
{1} {1}
</pre> </pre>
Отправьте сообщение, чтобы изменить текст. Отправьте сообщение, чтобы изменить текст.
""") """))
text = text.format(bot.name, bot.second_text if bot.second_text else "(отключено)") text = text.format(bot.name, bot.second_text if bot.second_text else _("(отключено)"))
if call: if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML") await edit_or_create(call, text, keyboard, parse_mode="HTML")
else: else:
@ -284,11 +286,11 @@ async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
await call.answer() await call.answer()
keyboard = types.InlineKeyboardMarkup(row_width=2) keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert( keyboard.insert(
types.InlineKeyboardButton(text="<< Завершить редактирование", types.InlineKeyboardButton(text=_("<< Завершить редактирование"),
callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty)) callback_data=menu_callback.new(level=1, bot_id=bot.id, operation=empty, chat=empty))
) )
text = dedent(""" text = dedent(_("""
Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны: Сейчас вы редактируете шаблоны ответов для @{0}. Текущие шаблоны:
<pre> <pre>
@ -296,7 +298,7 @@ async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
</pre> </pre>
Отправьте какую-нибудь фразу (например: "Ваш заказ готов, ожидайте!"), чтобы добавить её в шаблон. Отправьте какую-нибудь фразу (например: "Ваш заказ готов, ожидайте!"), чтобы добавить её в шаблон.
Чтобы удалить шаблон из списка, отправьте его номер в списке (например, 4) Чтобы удалить шаблон из списка, отправьте его номер в списке (например, 4)
""") """))
templates = await bot.answers templates = await bot.answers
@ -307,7 +309,7 @@ async def send_bot_templates_menu(bot: Bot, call: ty.Optional[types.CallbackQuer
templates_text = "\n".join(f"{n}. {wrap(template.text, max_len)}" for n, template in enumerate(templates)) templates_text = "\n".join(f"{n}. {wrap(template.text, max_len)}" for n, template in enumerate(templates))
if not templates_text: if not templates_text:
templates_text = "(нет шаблонов)" templates_text = _("(нет шаблонов)")
text = text.format(bot.name, templates_text) text = text.format(bot.name, templates_text)
if call: if call:
await edit_or_create(call, text, keyboard, parse_mode="HTML") await edit_or_create(call, text, keyboard, parse_mode="HTML")
@ -346,20 +348,21 @@ async def template_received(message: types.Message, state: FSMContext):
number = int(message.text) number = int(message.text)
templates = await bot.answers templates = await bot.answers
if not templates: if not templates:
await message.answer("У вас нет шаблонов, чтобы их удалять") await message.answer(_("У вас нет шаблонов, чтобы их удалять"))
if number < 0 or number >= len(templates): if number < 0 or number >= len(templates):
await message.answer(f"Неправильное число. Чтобы удалить шаблон, введите число от 0 до {len(templates)}") await message.answer(_("Неправильное число. Чтобы удалить шаблон, введите число от 0 до {0}").format(
len(templates)))
return return
await templates[number].delete() await templates[number].delete()
else: else:
# Add template # Add template
total_templates = len(await bot.answers) total_templates = len(await bot.answers)
if total_templates > 30: if total_templates > 30:
await message.answer("У вашего бота уже слишком много шаблонов") await message.answer(_("У вашего бота уже слишком много шаблонов"))
else: else:
answers = await bot.answers.filter(text=message.text) answers = await bot.answers.filter(text=message.text)
if answers: if answers:
await message.answer("Такой текст уже есть в списке шаблонов") await message.answer(_("Такой текст уже есть в списке шаблонов"))
else: else:
template = DefaultAnswer(text=message.text, bot=bot) template = DefaultAnswer(text=message.text, bot=bot)
await template.save() await template.save()
@ -377,7 +380,7 @@ async def callback(call: types.CallbackQuery, callback_data: dict, state: FSMCon
bot_id = callback_data.get("bot_id") bot_id = callback_data.get("bot_id")
bot = await Bot.get_or_none(id=bot_id) bot = await Bot.get_or_none(id=bot_id)
if not bot or (await bot.owner).telegram_id != call.from_user.id: if not bot or (await bot.owner).telegram_id != call.from_user.id:
await call.answer("У вас нет прав на этого бота", show_alert=True) await call.answer(_("У вас нет прав на этого бота"), show_alert=True)
return return
if level == "1": if level == "1":

View File

@ -7,6 +7,7 @@ from aiogram.dispatcher import FSMContext
from textwrap import dedent from textwrap import dedent
from olgram.settings import OlgramSettings from olgram.settings import OlgramSettings
from olgram.utils.permissions import public from olgram.utils.permissions import public
from locales.locale import _
from olgram.router import dp from olgram.router import dp
@ -19,9 +20,7 @@ async def start(message: types.Message, state: FSMContext):
""" """
await state.reset_state() await state.reset_state()
# TODO: locale await message.answer(dedent(_("""
await message.answer(dedent("""
Olgram Bot это конструктор ботов обратной связи в Telegram. Подробнее \ Olgram Bot это конструктор ботов обратной связи в Telegram. Подробнее \
<a href="https://olgram.readthedocs.io">читайте здесь</a>. <a href="https://olgram.readthedocs.io">читайте здесь</a>.
@ -31,7 +30,7 @@ async def start(message: types.Message, state: FSMContext):
/mybots - управление ботами /mybots - управление ботами
/help - помощь /help - помощь
"""), parse_mode="html") """)), parse_mode="html")
@dp.message_handler(commands=["help"], state="*") @dp.message_handler(commands=["help"], state="*")
@ -40,11 +39,11 @@ async def help(message: types.Message, state: FSMContext):
""" """
Команда /help Команда /help
""" """
await message.answer(dedent(f""" await message.answer(dedent(_("""
Читайте инструкции на нашем сайте https://olgram.readthedocs.io Читайте инструкции на нашем сайте https://olgram.readthedocs.io
Техническая поддержка: @civsocit_feedback_bot Техническая поддержка: @civsocit_feedback_bot
Версия {OlgramSettings.version()} Версия {0}
""")) """)).format(OlgramSettings.version()))
@dp.message_handler(commands=["chatid"], state="*") @dp.message_handler(commands=["chatid"], state="*")

View File

@ -3,6 +3,7 @@ from tortoise import fields
from uuid import uuid4 from uuid import uuid4
from textwrap import dedent from textwrap import dedent
from olgram.settings import DatabaseSettings from olgram.settings import DatabaseSettings
from locales.locale import _
class MetaInfo(Model): class MetaInfo(Model):
@ -26,10 +27,10 @@ class Bot(Model):
owner = fields.ForeignKeyField("models.User", related_name="bots") owner = fields.ForeignKeyField("models.User", related_name="bots")
name = fields.CharField(max_length=33) name = fields.CharField(max_length=33)
code = fields.UUIDField(default=uuid4, index=True) code = fields.UUIDField(default=uuid4, index=True)
start_text = fields.TextField(default=dedent(""" start_text = fields.TextField(default=dedent(_("""
Здравствуйте! Здравствуйте!
Напишите ваш вопрос и мы ответим вам в ближайшее время. Напишите ваш вопрос и мы ответим вам в ближайшее время.
""")) """)))
second_text = fields.TextField(null=True, default=None) second_text = fields.TextField(null=True, default=None)
group_chats = fields.ManyToManyField("models.GroupChat", related_name="bots", on_delete=fields.relational.CASCADE, group_chats = fields.ManyToManyField("models.GroupChat", related_name="bots", on_delete=fields.relational.CASCADE,

View File

@ -109,6 +109,14 @@ class BotSettings(AbstractSettings):
""" """
return cls._get_env("BOT_TOKEN") return cls._get_env("BOT_TOKEN")
@classmethod
def language(cls) -> str:
"""
Язык
"""
lang = cls._get_env("O_LANG", allow_none=True)
return lang.lower() if lang else "ru"
class DatabaseSettings(AbstractSettings): class DatabaseSettings(AbstractSettings):
@classmethod @classmethod

View File

@ -1,6 +1,7 @@
import aiogram.types as types import aiogram.types as types
from aiogram.dispatcher.handler import CancelHandler, current_handler from aiogram.dispatcher.handler import CancelHandler, current_handler
from aiogram.dispatcher.middlewares import BaseMiddleware from aiogram.dispatcher.middlewares import BaseMiddleware
from locales.locale import _
def public(): def public():
@ -36,7 +37,7 @@ class AccessMiddleware(BaseMiddleware):
return return
if message.chat.id != admin_id: if message.chat.id != admin_id:
await message.answer("Владелец бота ограничил доступ к этому функционалу 😞") await message.answer(_("Владелец бота ограничил доступ к этому функционалу 😞"))
raise CancelHandler() raise CancelHandler()
async def on_process_callback_query(self, call: types.CallbackQuery, data: dict): async def on_process_callback_query(self, call: types.CallbackQuery, data: dict):
@ -48,5 +49,5 @@ class AccessMiddleware(BaseMiddleware):
return return
if call.message.chat.id != admin_id: if call.message.chat.id != admin_id:
await call.answer("Владелец бота ограничил доступ к этому функционалу😞") await call.answer(_("Владелец бота ограничил доступ к этому функционалу😞"))
raise CancelHandler() raise CancelHandler()

13
poetry.lock generated
View File

@ -341,6 +341,14 @@ python-versions = ">=3.5"
[package.extras] [package.extras]
cli = ["click (>=5.0)"] cli = ["click (>=5.0)"]
[[package]]
name = "python-gettext"
version = "4.0"
description = "Python Gettext po to mo file compiler."
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2020.5" version = "2020.5"
@ -393,7 +401,7 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "20aec5e4517c3e0bc03d4410544c1c898f0469c9bcfc4f4fecf4c405b1765ded" content-hash = "9151864c69f349148a36cc7551bb54fd240229eb2533e92b82a0f85a251a56f3"
[metadata.files] [metadata.files]
aerich = [ aerich = [
@ -825,6 +833,9 @@ python-dotenv = [
{file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"}, {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
{file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"}, {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
] ]
python-gettext = [
{file = "python-gettext-4.0.tar.gz", hash = "sha256:626b501a51ac892fc3460cf550e60dca121f544eaa46eb69c90ce4682fc7ec02"},
]
pytz = [ pytz = [
{file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},

View File

@ -15,6 +15,7 @@ pycrypto = "^2.6.1"
aioredis = "1.3" aioredis = "1.3"
aerich = "0.5.x" aerich = "0.5.x"
tortoise-orm = {extras = ["asyncpg"], version = "^0.18.1"} tortoise-orm = {extras = ["asyncpg"], version = "^0.18.1"}
python-gettext = "^4.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "^4.0.1" flake8 = "^4.0.1"

1
refresh_lang.sh Normal file
View File

@ -0,0 +1 @@
/usr/lib/python3.10/Tools/i18n/pygettext.py -d chinese -o locales/zh/LC_MESSAGES/olgram.pot olgram/ server/

View File

@ -12,6 +12,7 @@ import logging
import typing as ty import typing as ty
from olgram.settings import ServerSettings from olgram.settings import ServerSettings
from olgram.models.models import Bot, GroupChat, BannedUser from olgram.models.models import Bot, GroupChat, BannedUser
from locales.locale import _
from server.inlines import inline_handler from server.inlines import inline_handler
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -36,18 +37,18 @@ def _thread_uniqie_id(bot_id: int, chat_id: int) -> str:
def _on_security_policy(message: types.Message, bot): def _on_security_policy(message: types.Message, bot):
text = "<b>Политика конфиденциальности</b>\n\n" \ text = _("<b>Политика конфиденциальности</b>\n\n" \
"Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд " \ "Этот бот не хранит ваши сообщения, имя пользователя и @username. При отправке сообщения (кроме команд " \
"/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом " \ "/start и /security_policy) ваш идентификатор пользователя записывается в кеш на некоторое время и потом " \
"удаляется из кеша. Этот идентификатор используется только для общения с оператором; боты Olgram " \ "удаляется из кеша. Этот идентификатор используется только для общения с оператором; боты Olgram " \
"не делают массовых рассылок.\n\n" "не делают массовых рассылок.\n\n")
if bot.enable_additional_info: if bot.enable_additional_info:
text += "При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя " \ text += _("При отправке сообщения (кроме команд /start и /security_policy) оператор <b>видит</b> ваши имя " \
"пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при " \ "пользователя, @username и идентификатор пользователя в силу настроек, которые оператор указал при " \
"создании бота." "создании бота.")
else: else:
text += "В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, " \ text += _("В зависимости от ваших настроек конфиденциальности Telegram, оператор может видеть ваш username, " \
"имя пользователя и другую информацию." "имя пользователя и другую информацию.")
return SendMessage(chat_id=message.chat.id, return SendMessage(chat_id=message.chat.id,
text=text, text=text,
@ -57,7 +58,7 @@ def _on_security_policy(message: types.Message, bot):
async def send_user_message(message: types.Message, super_chat_id: int, bot): async def send_user_message(message: types.Message, super_chat_id: int, bot):
"""Переслать сообщение от пользователя, добавлять к нему user info при необходимости""" """Переслать сообщение от пользователя, добавлять к нему user info при необходимости"""
if bot.enable_additional_info: if bot.enable_additional_info:
user_info = "Сообщение от пользователя " user_info = _("Сообщение от пользователя ")
user_info += message.from_user.full_name user_info += message.from_user.full_name
if message.from_user.username: if message.from_user.username:
user_info += " | @" + message.from_user.username user_info += " | @" + message.from_user.username
@ -84,7 +85,7 @@ async def handle_user_message(message: types.Message, super_chat_id: int, bot):
banned = await bot.banned_users.filter(telegram_id=message.chat.id) banned = await bot.banned_users.filter(telegram_id=message.chat.id)
if banned: if banned:
return SendMessage(chat_id=message.chat.id, return SendMessage(chat_id=message.chat.id,
text="Вы заблокированы в этом боте") text=_("Вы заблокированы в этом боте"))
# Пересылаем сообщение в супер-чат # Пересылаем сообщение в супер-чат
if is_super_group and bot.enable_threads: if is_super_group and bot.enable_threads:
@ -124,28 +125,28 @@ async def handle_operator_message(message: types.Message, super_chat_id: int, bo
chat_id = message.reply_to_message.forward_from_chat chat_id = message.reply_to_message.forward_from_chat
if not chat_id: if not chat_id:
return SendMessage(chat_id=message.chat.id, return SendMessage(chat_id=message.chat.id,
text="<i>Невозможно переслать сообщение: автор не найден " text=_("<i>Невозможно переслать сообщение: автор не найден (сообщение слишком "
"(сообщение слишком старое?)</i>", "старое?)</i>"),
parse_mode="HTML") parse_mode="HTML")
chat_id = int(chat_id) chat_id = int(chat_id)
if message.text == "/ban": if message.text == "/ban":
user, _ = await BannedUser.get_or_create(telegram_id=chat_id, bot=bot) user, create = await BannedUser.get_or_create(telegram_id=chat_id, bot=bot)
await user.save() await user.save()
return SendMessage(chat_id=message.chat.id, text="Пользователь заблокирован") return SendMessage(chat_id=message.chat.id, text=_("Пользователь заблокирован"))
if message.text == "/unban": if message.text == "/unban":
banned_user = await bot.banned_users.filter(telegram_id=chat_id).first() banned_user = await bot.banned_users.filter(telegram_id=chat_id).first()
if not banned_user: if not banned_user:
return SendMessage(chat_id=message.chat.id, text="Пользователь не был забанен") return SendMessage(chat_id=message.chat.id, text=_("Пользователь не был забанен"))
else: else:
await banned_user.delete() await banned_user.delete()
return SendMessage(chat_id=message.chat.id, text="Пользователь разбанен") return SendMessage(chat_id=message.chat.id, text=_("Пользователь разбанен"))
try: try:
await message.copy_to(chat_id) await message.copy_to(chat_id)
except (exceptions.MessageError, exceptions.Unauthorized): except (exceptions.MessageError, exceptions.Unauthorized):
await message.reply("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>", await message.reply(_("<i>Невозможно переслать сообщение (автор заблокировал бота?)</i>"),
parse_mode="HTML") parse_mode="HTML")
return return

View File

@ -5,6 +5,7 @@ from aiohttp import web
from asyncio import get_event_loop from asyncio import get_event_loop
import ssl import ssl
from olgram.settings import ServerSettings from olgram.settings import ServerSettings
from locales.locale import _
from .custom import CustomRequestHandler from .custom import CustomRequestHandler
import logging import logging
@ -37,8 +38,8 @@ async def register_token(bot: Bot) -> bool:
res = await a_bot.set_webhook(url_for_bot(bot), certificate=certificate, drop_pending_updates=True, res = await a_bot.set_webhook(url_for_bot(bot), certificate=certificate, drop_pending_updates=True,
max_connections=10) max_connections=10)
await a_bot.set_my_commands([ await a_bot.set_my_commands([
BotCommand("/start", "(Пере)запустить бота"), BotCommand("/start", _("(Пере)запустить бота")),
BotCommand("/security_policy", "Политика конфиденциальности") BotCommand("/security_policy", _("Политика конфиденциальности"))
]) ])
await a_bot.session.close() await a_bot.session.close()