Добавлена локализация, теперь приложение также переведено на английский

This commit is contained in:
kirill 2025-05-29 13:38:15 +03:00
parent 83f4cac86b
commit 7ecdf5ea39
7 changed files with 115 additions and 73 deletions

View File

@ -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

View File

@ -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
``` ```

View File

@ -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)

View File

@ -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"]

View File

@ -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: не показывается надпись, надо будет исправить

View File

@ -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;

View File

@ -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()