mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 11:10:18 +00:00
Localize logs severity filters
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Logs severity/filter i18n keys are now localized for Japanese, Russian, Spanish, German, Simplified Chinese, Traditional Chinese, Portuguese, and Korean instead of falling back to English TODO placeholders (closes #2098).
|
||||
- **PR #2136** by @LumenYoung — Stale stream writebacks no longer poison the active session transcript. `cancel_stream()` intentionally clears `active_stream_id` early so the UI can accept a follow-up turn while an old worker is unwinding — but the old worker could still return later from `run_conversation()` and persist its stale result over the newer transcript, causing visible transcript / turn journal / `state.db` to disagree (especially around cancel+retry on compressed continuations). Adds a single-line ownership check `_stream_writeback_is_current(session, stream_id)` (token equality against `session.active_stream_id`) and short-circuits both finalize paths: the success path in `_run_agent_streaming` and the cancel-handler path in `cancel_stream()`. When the stream no longer owns the writeback, both paths log `Skipping stale stream/cancel writeback` and return cleanly without persisting. 89-line regression suite in `tests/test_stale_stream_writeback.py`; companion updates to `tests/test_issue1361_cancel_data_loss.py` and `tests/test_sprint42.py` for the new return-without-persist behavior.
|
||||
|
||||
### Added
|
||||
|
||||
+40
-35
@@ -2865,11 +2865,11 @@ const LOCALES = {
|
||||
logs_no_mtime: '未書き込み',
|
||||
logs_truncated_hint: '大きなログファイルの末尾を表示しています。メモリ使用量を抑えるため、古いデータは省略されました。',
|
||||
logs_copied: 'ログをコピーしました',
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: '重大度',
|
||||
logs_severity_all: 'すべて',
|
||||
logs_severity_errors: 'エラー',
|
||||
logs_severity_warnings: '警告+',
|
||||
logs_filter_active: '表示中(フィルター有効)',
|
||||
|
||||
// Insights
|
||||
insights_title: '使用状況分析',
|
||||
@@ -3778,11 +3778,11 @@ const LOCALES = {
|
||||
logs_no_mtime: 'not written yet', // TODO: translate
|
||||
logs_truncated_hint: 'Showing the tail of a large log file; older bytes were skipped to keep memory bounded.', // TODO: translate
|
||||
logs_copied: 'Logs copied', // TODO: translate
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: 'Уровень',
|
||||
logs_severity_all: 'Все',
|
||||
logs_severity_errors: 'Ошибки',
|
||||
logs_severity_warnings: 'Предупреждения+',
|
||||
logs_filter_active: 'показано (фильтр активен)',
|
||||
new_conversation: 'Новая беседа',
|
||||
filter_conversations: 'Фильтр бесед...',
|
||||
session_time_unknown: 'Неизвестно',
|
||||
@@ -4820,11 +4820,11 @@ const LOCALES = {
|
||||
logs_no_mtime: 'not written yet', // TODO: translate
|
||||
logs_truncated_hint: 'Showing the tail of a large log file; older bytes were skipped to keep memory bounded.', // TODO: translate
|
||||
logs_copied: 'Logs copied', // TODO: translate
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: 'Severidad',
|
||||
logs_severity_all: 'Todo',
|
||||
logs_severity_errors: 'Errores',
|
||||
logs_severity_warnings: 'Advertencias+',
|
||||
logs_filter_active: 'mostrados (filtro activo)',
|
||||
new_conversation: 'Nueva conversación',
|
||||
filter_conversations: 'Filtrar conversaciones...',
|
||||
session_time_unknown: 'Desconocido',
|
||||
@@ -5845,11 +5845,11 @@ const LOCALES = {
|
||||
logs_no_mtime: 'not written yet', // TODO: translate
|
||||
logs_truncated_hint: 'Showing the tail of a large log file; older bytes were skipped to keep memory bounded.', // TODO: translate
|
||||
logs_copied: 'Logs copied', // TODO: translate
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: 'Schweregrad',
|
||||
logs_severity_all: 'Alle',
|
||||
logs_severity_errors: 'Fehler',
|
||||
logs_severity_warnings: 'Warnungen+',
|
||||
logs_filter_active: 'angezeigt (Filter aktiv)',
|
||||
new_conversation: 'Neuer Chat',
|
||||
filter_conversations: 'Chats filtern...',
|
||||
scheduled_jobs: 'Geplante Aufgaben',
|
||||
@@ -6903,11 +6903,11 @@ const LOCALES = {
|
||||
logs_no_mtime: '尚未写入',
|
||||
logs_truncated_hint: '此处显示的是日志文件的末尾内容。为节省内存,已省略较早的数据。',
|
||||
logs_copied: '日志已复制',
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: '严重性',
|
||||
logs_severity_all: '全部',
|
||||
logs_severity_errors: '错误',
|
||||
logs_severity_warnings: '警告+',
|
||||
logs_filter_active: '已显示(筛选器已启用)',
|
||||
new_conversation: '新建对话',
|
||||
filter_conversations: '筛选对话…',
|
||||
session_time_unknown: '未知',
|
||||
@@ -7933,6 +7933,11 @@ const LOCALES = {
|
||||
kanban_dispatch_auto_blocked: '自動封鎖',
|
||||
kanban_dispatch_timed_out: '逾時',
|
||||
kanban_dispatch_crashed: '崩潰',
|
||||
logs_severity: '嚴重性',
|
||||
logs_severity_all: '全部',
|
||||
logs_severity_errors: '錯誤',
|
||||
logs_severity_warnings: '警告+',
|
||||
logs_filter_active: '已顯示(篩選器已啟用)',
|
||||
new_conversation: '新對話',
|
||||
filter_conversations: '篩選對話',
|
||||
scheduled_jobs: '排程任務',
|
||||
@@ -9138,11 +9143,11 @@ const LOCALES = {
|
||||
logs_no_mtime: 'not written yet', // TODO: translate
|
||||
logs_truncated_hint: 'Showing the tail of a large log file; older bytes were skipped to keep memory bounded.', // TODO: translate
|
||||
logs_copied: 'Logs copied', // TODO: translate
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: 'Severidade',
|
||||
logs_severity_all: 'Todos',
|
||||
logs_severity_errors: 'Erros',
|
||||
logs_severity_warnings: 'Avisos+',
|
||||
logs_filter_active: 'exibidos (filtro ativo)',
|
||||
new_conversation: 'Nova conversa',
|
||||
filter_conversations: 'Filtrar conversas...',
|
||||
session_time_unknown: 'Desconhecido',
|
||||
@@ -10144,11 +10149,11 @@ const LOCALES = {
|
||||
logs_no_mtime: 'not written yet', // TODO: translate
|
||||
logs_truncated_hint: 'Showing the tail of a large log file; older bytes were skipped to keep memory bounded.', // TODO: translate
|
||||
logs_copied: 'Logs copied', // TODO: translate
|
||||
logs_severity: 'Severity', // TODO: translate
|
||||
logs_severity_all: 'All', // TODO: translate
|
||||
logs_severity_errors: 'Errors', // TODO: translate
|
||||
logs_severity_warnings: 'Warnings+', // TODO: translate
|
||||
logs_filter_active: 'shown (filter active)', // TODO: translate
|
||||
logs_severity: '심각도',
|
||||
logs_severity_all: '전체',
|
||||
logs_severity_errors: '오류',
|
||||
logs_severity_warnings: '경고+',
|
||||
logs_filter_active: '표시됨(필터 활성)',
|
||||
new_conversation: '새 대화',
|
||||
filter_conversations: '대화 필터…',
|
||||
session_time_unknown: 'Unknown',
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
I18N_PATH = Path(__file__).resolve().parent.parent / "static" / "i18n.js"
|
||||
|
||||
|
||||
LOGS_FILTER_KEYS = {
|
||||
"ja": {
|
||||
"logs_severity": "重大度",
|
||||
"logs_severity_all": "すべて",
|
||||
"logs_severity_errors": "エラー",
|
||||
"logs_severity_warnings": "警告+",
|
||||
"logs_filter_active": "表示中(フィルター有効)",
|
||||
},
|
||||
"ru": {
|
||||
"logs_severity": "Уровень",
|
||||
"logs_severity_all": "Все",
|
||||
"logs_severity_errors": "Ошибки",
|
||||
"logs_severity_warnings": "Предупреждения+",
|
||||
"logs_filter_active": "показано (фильтр активен)",
|
||||
},
|
||||
"es": {
|
||||
"logs_severity": "Severidad",
|
||||
"logs_severity_all": "Todo",
|
||||
"logs_severity_errors": "Errores",
|
||||
"logs_severity_warnings": "Advertencias+",
|
||||
"logs_filter_active": "mostrados (filtro activo)",
|
||||
},
|
||||
"de": {
|
||||
"logs_severity": "Schweregrad",
|
||||
"logs_severity_all": "Alle",
|
||||
"logs_severity_errors": "Fehler",
|
||||
"logs_severity_warnings": "Warnungen+",
|
||||
"logs_filter_active": "angezeigt (Filter aktiv)",
|
||||
},
|
||||
"zh": {
|
||||
"logs_severity": "严重性",
|
||||
"logs_severity_all": "全部",
|
||||
"logs_severity_errors": "错误",
|
||||
"logs_severity_warnings": "警告+",
|
||||
"logs_filter_active": "已显示(筛选器已启用)",
|
||||
},
|
||||
"zh-Hant": {
|
||||
"logs_severity": "嚴重性",
|
||||
"logs_severity_all": "全部",
|
||||
"logs_severity_errors": "錯誤",
|
||||
"logs_severity_warnings": "警告+",
|
||||
"logs_filter_active": "已顯示(篩選器已啟用)",
|
||||
},
|
||||
"pt": {
|
||||
"logs_severity": "Severidade",
|
||||
"logs_severity_all": "Todos",
|
||||
"logs_severity_errors": "Erros",
|
||||
"logs_severity_warnings": "Avisos+",
|
||||
"logs_filter_active": "exibidos (filtro ativo)",
|
||||
},
|
||||
"ko": {
|
||||
"logs_severity": "심각도",
|
||||
"logs_severity_all": "전체",
|
||||
"logs_severity_errors": "오류",
|
||||
"logs_severity_warnings": "경고+",
|
||||
"logs_filter_active": "표시됨(필터 활성)",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _i18n_locale_block(locale: str) -> str:
|
||||
src = I18N_PATH.read_text(encoding="utf-8")
|
||||
if "-" in locale:
|
||||
head = re.compile(rf"^ '{re.escape(locale)}':\s*\{{", re.M)
|
||||
else:
|
||||
head = re.compile(rf"^ {re.escape(locale)}:\s*\{{", re.M)
|
||||
match = head.search(src)
|
||||
assert match, f"locale {locale!r} not found"
|
||||
body_start = match.end()
|
||||
depth = 1
|
||||
i = body_start
|
||||
while i < len(src) and depth > 0:
|
||||
ch = src[i]
|
||||
if ch == "/" and i + 1 < len(src) and src[i + 1] == "/":
|
||||
newline = src.find("\n", i)
|
||||
i = len(src) if newline < 0 else newline + 1
|
||||
continue
|
||||
if ch in ("'", '"'):
|
||||
quote = ch
|
||||
i += 1
|
||||
while i < len(src) and src[i] != quote:
|
||||
i += 2 if src[i] == "\\" else 1
|
||||
i += 1
|
||||
continue
|
||||
if ch == "`":
|
||||
i += 1
|
||||
while i < len(src) and src[i] != "`":
|
||||
i += 2 if src[i] == "\\" else 1
|
||||
i += 1
|
||||
continue
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return src[body_start:i]
|
||||
i += 1
|
||||
raise AssertionError(f"locale {locale!r} block never closed")
|
||||
|
||||
|
||||
def _string_value(block: str, key: str) -> str:
|
||||
match = re.search(rf"^\s+{re.escape(key)}:\s+'([^']*)',(?P<tail>[^\n]*)$", block, re.M)
|
||||
assert match, f"{key} missing"
|
||||
assert "TODO: translate" not in match.group("tail")
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def test_logs_severity_filter_keys_are_translated_for_non_english_locales():
|
||||
for locale, expected in LOGS_FILTER_KEYS.items():
|
||||
block = _i18n_locale_block(locale)
|
||||
for key, value in expected.items():
|
||||
assert _string_value(block, key) == value
|
||||
Reference in New Issue
Block a user