add translate

This commit is contained in:
unknown 2025-09-29 01:45:47 +03:00
parent d72239b3ab
commit 99029541d3
5 changed files with 72 additions and 39 deletions

View File

@ -1,17 +1,23 @@
from PySide6.QtWidgets import QStackedWidget from PySide6.QtWidgets import QStackedWidget
from PySide6.QtCore import Signal
from app.ui.views.login_view import LoginView 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 typing import Optional
from threading import Thread from threading import Thread
import time # эмуляция задержки от сервера import time
import asyncio import asyncio
from app.core.database import get_last_login, get_session, set_last_login from app.core.database import get_last_login, get_session, set_last_login
class MainController(QStackedWidget): class MainController(QStackedWidget):
# Сигнал для показа уведомлений из любого потока
# (message, is_error)
notification_requested = Signal(str, bool)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.login_view: Optional[LoginView] = None self.login_view: Optional[LoginView] = None
self.yobble_home_view: Optional[YobbleHomeView] = None # <--- Изменено self.yobble_home_view: Optional[YobbleHomeView] = None
self.init_app() self.init_app()
@ -41,29 +47,33 @@ class MainController(QStackedWidget):
self.removeWidget(self.login_view) self.removeWidget(self.login_view)
self.login_view = None self.login_view = None
# Отображаем новый главный экран
self.yobble_home_view = YobbleHomeView(username=username) 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.addWidget(self.yobble_home_view)
self.setCurrentWidget(self.yobble_home_view) self.setCurrentWidget(self.yobble_home_view)
self.yobble_home_view.show() self.yobble_home_view.show()
# 🔹 Оставляем фоновое обновление на будущее
Thread(target=self.update_data_from_server, args=(username,), daemon=True).start() Thread(target=self.update_data_from_server, args=(username,), daemon=True).start()
def update_data_from_server(self, username: str): def update_data_from_server(self, username: str):
""" """
В фоновом режиме обновляет данные с сервера. В фоновом режиме обновляет данные с сервера.
Эмулирует задержку и предзагружает права доступа.
""" """
# Эмуляция запроса
time.sleep(2) time.sleep(2)
print(f"[Sync] Обновляем данные для пользователя: {username}") print(f"[Sync] Обновляем данные для пользователя: {username}")
if self.yobble_home_view: if self.yobble_home_view:
print("[Sync] Запускаем предзагрузку прав доступа...") print("[Sync] Запускаем предзагрузку прав доступа...")
try: try:
# Запускаем асинхронную функцию в текущем потоке # `preload_permissions` теперь возвращает кортеж (успех, данные)
asyncio.run(self.yobble_home_view.preload_permissions()) # TODO добавить из sqlite # asyncio.run() выполняет async функцию и возвращает её результат
print("[Sync] Предзагрузка прав доступа завершена.") asyncio.run(self.yobble_home_view.preload_permissions())
except Exception as e: except Exception as e:
print(f"[Sync] Ошибка во время предзагрузки прав: {e}") error_message = f"Ошибка предзагрузки: {e}"
print(f"[Sync] {error_message}")
# Отправляем сигнал вместо прямого вызова
self.notification_requested.emit(error_message, True)

View File

@ -119,18 +119,18 @@ async def refresh_token(access_token: str, refresh_token: str):
) )
return True, token_data return True, token_data
else: else:
return False, data.get("detail", "Unknown error") return False, data.get("detail", localizer.translate("Unknown error"))
elif response.status_code == 401: elif response.status_code == 401:
return False, "Refresh token is invalid or expired" return False, localizer.translate("Refresh token is invalid or expired")
else: else:
return False, f"Server error: {response.status_code}" return False, f"{localizer.translate('Server error')}: {response.status_code}"
except httpx.RequestError as e: except httpx.RequestError as e:
return False, f"Network error: {e}" return False, f"{localizer.translate('Network error')}: {e}"
except Exception as 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): 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: if response.status_code == 200:
data = response.json() 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: elif response.status_code == 401:
# Токен истек, пытаемся обновить # Токен истек, пытаемся обновить
session = get_session(login) session = get_session(login)
if not session or not session['refresh_token']: 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']) 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: if response.status_code == 200:
data = response.json() 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) logout(access_token)
return False, "Session expired, please log in again" return False, localizer.translate("Session expired, please log in again")
else: else:
return False, f"Server error: {response.status_code}" return False, f"{localizer.translate('Server error')}: {response.status_code}"
except httpx.RequestError as e: except httpx.RequestError as e:
return False, f"Network error: {e}" return False, f"{localizer.translate('Network error')}: {e}"
except Exception as e: except Exception as e:
return False, f"An error occurred: {e}" return False, f"{localizer.translate('An error occurred')}: {e}"

