['p]\n    return cur\n\nwith open(\'static/i18n/en.json\') as f:\n    en = json.load(f)\n\nwith open(\'TRANSLATIONS_TODO.md\') as f:\n    content = f.read()\n\ntodo_keys = []\nfor line in content.split(\'\n\')[90:470]:\n    line = line.strip()\n    if line and not line.startswith(\'#\') and not line.startswith(\'```\') and not line.startswith(\'|\') and \'.\' in line:\n        if line.startswith(\'`\') and line.endswith(\'`\'):\n            todo_keys.append(line.strip(\'`\'))\n        else:\n            todo_keys.append(line)\n\nlp_patterns = [\'_hint\', \'_desc\']\nextra_lp = [\n    \'signup_page.username_hint\',\'signup_page.email_hint\',\'signup_page.password_hint\',\n    \'forgot_page.intro\',\'forgot_page.sent\',\'reset_page.intro\',\'reset_page.password_hint\',\n    \'reset_page.success\',\'reset_page.go_to_login\',\'reset_page.invalid_token\',\'reset_page.request_new\',\n    \'email_page.password_hint\',\'profile_page.display_name_hint\',\'profile_page.no_email\',\n    \'profile_page.add_email\',\'profile_page.change_email\',\'profile_page.email_requires_verify\',\n    \'profile_page.upload_image\',\'profile_page.upload_hint\',\'profile_page.danger_zone\',\n    \'profile_page.danger_zone_desc\',\'profile_page.delete_account\',\'profile_page.uploading\',\n    \'profile_page.upload_pct\',\'profile_page.upload_success\',\'profile_page.upload_too_large\',\n    \'profile_page.upload_invalid_type\',\'profile_page.upload_failed\',\n    \'usage_page.manage_subscription\',\'usage_page.current_plan\',\'usage_page.activity_quotas\',\n    \'usage_page.activity_quotas_desc\',\'usage_page.config_limits\',\'usage_page.config_limits_desc\',\n    \'usage_page.requests_today\',\'usage_page.resets_midnight\',\'usage_page.resets_in\',\n    \'usage_page.requests_month\',\'usage_page.resets_on_1st\',\'usage_page.resets_in_days\',\n    \'usage_page.resets_in_days_plural\',\'usage_page.tokens_24h\',\'usage_page.tokens_combined\',\n    \'usage_page.tokens_used\',\'usage_page.unlimited\',\'usage_page.quota_reached\',\n    \'usage_page.remaining\',\'usage_page.ai_providers\',\'usage_page.ai_providers_desc\',\n    \'usage_page.rotations\',\'usage_page.rotations_desc\',\'usage_page.autoselections\',\n    \'usage_page.autoselections_desc\',\'usage_page.unlimited_slots\',\n    \'usage_page.pct_used_slots_free\',\'usage_page.pct_used_slots_free_plural\',\n    \'usage_page.need_higher_limits\',\'usage_page.upgrade_desc\',\'usage_page.view_plans\',\n    \'prompts_page.select_file\',\'prompts_page.content_hint\',\'prompts_page.reset_confirm\',\n    \'prompts_page.reset_confirm_title\',\'user_overview.admin_access\',\'user_overview.admin_access_desc\',\n    \'user_overview.token_required\',\'user_overview.manage_tokens\'\n]\n\nhp_keys = [k for k in todo_keys if not (any(p in k for p in lp_patterns) or k in extra_lp)]\n\nwith open(\'static/i18n/qya.json\') as f:\n    qya = json.load(f)\n\nmissing_hp = []\nfor k in hp_keys:\n    v_cur = get_value(qya, k)\n    v_en = get_value(en, k)\n    if v_cur is None or v_cur == v_en:\n        missing_hp.append((k, v_en))\n\nprint(f"Quenya HP missing: {len(missing_hp)} out of {len(hp_keys)}")\nprint("\nFirst 20 missing keys:', 'for k, v in missing_hp[:20]:\n    print(f', {'k}': {'v[': 80}, 'w': 'as f:\n    for k', 'missing_hp': 'f.write(f', 'print(f': 'nFull missing list saved to /tmp/qya_missing_full.txt ({len(missing_hp)'}, 'entries)']