From a13636ca49c3e4e1ebf4e98d0775b5d512214f40 Mon Sep 17 00:00:00 2001 From: "s.galanskiy" Date: Mon, 17 Nov 2025 12:05:43 +0500 Subject: [PATCH] Edit depends call in i18n --- SRV/api/v1/network.py | 5 ++-- SRV/services/arp_service.py | 5 ++-- SRV/services/auth_service.py | 6 ++-- SRV/services/connect_service.py | 9 +----- SRV/services/network_service.py | 7 ++--- SRV/services/settings_service.py | 7 ++--- SRV/services/wifi_service.py | 15 +++++----- SRV/utils/i18n.py | 51 +++++++++++++++++++------------- 8 files changed, 52 insertions(+), 53 deletions(-) diff --git a/SRV/api/v1/network.py b/SRV/api/v1/network.py index d298887..317de4a 100644 --- a/SRV/api/v1/network.py +++ b/SRV/api/v1/network.py @@ -44,7 +44,7 @@ class ContainerPositionManager: }, } - def get_position_and_state(self, container_name: str, translations: Translations = Depends(get_translations())) -> Tuple[int, int, bool]: + def get_position_and_state(self, container_name: str, translations: Translations = None) -> Tuple[int, int, bool]: """Получает текущие координаты и состояние указанного контейнера. Args: @@ -57,6 +57,7 @@ class ContainerPositionManager: Raises: ValueError: Если указан неверный идентификатор контейнера. """ + translations = translations or get_translations()() container = self.positions.get(container_name) if not container: raise ValueError(translations.gettext("Неверное имя контейнера: {container_name}").format(container_name=container_name)) @@ -222,4 +223,4 @@ async def get_hostname(translations: Translations = Depends(get_translations())) return hostname_info except Exception as e: logger.error(f"Error in get_hostname: {e}") - raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить имя хоста")) \ No newline at end of file + raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить имя хоста")) diff --git a/SRV/services/arp_service.py b/SRV/services/arp_service.py index 0766372..4876696 100644 --- a/SRV/services/arp_service.py +++ b/SRV/services/arp_service.py @@ -18,7 +18,6 @@ """ import os import logging -from fastapi import Depends import netifaces from netaddr import IPNetwork from utils.i18n import get_translations, _ @@ -45,7 +44,7 @@ class NetworkScanner: вывода информации о клиентах. """ - def __init__(self, interface=None, translations: Translations = Depends(get_translations())): + def __init__(self, interface=None, translations: Translations = None): """Инициализация параметров сетевого сканера. Args: @@ -55,7 +54,7 @@ class NetworkScanner: Инициализирует интерфейс, информацию об IP-адресах и локальный MAC-адрес. """ - self.translations = translations + self.translations = translations or get_translations()() self.interface = None self.ip_info = [] self.networks = [] diff --git a/SRV/services/auth_service.py b/SRV/services/auth_service.py index 0139c85..3adbca2 100644 --- a/SRV/services/auth_service.py +++ b/SRV/services/auth_service.py @@ -1,6 +1,6 @@ from jose import JWTError, jwt from datetime import datetime, timedelta -from fastapi import HTTPException, Depends, Request +from fastapi import HTTPException, Depends from fastapi.security import OAuth2PasswordBearer from models.auth import UserCreate, UserLogin, UserResponse, Token from storage.json_storage import JsonStorage @@ -35,10 +35,10 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") class AuthService: """Сервис для регистрации, авторизации и управления сессиями""" - def __init__(self, translations: Translations = Depends(get_translations())): + def __init__(self, translations: Optional[Translations] = None): self.storage = JsonStorage() self.session_storage = SessionStorage() - self.translations = translations + self.translations = translations or get_translations()() def create_access_token(self, data: dict) -> str: """Создаёт токен""" diff --git a/SRV/services/connect_service.py b/SRV/services/connect_service.py index 04fee67..25de871 100644 --- a/SRV/services/connect_service.py +++ b/SRV/services/connect_service.py @@ -20,7 +20,6 @@ import logging from typing import Tuple, Optional from utils.i18n import get_translations, _ from babel.support import Translations -from fastapi import Depends logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) @@ -37,13 +36,7 @@ class WifiConnectionService: Автоматически определяет доступный Wi-Fi-интерфейс и сохраняет его имя. """ self.interface = self._get_wifi_interface() - if translations is None: - try: - self.translations = Translations.load("locales", ["ru"], "messages") - except Exception: - self.translations = Translations() - else: - self.translations = translations + self.translations = translations or get_translations()() def _get_wifi_interface(self) -> str: """Автоматически определяет Wi-Fi-интерфейс. diff --git a/SRV/services/network_service.py b/SRV/services/network_service.py index 30b5e25..011f372 100644 --- a/SRV/services/network_service.py +++ b/SRV/services/network_service.py @@ -34,7 +34,6 @@ from models.network import ( import subprocess from utils.i18n import get_translations, _ from babel.support import Translations -from fastapi import Depends class NetworkService: """Сервис для взаимодействия с NetworkManager через D-Bus. @@ -43,7 +42,7 @@ class NetworkService: их конфигурациях IP, Wi-Fi и состояниях. """ - def __init__(self, translations: Translations = Depends(get_translations())): + def __init__(self, translations: Translations = None): """Инициализирует сервис NetworkManager. Устанавливает системный D-Bus и создаёт объекты для взаимодействия с @@ -52,7 +51,7 @@ class NetworkService: self.system_bus = sd_bus_open_system() self.nm = NetworkManager(self.system_bus) self.nm_settings = NetworkManagerSettings(self.system_bus) - self.translations = translations + self.translations = translations or get_translations()() async def get_ip_method(self, device: NetworkDeviceGeneric, ip_type: str) -> str: """Получает метод IP (например, "auto", "manual") для указанного устройства. @@ -383,4 +382,4 @@ class NetworkService: NetworkInterfacesContainer: Объект с неактивными интерфейсами и координатами. """ interfaces = await self.get_deactivated_interfaces(initial_positions) - return NetworkInterfacesContainer(interfaces=interfaces, container_x=container_x, container_y=container_y) \ No newline at end of file + return NetworkInterfacesContainer(interfaces=interfaces, container_x=container_x, container_y=container_y) diff --git a/SRV/services/settings_service.py b/SRV/services/settings_service.py index df2c7dc..1703c93 100644 --- a/SRV/services/settings_service.py +++ b/SRV/services/settings_service.py @@ -4,7 +4,6 @@ from typing import Optional from utils.i18n import get_translations from babel.support import Translations -from fastapi import Depends from pathlib import Path logging.basicConfig(level=logging.INFO) @@ -13,10 +12,10 @@ logger = logging.getLogger(__name__) class SettingsService: """Сервис для управления настройками и очистки данных сети.""" - def __init__(self, translations: Translations = Depends(get_translations())): + def __init__(self, translations: Translations = None): self.subnet_base_path = "storage/subnets" self.wifi_file = "storage/wifi/wifi.json" - self.translations = translations + self.translations = translations or get_translations()() def clear_subnet(self, subnet: Optional[str] = None) -> bool: """Очищает данные узлов подсети в указанном файле или во всех файлах. @@ -101,4 +100,4 @@ class SettingsService: return True except Exception as e: logger.error(f"Error clearing all network data: {str(e)}") - raise Exception(self.translations.gettext("Ошибка очистки всех сетевых данных: {error}").format(error=str(e))) \ No newline at end of file + raise Exception(self.translations.gettext("Ошибка очистки всех сетевых данных: {error}").format(error=str(e))) diff --git a/SRV/services/wifi_service.py b/SRV/services/wifi_service.py index 3acdc94..20f1dab 100644 --- a/SRV/services/wifi_service.py +++ b/SRV/services/wifi_service.py @@ -11,7 +11,6 @@ from pathlib import Path import json from utils.i18n import get_translations from babel.support import Translations -from fastapi import Depends # Настройка логирования logging.basicConfig( @@ -34,10 +33,10 @@ class WifiStorage: - Создаёт директорию хранения при инициализации, ведёт логирование путей. - Валидирует структуру при чтении и безопасно записывает JSON с Unicode и отступами. """ - def __init__(self, storage_path: str = "storage/wifi/wifi.json", translations: Translations = Depends(get_translations())): + def __init__(self, storage_path: str = "storage/wifi/wifi.json", translations: Translations = None): self.storage_file = Path(storage_path) self.storage_file.parent.mkdir(parents=True, exist_ok=True) - self.translations = translations + self.translations = translations or get_translations()() logger.info(f"Wi-Fi storage initialized at: {self.storage_file.absolute()}") def load_notes(self) -> Dict[str, Optional[str]]: @@ -85,15 +84,15 @@ class WifiService: - Кэширование определения вендора. - Сохранение SSID даже без заметок для будущих операций. """ - def __init__(self, translations: Translations = Depends(get_translations())): + def __init__(self, translations: Translations = None): self.mac_lookup = MacLookup() self.wifi = pywifi.PyWiFi() self.interface = self._get_wifi_interface() - if not hasattr(translations, 'gettext'): + validated_translations = translations or get_translations()() + if not hasattr(validated_translations, "gettext"): logger.warning("Translations object does not support gettext, using fallback") - self.translations = Translations.load("utils/locales/en/LC_MESSAGES/services.mo") - else: - self.translations = translations + validated_translations = get_translations()() + self.translations = validated_translations self.storage = WifiStorage() def _get_wifi_interface(self) -> Optional[object]: diff --git a/SRV/utils/i18n.py b/SRV/utils/i18n.py index 75a0977..a15b3e4 100644 --- a/SRV/utils/i18n.py +++ b/SRV/utils/i18n.py @@ -16,40 +16,49 @@ региональных суффиксов) для упрощения. - Модуль ведёт отладочное логирование выбора и загрузки локали. """ -from fastapi import Request, Depends +from fastapi import Request, WebSocket from babel.support import Translations import logging +from typing import Optional, Union +from pathlib import Path logger = logging.getLogger(__name__) +LOCALES_DIR = Path(__file__).resolve().parent.parent / "locales" + +def _load_translations(locale: str) -> Translations: + """Подгружает словарь переводов с fallback на ru.""" + try: + translations = Translations.load(str(LOCALES_DIR), [locale], "messages") + if not translations._catalog: + logger.warning(f"Translations for locale {locale} not found, falling back to ru") + translations = Translations.load(str(LOCALES_DIR), ["ru"], "messages") + return translations + except Exception as e: # pragma: no cover - логируем и возвращаем ru + logger.error(f"Failed to load translations for locale {locale}: {str(e)}") + return Translations.load(str(LOCALES_DIR), ["ru"], "messages") def get_translations() -> callable: """ Создаёт зависимость для загрузки переводов на основе заголовка Accept-Language или параметра запроса 'locale'. По умолчанию используется русский язык. """ - def translations_dependency(request: Request) -> Translations: + def translations_dependency(request: Optional[Union[Request, WebSocket]] = None) -> Translations: + if request is None: + logger.warning("Translations dependency called without request/websocket; fallback to ru") + return _load_translations("ru") + # Проверяем параметр запроса 'locale', если он есть - locale = request.query_params.get("locale") - if not locale: - # Если параметр отсутствует, проверяем куку 'locale' + locale = None + if hasattr(request, "query_params"): + locale = request.query_params.get("locale") + if not locale and hasattr(request, "cookies"): locale = request.cookies.get("locale") - if not locale: - # Если куки нет, используем заголовок Accept-Language + if not locale and hasattr(request, "headers"): locale = request.headers.get("Accept-Language", "ru").split(",")[0].split("-")[0] + locale = locale or "ru" logger.debug(f"Determined locale: {locale}") - try: - translations = Translations.load("locales", [locale], "messages") - if not translations._catalog: - logger.warning(f"Translations for locale {locale} not found, falling back to ru") - translations = Translations.load("locales", ["ru"], "messages") - logger.debug(f"Loaded translations for locale {locale}, catalog size: {len(translations._catalog)}") - return translations - except Exception as e: - logger.error(f"Failed to load translations for locale {locale}: {str(e)}") - # Fallback на русский - logger.debug("Falling back to ru translations") - return Translations.load("locales", ["ru"], "messages") + return _load_translations(locale) return translations_dependency @@ -66,9 +75,9 @@ def _(msgid: str, request: Request = None) -> str: """ if request is None: # Язык, который будет отображаться по умолчанию - translations = Translations.load("locales", ["ru"], "messages") + translations = _load_translations("ru") logger.debug(f"Using default ru translations for msgid: {msgid}") else: translations = get_translations()(request) logger.debug(f"Using translations for locale {request.cookies.get('locale', 'ru')} for msgid: {msgid}") - return translations.gettext(msgid) \ No newline at end of file + return translations.gettext(msgid)