Files
guarddog-nexus/guarddog_nexus/i18n.py
Marker689 6984844161 feat: LLM-анализ — индикатор прогресса, кнопка рескана, статистика на дашборде
- Добавлен статус {"status": "analyzing"} в finding.report на время LLM-анализа
- Кнопка рескана (Retry) под LLM-отчётом в ручном режиме
- LLM-статистика на дашборде: analysed / pending
- Защита от двойного анализа через per-finding asyncio.Lock
- _llm_spinner.html — фрагмент спиннера для состояния analysing
- Удалён мёртвый код: constants, i18n, CSS, queries
- Фиксы: _env_int, индексы БД, UnicodeDecodeError, time.mktime и др.
- Шаблоны: shared includes (_status_badge, _pagination)
- AGENTS.md: workflow (lint, test, commit, rebuild)
2026-05-10 09:54:04 +03:00

113 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Internationalisation — RU / EN dictionaries and helpers."""
_STRINGS = {
"nav_dashboard": {"en": "Dashboard", "ru": "Панель"},
"nav_scans": {"en": "Scans", "ru": "Сканирования"},
"nav_packages": {"en": "Packages", "ru": "Пакеты"},
"title_dashboard": {"en": "Dashboard — GuardDog Nexus", "ru": "Панель — GuardDog Nexus"},
"title_scans": {"en": "Scans — GuardDog Nexus", "ru": "Сканирования — GuardDog Nexus"},
"title_packages": {"en": "Packages — GuardDog Nexus", "ru": "Пакеты — GuardDog Nexus"},
"title_scan_detail": {
"en": "Scan #{} — GuardDog Nexus",
"ru": "Сканирование #{} — GuardDog Nexus",
},
"title_package_detail": {"en": "{} v{} — GuardDog Nexus", "ru": "{} v{} — GuardDog Nexus"},
"heading_dashboard": {"en": "Dashboard", "ru": "Панель управления"},
"heading_scans": {"en": "Scans", "ru": "Сканирования"},
"heading_packages": {"en": "Packages", "ru": "Пакеты"},
"heading_latest_flagged": {"en": "Latest Flagged", "ru": "Последние обнаружения"},
"heading_latest_scans": {"en": "Latest Scans", "ru": "Последние сканирования"},
"heading_findings_count": {"en": "Findings ({})", "ru": "Находки ({})"},
"heading_scans_count": {"en": "Scans ({})", "ru": "Сканирований ({})"},
"col_id": {"en": "ID", "ru": "ID"},
"col_package": {"en": "Package", "ru": "Пакет"},
"col_version": {"en": "Version", "ru": "Версия"},
"col_repo": {"en": "Repo", "ru": "Репозиторий"},
"col_status": {"en": "Status", "ru": "Статус"},
"col_findings": {"en": "Findings", "ru": "Находки"},
"col_time": {"en": "Time", "ru": "Время"},
"col_name": {"en": "Name", "ru": "Название"},
"col_ecosystem": {"en": "Ecosystem", "ru": "Экосистема"},
"col_flagged": {"en": "Flagged", "ru": "Помечен"},
"col_last_scan": {"en": "Last Scan", "ru": "Последнее сканирование"},
"filter_search": {"en": "Search packages...", "ru": "Поиск пакетов..."},
"filter_all_statuses": {"en": "All Statuses", "ru": "Все статусы"},
"filter_pending": {"en": "Pending", "ru": "Ожидание"},
"filter_scanning": {"en": "Scanning", "ru": "Сканирование"},
"filter_completed": {"en": "Completed", "ru": "Завершено"},
"filter_failed": {"en": "Failed", "ru": "Ошибка"},
"btn_show_all": {"en": "Show all", "ru": "Показать все"},
"btn_flagged_only": {"en": "Flagged only", "ru": "Только помеченные"},
"btn_export_csv": {"en": "Export CSV", "ru": "Экспорт CSV"},
"btn_analyze_llm": {"en": "Analyze with LLM", "ru": "Анализировать через LLM"},
"btn_copy": {"en": "Copy", "ru": "Копировать"},
"btn_prev": {"en": "Prev", "ru": "Пред."},
"btn_next": {"en": "Next", "ru": "След."},
"scan_info_package": {"en": "Package", "ru": "Пакет"},
"scan_info_version": {"en": "Version", "ru": "Версия"},
"scan_info_ecosystem": {"en": "Ecosystem", "ru": "Экосистема"},
"scan_info_repository": {"en": "Repository", "ru": "Репозиторий"},
"scan_info_status": {"en": "Status", "ru": "Статус"},
"scan_info_sha256": {"en": "SHA256", "ru": "SHA256"},
"scan_info_started": {"en": "Started", "ru": "Запущено"},
"scan_info_finished": {"en": "Finished", "ru": "Завершено"},
"scan_info_initiated": {"en": "Initiated by", "ru": "Инициатор"},
"scan_info_source_ip": {"en": "Source IP", "ru": "IP-адрес"},
"scan_info_error": {"en": "Error", "ru": "Ошибка"},
"empty_no_scans": {
"en": "No scans yet — scans will appear here once packages are processed.",
"ru": "Сканирования отсутствуют — появятся после обработки пакетов.",
},
"empty_no_packages": {
"en": "No packages yet — packages will appear here once scans are processed.",
"ru": "Пакеты отсутствуют — появятся после сканирования.",
},
"empty_no_findings": {
"en": "No findings — package looks clean.",
"ru": "Находки отсутствуют — пакет не содержит угроз.",
},
"view_all_scans": {"en": "View all scans →", "ru": "Все сканирования →"},
"refresh_hint": {
"en": "Last refresh: {} (auto every 30s)",
"ru": "Последнее обновление: {} (автоматически каждые 30 с)",
},
"llm_disabled": {"en": "LLM analysis is disabled", "ru": "LLM-анализ отключён"},
"llm_failed": {"en": "LLM analysis failed", "ru": "Ошибка LLM-анализа"},
"llm_not_found": {"en": "Finding not found", "ru": "Находка не найдена"},
"llm_disclaimer": {
"en": "⚠ AI-generated analysis — may contain inaccuracies. "
"Always verify findings before taking action.",
"ru": "⚠ Анализ сгенерирован AI — может содержать неточности. "
"Всегда проверяйте находки перед принятием мер.",
},
"llm_analyzing": {"en": "Analyzing...", "ru": "Анализирую..."},
"llm_retry": {"en": "Retry", "ru": "Повторить"},
"llm_analyzed": {"en": "LLM analyzed", "ru": "LLM проанализ."},
"llm_pending": {"en": "Pending", "ru": "Ожидают"},
"not_found": {"en": "Not found", "ru": "Не найдено"},
"breadcrumb_home": {"en": "Home", "ru": "Главная"},
"breadcrumb_dashboard": {"en": "Dashboard", "ru": "Панель"},
"breadcrumb_scans": {"en": "Scans", "ru": "Сканирования"},
"breadcrumb_packages": {"en": "Packages", "ru": "Пакеты"},
"page_of": {"en": "of", "ru": "из"},
"page_label": {"en": "Page", "ru": "Стр."},
"total_scans": {"en": "{} total scans", "ru": "{} сканирований всего"},
"total_packages": {"en": "{} total packages", "ru": "{} пакетов всего"},
"scan_detail_title": {"en": "Scan #{}", "ru": "Сканирование #{}"},
}
LANGUAGES = {"en": "English", "ru": "Русский"}
DEFAULT_LANG = "en"
def t(key: str, lang: str = DEFAULT_LANG, *args, **kwargs) -> str:
"""Look up a string in the given language, with optional formatting."""
entry = _STRINGS.get(key, {})
text = entry.get(lang) or entry.get(DEFAULT_LANG, key)
if args or kwargs:
try:
return text.format(*args, **kwargs)
except (KeyError, IndexError):
return text
return text