from collections import Counter from pathlib import Path import re from tests.test_issue2147_profile_concept_help import PROFILE_CONCEPT_KEYS REPO = Path(__file__).resolve().parent.parent PROFILE_CONCEPT_FALLBACK_KEYS = { *PROFILE_CONCEPT_KEYS, "workspace_artifact_source_session", } def read(path: Path) -> str: return path.read_text(encoding="utf-8") 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" start = start_match.end() - 1 depth = 0 in_single = False in_double = False in_backtick = False escape = False for i in range(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[start + 1 : i] raise AssertionError(f"{locale_key} locale block braces are not balanced") def locale_keys(src: str, locale_key: str) -> list[str]: # Locale objects are flat. Match every key regardless of indentation so # accidental 2-space lines cannot hide from duplicate-key checks. key_pattern = re.compile(r"^\s*([a-zA-Z0-9_]+)\s*:", re.MULTILINE) return key_pattern.findall(extract_locale_block(src, locale_key)) def test_korean_locale_block_exists(): src = read(REPO / "static" / "i18n.js") assert "\n ko: {" in src assert "_lang: 'ko'" in src assert "_label: '한국어'" in src assert "_speech: 'ko-KR'" in src def test_korean_locale_includes_representative_translations(): src = read(REPO / "static" / "i18n.js") expected = [ "settings_title: '설정'", "settings_label_language: '언어'", "login_title: '로그인'", "approval_heading: '승인 필요'", "tab_chat: '채팅'", "tab_tasks: '작업'", "tab_profiles: 'Agent 프로필'", "empty_title: '무엇을 도와드릴까요?'", "onboarding_title: 'Hermes Web UI에 오신 것을 환영합니다'", ] for entry in expected: assert entry in src def test_korean_settings_detail_descriptions_are_translated(): src = read(REPO / "static" / "i18n.js") expected = [ "settings_desc_workspace_panel_open: '활성화하면 새 세션마다 워크스페이스/파일 브라우저 패널이 자동으로 열립니다. 언제든지 수동으로 닫을 수 있습니다.'", "settings_desc_notifications: '앱이 백그라운드에 있을 때 응답이 완료되면 시스템 알림을 표시합니다.'", "settings_desc_token_usage: '각 Assistant 응답 아래에 입력/출력 토큰 수를 표시합니다. /usage로도 전환할 수 있습니다.'", "settings_desc_sidebar_density: '왼쪽 사이드바의 세션 목록에 표시할 메타데이터 양을 제어합니다.'", "settings_desc_auto_title_refresh: '최신 대화를 바탕으로 세션 제목을 자동으로 다시 생성해 대화가 진행되어도 제목을 관련 있게 유지합니다. LLM 제목 생성 모델 설정이 필요합니다.'", "settings_desc_external_sessions: 'CLI, Telegram, Discord, Slack 및 기타 채널의 대화를 세션 목록에 표시합니다. 클릭하여 가져오고 계속하세요.'", "settings_desc_sync_insights: 'WebUI 토큰 사용량을 state.db에 반영하여 hermes /insights에 브라우저 세션 데이터가 포함되도록 합니다. 기본값은 꺼짐입니다.'", "settings_desc_check_updates: 'WebUI 또는 Agent의 새 버전이 있으면 배너를 표시합니다. 백그라운드에서 주기적으로 git fetch를 실행합니다.'", "settings_desc_bot_name: '기본 프로필에만 사용됩니다. 다른 프로필은 각 프로필 이름을 사용합니다.'", "settings_desc_password: '새 비밀번호를 설정하거나 변경하려면 입력하세요. 현재 설정을 유지하려면 비워 두세요.'", ] for entry in expected: assert entry in src def test_korean_locale_matches_english_key_coverage(): src = read(REPO / "static" / "i18n.js") en_keys = set(locale_keys(src, "en")) ko_keys = set(locale_keys(src, "ko")) assert sorted((en_keys - ko_keys) - PROFILE_CONCEPT_FALLBACK_KEYS) == [] assert sorted(ko_keys - en_keys) == [] def test_korean_locale_has_no_duplicate_keys(): src = read(REPO / "static" / "i18n.js") keys = locale_keys(src, "ko") duplicates = sorted(k for k, count in Counter(keys).items() if count > 1) assert not duplicates, f"Korean locale has duplicate keys: {duplicates}" def test_korean_locale_keys_use_standard_indentation(): src = read(REPO / "static" / "i18n.js") ko_block = extract_locale_block(src, "ko") badly_indented = [ line.strip() for line in ko_block.splitlines() if re.match(r"^\s{1,3}[a-zA-Z0-9_]+\s*:", line) ] assert badly_indented == []