View File

@ -30,9 +30,20 @@
"Регистрация в данный момент отключена.": "Registration is currently disabled.", "Регистрация в данный момент отключена.": "Registration is currently disabled.",
"Данные не прошли валидацию. Проверьте длину логина и пароля.": "Data validation failed. Check username and password length.", "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Data validation failed. Check username and password length.",
"Загрузка...": "Загрузка...", "Загрузка...": "Loading...",
"Доступ запрещен": "Access denied", "Доступ запрещен": "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 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 contain whitespace characters": "Login must not contain whitespace characters",
"Login must not start with an underscore": "Login must not start with an underscore", "Login must not start with an underscore": "Login must not start with an underscore",

View File

@ -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 be between 3 and 32 characters long": "Логин должен быть от 3 до 32 символов",
"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": "Логин не должен начинаться с символа подчёркивания",

View File

@ -141,17 +141,17 @@ class YobbleHomeView(QWidget):
def show_notification(self, message, is_error=True, duration=3000): def show_notification(self, message, is_error=True, duration=3000):
"""Показывает всплывающее уведомление.""" """Показывает всплывающее уведомление."""
self.notification_label.setText(message) self.notification_label.setText(message)
self.notification_widget.setProperty("is_error", is_error) self.notification_widget.setProperty("is_error", is_error)
self.notification_widget.style().unpolish(self.notification_widget) self.notification_widget.style().unpolish(self.notification_widget)
self.notification_widget.style().polish(self.notification_widget) self.notification_widget.style().polish(self.notification_widget)
# Позиционирование # Позиционирование
self.notification_widget.adjustSize() self.notification_widget.adjustSize()
x = (self.width() - self.notification_widget.width()) / 2 x = (self.width() - self.notification_widget.width()) / 2
y = 30 # Отступ сверху y = self.height() - self.notification_widget.height() - self.bottom_bar.height() - 15 # Отступ снизу
self.notification_widget.move(int(x), y) self.notification_widget.move(int(x), int(y))
self.notification_widget.show() self.notification_widget.show()
self.notification_widget.raise_() # Поднять поверх всех self.notification_widget.raise_() # Поднять поверх всех
@ -222,7 +222,8 @@ class YobbleHomeView(QWidget):
if hasattr(self, 'notification_widget') and self.notification_widget.isVisible(): if hasattr(self, 'notification_widget') and self.notification_widget.isVisible():
self.notification_widget.adjustSize() self.notification_widget.adjustSize()
x = (self.width() - self.notification_widget.width()) / 2 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): def update_styles(self):
"""Обновляет стили компонента при смене темы.""" """Обновляет стили компонента при смене темы."""
@ -394,27 +395,28 @@ class YobbleHomeView(QWidget):
access_token = await get_current_access_token() access_token = await get_current_access_token()
if not access_token: if not access_token:
print("[Permissions] Preload failed: No access token.") print("[Permissions] Preload failed: No access token.")
# Поскольку мы уже в async-контексте, который будет запущен
# в основном потоке через QMetaObject.invokeMethod или сигнал,
# можно вызвать напрямую, но сигнал надежнее.
# Здесь прямой вызов может быть небезопасен, если async-задача
# выполняется в другом потоке. Но т.к. мы используем сигнал
# в контроллере, этот код не будет вызван.
return return
success, data = await get_user_role(access_token, self.username) success, data = await get_user_role(access_token, self.username)
if success: if success:
user_permissions = set(data.get("user_permissions", [])) user_permissions = set(data.get("user_permissions", []))
# Загружаем ВСЕ права пользователя в кэш
self.permission_cache = 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_last = time.time()
self.permissions_preloaded = True self.permissions_preloaded = True
self.preload_permissions_first = True self.preload_permissions_first = True
print(f"[Permissions] Preloaded. Cache: {self.permission_cache}") print(f"[Permissions] Preloaded. Cache: {self.permission_cache}")
else: else:
# В случае ошибки при запросе, выбрасываем исключение,
# которое будет поймано в вызывающем потоке.
print(f"[Permissions] Preload failed: {data}") print(f"[Permissions] Preload failed: {data}")
raise ConnectionError(data)
return success, data
# def update_current_tab_content(self): # def update_current_tab_content(self):
# """Обновляет контент текущей активной вкладки после предзагрузки прав.""" # """Обновляет контент текущей активной вкладки после предзагрузки прав."""