Merge remote-tracking branch 'origin/master' into fix/4768-cron-graceful-degrade

This commit is contained in:
nesquena-hermes committed 2026-06-24 09:23:37 +00:00
commit f4e150e105
17 files changed
+1764 -22

No files matched your search

+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.621] — 2026-06-24 — Release WB (Vietnamese UI language)
### Added
- **Vietnamese (Tiếng Việt) is now a selectable UI language.** Adds a full `vi` locale to the language picker, including the login page. Thanks @thanhtantran. (#4241)
## [v0.51.620] — 2026-06-24 — Release WA (fix /api/sessions CPU spike + slowness during streaming)
### Fixed
+9
View File
@@ -7052,6 +7052,15 @@ _LOGIN_LOCALE = {
"invalid_pw": "Nieprawid\u0142owe has\u0142o",
"conn_failed": "Po\u0142\u0105czenie nie powiod\u0142o si\u0119",
},
"vi": {
"lang": "vi",
"title": "\u0110\u0103ng nh\u1eadp",
"subtitle": "Nh\u1eadp m\u1eadt kh\u1ea9u c\u1ee7a b\u1ea1n \u0111\u1ec3 ti\u1ebfp t\u1ee5c",
"placeholder": "M\u1eadt kh\u1ea9u",
"btn": "\u0110\u0103ng nh\u1eadp",
"invalid_pw": "M\u1eadt kh\u1ea9u kh\u00f4ng h\u1ee3p l\u1ec7",
"conn_failed": "K\u1ebft n\u1ed1i th\u1ea5t b\u1ea1i",
},
}
+1615
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -229,7 +229,7 @@ class TestOpenInVsCodeI18n:
"""open_in_vscode key must appear exactly once per locale (11 total)."""
src = I18N.read_text(encoding="utf-8")
count = src.count("open_in_vscode:")
assert count == 12, (
assert count == 13, (
f"Expected 11 open_in_vscode: entries (one per locale), found {count}"
)
@@ -237,7 +237,7 @@ class TestOpenInVsCodeI18n:
"""open_in_vscode_failed key must appear exactly once per locale (11 total)."""
src = I18N.read_text(encoding="utf-8")
count = src.count("open_in_vscode_failed:")
assert count == 12, (
assert count == 13, (
f"Expected 11 open_in_vscode_failed: entries (one per locale), found {count}"
)
+2 -2
View File
@@ -305,8 +305,8 @@ class TestAuxiliaryModelsI18n:
"""Count of each key should equal the number of locales (12 with Turkish)."""
for key in self.REQUIRED_KEYS:
count = I18N_JS.count(f"{key}:")
assert count == 13, (
f"i18n key '{key}' found {count} times — expected 12 (one per locale)"
assert count == 14, (
f"i18n key '{key}' found {count} times — expected 14 (one per locale)"
)
@@ -123,7 +123,7 @@ class TestComposerVoiceButtonI18n:
"voice_mode_toggle_active",
)
LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr", "pl")
LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr", "pl", "vi")
def test_legacy_voice_toggle_key_removed(self):
"""The old key whose string was 'Voice input' caused the duplicate-
@@ -171,7 +171,7 @@ class TestComposerVoiceButtonI18n:
class TestVoiceModePreferenceGate:
"""boot.js must hide btnVoiceMode by default, surface it via Preferences."""
LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr", "pl")
LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr", "pl", "vi")
def test_voice_mode_pref_is_localstorage_backed(self):
"""The pref reads from localStorage key 'hermes-voice-mode-button'."""
+2 -2
View File
@@ -63,8 +63,8 @@ def test_context_indicator_surfaces_cache_hit_rate():
def test_cache_usage_labels_are_localized():
src = (ROOT / "static" / "i18n.js").read_text()
assert src.count("usage_cache_hit_detail:") == 13
assert src.count("usage_cached_percent:") == 13
assert src.count("usage_cache_hit_detail:") == 14
assert src.count("usage_cached_percent:") == 14
assert "usage_cache_hit_detail: 'Cache: {0}% hit ({1} read / {2} write)'" in src
assert "usage_cached_percent: '{0}% cached'" in src
+2 -2
View File
@@ -50,8 +50,8 @@ def test_panels_round_trip_and_hot_apply_hide_suggestions():
def test_hide_suggestions_i18n_all_locales_and_changelog():
js = I18N.read_text(encoding="utf-8")
assert js.count("settings_label_hide_suggestions:") == 13
assert js.count("settings_desc_hide_suggestions:") == 13
assert js.count("settings_label_hide_suggestions:") == 14
assert js.count("settings_desc_hide_suggestions:") == 14
changelog = CHANGELOG.read_text(encoding="utf-8")
assert "#2679" in changelog
assert "hide_empty_state_suggestions" in changelog
+1 -1
View File
@@ -6,7 +6,7 @@ PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
LOCALE_COUNT = 13 # en, it, ja, ru, es, de, zh, zh-TW, pt, ko, fr, tr, pl
LOCALE_COUNT = 14 # en, it, ja, ru, es, de, zh, zh-TW, pt, ko, fr, tr, pl
def test_help_nav_button_present():
+1 -1
View File
@@ -5,7 +5,7 @@ BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
LOCALE_COUNT = 13
LOCALE_COUNT = 14
def test_raw_audio_active_recording_uses_dedicated_i18n_key():
@@ -76,8 +76,8 @@ def test_panels_round_trip_and_hot_apply_virtualize_toggle():
def test_virtualize_toggle_i18n_all_locales():
js = I18N.read_text(encoding="utf-8")
assert js.count("settings_label_virtualize_transcript:") == 13
assert js.count("settings_desc_virtualize_transcript:") == 13
assert js.count("settings_label_virtualize_transcript:") == 14
assert js.count("settings_desc_virtualize_transcript:") == 14
# ── #4343 force-off-for-everyone migration (load_settings behavior) ──────────
+2 -2
View File
@@ -80,10 +80,10 @@ def test_panels_has_no_git_branch():
def test_i18n_no_git_key_all_locales():
"""settings_update_no_git key appears in all 13 locale blocks in static/i18n.js."""
"""settings_update_no_git key appears in all 14 locale blocks in static/i18n.js."""
i18n_path = Path(__file__).resolve().parent.parent / 'static' / 'i18n.js'
content = i18n_path.read_text()
# Count occurrences of the new key (should be exactly 13, one per locale)
count = content.count("settings_update_no_git")
assert count == 13, f"Expected exactly 13 settings_update_no_git keys, found {count}"
assert count == 14, f"Expected exactly 14 settings_update_no_git keys, found {count}"
+2 -2
View File
@@ -112,5 +112,5 @@ def test_rtl_in_config_defaults_and_writable_keys():
def test_rtl_localized_in_all_locales():
js = I18N.read_text(encoding="utf-8")
# Count occurrences — should match the 11 locale blocks
assert js.count("settings_label_rtl:") == 13
assert js.count("settings_desc_rtl:") == 13
assert js.count("settings_label_rtl:") == 14
assert js.count("settings_desc_rtl:") == 14
+2 -2
View File
@@ -88,5 +88,5 @@ def test_quota_chip_panels_round_trip():
def test_quota_chip_localized_in_all_locales():
js = I18N.read_text(encoding="utf-8")
assert js.count("settings_label_quota_chip:") == 13, "12 locales expected"
assert js.count("settings_desc_quota_chip:") == 13, "12 locales expected"
assert js.count("settings_label_quota_chip:") == 14, "12 locales expected"
assert js.count("settings_desc_quota_chip:") == 14, "12 locales expected"
+1 -1
View File
@@ -38,4 +38,4 @@ def test_verdigris_has_no_light_variant():
def test_verdigris_i18n_lists_skin_in_all_locales():
# There are 12 locales; each should now include verdigris as the trailing skin.
# 10 locales use ASCII closing paren, 2 Chinese locales use full-width paren.
assert I18N_JS.count("verdigris)") + I18N_JS.count("verdigris)") == 13
assert I18N_JS.count("verdigris)") + I18N_JS.count("verdigris)") == 14
+112
View File
@@ -0,0 +1,112 @@
from collections import Counter
from pathlib import Path
import re
REPO = Path(__file__).resolve().parent.parent
def read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def test_vietnamese_locale_block_exists():
src = read(REPO / "static" / "i18n.js")
assert "\n _lang: 'vi'," in src
assert "_label: 'Tiếng Việt'" in src
assert "_speech: 'vi-VN'" in src
def extract_locale_block(src: str, locale_key: str) -> str:
start_match = re.search(rf"\b{re.escape(locale_key)}\s*:\s*\{{", src)
assert start_match, f"{locale_key} locale block not found"
brace_start = start_match.end() - 1
depth = 0
in_single = False
in_double = False
in_backtick = False
escape = False
for i in range(brace_start, len(src)):
ch = src[i]
if escape:
escape = False
continue
if in_single:
if ch == "\\":
escape = True
elif ch == "'":
in_single = False
continue
if in_double:
if ch == "\\":
escape = True
elif ch == '"':
in_double = False
continue
if in_backtick:
if ch == "\\":
escape = True
elif ch == "`":
in_backtick = False
continue
if ch == "'":
in_single = True
continue
if ch == '"':
in_double = True
continue
if ch == "`":
in_backtick = True
continue
if ch == "{":
depth += 1
continue
if ch == "}":
depth -= 1
if depth == 0:
return src[brace_start + 1 : i]
raise AssertionError(f"{locale_key} locale block braces are not balanced")
def test_vietnamese_locale_includes_representative_translations():
src = read(REPO / "static" / "i18n.js")
vi_block = extract_locale_block(src, "vi")
expected = [
"settings_heading_title: 'Trung tâm điều khiển'",
"settings_heading_subtitle: 'Tùy chọn, công cụ hội thoại và điều khiển hệ thống.'",
"approval_skip_all: '⚡ Bỏ qua tất cả trong phiên này'",
"checkpoint_title: 'Checkpoint'",
"composer_send: 'Gửi tin nhắn'",
"gateway_restart: 'Khởi động lại'",
"wiki_browse: 'Duyệt wiki'",
"yolo_pill_title_active: 'Chế độ YOLO đang bật — bấm để tắt'",
]
for entry in expected:
assert entry in vi_block
def test_vietnamese_locale_covers_english_keys():
src = read(REPO / "static" / "i18n.js")
key_pattern = re.compile(r"^\s{4}([a-zA-Z0-9_]+):", re.MULTILINE)
en_keys = set(key_pattern.findall(extract_locale_block(src, "en")))
vi_keys = set(key_pattern.findall(extract_locale_block(src, "vi")))
missing = sorted(en_keys - vi_keys)
assert not missing, f"Vietnamese locale missing keys: {missing}"
def test_vietnamese_locale_has_no_duplicate_keys():
src = read(REPO / "static" / "i18n.js")
key_pattern = re.compile(r"^\s{4}([a-zA-Z0-9_]+):", re.MULTILINE)
keys = key_pattern.findall(extract_locale_block(src, "vi"))
duplicates = sorted(k for k, count in Counter(keys).items() if count > 1)
assert not duplicates, f"Vietnamese locale has duplicate keys: {duplicates}"
+1 -1
View File
@@ -45,4 +45,4 @@ def test_zeus_modals_are_not_navy():
def test_zeus_i18n_lists_skin_in_all_locales():
# There are 12 locales; each should include zeus in the skin list.
assert I18N_JS.count("/zeus/") == 13
assert I18N_JS.count("/zeus/") == 14