mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 19:26:10 +00:00
Добавлена локализация, теперь приложение также переведено на английский
This commit is contained in:
parent
83f4cac86b
commit
7ecdf5ea39
@ -10,6 +10,6 @@ API_HASH=b18441a1ff607e10a989891a5462e627
|
|||||||
|
|
||||||
# Конфиг
|
# Конфиг
|
||||||
CURRENT_USER = "user"
|
CURRENT_USER = "user"
|
||||||
DO_NOTIFY = False
|
DO_NOTIFY = 0 # Linux-only for now
|
||||||
UTC_OFFSET = 0
|
UTC_OFFSET = 0
|
||||||
LANGUAGE = "en"
|
LANGUAGE = "en" # en, ru
|
||||||
|
@ -31,7 +31,7 @@ pip install -r requirements.txt
|
|||||||
4. Настройте переменные окружения:
|
4. Настройте переменные окружения:
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Отредактируйте .env файл, добавив свои API ключи
|
# Настройте .env файл и добавьте свои API ключи
|
||||||
# Получите ключи на https://my.telegram.org/apps
|
# Получите ключи на https://my.telegram.org/apps
|
||||||
```
|
```
|
||||||
|
|
||||||
|
42
src/app.py
42
src/app.py
@ -4,30 +4,60 @@ from os import getenv
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from telethon import TelegramClient
|
from telethon import TelegramClient
|
||||||
from textual.app import App
|
from textual.app import App
|
||||||
|
from textual.keys import Keys, _character_to_key
|
||||||
|
from datetime import timezone, timedelta
|
||||||
from src.screens import AuthScreen, ChatScreen
|
from src.screens import AuthScreen, ChatScreen
|
||||||
import src.locales
|
import src.locales
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
API_ID = getenv("API_ID")
|
API_ID = getenv("API_ID")
|
||||||
API_HASH = getenv("API_HASH")
|
API_HASH = getenv("API_HASH")
|
||||||
|
LANGUAGE = getenv("LANGUAGE")
|
||||||
|
UTC_OFFSET = getenv("UTC_OFFSET")
|
||||||
|
|
||||||
if not API_ID or not API_HASH:
|
if "" in [API_ID, API_HASH, LANGUAGE, UTC_OFFSET]:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"API_ID и API_HASH не найдены в .env файле. "
|
"Недостаточно параметров в .env файле."
|
||||||
"Пожалуйста, скопируйте .env.example в .env и заполните свои ключи."
|
"Скопируйте .env.example в .env и заполните свои API-ключи."
|
||||||
|
"Not enough settings in .env file."
|
||||||
|
"Copy .env.example into .env and fill your API-keys."
|
||||||
)
|
)
|
||||||
|
|
||||||
API_ID = int(API_ID)
|
API_ID = int(API_ID)
|
||||||
|
locale = dict(zip(
|
||||||
#locale = locales.
|
getattr(src.locales, "codes"), getattr(src.locales, LANGUAGE)
|
||||||
|
))
|
||||||
|
|
||||||
class Talc(App):
|
class Talc(App):
|
||||||
"""Класс приложения"""
|
"""Класс приложения"""
|
||||||
|
|
||||||
CSS_PATH = "style.tcss"
|
CSS_PATH = "style.tcss"
|
||||||
|
BINDINGS = [
|
||||||
|
(Keys.ControlE, "notify(\"Нажата кнопка профиля\")", locale["you"]),
|
||||||
|
(Keys.ControlF, "notify(\"Нажата кнопка папок\")", locale["folders"]),
|
||||||
|
(Keys.Tab, "notify(\"Нажат таб\")", locale["switch_focus"]),
|
||||||
|
(Keys.Enter, "notify(\"Нажат энтер\")", locale["enter"]),
|
||||||
|
(Keys.Escape, "notify(\"Нажат эскейп\")", locale["back"]),
|
||||||
|
(_character_to_key("/"), "notify(\"Нажат слэш\")", locale["search"])
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
driver_class = None,
|
||||||
|
css_path = None,
|
||||||
|
watch_css = False,
|
||||||
|
ansi_color = False
|
||||||
|
):
|
||||||
|
super().__init__(driver_class, css_path, watch_css, ansi_color)
|
||||||
|
self.locale = locale
|
||||||
|
self.timezone = timezone(timedelta(hours=int(UTC_OFFSET)))
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
self.telegram_client = TelegramClient(getenv("CURRENT_USER"), API_ID, API_HASH)
|
self.telegram_client = TelegramClient(
|
||||||
|
getenv("CURRENT_USER"),
|
||||||
|
API_ID,
|
||||||
|
API_HASH
|
||||||
|
)
|
||||||
await self.telegram_client.connect()
|
await self.telegram_client.connect()
|
||||||
|
|
||||||
chat_screen = ChatScreen(telegram_client=self.telegram_client)
|
chat_screen = ChatScreen(telegram_client=self.telegram_client)
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
ru = {"greeting_auth": "Добро пожаловать в Тальк", "phone_number": "Номер телефона", "code": "Код", "password": "Пароль", "you": "Вы", "mention": "Вас упомянули"}
|
codes = ["auth_greeting", "phone_number", "code", "password", "you", "mention", "media", "message", "folders", "switch_focus", "enter", "back", "search", "start_converse"]
|
||||||
en = {"greeting_auth": "Welcome to Talc", "phone_number": "Phone number", "code": "Code", "password": "Password", "you": "You", "mention": "You got mentioned"}
|
ru = ["Добро пожаловать в Тальк", "Номер телефона", "Код", "Пароль", "Вы", "Вас упомянули", "Медиа", "Сообщение", "Папки", "Переключение фокуса", "Открыть", "Назад", "Поиск", "Нажмите на чат в панели слева, чтобы начать общаться"]
|
||||||
|
en = ["Welcome to Talc", "Phone number", "Code", "Password", "You", "You got mentioned", "Media", "Message", "Folders", "Switch focus", "Enter", "Back", "Search", "Click on the chat to start conversation"]
|
@ -22,17 +22,18 @@ class AuthScreen(Screen):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.client = telegram_client
|
self.client = telegram_client
|
||||||
|
self.locale = self.app.locale
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.ac = self.query_one("#auth_container")
|
self.ac = self.query_one("#auth_container")
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Vertical(id="auth_container"):
|
with Vertical(id="auth_container"):
|
||||||
yield Label("Добро пожаловать в Talc")
|
yield Label(self.locale["auth_greeting"])
|
||||||
yield Input(placeholder="Номер телефона", id="phone")
|
yield Input(placeholder=self.locale["phone_number"], id="phone")
|
||||||
yield Input(placeholder="Код", id="code", disabled=True)
|
yield Input(placeholder=self.locale["code"], id="code", disabled=True)
|
||||||
yield Input(
|
yield Input(
|
||||||
placeholder="Пароль",
|
placeholder=self.locale["password"],
|
||||||
id="password",
|
id="password",
|
||||||
password=True,
|
password=True,
|
||||||
disabled=True
|
disabled=True
|
||||||
@ -75,6 +76,7 @@ class ChatScreen(Screen):
|
|||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.telegram_client = telegram_client
|
self.telegram_client = telegram_client
|
||||||
self.DO_NOTIFY = getenv("DO_NOTIFY")
|
self.DO_NOTIFY = getenv("DO_NOTIFY")
|
||||||
|
self.locale = self.app.locale
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
self.limit = 100
|
self.limit = 100
|
||||||
@ -152,6 +154,7 @@ class ChatScreen(Screen):
|
|||||||
|
|
||||||
chat.peername = str(dialogs[i].name)
|
chat.peername = str(dialogs[i].name)
|
||||||
chat.is_group = dialogs[i].is_group
|
chat.is_group = dialogs[i].is_group
|
||||||
|
chat.is_channel = dialogs[i].is_channel
|
||||||
chat.peer_id = dialogs[i].id
|
chat.peer_id = dialogs[i].id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -161,7 +164,7 @@ class ChatScreen(Screen):
|
|||||||
is_my_msg = dialogs[i].id == self.me_id
|
is_my_msg = dialogs[i].id == self.me_id
|
||||||
|
|
||||||
if dialogs[i].is_group and is_my_msg:
|
if dialogs[i].is_group and is_my_msg:
|
||||||
chat.username = "Вы"
|
chat.username = self.locale["you"]
|
||||||
chat.msg = str(dialogs[i].message.message)
|
chat.msg = str(dialogs[i].message.message)
|
||||||
elif dialogs[i].is_group:
|
elif dialogs[i].is_group:
|
||||||
chat.username = str(
|
chat.username = str(
|
||||||
@ -169,7 +172,7 @@ class ChatScreen(Screen):
|
|||||||
)
|
)
|
||||||
chat.msg = str(dialogs[i].message.message)
|
chat.msg = str(dialogs[i].message.message)
|
||||||
elif is_my_msg:
|
elif is_my_msg:
|
||||||
chat.msg = "Вы: " * is_my_msg + str(
|
chat.msg = f"{self.locale["you"]}: " * is_my_msg + str(
|
||||||
dialogs[i].message.message
|
dialogs[i].message.message
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -183,8 +186,8 @@ class ChatScreen(Screen):
|
|||||||
def notify_send(self, event) -> None:
|
def notify_send(self, event) -> None:
|
||||||
if not event:
|
if not event:
|
||||||
return None
|
return None
|
||||||
if bool(self.DO_NOTIFY) and event.mentioned and not self.app.focused:
|
if int(self.DO_NOTIFY) and not self.app.focused and event.mentioned:
|
||||||
system(f"notify-send \"Вас упомянули\" Talc")
|
system(f"notify-send \"{self.locale["mention"]}\" Talc")
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Footer() # Нижняя панель с подсказками
|
yield Footer() # Нижняя панель с подсказками
|
||||||
@ -196,6 +199,6 @@ class ChatScreen(Screen):
|
|||||||
with ContentSwitcher(id="dialog_switcher"):
|
with ContentSwitcher(id="dialog_switcher"):
|
||||||
# ↑ Внутри него как раз крутятся диалоги
|
# ↑ Внутри него как раз крутятся диалоги
|
||||||
yield Label(
|
yield Label(
|
||||||
"Нажмите на чат в панели слева, чтобы начать общаться",
|
self.locale["start_converse"],
|
||||||
id="begin_talk_label"
|
id="start_converse_label"
|
||||||
) #TODO: не показывается надпись, надо будет исправить
|
) #TODO: не показывается надпись, надо будет исправить
|
||||||
|
@ -74,7 +74,7 @@ ContentSwitcher {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.begin_talk_label {
|
.start_converse_label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -6,6 +6,7 @@ from textual.reactive import Reactive
|
|||||||
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
||||||
from textual.app import ComposeResult, RenderResult
|
from textual.app import ComposeResult, RenderResult
|
||||||
from textual.content import Content
|
from textual.content import Content
|
||||||
|
from textual.style import Style
|
||||||
from telethon import TelegramClient, events, utils, types
|
from telethon import TelegramClient, events, utils, types
|
||||||
import datetime
|
import datetime
|
||||||
from os import getenv
|
from os import getenv
|
||||||
@ -17,6 +18,7 @@ class Chat(Widget):
|
|||||||
peername: Reactive[str] = Reactive(" ", recompose=True)
|
peername: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
msg: Reactive[str] = Reactive(" ", recompose=True)
|
msg: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
is_group: Reactive[bool] = Reactive(False, recompose=True)
|
is_group: Reactive[bool] = Reactive(False, recompose=True)
|
||||||
|
is_channel: Reactive[bool] = Reactive(False, recompose=True)
|
||||||
peer_id: Reactive[int] = Reactive(0)
|
peer_id: Reactive[int] = Reactive(0)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -46,7 +48,8 @@ class Chat(Widget):
|
|||||||
self.switcher.mount(Dialog(
|
self.switcher.mount(Dialog(
|
||||||
telegram_client=self.app.telegram_client,
|
telegram_client=self.app.telegram_client,
|
||||||
chat_id=self.peer_id,
|
chat_id=self.peer_id,
|
||||||
id=dialog_id
|
id=dialog_id,
|
||||||
|
is_channel=self.is_channel and not self.is_group
|
||||||
))
|
))
|
||||||
except:
|
except:
|
||||||
# Диалог уже есть: ничего не делаем
|
# Диалог уже есть: ничего не делаем
|
||||||
@ -59,10 +62,10 @@ class Chat(Widget):
|
|||||||
with Horizontal():
|
with Horizontal():
|
||||||
yield Label(f"┌───┐\n│ {self.peername[:1]:1} │\n└───┘")
|
yield Label(f"┌───┐\n│ {self.peername[:1]:1} │\n└───┘")
|
||||||
with Vertical():
|
with Vertical():
|
||||||
yield Label(self.peername, id="peername")
|
yield Label(self.peername, id="peername", markup=False)
|
||||||
if self.is_group:
|
if self.is_group:
|
||||||
yield Label(self.username, id="name")
|
yield Label(self.username, id="name", markup=False)
|
||||||
yield Label(self.msg, id="last_msg")
|
yield Label(self.msg, id="last_msg", markup=False)
|
||||||
|
|
||||||
class Dialog(Widget):
|
class Dialog(Widget):
|
||||||
"""Класс окна диалога"""
|
"""Класс окна диалога"""
|
||||||
@ -73,19 +76,20 @@ class Dialog(Widget):
|
|||||||
classes=None,
|
classes=None,
|
||||||
disabled=None,
|
disabled=None,
|
||||||
telegram_client: TelegramClient | None = None,
|
telegram_client: TelegramClient | None = None,
|
||||||
chat_id = None
|
chat_id: int | None = None,
|
||||||
|
is_channel: bool | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(id=id, classes=classes, disabled=disabled)
|
super().__init__(id=id, classes=classes, disabled=disabled)
|
||||||
self.telegram_client = telegram_client
|
self.telegram_client = telegram_client
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.is_msg_update_blocked = False
|
self.is_msg_update_blocked = False
|
||||||
self.timezone = datetime.timezone(
|
self.timezone = self.app.timezone
|
||||||
datetime.timedelta(hours=int(getenv("UTC_OFFSET")))
|
self.is_channel = is_channel
|
||||||
)
|
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
self.limit = 10
|
self.limit = 10
|
||||||
|
|
||||||
|
if not self.is_channel:
|
||||||
self.msg_input = self.query_one("#msg_input")
|
self.msg_input = self.query_one("#msg_input")
|
||||||
self.dialog = self.query_one(Vertical).query_one("#dialog")
|
self.dialog = self.query_one(Vertical).query_one("#dialog")
|
||||||
|
|
||||||
@ -102,7 +106,7 @@ class Dialog(Widget):
|
|||||||
event(chats=(self.chat_id))
|
event(chats=(self.chat_id))
|
||||||
)(self.update_dialog)
|
)(self.update_dialog)
|
||||||
|
|
||||||
self.dialog.scroll_down(animate=False, immediate=True)
|
#self.dialog.scroll_down(animate=False, immediate=True)
|
||||||
|
|
||||||
def mount_messages(self, limit: int) -> None:
|
def mount_messages(self, limit: int) -> None:
|
||||||
print("Загрузка виджетов сообщений...")
|
print("Загрузка виджетов сообщений...")
|
||||||
@ -135,15 +139,8 @@ class Dialog(Widget):
|
|||||||
|
|
||||||
for i in range(limit):
|
for i in range(limit):
|
||||||
msg = self.dialog.query_one(f"#msg-{i + 1}")
|
msg = self.dialog.query_one(f"#msg-{i + 1}")
|
||||||
|
message = Content(str(messages[i].message))
|
||||||
if str(messages[i].message):
|
if str(messages[i].message):
|
||||||
message = Content(
|
|
||||||
"[Медиа] " * \
|
|
||||||
bool(messages[i].media) + \
|
|
||||||
str(messages[i].message)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
message = Content("[Медиа]" * bool(messages[i].media) + "")
|
|
||||||
|
|
||||||
entities = messages[i].entities
|
entities = messages[i].entities
|
||||||
if entities:
|
if entities:
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
@ -173,6 +170,13 @@ class Dialog(Widget):
|
|||||||
entity.offset + entity.length
|
entity.offset + entity.length
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if messages[i].media and str(message):
|
||||||
|
msg.message = Content(f"[{self.app.locale["media"]}] ")\
|
||||||
|
.stylize("link", 0, 6) + message
|
||||||
|
elif messages[i].media:
|
||||||
|
msg.message = Content(f"[{self.app.locale["media"]}]")\
|
||||||
|
.stylize("link", 0, 6)
|
||||||
|
else:
|
||||||
msg.message = message
|
msg.message = message
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -195,8 +199,12 @@ class Dialog(Widget):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Vertical():
|
with Vertical():
|
||||||
yield VerticalScroll(id="dialog")
|
yield VerticalScroll(id="dialog")
|
||||||
|
if not self.is_channel:
|
||||||
with Horizontal(id="input_place"):
|
with Horizontal(id="input_place"):
|
||||||
yield Input(placeholder="Сообщение", id="msg_input")
|
yield Input(
|
||||||
|
placeholder=self.app.locale["message"],
|
||||||
|
id="msg_input"
|
||||||
|
)
|
||||||
yield Button(label="➤", id="send", variant="primary")
|
yield Button(label="➤", id="send", variant="primary")
|
||||||
|
|
||||||
async def on_button_pressed(self, event = None) -> None:
|
async def on_button_pressed(self, event = None) -> None:
|
||||||
@ -212,7 +220,7 @@ class Dialog(Widget):
|
|||||||
str(self.msg_input.value)
|
str(self.msg_input.value)
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.app.notify("Ошибка отправки")
|
print("Ошибка отправки")
|
||||||
self.msg_input.value = ""
|
self.msg_input.value = ""
|
||||||
await self.update_dialog()
|
await self.update_dialog()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user