From 99029541d3d9baebaef6f9bb9eceda884981dcf3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 29 Sep 2025 01:45:47 +0300 Subject: [PATCH] add translate --- app/controllers/main_controller.py | 32 ++++++++++++++++++++---------- app/core/services/auth_service.py | 24 +++++++++++----------- app/locales/en.json | 13 +++++++++++- app/locales/ru.json | 12 ++++++++++- app/ui/views/yobble_home_view.py | 30 +++++++++++++++------------- 5 files changed, 72 insertions(+), 39 deletions(-) diff --git a/app/controllers/main_controller.py b/app/controllers/main_controller.py index 2acaf8a..e2534fb 100644 --- a/app/controllers/main_controller.py +++ b/app/controllers/main_controller.py @@ -1,17 +1,23 @@ from PySide6.QtWidgets import QStackedWidget +from PySide6.QtCore import Signal + from app.ui.views.login_view import LoginView -from app.ui.views.yobble_home_view import YobbleHomeView # <--- Изменено +from app.ui.views.yobble_home_view import YobbleHomeView from typing import Optional from threading import Thread -import time # эмуляция задержки от сервера +import time import asyncio from app.core.database import get_last_login, get_session, set_last_login class MainController(QStackedWidget): + # Сигнал для показа уведомлений из любого потока + # (message, is_error) + notification_requested = Signal(str, bool) + def __init__(self): super().__init__() self.login_view: Optional[LoginView] = None - self.yobble_home_view: Optional[YobbleHomeView] = None # <--- Изменено + self.yobble_home_view: Optional[YobbleHomeView] = None self.init_app() @@ -41,29 +47,33 @@ class MainController(QStackedWidget): self.removeWidget(self.login_view) self.login_view = None - # Отображаем новый главный экран self.yobble_home_view = YobbleHomeView(username=username) + + # Подключаем сигнал к слоту в YobbleHomeView + self.notification_requested.connect(self.yobble_home_view.show_notification) + self.addWidget(self.yobble_home_view) self.setCurrentWidget(self.yobble_home_view) self.yobble_home_view.show() - # 🔹 Оставляем фоновое обновление на будущее Thread(target=self.update_data_from_server, args=(username,), daemon=True).start() def update_data_from_server(self, username: str): """ В фоновом режиме обновляет данные с сервера. - Эмулирует задержку и предзагружает права доступа. """ - # Эмуляция запроса time.sleep(2) print(f"[Sync] Обновляем данные для пользователя: {username}") if self.yobble_home_view: print("[Sync] Запускаем предзагрузку прав доступа...") try: - # Запускаем асинхронную функцию в текущем потоке - asyncio.run(self.yobble_home_view.preload_permissions()) # TODO добавить из sqlite - print("[Sync] Предзагрузка прав доступа завершена.") + # `preload_permissions` теперь возвращает кортеж (успех, данные) + # asyncio.run() выполняет async функцию и возвращает её результат + asyncio.run(self.yobble_home_view.preload_permissions()) + except Exception as e: - print(f"[Sync] Ошибка во время предзагрузки прав: {e}") + error_message = f"Ошибка предзагрузки: {e}" + print(f"[Sync] {error_message}") + # Отправляем сигнал вместо прямого вызова + self.notification_requested.emit(error_message, True) diff --git a/app/core/services/auth_service.py b/app/core/services/auth_service.py index 3d3002c..e82afdf 100644 --- a/app/core/services/auth_service.py +++ b/app/core/services/auth_service.py @@ -119,18 +119,18 @@ async def refresh_token(access_token: str, refresh_token: str): ) return True, token_data else: - return False, data.get("detail", "Unknown error") + return False, data.get("detail", localizer.translate("Unknown error")) elif response.status_code == 401: - return False, "Refresh token is invalid or expired" + return False, localizer.translate("Refresh token is invalid or expired") else: - return False, f"Server error: {response.status_code}" + return False, f"{localizer.translate('Server error')}: {response.status_code}" except httpx.RequestError as e: - return False, f"Network error: {e}" + return False, f"{localizer.translate('Network error')}: {e}" except Exception as e: - return False, f"An error occurred: {e}" + return False, f"{localizer.translate('An error occurred')}: {e}" async def get_user_role(access_token: str, login: str): @@ -147,13 +147,13 @@ async def get_user_role(access_token: str, login: str): if response.status_code == 200: data = response.json() - return (True, data['data']) if data.get("status") == "fine" else (False, data.get("detail", "Unknown error")) + return (True, data['data']) if data.get("status") == "fine" else (False, data.get("detail", localizer.translate("Unknown error"))) elif response.status_code == 401: # Токен истек, пытаемся обновить session = get_session(login) if not session or not session['refresh_token']: - return False, "No refresh token found" + return False, localizer.translate("No refresh token found") refresh_success, refresh_data = await refresh_token(access_token, session['refresh_token']) @@ -165,16 +165,16 @@ async def get_user_role(access_token: str, login: str): if response.status_code == 200: data = response.json() - return (True, data['data']) if data.get("status") == "fine" else (False, "Failed to get role after refresh") + return (True, data['data']) if data.get("status") == "fine" else (False, localizer.translate("Failed to get role after refresh")) # Если обновление не удалось, выходим из системы logout(access_token) - return False, "Session expired, please log in again" + return False, localizer.translate("Session expired, please log in again") else: - return False, f"Server error: {response.status_code}" + return False, f"{localizer.translate('Server error')}: {response.status_code}" except httpx.RequestError as e: - return False, f"Network error: {e}" + return False, f"{localizer.translate('Network error')}: {e}" except Exception as e: - return False, f"An error occurred: {e}" + return False, f"{localizer.translate('An error occurred')}: {e}" diff --git a/app/locales/en.json b/app/locales/en.json index 43e5fdc..31ab644 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -30,9 +30,20 @@ "Регистрация в данный момент отключена.": "Registration is currently disabled.", "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Data validation failed. Check username and password length.", - "Загрузка...": "Загрузка...", + "Загрузка...": "Loading...", "Доступ запрещен": "Access denied", + "Ошибка предзагрузки": "Preload error", + "Unknown error": "Unknown error", + "Refresh token is invalid or expired": "Refresh token is invalid or expired", + "Server error": "Server error", + "Network error": "Network error", + "An error occurred": "An error occurred", + "No refresh token found": "No refresh token found", + "Failed to get role after refresh": "Failed to get role after refresh", + "Session expired, please log in again": "Session expired, please log in again", + + "Login must be between 3 and 32 characters long": "Login must be between 3 and 32 characters long", "Login must not contain whitespace characters": "Login must not contain whitespace characters", "Login must not start with an underscore": "Login must not start with an underscore", diff --git a/app/locales/ru.json b/app/locales/ru.json index f419444..8305608 100644 --- a/app/locales/ru.json +++ b/app/locales/ru.json @@ -30,9 +30,19 @@ "Регистрация в данный момент отключена.": "Регистрация в данный момент отключена.", "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Данные не прошли валидацию. Проверьте длину логина и пароля.", - "Загрузка...": "Loading...", + "Загрузка...": "Загрузка...", "Доступ запрещен": "Доступ запрещен", + "Ошибка предзагрузки": "Ошибка предзагрузки", + "Unknown error": "Неизвестная ошибка", + "Refresh token is invalid or expired": "Токен обновления недействителен или просрочен", + "Server error": "Ошибка сервера", + "Network error": "Ошибка сети", + "An error occurred": "Произошла ошибка", + "No refresh token found": "Токен обновления не найден", + "Failed to get role after refresh": "Не удалось получить роль после обновления", + "Session expired, please log in again": "Сессия истекла, пожалуйста, войдите снова", + "Login must be between 3 and 32 characters long": "Логин должен быть от 3 до 32 символов", "Login must not contain whitespace characters": "Логин не должен содержать пробелы", "Login must not start with an underscore": "Логин не должен начинаться с символа подчёркивания", diff --git a/app/ui/views/yobble_home_view.py b/app/ui/views/yobble_home_view.py index cb0a9fd..91c49e7 100644 --- a/app/ui/views/yobble_home_view.py +++ b/app/ui/views/yobble_home_view.py @@ -141,17 +141,17 @@ class YobbleHomeView(QWidget): def show_notification(self, message, is_error=True, duration=3000): """Показывает всплывающее уведомление.""" self.notification_label.setText(message) - + self.notification_widget.setProperty("is_error", is_error) - + self.notification_widget.style().unpolish(self.notification_widget) self.notification_widget.style().polish(self.notification_widget) # Позиционирование self.notification_widget.adjustSize() x = (self.width() - self.notification_widget.width()) / 2 - y = 30 # Отступ сверху - self.notification_widget.move(int(x), y) + y = self.height() - self.notification_widget.height() - self.bottom_bar.height() - 15 # Отступ снизу + self.notification_widget.move(int(x), int(y)) self.notification_widget.show() self.notification_widget.raise_() # Поднять поверх всех @@ -222,7 +222,8 @@ class YobbleHomeView(QWidget): if hasattr(self, 'notification_widget') and self.notification_widget.isVisible(): self.notification_widget.adjustSize() x = (self.width() - self.notification_widget.width()) / 2 - self.notification_widget.move(int(x), 30) + y = self.height() - self.notification_widget.height() - self.bottom_bar.height() - 15 + self.notification_widget.move(int(x), int(y)) def update_styles(self): """Обновляет стили компонента при смене темы.""" @@ -394,27 +395,28 @@ class YobbleHomeView(QWidget): access_token = await get_current_access_token() if not access_token: print("[Permissions] Preload failed: No access token.") + # Поскольку мы уже в async-контексте, который будет запущен + # в основном потоке через QMetaObject.invokeMethod или сигнал, + # можно вызвать напрямую, но сигнал надежнее. + # Здесь прямой вызов может быть небезопасен, если async-задача + # выполняется в другом потоке. Но т.к. мы используем сигнал + # в контроллере, этот код не будет вызван. return success, data = await get_user_role(access_token, self.username) if success: user_permissions = set(data.get("user_permissions", [])) - - # Загружаем ВСЕ права пользователя в кэш self.permission_cache = user_permissions - print("self.permission_cache", self.permission_cache) - - # for permission_code in self.REQUIRED_PERMISSIONS.values(): - # if permission_code in user_permissions: - # self.permission_cache.add(permission_code) - self.permissions_preloaded_last = time.time() self.permissions_preloaded = True self.preload_permissions_first = True - print(f"[Permissions] Preloaded. Cache: {self.permission_cache}") else: + # В случае ошибки при запросе, выбрасываем исключение, + # которое будет поймано в вызывающем потоке. print(f"[Permissions] Preload failed: {data}") + raise ConnectionError(data) + return success, data # def update_current_tab_content(self): # """Обновляет контент текущей активной вкладки после предзагрузки прав."""