using System.Globalization;

namespace PSLauncher.Core.Localization;

/// <summary>
/// Strings localisés du launcher. Approche statique : la langue est résolue au
/// startup (auto-détection ou setting utilisateur) puis fixée pour toute la
/// session. Tout changement de langue nécessite un redémarrage du launcher
/// (les bindings XAML x:Static ne refresh pas dynamiquement).
///
/// Langues supportées : fr (par défaut), en, zh (Simplified), th, ar.
/// Pour Arabic, FlowDirection doit aussi être basculé en RTL côté Window.
/// </summary>
public static class Strings
{
    private static string _lang = "fr";

    /// <summary>Code ISO 639-1 (2 lettres) actuellement actif.</summary>
    public static string Lang => _lang;

    /// <summary>True quand la langue actuelle s'écrit de droite à gauche.</summary>
    public static bool IsRightToLeft => _lang == "ar";

    /// <summary>Langues proposées au sélecteur des Settings (code → libellé natif).</summary>
    public static readonly (string Code, string Name)[] Available =
    {
        ("auto", "Automatique (langue du système)"),
        ("fr",   "Français"),
        ("en",   "English"),
        ("zh",   "中文"),
        ("th",   "ไทย"),
        ("ar",   "العربية"),
    };

    /// <summary>
    /// Initialise la langue à partir de la valeur enregistrée dans la config.
    /// "auto" ou code inconnu → on prend la langue Windows si elle correspond
    /// à une des langues supportées, sinon on tombe sur l'anglais.
    /// </summary>
    public static void Init(string? configValue)
    {
        var supported = new[] { "fr", "en", "zh", "th", "ar" };
        var v = configValue?.Trim().ToLowerInvariant() ?? "auto";

        if (v == "auto" || string.IsNullOrEmpty(v) || Array.IndexOf(supported, v) < 0)
        {
            // Auto-détection depuis la culture utilisateur Windows
            var sys = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
            v = Array.IndexOf(supported, sys) >= 0 ? sys : "en";
        }

        _lang = v;
        // On force aussi la culture système sur cette langue (formats date / nombre)
        try
        {
            var ci = CultureInfo.GetCultureInfo(_lang);
            CultureInfo.DefaultThreadCurrentCulture = ci;
            CultureInfo.DefaultThreadCurrentUICulture = ci;
            Thread.CurrentThread.CurrentCulture = ci;
            Thread.CurrentThread.CurrentUICulture = ci;
        }
        catch { /* culture inconnue, on garde le défaut */ }
    }

    private static string T(string fr, string en, string zh, string th, string ar) => _lang switch
    {
        "en" => en,
        "zh" => zh,
        "th" => th,
        "ar" => ar,
        _ => fr,
    };

    // ==================== TOP BAR ====================
    public static string TopBarCheckUpdates => T("🔄  Vérifier les MAJ", "🔄  Check for updates", "🔄  检查更新", "🔄  ตรวจสอบการอัปเดต", "🔄  التحقق من التحديثات");
    public static string TopBarSettings     => T("⚙  Paramètres",       "⚙  Settings",         "⚙  设置",        "⚙  ตั้งค่า",          "⚙  الإعدادات");

    // ==================== BODY ====================
    public static string BodyVersions       => T("Versions", "Versions", "版本", "เวอร์ชัน", "النسخ");
    public static string BodyVersionsHint   => T(
        "Lance, installe ou supprime une version. Les versions installées et celles disponibles sur le serveur cohabitent.",
        "Launch, install or remove a version. Installed and available versions can coexist.",
        "启动、安装或删除版本。已安装的版本与服务器上可用的版本可以共存。",
        "เปิด ติดตั้ง หรือลบเวอร์ชัน เวอร์ชันที่ติดตั้งและที่มีในเซิร์ฟเวอร์อยู่ร่วมกันได้",
        "تشغيل أو تثبيت أو إزالة نسخة. النسخ المثبتة والمتاحة على الخادم يمكن أن تتعايش."
    );

    public static string FeaturedCurrent    => T("VERSION COURANTE", "CURRENT VERSION", "当前版本", "เวอร์ชันปัจจุบัน", "النسخة الحالية");
    public static string SectionOther       => T("AUTRES VERSIONS",  "OTHER VERSIONS",  "其他版本",   "เวอร์ชันอื่นๆ",   "نسخ أخرى");
    public static string ReleasedOn         => T("Sortie le ",       "Released ",       "发布于 ",   "เผยแพร่เมื่อ ",   "صدر في ");

    // ==================== STATUS BADGES ====================
    public static string StatusInstalled    => T("● Installée",          "● Installed",          "● 已安装",      "● ติดตั้งแล้ว",        "● مثبتة");
    public static string StatusAvailable    => T("○ Disponible",         "○ Available",          "○ 可用",       "○ พร้อมใช้งาน",       "○ متاحة");
    public static string StatusDownloading  => T("⬇ Téléchargement…",    "⬇ Downloading…",       "⬇ 下载中…",    "⬇ กำลังดาวน์โหลด…",  "⬇ جارٍ التنزيل…");
    public static string StatusInstalling   => T("📦 Installation…",     "📦 Installing…",       "📦 安装中…",   "📦 กำลังติดตั้ง…",    "📦 جارٍ التثبيت…");
    public static string StatusUninstalling => T("🗑 Suppression…",      "🗑 Uninstalling…",     "🗑 卸载中…",   "🗑 กำลังถอนการติดตั้ง…", "🗑 جارٍ الإزالة…");
    public static string StatusVerifying    => T("🔍 Vérification…",     "🔍 Verifying…",        "🔍 校验中…",   "🔍 กำลังตรวจสอบ…",     "🔍 جارٍ التحقق…");

    // ==================== BETA BADGE ====================
    /// <summary>Texte affiché dans la pill orange à côté de la version. Court et localisé.</summary>
    public static string BetaBadgeLabel => T("BÊTA", "BETA", "测试版", "เบต้า", "تجريبي");

    /// <summary>Tooltip par défaut quand la version est tag BÊTA mais sans note des testeurs.</summary>
    public static string BetaBadgeTooltipDefault => T(
        "Version BÊTA — utilisée pour les tests internes. Vos retours nous aident à la valider avant publication officielle.",
        "BETA version — used for internal testing. Your feedback helps us validate it before official release.",
        "测试版 — 用于内部测试。您的反馈有助于我们在正式发布前验证此版本。",
        "เวอร์ชันเบต้า — ใช้สำหรับการทดสอบภายใน ความคิดเห็นของคุณช่วยให้เรายืนยันก่อนการเปิดตัวอย่างเป็นทางการ",
        "إصدار تجريبي — يُستخدم للاختبار الداخلي. ملاحظاتك تساعدنا في التحقق منه قبل الإصدار الرسمي."
    );

    /// <summary>Tooltip avec les notes spécifiques fournies par l'admin côté serveur.</summary>
    public static string BetaBadgeTooltip(string testerNotes) => T(
        $"Version BÊTA — {testerNotes}",
        $"BETA version — {testerNotes}",
        $"测试版 — {testerNotes}",
        $"เวอร์ชันเบต้า — {testerNotes}",
        $"إصدار تجريبي — {testerNotes}"
    );

    // ==================== ACTIONS ====================
    public static string ActionLaunch       => T("▶  Lancer",       "▶  Launch",       "▶  启动", "▶  เปิด",     "▶  تشغيل");
    public static string ActionLaunchBig    => T("▶  LANCER",       "▶  LAUNCH",       "▶  启动", "▶  เปิด",     "▶  تشغيل");
    public static string ActionInstall      => T("⬇  Installer",    "⬇  Install",      "⬇  安装", "⬇  ติดตั้ง",  "⬇  تثبيت");
    public static string ActionInstallBig   => T("⬇  INSTALLER",    "⬇  INSTALL",      "⬇  安装", "⬇  ติดตั้ง",  "⬇  تثبيت");
    public static string ActionCancel       => T("Annuler",         "Cancel",          "取消",   "ยกเลิก",     "إلغاء");
    public static string ActionOk           => T("OK",              "OK",              "确定",   "ตกลง",       "موافق");
    public static string ActionYes          => T("Oui",             "Yes",             "是",     "ใช่",        "نعم");
    public static string ActionNo           => T("Non",             "No",              "否",     "ไม่",        "لا");
    public static string ActionLater        => T("Plus tard",       "Later",           "稍后",   "ภายหลัง",    "لاحقاً");
    public static string ActionSave         => T("Enregistrer",     "Save",            "保存",   "บันทึก",     "حفظ");
    public static string ActionClose        => T("Fermer",          "Close",           "关闭",   "ปิด",        "إغلاق");
    // Affiché sur le bouton install d'une version dont le minLicenseDate dépasse l'entitlement de la license.
    // Concrètement : ta license expire le 2024-12-31 mais cette version a été sortie le 2025-03-15 → tu ne peux pas la télécharger.
    // Tu peux toujours installer/lancer une version antérieure couverte par ta période de license.
    public static string ActionLicenseInsufficient => T(
        "🔒  License de mise à jour requise pour cette version",
        "🔒  Valid update license needed for this version",
        "🔒  此版本需要有效的更新许可证",
        "🔒  เวอร์ชันนี้ต้องมีใบอนุญาตอัปเดตที่ใช้ได้",
        "🔒  هذه النسخة تتطلب ترخيص تحديث صالح"
    );

    // ==================== MENU "..." ====================
    public static string MenuReleaseNotes   => T("📜  Voir les release notes", "📜  View release notes", "📜  查看版本说明",      "📜  ดูบันทึกการเปลี่ยนแปลง", "📜  عرض ملاحظات الإصدار");
    public static string MenuOpenFolder     => T("📁  Ouvrir le dossier",      "📁  Open folder",        "📁  打开文件夹",       "📁  เปิดโฟลเดอร์",         "📁  فتح المجلد");
    public static string MenuUninstall      => T("🗑  Supprimer cette version…", "🗑  Uninstall this version…", "🗑  删除此版本…",     "🗑  ถอนการติดตั้งเวอร์ชันนี้…", "🗑  إزالة هذه النسخة…");

    // ==================== LICENSE BADGE ====================
    // Note : on parle de « License de mise à jour » (Software Update License) plutôt
    // que de « License Proserve » : Proserve lui-même est librement utilisable une
    // fois installé, la license sert UNIQUEMENT à donner accès aux téléchargements
    // de nouvelles versions. Un user expiré garde l'accès à toutes les versions
    // sorties pendant sa période de license + peut lancer ses versions installées.
    public static string LicenseNone        => T("Aucune license de MAJ",       "No update license",      "无更新许可证",       "ไม่มีใบอนุญาตอัปเดต",   "لا يوجد ترخيص تحديث");
    public static string LicenseRevoked     => T("Révoquée",                    "Revoked",                "已撤销",            "ถูกเพิกถอน",          "تم إلغاؤه");
    public static string LicenseTooltip     => T("Voir / changer la license de mise à jour", "View / change update license", "查看 / 更改更新许可证", "ดู / เปลี่ยนใบอนุญาตอัปเดต", "عرض / تغيير ترخيص التحديث");

    // ==================== SETTINGS ====================
    public static string SettingsTitle      => T("Paramètres", "Settings", "设置", "ตั้งค่า", "الإعدادات");
    public static string SettingsLicense    => T("LICENSE DE MISE À JOUR", "SOFTWARE UPDATE LICENSE", "软件更新许可证", "ใบอนุญาตอัปเดตซอฟต์แวร์", "ترخيص تحديث البرنامج");
    public static string SettingsServer     => T("SERVEUR",        "SERVER",         "服务器",         "เซิร์ฟเวอร์",   "الخادم");
    public static string SettingsInstallation => T("INSTALLATION", "INSTALLATION",   "安装",          "การติดตั้ง",     "التثبيت");
    public static string SettingsCache      => T("CACHE DE TÉLÉCHARGEMENTS", "DOWNLOAD CACHE", "下载缓存", "แคชดาวน์โหลด", "ذاكرة التخزين المؤقت للتنزيلات");
    public static string SettingsLogs       => T("LOGS & APPLICATION", "LOGS & APP",  "日志与应用",     "บันทึกและแอป", "السجلات والتطبيق");
    public static string SettingsDatabase   => T("BASE DE DONNÉES (XAMPP / MySQL)", "DATABASE (XAMPP / MySQL)", "数据库（XAMPP / MySQL）", "ฐานข้อมูล (XAMPP / MySQL)", "قاعدة البيانات (XAMPP / MySQL)");
    public static string SettingsDbHost     => T("Hôte",                "Host",                "主机",          "โฮสต์",            "المضيف");
    public static string SettingsDbPort     => T("Port",                "Port",                "端口",          "พอร์ต",           "المنفذ");
    public static string SettingsDbUser     => T("Utilisateur",         "User",                "用户",          "ผู้ใช้",           "المستخدم");
    public static string SettingsDbPassword => T("Mot de passe",        "Password",            "密码",          "รหัสผ่าน",         "كلمة المرور");
    public static string SettingsDbName     => T("Nom de la base",      "Database name",       "数据库名称",     "ชื่อฐานข้อมูล",     "اسم قاعدة البيانات");
    public static string SettingsDbAutoMigrate => T(
        "Appliquer automatiquement les migrations à l'install d'une nouvelle version",
        "Automatically apply migrations when installing a new version",
        "安装新版本时自动应用迁移",
        "ปรับใช้การโยกย้ายโดยอัตโนมัติเมื่อติดตั้งเวอร์ชันใหม่",
        "تطبيق الترقيات تلقائياً عند تثبيت نسخة جديدة"
    );
    public static string SettingsDbReplay   => T("🔁  Rejouer les migrations", "🔁  Replay migrations", "🔁  重新执行迁移", "🔁  เรียกใช้การโยกย้ายอีกครั้ง", "🔁  إعادة تشغيل الترقيات");
    // ==================== RIGHT NAV SIDEBAR ====================
    public static string NavLibrary       => T("Bibliothèque", "Library",       "库",          "ห้องสมุด",       "المكتبة");
    public static string NavReport        => T("Statistiques", "Reports",       "统计",         "รายงาน",         "التقارير");
    public static string NavDocumentation => T("Documentation", "Documentation","文档",         "เอกสาร",         "التوثيق");

    public static string SettingsReportUrl    => T("URL de l'outil de reporting local", "Local reporting tool URL", "本地报告工具 URL", "URL ของเครื่องมือรายงานในเครื่อง", "عنوان أداة التقارير المحلية");
    public static string SettingsDocsUrl      => T("URL de la documentation",          "Documentation URL",       "文档 URL",        "URL ของเอกสาร",                "عنوان التوثيق");
    public static string DocsPlaceholder      => T(
        "Aucune URL de documentation configurée.\n\nVa dans Paramètres → Avancés → Base de données pour pointer vers la documentation locale ou en ligne.",
        "No documentation URL configured.\n\nGo to Settings → Advanced → Database to point to local or online documentation.",
        "未配置文档 URL。\n\n前往 设置 → 高级 → 数据库 以指向本地或在线文档。",
        "ยังไม่ได้กำหนด URL เอกสาร\n\nไปที่ การตั้งค่า → ขั้นสูง → ฐานข้อมูล เพื่อชี้ไปยังเอกสารในเครื่องหรือออนไลน์",
        "لم يتم تكوين عنوان التوثيق.\n\nاذهب إلى الإعدادات → متقدم → قاعدة البيانات للإشارة إلى التوثيق المحلي أو عبر الإنترنت."
    );
    public static string WebViewLoadError(string url, string detail) => T(
        $"Impossible de charger {url}\n\n{detail}\n\nVérifie que XAMPP est démarré et que l'URL est correcte (Paramètres → Avancés).",
        $"Could not load {url}\n\n{detail}\n\nCheck that XAMPP is running and the URL is correct (Settings → Advanced).",
        $"无法加载 {url}\n\n{detail}\n\n请检查 XAMPP 是否正在运行以及 URL 是否正确（设置 → 高级）。",
        $"ไม่สามารถโหลด {url}\n\n{detail}\n\nตรวจสอบว่า XAMPP กำลังทำงานและ URL ถูกต้อง (ตั้งค่า → ขั้นสูง)",
        $"تعذر تحميل {url}\n\n{detail}\n\nتأكد من تشغيل XAMPP وأن العنوان صحيح (الإعدادات → متقدم)."
    );

    public static string SettingsParallelSegments => T(
        "Connexions parallèles pour les téléchargements (1-32, défaut 16)",
        "Parallel connections for downloads (1-32, default 16)",
        "下载的并行连接数 (1-32，默认 16)",
        "การเชื่อมต่อแบบขนานสำหรับการดาวน์โหลด (1-32, ค่าเริ่มต้น 16)",
        "اتصالات متوازية للتنزيلات (1-32، الافتراضي 16)"
    );
    public static string SettingsParallelSegmentsHint => T(
        "Plus de connexions = débit pic plus élevé et queue de fin de DL plus courte. Au-delà de 16 le serveur peut throttler.",
        "More connections = higher peak throughput and shorter end-of-DL tail. Above 16 the server may throttle.",
        "更多连接 = 更高的峰值吞吐量和更短的下载尾部。超过 16 服务器可能会限速。",
        "การเชื่อมต่อมากขึ้น = ปริมาณงานสูงสุดมากขึ้นและส่วนท้ายของการดาวน์โหลดสั้นลง สูงกว่า 16 เซิร์ฟเวอร์อาจจำกัด",
        "اتصالات أكثر = إنتاجية ذروة أعلى وذيل تنزيل أقصر. فوق 16 قد يقيّد الخادم."
    );

    public static string SettingsAdvanced   => T(
        "▸  PARAMÈTRES AVANCÉS (serveur, installation, base de données, logs)",
        "▸  ADVANCED SETTINGS (server, installation, database, logs)",
        "▸  高级设置（服务器、安装、数据库、日志）",
        "▸  ตั้งค่าขั้นสูง (เซิร์ฟเวอร์ การติดตั้ง ฐานข้อมูล บันทึก)",
        "▸  الإعدادات المتقدمة (الخادم، التثبيت، قاعدة البيانات، السجلات)"
    );
    public static string SettingsAbout      => T("À PROPOS", "ABOUT", "关于", "เกี่ยวกับ", "حول");
    public static string SettingsDbHint     => T(
        "Le launcher applique automatiquement les scripts SQL bundled dans _migrations/ de chaque ZIP PROSERVE. Mot de passe stocké chiffré DPAPI.",
        "The launcher automatically applies SQL scripts bundled in each PROSERVE ZIP's _migrations/ folder. Password stored DPAPI-encrypted.",
        "启动器自动应用每个 PROSERVE ZIP 的 _migrations/ 文件夹中捆绑的 SQL 脚本。密码使用 DPAPI 加密存储。",
        "Launcher จะปรับใช้สคริปต์ SQL ที่บรรจุใน _migrations/ ของ ZIP PROSERVE แต่ละชุดโดยอัตโนมัติ รหัสผ่านถูกเก็บแบบเข้ารหัส DPAPI",
        "يطبق المُشغِّل تلقائياً نصوص SQL المرفقة في _migrations/ لكل ZIP من PROSERVE. كلمة المرور مُخزَّنة بتشفير DPAPI."
    );
    public static string SettingsLanguage   => T("LANGUE",         "LANGUAGE",       "语言",          "ภาษา",          "اللغة");
    public static string SettingsLanguageHint => T(
        "Le launcher redémarrera pour appliquer le changement.",
        "The launcher will restart to apply the change.",
        "启动器将重启以应用更改。",
        "Launcher จะรีสตาร์ทเพื่อใช้การเปลี่ยนแปลง",
        "سيتم إعادة تشغيل المُشغِّل لتطبيق التغيير."
    );
    public static string SettingsTest       => T("Tester",     "Test",     "测试",  "ทดสอบ",   "اختبار");
    public static string SettingsBrowse     => T("📁  Parcourir", "📁  Browse", "📁  浏览", "📁  เรียกดู", "📁  استعراض");
    public static string SettingsClearCache => T("🗑  Vider",   "🗑  Clear", "🗑  清空", "🗑  ล้าง",  "🗑  مسح");

    // ==================== ONBOARDING ====================
    public static string OnboardingTitle    => T("Activation PROSERVE Launcher", "PROSERVE Launcher activation", "PROSERVE Launcher 激活", "เปิดใช้งาน PROSERVE Launcher", "تنشيط PROSERVE Launcher");
    public static string OnboardingHeading  => T("Activer votre license de mise à jour", "Activate your software update license", "激活您的软件更新许可证", "เปิดใช้งานใบอนุญาตอัปเดตซอฟต์แวร์", "تنشيط ترخيص تحديث البرنامج");
    public static string OnboardingBody     => T(
        "Saisis la clé fournie par ASTERION. Elle te donne accès aux téléchargements de toutes les versions sorties jusqu'à la date de validité associée. Une fois installées, les versions sont utilisables sans connexion.",
        "Enter the key provided by ASTERION. It grants download access to all versions released until the associated expiration date. Once installed, versions can be used offline.",
        "输入 ASTERION 提供的密钥。它授予您在关联到期日期之前发布的所有版本的下载权限。一旦安装，版本可离线使用。",
        "ป้อนคีย์ที่ ASTERION ให้มา ซึ่งให้สิทธิ์ดาวน์โหลดเวอร์ชันทั้งหมดที่เผยแพร่จนถึงวันหมดอายุที่เกี่ยวข้อง เมื่อติดตั้งแล้วเวอร์ชันสามารถใช้งานได้แบบออฟไลน์",
        "أدخل المفتاح المقدم من ASTERION. يمنح حق تنزيل جميع النسخ الصادرة حتى تاريخ انتهاء الصلاحية المرتبط. بمجرد التثبيت، يمكن استخدام النسخ دون اتصال."
    );
    public static string OnboardingLicenseKey => T("Clé de license", "License key", "许可证密钥", "คีย์ใบอนุญาต", "مفتاح الترخيص");
    public static string OnboardingActivate   => T("Activer", "Activate", "激活", "เปิดใช้งาน", "تنشيط");

    // ==================== UPDATE ====================
    public static string UpdateAvailableTitle => T("Mise à jour disponible", "Update available", "有可用更新", "มีการอัปเดต", "تحديث متاح");
    public static string UpdateDownload       => T("⬇  Télécharger", "⬇  Download", "⬇  下载", "⬇  ดาวน์โหลด", "⬇  تنزيل");
    public static string LauncherUpdateTitle  => T("Mise à jour du launcher", "Launcher update", "启动器更新", "อัปเดต Launcher", "تحديث المُشغِّل");
    public static string LauncherUpdateAvailable => T("Mise à jour du launcher disponible", "Launcher update available", "启动器更新可用", "มีการอัปเดต Launcher", "تحديث المُشغِّل متاح");
    public static string LauncherUpdateBody   => T(
        "Le launcher va se mettre à jour : il téléchargera le nouveau binaire, se fermera, sera remplacé, puis se relancera automatiquement. Cela prend quelques secondes.",
        "The launcher will update: it will download the new binary, close, be replaced, then restart automatically. This takes a few seconds.",
        "启动器将进行更新：下载新二进制文件、关闭、替换，然后自动重新启动。这需要几秒钟。",
        "Launcher จะอัปเดต: ดาวน์โหลดไฟล์ใหม่ ปิด แทนที่ แล้วรีสตาร์ทอัตโนมัติ ใช้เวลาไม่กี่วินาที",
        "سيتم تحديث المُشغِّل: سينزل البرنامج الجديد، يغلق، يُستبدل، ثم يعيد التشغيل تلقائياً. يستغرق ذلك بضع ثوان."
    );
    public static string LauncherUpdateNow    => T("⬇  Mettre à jour", "⬇  Update now", "⬇  立即更新", "⬇  อัปเดตเดี๋ยวนี้", "⬇  تحديث الآن");

    // ==================== MESSAGEBOX TITLES ====================
    public static string MsgBoxError       => T("Erreur",         "Error",          "错误",         "ข้อผิดพลาด",      "خطأ");
    public static string MsgBoxLaunchError => T("Erreur de lancement", "Launch error", "启动错误",  "เปิดใช้งานล้มเหลว", "خطأ في التشغيل");
    public static string MsgBoxConfirm     => T("Confirmer",      "Confirm",        "确认",         "ยืนยัน",          "تأكيد");
    public static string MsgBoxInfo        => T("Information",    "Information",    "信息",         "ข้อมูล",          "معلومة");
    public static string MsgBoxPatience    => T("Patience",       "Please wait",    "请稍候",       "โปรดรอ",         "يرجى الانتظار");
    public static string MsgBoxLanguageChange => T("Changement de langue", "Language change", "更改语言", "เปลี่ยนภาษา", "تغيير اللغة");
    public static string MsgBoxReleaseNotes => T("Release notes", "Release notes",   "版本说明",     "บันทึกการเปลี่ยนแปลง", "ملاحظات الإصدار");

    // ==================== MESSAGEBOX MESSAGES ====================
    public static string MsgBusy => T(
        "Une autre opération est déjà en cours.",
        "Another operation is already in progress.",
        "另一个操作正在进行中。",
        "มีการดำเนินการอื่นอยู่แล้ว",
        "هناك عملية أخرى قيد التنفيذ بالفعل."
    );

    public static string MsgLaunchFailed(string detail) => T(
        $"Impossible de lancer la version :\n\n{detail}",
        $"Could not launch the version:\n\n{detail}",
        $"无法启动该版本：\n\n{detail}",
        $"ไม่สามารถเปิดเวอร์ชันนี้ได้:\n\n{detail}",
        $"تعذر تشغيل النسخة:\n\n{detail}"
    );

    public static string MsgSelfUpdateFailed(string detail) => T(
        $"Mise à jour du launcher échouée :\n\n{detail}",
        $"Launcher update failed:\n\n{detail}",
        $"启动器更新失败：\n\n{detail}",
        $"การอัปเดต Launcher ล้มเหลว:\n\n{detail}",
        $"فشل تحديث المُشغِّل:\n\n{detail}"
    );

    public static string MsgInstallFailed(string detail) => T(
        $"Le téléchargement ou l'installation a échoué :\n\n{detail}",
        $"Download or installation failed:\n\n{detail}",
        $"下载或安装失败：\n\n{detail}",
        $"การดาวน์โหลดหรือติดตั้งล้มเหลว:\n\n{detail}",
        $"فشل التنزيل أو التثبيت:\n\n{detail}"
    );

    // ---- SHA-256 mismatch après DL : message ciblé avec source + action ----
    public static string SourceOvh => T("OVH (CDN public)", "OVH (public CDN)", "OVH（公共 CDN）", "OVH (CDN สาธารณะ)", "OVH (CDN عام)");
    public static string SourcePeer(string host) => T(
        $"peer LAN « {host} »", $"LAN peer « {host} »", $"局域网节点 « {host} »",
        $"LAN peer « {host} »", $"عقدة LAN « {host} »");
    public static string MsgBoxShaMismatch => T(
        "Fichier téléchargé corrompu ou obsolète",
        "Downloaded file corrupted or outdated",
        "下载的文件已损坏或过时",
        "ไฟล์ที่ดาวน์โหลดเสียหายหรือล้าสมัย",
        "الملف المنزّل تالف أو قديم"
    );
    public static string MsgShaMismatchBody(string version, string sourceLabel) => T(
        $"Le ZIP de v{version} a été téléchargé depuis {sourceLabel}, mais son SHA-256 ne correspond pas à celui attendu par le manifest.\n\nCauses probables :\n  • Le cache CDN OVH n'a pas encore expiré après un re-build serveur (TTL 24-72 h)\n  • Un peer LAN sert un ZIP avec un sidecar SHA corrompu\n  • Le SHA dans le manifest a été modifié sans re-build du ZIP\n\nLe cache local pour cette version a été automatiquement purgé. Réessaye l'installation dans quelques minutes (le temps que la source se rafraîchisse) ou utilise « ↻ Forcer re-téléchargement » dans le menu \"…\" de la version.",
        $"The v{version} ZIP was downloaded from {sourceLabel}, but its SHA-256 does not match the manifest's expected value.\n\nLikely causes:\n  • OVH CDN cache hasn't expired yet after a server rebuild (TTL 24-72h)\n  • A LAN peer is serving a ZIP with a corrupt SHA sidecar\n  • The manifest SHA was changed without rebuilding the ZIP\n\nThe local cache for this version was automatically purged. Retry the install in a few minutes (let the source refresh) or use « ↻ Force re-download » in the version's \"…\" menu.",
        $"v{version} 的 ZIP 是从 {sourceLabel} 下载的，但其 SHA-256 与清单中预期的值不匹配。\n\n可能的原因：\n  • 服务器重建后 OVH CDN 缓存尚未过期（TTL 24-72 小时）\n  • 局域网节点提供带有损坏的 SHA sidecar 的 ZIP\n  • 清单 SHA 已更改但未重建 ZIP\n\n此版本的本地缓存已自动清除。请在几分钟后重试安装（让源刷新）或使用版本的 \"…\" 菜单中的 \"↻ 强制重新下载\"。",
        $"ZIP ของ v{version} ดาวน์โหลดจาก {sourceLabel} แต่ SHA-256 ไม่ตรงกับค่าที่คาดหวังจาก manifest\n\nสาเหตุที่เป็นไปได้:\n  • แคช OVH CDN ยังไม่หมดอายุหลังการ rebuild เซิร์ฟเวอร์ (TTL 24-72 ชม.)\n  • LAN peer ให้บริการ ZIP ที่มี SHA sidecar เสียหาย\n  • SHA ใน manifest ถูกแก้ไขโดยไม่ rebuild ZIP\n\nแคชในเครื่องสำหรับเวอร์ชันนี้ถูกล้างอัตโนมัติ ลองติดตั้งอีกครั้งในไม่กี่นาที (ให้ source รีเฟรช) หรือใช้ \"↻ บังคับดาวน์โหลดใหม่\" ในเมนู \"…\" ของเวอร์ชัน",
        $"تم تنزيل ZIP v{version} من {sourceLabel}، لكن SHA-256 الخاص به لا يطابق القيمة المتوقعة من البيان.\n\nالأسباب المحتملة:\n  • لم تنته صلاحية ذاكرة التخزين المؤقت لـ OVH CDN بعد إعادة بناء الخادم (TTL 24-72 ساعة)\n  • عقدة LAN تقدم ZIP بـ SHA sidecar تالف\n  • تم تعديل SHA في البيان دون إعادة بناء ZIP\n\nتم مسح ذاكرة التخزين المؤقت المحلية لهذا الإصدار تلقائياً. أعد المحاولة بعد دقائق (دع المصدر ينعش) أو استخدم \"↻ فرض إعادة التنزيل\" في قائمة \"…\" للإصدار."
    );
    public static string StatusShaMismatch(string version) => T(
        $"⚠ v{version} : SHA-256 invalide — cache purgé, réessaye",
        $"⚠ v{version}: SHA-256 invalid — cache purged, retry",
        $"⚠ v{version}：SHA-256 无效 — 缓存已清除，请重试",
        $"⚠ v{version}: SHA-256 ไม่ถูกต้อง — แคชถูกล้าง ลองใหม่",
        $"⚠ v{version}: SHA-256 غير صالح — تم مسح الذاكرة، أعد المحاولة"
    );
    // ---- HTTP 404 mid-DL : manifest local obsolète (fichier renommé serveur) ----
    public static string MsgBoxStaleManifest => T(
        "Manifest local obsolète",
        "Local manifest outdated",
        "本地清单已过时",
        "Manifest ในเครื่องล้าสมัย",
        "البيان المحلي قديم"
    );
    public static string MsgStaleManifestBody(string version) => T(
        $"Le fichier ZIP de v{version} a été renommé ou supprimé côté serveur (typique d'un re-upload pour invalider le cache OVH CDN).\n\nLe launcher a automatiquement re-fetché le manifest depuis le serveur — la nouvelle URL devrait maintenant être connue.\n\nRe-clique « Installer » pour réessayer avec l'URL fraîche.",
        $"The v{version} ZIP file was renamed or deleted on the server (typical of a re-upload to invalidate OVH CDN cache).\n\nThe launcher has automatically re-fetched the manifest from the server — the new URL should now be known.\n\nClick « Install » again to retry with the fresh URL.",
        $"v{version} 的 ZIP 文件已在服务器端重命名或删除（典型的重新上传以使 OVH CDN 缓存失效）。\n\n启动器已自动从服务器重新获取清单 — 新 URL 现在应该已知。\n\n再次点击 \"安装\" 以使用新 URL 重试。",
        $"ไฟล์ ZIP ของ v{version} ถูกเปลี่ยนชื่อหรือลบบนเซิร์ฟเวอร์ (ทั่วไปจากการอัปโหลดใหม่เพื่อล้างแคช OVH CDN)\n\nLauncher ดึง manifest จากเซิร์ฟเวอร์อัตโนมัติแล้ว — ตอนนี้ควรรู้ URL ใหม่\n\nคลิก \"ติดตั้ง\" อีกครั้งเพื่อลองด้วย URL ใหม่",
        $"تم إعادة تسمية أو حذف ملف ZIP الخاص بـ v{version} من جانب الخادم (نموذجي لإعادة الرفع لإبطال ذاكرة التخزين المؤقت لـ OVH CDN).\n\nقام المشغل بإعادة جلب البيان تلقائياً من الخادم — يجب أن يكون عنوان URL الجديد معروفاً الآن.\n\nانقر فوق \"تثبيت\" مرة أخرى لإعادة المحاولة باستخدام عنوان URL الجديد."
    );
    public static string StatusManifestRefreshed => T(
        "Manifest rafraîchi — re-clique Installer",
        "Manifest refreshed — click Install again",
        "清单已刷新 — 再次点击安装",
        "Manifest รีเฟรชแล้ว — คลิกติดตั้งอีกครั้ง",
        "تم تحديث البيان — انقر فوق تثبيت مرة أخرى"
    );
    public static string StatusAutoRetryAfter404(string version) => T(
        $"v{version} : manifest rafraîchi, re-lancement automatique du téléchargement…",
        $"v{version}: manifest refreshed, auto-retrying download…",
        $"v{version}：清单已刷新，自动重试下载…",
        $"v{version}: รีเฟรช manifest แล้ว ลองดาวน์โหลดใหม่อัตโนมัติ…",
        $"v{version}: تم تحديث البيان، إعادة محاولة التنزيل تلقائياً…"
    );
    public static string MsgServerSideStaleBody(string version) => T(
        $"Le téléchargement de v{version} échoue toujours en 404 même APRÈS un refresh du manifest depuis le serveur.\n\nDiagnostic :\n  • Le manifest serveur lui-même est obsolète — il référence l'ancien nom de fichier qui n'existe plus.\n  • Action côté serveur requise : le manifest doit être mis à jour pour pointer vers le nouveau nom du ZIP (ou re-uploader avec l'ancien nom).\n\nVérifie le backoffice serveur. Une fois le manifest corrigé, clique « Vérifier les MAJ » puis « Installer ».",
        $"v{version} download still fails with 404 EVEN AFTER refreshing the manifest from the server.\n\nDiagnosis:\n  • The server-side manifest itself is outdated — it references the old filename which no longer exists.\n  • Server-side action required: the manifest must be updated to point to the new ZIP filename (or re-upload with the old name).\n\nCheck the server backoffice. Once the manifest is fixed, click « Check Updates » then « Install ».",
        $"v{version} 下载即使在从服务器刷新清单后仍因 404 失败。\n\n诊断：\n  • 服务器端清单本身已过时 — 它引用了不再存在的旧文件名。\n  • 需要服务器端操作：清单必须更新以指向新的 ZIP 文件名（或以旧名称重新上传）。\n\n检查服务器后台。修复清单后，点击 \"检查更新\" 然后 \"安装\"。",
        $"การดาวน์โหลด v{version} ยังคงล้มเหลวด้วย 404 แม้หลังจากรีเฟรช manifest จากเซิร์ฟเวอร์\n\nการวินิจฉัย:\n  • Manifest ฝั่งเซิร์ฟเวอร์เองล้าสมัย — อ้างถึงชื่อไฟล์เก่าที่ไม่มีอยู่แล้ว\n  • ต้องดำเนินการฝั่งเซิร์ฟเวอร์: manifest ต้องอัปเดตให้ชี้ไปยังชื่อ ZIP ใหม่ (หรืออัปโหลดใหม่ด้วยชื่อเก่า)\n\nตรวจสอบ backoffice เซิร์ฟเวอร์ เมื่อ manifest ถูกแก้ไขแล้ว ให้คลิก \"ตรวจสอบการอัปเดต\" จากนั้น \"ติดตั้ง\"",
        $"لا يزال تنزيل v{version} يفشل بـ 404 حتى بعد تحديث البيان من الخادم.\n\nالتشخيص:\n  • البيان من جانب الخادم نفسه قديم — يشير إلى اسم الملف القديم الذي لم يعد موجوداً.\n  • مطلوب إجراء من جانب الخادم: يجب تحديث البيان للإشارة إلى اسم ZIP الجديد (أو إعادة الرفع بالاسم القديم).\n\nتحقق من backoffice الخادم. بعد إصلاح البيان، انقر فوق \"التحقق من التحديثات\" ثم \"تثبيت\"."
    );

    public static string MenuForceFreshDownload => T(
        "↻ Forcer re-téléchargement (vider cache local)",
        "↻ Force re-download (purge local cache)",
        "↻ 强制重新下载（清除本地缓存）",
        "↻ บังคับดาวน์โหลดใหม่ (ล้างแคช)",
        "↻ فرض إعادة التنزيل (مسح الذاكرة)"
    );
    public static string MsgBoxForceFresh => T(
        "Forcer le re-téléchargement",
        "Force re-download",
        "强制重新下载",
        "บังคับดาวน์โหลดใหม่",
        "فرض إعادة التنزيل"
    );
    public static string MsgForceFreshConfirm(string version) => T(
        $"Vider le cache local pour v{version} et forcer un re-téléchargement complet ?\n\nCela supprimera le ZIP cache + le sidecar SHA + tout partial de DL en cours pour cette version. Utile quand le SHA-256 a changé côté serveur et que la source précédente était obsolète.",
        $"Purge the local cache for v{version} and force a full re-download?\n\nThis will delete the cached ZIP + SHA sidecar + any in-progress partial for this version. Useful when the server-side SHA-256 has changed and the previous source was stale.",
        $"清除 v{version} 的本地缓存并强制完全重新下载？\n\n这将删除缓存的 ZIP + SHA sidecar + 此版本的任何进行中的部分下载。当服务器端 SHA-256 已更改且之前的源已过时时很有用。",
        $"ล้างแคชในเครื่องสำหรับ v{version} และบังคับให้ดาวน์โหลดใหม่ทั้งหมด?\n\nสิ่งนี้จะลบ ZIP ที่แคช + SHA sidecar + การดาวน์โหลดบางส่วนที่กำลังดำเนินการสำหรับเวอร์ชันนี้ มีประโยชน์เมื่อ SHA-256 ฝั่งเซิร์ฟเวอร์เปลี่ยนแปลงและแหล่งที่มาก่อนหน้านี้ล้าสมัย",
        $"مسح ذاكرة التخزين المؤقت المحلية لـ v{version} وفرض إعادة تنزيل كاملة؟\n\nسيؤدي هذا إلى حذف ZIP المخزن مؤقتاً + SHA sidecar + أي تنزيل جزئي قيد التقدم لهذا الإصدار. مفيد عندما يتم تغيير SHA-256 على جانب الخادم وكان المصدر السابق قديماً."
    );

    public static string MsgUninstallFailed(string detail) => T(
        $"Suppression échouée : {detail}",
        $"Uninstall failed: {detail}",
        $"卸载失败：{detail}",
        $"การถอนการติดตั้งล้มเหลว: {detail}",
        $"فشل الإزالة: {detail}"
    );

    public static string MsgUninstallConfirm(string version, string folder, string size) => T(
        $"Supprimer la version v{version} ?\n\nDossier : {folder}\nLibérera {size}.\n\nCette action est irréversible.",
        $"Uninstall version v{version}?\n\nFolder: {folder}\nWill free {size}.\n\nThis action is irreversible.",
        $"卸载版本 v{version}？\n\n文件夹：{folder}\n将释放 {size}。\n\n此操作不可逆。",
        $"ถอนการติดตั้งเวอร์ชัน v{version}?\n\nโฟลเดอร์: {folder}\nจะคืนพื้นที่ {size}\n\nการกระทำนี้ไม่สามารถย้อนกลับได้",
        $"إلغاء تثبيت النسخة v{version}؟\n\nالمجلد: {folder}\nسيتم تحرير {size}.\n\nهذا الإجراء لا رجعة فيه."
    );

    public static string MsgNoReleaseNotes => T(
        "Aucune release note disponible pour cette version.",
        "No release notes available for this version.",
        "此版本没有可用的版本说明。",
        "ไม่มีบันทึกการเปลี่ยนแปลงสำหรับเวอร์ชันนี้",
        "لا توجد ملاحظات إصدار متاحة لهذه النسخة."
    );

    public static string MsgReleaseNotesFetchFailed(string detail) => T(
        $"Impossible de récupérer les release notes :\n{detail}",
        $"Could not fetch release notes:\n{detail}",
        $"无法获取版本说明：\n{detail}",
        $"ไม่สามารถดึงบันทึกการเปลี่ยนแปลงได้:\n{detail}",
        $"تعذر جلب ملاحظات الإصدار:\n{detail}"
    );

    public static string MsgClearCacheConfirm => T(
        "Vider le cache de téléchargements ? Les téléchargements en cours/pause seront perdus.",
        "Clear the download cache? In-progress / paused downloads will be lost.",
        "清空下载缓存？正在进行或暂停的下载将丢失。",
        "ล้างแคชดาวน์โหลด? การดาวน์โหลดที่กำลังดำเนินการ/หยุดชั่วคราวจะสูญหาย",
        "مسح ذاكرة التخزين المؤقت للتنزيلات؟ ستفقد التنزيلات قيد التقدم أو الموقوفة مؤقتًا."
    );

    public static string MsgDeactivateLicenseConfirm => T(
        "Désactiver la license sur cette machine ?\nTu pourras la réactiver avec ta clé.",
        "Deactivate the license on this machine?\nYou can reactivate it later with your key.",
        "在此机器上停用许可证？\n您可以稍后用密钥重新激活。",
        "ปิดใช้งานใบอนุญาตบนเครื่องนี้?\nคุณสามารถเปิดใช้งานใหม่ในภายหลังด้วยคีย์ของคุณ",
        "إلغاء تنشيط الترخيص على هذا الجهاز؟\nيمكنك إعادة تنشيطه لاحقًا بمفتاحك."
    );

    public static string MsgDeactivateLicenseDetailedConfirm => T(
        "Désactiver la license sur cette machine ?\n\nTu pourras la réactiver plus tard avec ta clé. Les versions déjà installées restent utilisables.",
        "Deactivate the license on this machine?\n\nYou can reactivate it later with your key. Already-installed versions remain usable.",
        "在此机器上停用许可证？\n\n您可以稍后用密钥重新激活。已安装的版本仍可使用。",
        "ปิดใช้งานใบอนุญาตบนเครื่องนี้?\n\nคุณสามารถเปิดใช้งานใหม่ในภายหลังด้วยคีย์ของคุณ เวอร์ชันที่ติดตั้งแล้วยังคงใช้งานได้",
        "إلغاء تنشيط الترخيص على هذا الجهاز؟\n\nيمكنك إعادة تنشيطه لاحقًا بمفتاحك. تظل النسخ المثبتة قابلة للاستخدام."
    );

    public static string MsgLanguageRestart => T(
        "Le launcher va redémarrer pour appliquer la nouvelle langue.",
        "The launcher will restart to apply the new language.",
        "启动器将重启以应用新语言。",
        "Launcher จะรีสตาร์ทเพื่อใช้ภาษาใหม่",
        "سيتم إعادة تشغيل المُشغِّل لتطبيق اللغة الجديدة."
    );

    // ==================== STATUS MESSAGES (footer) ====================
    public static string StatusCheckingUpdates => T("Vérification des mises à jour…", "Checking for updates…", "正在检查更新…", "กำลังตรวจสอบการอัปเดต…", "جارٍ التحقق من التحديثات…");
    public static string StatusNoRemote        => T("Aucune version distante trouvée", "No remote version found", "未找到远程版本", "ไม่พบเวอร์ชันระยะไกล", "لم يتم العثور على نسخة عن بُعد");
    public static string StatusDownloadCancelled => T("Téléchargement annulé", "Download canceled", "下载已取消", "ยกเลิกการดาวน์โหลด", "تم إلغاء التنزيل");
    public static string StatusDownloadingLauncher => T("Téléchargement du nouveau launcher…", "Downloading new launcher…", "正在下载新启动器…", "กำลังดาวน์โหลด Launcher ใหม่…", "جارٍ تنزيل المُشغِّل الجديد…");
    public static string StatusLauncherUpdating => T(
        "Mise à jour du launcher en cours… le launcher va se relancer.",
        "Launcher updating… it will restart.",
        "启动器正在更新…即将重启。",
        "Launcher กำลังอัปเดต… จะรีสตาร์ท",
        "جارٍ تحديث المُشغِّل… سيتم إعادة تشغيله."
    );

    public static string StatusError(string detail) => T(
        $"Erreur : {detail}",
        $"Error: {detail}",
        $"错误：{detail}",
        $"ข้อผิดพลาด: {detail}",
        $"خطأ: {detail}"
    );

    public static string StatusSelfUpdateError(string detail) => T(
        $"Erreur self-update : {detail}",
        $"Self-update error: {detail}",
        $"自更新错误：{detail}",
        $"ข้อผิดพลาดอัปเดตตัวเอง: {detail}",
        $"خطأ في التحديث الذاتي: {detail}"
    );

    public static string StatusUpToDate(string version) => T(
        $"À jour (v{version})",
        $"Up to date (v{version})",
        $"已是最新（v{version}）",
        $"เป็นเวอร์ชันล่าสุด (v{version})",
        $"محدّثة (v{version})"
    );

    public static string StatusNewAvailable(string version) => T(
        $"Nouvelle version disponible : v{version}",
        $"New version available: v{version}",
        $"有新版本可用：v{version}",
        $"มีเวอร์ชันใหม่: v{version}",
        $"نسخة جديدة متاحة: v{version}"
    );

    public static string StatusDownloadingVersion(string version) => T(
        $"Téléchargement v{version}…",
        $"Downloading v{version}…",
        $"正在下载 v{version}…",
        $"กำลังดาวน์โหลด v{version}…",
        $"جارٍ تنزيل v{version}…"
    );

    public static string StatusInstallingVersion(string version) => T(
        $"Installation v{version}…",
        $"Installing v{version}…",
        $"正在安装 v{version}…",
        $"กำลังติดตั้ง v{version}…",
        $"جارٍ تثبيت v{version}…"
    );

    public static string StatusInstalledSuccess(string version) => T(
        $"v{version} installée avec succès",
        $"v{version} installed successfully",
        $"v{version} 安装成功",
        $"ติดตั้ง v{version} สำเร็จ",
        $"تم تثبيت v{version} بنجاح"
    );

    public static string StatusUninstallingVersion(string version) => T(
        $"Suppression v{version}…",
        $"Uninstalling v{version}…",
        $"正在卸载 v{version}…",
        $"กำลังถอน v{version}…",
        $"جارٍ إزالة v{version}…"
    );

    public static string StatusUninstalledVersion(string version) => T(
        $"v{version} supprimée",
        $"v{version} uninstalled",
        $"v{version} 已卸载",
        $"ถอน v{version} แล้ว",
        $"تمت إزالة v{version}"
    );

    // ==================== PROGRESS DETAIL ====================
    public static string ProgressLauncherDl(string version, string done, string total) => T(
        $"⬇ Launcher v{version} : {done} / {total}",
        $"⬇ Launcher v{version}: {done} / {total}",
        $"⬇ 启动器 v{version}：{done} / {total}",
        $"⬇ Launcher v{version}: {done} / {total}",
        $"⬇ المُشغِّل v{version}: {done} / {total}"
    );

    public static string ProgressExtraction(string version, long entriesDone, long entriesTotal, string bytesDone, string bytesTotal) => T(
        $"Extraction v{version} : {entriesDone}/{entriesTotal} fichiers ({bytesDone} / {bytesTotal})",
        $"Extracting v{version}: {entriesDone}/{entriesTotal} files ({bytesDone} / {bytesTotal})",
        $"正在解压 v{version}：{entriesDone}/{entriesTotal} 文件 ({bytesDone} / {bytesTotal})",
        $"กำลังแตก v{version}: {entriesDone}/{entriesTotal} ไฟล์ ({bytesDone} / {bytesTotal})",
        $"جارٍ فك v{version}: {entriesDone}/{entriesTotal} ملف ({bytesDone} / {bytesTotal})"
    );

    public static string StatusVerifyingIntegrity(string version) => T(
        $"Vérification de l'intégrité v{version}…",
        $"Verifying integrity of v{version}…",
        $"正在校验 v{version} 的完整性…",
        $"กำลังตรวจสอบความถูกต้อง v{version}…",
        $"جارٍ التحقق من سلامة v{version}…"
    );

    public static string ProgressVerifying(string version, int percent) => T(
        $"🔍 Vérification SHA-256 v{version} : {percent}%",
        $"🔍 SHA-256 verification v{version}: {percent}%",
        $"🔍 SHA-256 校验 v{version}：{percent}%",
        $"🔍 ตรวจสอบ SHA-256 v{version}: {percent}%",
        $"🔍 التحقق من SHA-256 v{version}: {percent}%"
    );

    // ==================== DB MIGRATIONS ====================
    public static string StatusApplyingMigrations(string version) => T(
        $"Application des migrations base de données v{version}…",
        $"Applying database migrations for v{version}…",
        $"正在应用 v{version} 的数据库迁移…",
        $"กำลังปรับใช้การโยกย้ายฐานข้อมูล v{version}…",
        $"جارٍ تطبيق ترقيات قاعدة البيانات v{version}…"
    );
    public static string ProgressMigration(int done, int total, string filename) => T(
        $"🗄 Migration {done}/{total} : {filename}",
        $"🗄 Migration {done}/{total}: {filename}",
        $"🗄 迁移 {done}/{total}：{filename}",
        $"🗄 การโยกย้าย {done}/{total}: {filename}",
        $"🗄 الترقية {done}/{total}: {filename}"
    );
    public static string MsgBoxMigrationFailed => T("Migration DB échouée", "DB migration failed", "数据库迁移失败", "การโยกย้าย DB ล้มเหลว", "فشل ترقية قاعدة البيانات");

    // ==================== REPORT TOOL DEPLOY ====================
    public static string StatusDeployingReport(string version) => T(
        $"Déploiement de l'outil Report v{version}…",
        $"Deploying Report tool for v{version}…",
        $"正在部署 v{version} 的报告工具…",
        $"กำลังปรับใช้เครื่องมือรายงาน v{version}…",
        $"جارٍ نشر أداة التقارير v{version}…"
    );
    public static string ProgressReportDeploy(int done, int total, string filename) => T(
        $"📂 Déploiement Report : {done}/{total} — {filename}",
        $"📂 Deploying Report: {done}/{total} — {filename}",
        $"📂 部署报告：{done}/{total} — {filename}",
        $"📂 ปรับใช้รายงาน: {done}/{total} — {filename}",
        $"📂 نشر التقرير: {done}/{total} — {filename}"
    );
    public static string MsgBoxReportDeployFailed => T("Déploiement Report échoué", "Report deploy failed", "报告部署失败", "การปรับใช้รายงานล้มเหลว", "فشل نشر التقرير");
    public static string MsgReportXamppNotFound(string path) => T(
        $"L'outil Report n'a pas pu être déployé : le dossier XAMPP {path} est introuvable.\n\nVérifie que XAMPP est installé à cet emplacement (par défaut C:\\xampp) ou ajuste le chemin dans Paramètres → Avancés → Outil Report.",
        $"The Report tool could not be deployed: XAMPP folder {path} not found.\n\nMake sure XAMPP is installed there (default C:\\xampp) or adjust the path in Settings → Advanced → Report Tool.",
        $"无法部署报告工具：找不到 XAMPP 文件夹 {path}。\n\n请确保 XAMPP 安装在该位置（默认 C:\\xampp），或在 设置 → 高级 → 报告工具 中调整路径。",
        $"ไม่สามารถปรับใช้เครื่องมือรายงานได้: ไม่พบโฟลเดอร์ XAMPP {path}\n\nตรวจสอบว่า XAMPP ติดตั้งที่ตำแหน่งนั้น (ค่าเริ่มต้น C:\\xampp) หรือปรับเส้นทางใน ตั้งค่า → ขั้นสูง → เครื่องมือรายงาน",
        $"تعذر نشر أداة التقارير: مجلد XAMPP {path} غير موجود.\n\nتأكد من تثبيت XAMPP في هذا الموقع (الافتراضي C:\\xampp) أو اضبط المسار في الإعدادات → متقدم → أداة التقارير."
    );
    public static string MsgReportDeployFailed(string detail) => T(
        $"Le déploiement de l'outil Report a échoué :\n\n{detail}\n\nLa version PROSERVE est installée mais l'onglet Statistiques pourrait être désynchronisé. Tu peux retenter via Paramètres → Avancés → Re-déployer le Report.",
        $"Report tool deploy failed:\n\n{detail}\n\nThe PROSERVE version is installed but the Reports tab might be out of sync. You can retry via Settings → Advanced → Re-deploy Report.",
        $"报告工具部署失败：\n\n{detail}\n\nPROSERVE 版本已安装，但报告选项卡可能不同步。您可以通过 设置 → 高级 → 重新部署报告 重试。",
        $"การปรับใช้เครื่องมือรายงานล้มเหลว:\n\n{detail}\n\nเวอร์ชัน PROSERVE ติดตั้งแล้วแต่แท็บรายงานอาจไม่ซิงค์ คุณสามารถลองใหม่ผ่าน ตั้งค่า → ขั้นสูง → ปรับใช้รายงานใหม่",
        $"فشل نشر أداة التقارير:\n\n{detail}\n\nنسخة PROSERVE مثبتة لكن علامة التبويب التقارير قد لا تكون متزامنة. يمكنك إعادة المحاولة عبر الإعدادات → متقدم → إعادة نشر التقرير."
    );

    public static string SettingsReportTool       => T("OUTIL REPORT (XAMPP htdocs)", "REPORT TOOL (XAMPP htdocs)", "报告工具 (XAMPP htdocs)", "เครื่องมือรายงาน (XAMPP htdocs)", "أداة التقارير (XAMPP htdocs)");
    public static string SettingsReportHtdocs     => T("Racine XAMPP htdocs", "XAMPP htdocs root", "XAMPP htdocs 根目录", "รากของ XAMPP htdocs", "جذر XAMPP htdocs");
    public static string SettingsReportFolder     => T("Nom du dossier de déploiement", "Deploy folder name", "部署文件夹名称", "ชื่อโฟลเดอร์ปรับใช้", "اسم مجلد النشر");
    public static string SettingsReportAutoDeploy => T(
        "Déployer automatiquement l'outil Report à l'install d'une nouvelle version",
        "Automatically deploy the Report tool when installing a new version",
        "安装新版本时自动部署报告工具",
        "ปรับใช้เครื่องมือรายงานโดยอัตโนมัติเมื่อติดตั้งเวอร์ชันใหม่",
        "نشر أداة التقارير تلقائياً عند تثبيت نسخة جديدة"
    );
    public static string SettingsReportRedeploy   => T("📂  Re-déployer le Report maintenant", "📂  Re-deploy Report now", "📂  立即重新部署报告", "📂  ปรับใช้รายงานใหม่เดี๋ยวนี้", "📂  إعادة نشر التقرير الآن");

    // ---- Doc tool (mêmes mécanique que Report, autres libellés) ----
    public static string StatusDeployingDoc(string version) => T(
        $"Déploiement de la Documentation v{version}…",
        $"Deploying Documentation for v{version}…",
        $"正在部署 v{version} 的文档…",
        $"กำลังปรับใช้เอกสาร v{version}…",
        $"جارٍ نشر التوثيق v{version}…"
    );
    public static string ProgressDocDeploy(int done, int total, string filename) => T(
        $"📖 Déploiement Doc : {done}/{total} — {filename}",
        $"📖 Deploying Doc: {done}/{total} — {filename}",
        $"📖 部署文档：{done}/{total} — {filename}",
        $"📖 ปรับใช้เอกสาร: {done}/{total} — {filename}",
        $"📖 نشر التوثيق: {done}/{total} — {filename}"
    );
    public static string MsgBoxDocDeployFailed => T("Déploiement Doc échoué", "Doc deploy failed", "文档部署失败", "การปรับใช้เอกสารล้มเหลว", "فشل نشر التوثيق");
    public static string MsgDocDeployFailed(string detail) => T(
        $"Le déploiement de la Documentation a échoué :\n\n{detail}\n\nLa version PROSERVE est installée mais l'onglet Documentation pourrait être désynchronisé. Tu peux retenter via Paramètres → Avancés → Re-déployer la Doc.",
        $"Documentation deploy failed:\n\n{detail}\n\nThe PROSERVE version is installed but the Documentation tab might be out of sync. You can retry via Settings → Advanced → Re-deploy Doc.",
        $"文档部署失败：\n\n{detail}\n\nPROSERVE 版本已安装，但文档选项卡可能不同步。您可以通过 设置 → 高级 → 重新部署文档 重试。",
        $"การปรับใช้เอกสารล้มเหลว:\n\n{detail}\n\nเวอร์ชัน PROSERVE ติดตั้งแล้วแต่แท็บเอกสารอาจไม่ซิงค์ คุณสามารถลองใหม่ผ่าน ตั้งค่า → ขั้นสูง → ปรับใช้เอกสารใหม่",
        $"فشل نشر التوثيق:\n\n{detail}\n\nنسخة PROSERVE مثبتة لكن علامة التبويب التوثيق قد لا تكون متزامنة. يمكنك إعادة المحاولة عبر الإعدادات → متقدم → إعادة نشر التوثيق."
    );

    public static string SettingsDocTool       => T("OUTIL DOCUMENTATION (XAMPP htdocs)", "DOCUMENTATION TOOL (XAMPP htdocs)", "文档工具 (XAMPP htdocs)", "เครื่องมือเอกสาร (XAMPP htdocs)", "أداة التوثيق (XAMPP htdocs)");
    public static string SettingsDocAutoDeploy => T(
        "Déployer automatiquement la Documentation à l'install d'une nouvelle version",
        "Automatically deploy the Documentation when installing a new version",
        "安装新版本时自动部署文档",
        "ปรับใช้เอกสารโดยอัตโนมัติเมื่อติดตั้งเวอร์ชันใหม่",
        "نشر التوثيق تلقائياً عند تثبيت نسخة جديدة"
    );
    public static string SettingsDocRedeploy   => T("📖  Re-déployer la Doc maintenant", "📖  Re-deploy Doc now", "📖  立即重新部署文档", "📖  ปรับใช้เอกสารใหม่เดี๋ยวนี้", "📖  إعادة نشر التوثيق الآن");
    public static string SettingsDocBackups    => SettingsReportBackups;
    public static string SettingsDocMaxBackups => SettingsReportMaxBackups;

    // ---- API tool (mêmes mécanique que Report/Doc, autres libellés) ----
    public static string StatusDeployingApi(string version) => T(
        $"Déploiement de l'API v{version}…",
        $"Deploying API for v{version}…",
        $"正在部署 v{version} 的 API…",
        $"กำลังปรับใช้ API v{version}…",
        $"جارٍ نشر واجهة برمجة التطبيقات v{version}…"
    );
    public static string ProgressApiDeploy(int done, int total, string filename) => T(
        $"🔌 Déploiement API : {done}/{total} — {filename}",
        $"🔌 Deploying API: {done}/{total} — {filename}",
        $"🔌 部署 API：{done}/{total} — {filename}",
        $"🔌 ปรับใช้ API: {done}/{total} — {filename}",
        $"🔌 نشر API: {done}/{total} — {filename}"
    );
    public static string MsgBoxApiDeployFailed => T("Déploiement API échoué", "API deploy failed", "API 部署失败", "การปรับใช้ API ล้มเหลว", "فشل نشر API");
    public static string MsgApiDeployFailed(string detail) => T(
        $"Le déploiement de l'API a échoué :\n\n{detail}\n\nLa version PROSERVE est installée mais l'API de stats pourrait être désynchronisée. Tu peux retenter via Paramètres → Avancés → Re-déployer l'API.",
        $"API deploy failed:\n\n{detail}\n\nThe PROSERVE version is installed but the stats API might be out of sync. You can retry via Settings → Advanced → Re-deploy API.",
        $"API 部署失败：\n\n{detail}\n\nPROSERVE 版本已安装，但统计 API 可能不同步。您可以通过 设置 → 高级 → 重新部署 API 重试。",
        $"การปรับใช้ API ล้มเหลว:\n\n{detail}\n\nเวอร์ชัน PROSERVE ติดตั้งแล้วแต่ API สถิติอาจไม่ซิงค์ คุณสามารถลองใหม่ผ่าน ตั้งค่า → ขั้นสูง → ปรับใช้ API ใหม่",
        $"فشل نشر API:\n\n{detail}\n\nنسخة PROSERVE مثبتة لكن API الإحصائيات قد لا يكون متزامناً. يمكنك إعادة المحاولة عبر الإعدادات → متقدم → إعادة نشر API."
    );

    public static string SettingsApiTool       => T("API PHP DE STATS (XAMPP htdocs)", "STATS PHP API (XAMPP htdocs)", "统计 PHP API (XAMPP htdocs)", "API PHP สถิติ (XAMPP htdocs)", "واجهة API PHP للإحصائيات (XAMPP htdocs)");
    public static string SettingsApiAutoDeploy => T(
        "Déployer automatiquement l'API à l'install d'une nouvelle version",
        "Automatically deploy the API when installing a new version",
        "安装新版本时自动部署 API",
        "ปรับใช้ API โดยอัตโนมัติเมื่อติดตั้งเวอร์ชันใหม่",
        "نشر API تلقائياً عند تثبيت نسخة جديدة"
    );
    public static string SettingsApiRedeploy   => T("🔌  Re-déployer l'API maintenant", "🔌  Re-deploy API now", "🔌  立即重新部署 API", "🔌  ปรับใช้ API ใหม่เดี๋ยวนี้", "🔌  إعادة نشر API الآن");
    public static string SettingsApiBackups    => SettingsReportBackups;
    public static string SettingsApiMaxBackups => SettingsReportMaxBackups;

    // ---- SteamVR settings merge (tue SteamVR + Vive Business Streaming + merge JSON) ----
    public static string StatusDeployingSteamVr(string version) => T(
        $"Configuration SteamVR pour v{version}…",
        $"Configuring SteamVR for v{version}…",
        $"正在为 v{version} 配置 SteamVR…",
        $"กำลังกำหนดค่า SteamVR สำหรับ v{version}…",
        $"جارٍ تهيئة SteamVR لـ v{version}…"
    );
    public static string MsgBoxSteamVrMergeFailed => T(
        "Configuration SteamVR échouée",
        "SteamVR configuration failed",
        "SteamVR 配置失败",
        "การกำหนดค่า SteamVR ล้มเหลว",
        "فشل تهيئة SteamVR"
    );
    public static string MsgSteamVrPreFlightTitle => T(
        "Fermeture de SteamVR & Vive Business Streaming",
        "Closing SteamVR & Vive Business Streaming",
        "正在关闭 SteamVR 和 Vive Business Streaming",
        "ปิด SteamVR และ Vive Business Streaming",
        "إغلاق SteamVR و Vive Business Streaming"
    );
    public static string MsgSteamVrPreFlightBody(string processesPreview, int rootKeysChanging) => T(
        $"{rootKeysChanging} bloc(s) de la configuration SteamVR vont être mis à jour.\n\nPour appliquer ces changements (mapping trackers, bindings), le launcher doit fermer ces programmes :\n\n  • {processesPreview}\n\nIls pourront être relancés normalement après l'install. Continuer ?",
        $"{rootKeysChanging} SteamVR config block(s) will be updated.\n\nTo apply these changes (tracker mappings, bindings), the launcher must close these programs:\n\n  • {processesPreview}\n\nYou'll be able to relaunch them normally after the install. Proceed?",
        $"将更新 {rootKeysChanging} 个 SteamVR 配置块。\n\n为应用这些更改（追踪器映射、绑定），启动器必须关闭这些程序：\n\n  • {processesPreview}\n\n安装完成后您可以正常重新启动它们。继续吗？",
        $"จะอัปเดตบล็อกการกำหนดค่า SteamVR {rootKeysChanging} บล็อก\n\nเพื่อใช้การเปลี่ยนแปลงเหล่านี้ (การจับคู่ตัวติดตาม การผูก) ตัวเปิดต้องปิดโปรแกรมเหล่านี้:\n\n  • {processesPreview}\n\nคุณสามารถเปิดใหม่ได้ตามปกติหลังติดตั้ง ดำเนินการต่อ?",
        $"سيتم تحديث {rootKeysChanging} كتلة (كتل) من تكوين SteamVR.\n\nلتطبيق هذه التغييرات (تعيينات أجهزة التتبع، الارتباطات)، يجب أن يغلق المشغل هذه البرامج:\n\n  • {processesPreview}\n\nيمكنك إعادة تشغيلها بشكل طبيعي بعد التثبيت. متابعة؟"
    );
    public static string MsgSteamVrMergeFailed(string detail) => T(
        $"Le merge des settings SteamVR a échoué :\n\n{detail}\n\nLa version PROSERVE est installée et fonctionnera, mais le mapping des trackers Vive Business Streaming pourrait être incorrect. Vérifie depuis SteamVR → Paramètres → Périphériques après le premier lancement.",
        $"SteamVR settings merge failed:\n\n{detail}\n\nThe PROSERVE version is installed and will run, but Vive Business Streaming tracker bindings might be off. Verify from SteamVR → Settings → Devices after the first launch.",
        $"SteamVR 设置合并失败：\n\n{detail}\n\nPROSERVE 版本已安装且将运行，但 Vive Business Streaming 跟踪器绑定可能不正确。首次启动后请通过 SteamVR → 设置 → 设备 进行检查。",
        $"การรวมการตั้งค่า SteamVR ล้มเหลว:\n\n{detail}\n\nเวอร์ชัน PROSERVE ติดตั้งแล้วและจะทำงาน แต่การจับคู่ตัวติดตาม Vive Business Streaming อาจไม่ถูกต้อง ตรวจสอบจาก SteamVR → การตั้งค่า → อุปกรณ์ หลังเปิดครั้งแรก",
        $"فشل دمج إعدادات SteamVR:\n\n{detail}\n\nنسخة PROSERVE مثبتة وستعمل، لكن قد يكون ربط أجهزة تتبع Vive Business Streaming غير صحيح. تحقق من SteamVR ← الإعدادات ← الأجهزة بعد التشغيل الأول."
    );

    public static string SettingsSteamVr => T(
        "STEAMVR + VIVE BUSINESS STREAMING",
        "STEAMVR + VIVE BUSINESS STREAMING",
        "STEAMVR + VIVE BUSINESS STREAMING",
        "STEAMVR + VIVE BUSINESS STREAMING",
        "STEAMVR + VIVE BUSINESS STREAMING"
    );
    public static string SettingsSteamVrAutoMerge => T(
        "Fusionner steamvr.vrsettings à l'install (tue SteamVR + Vive Business Streaming, modifie le JSON, laisse SteamVR redémarrer au prochain lancement PROSERVE)",
        "Merge steamvr.vrsettings on install (kills SteamVR + Vive Business Streaming, edits the JSON, lets SteamVR restart on next PROSERVE launch)",
        "安装时合并 steamvr.vrsettings（终止 SteamVR + Vive Business Streaming，编辑 JSON，下次启动 PROSERVE 时让 SteamVR 重新启动）",
        "รวม steamvr.vrsettings เมื่อติดตั้ง (ปิด SteamVR + Vive Business Streaming, แก้ JSON, ปล่อยให้ SteamVR เริ่มใหม่เมื่อเปิด PROSERVE ครั้งถัดไป)",
        "دمج steamvr.vrsettings عند التثبيت (إنهاء SteamVR + Vive Business Streaming، تعديل JSON، السماح لـ SteamVR بإعادة التشغيل عند تشغيل PROSERVE التالي)"
    );
    public static string SettingsSteamVrSettingsPath => T(
        "Chemin vers steamvr.vrsettings (vide = auto-détection via registre Steam)",
        "Path to steamvr.vrsettings (empty = auto-detect via Steam registry)",
        "steamvr.vrsettings 路径（空 = 通过 Steam 注册表自动检测）",
        "พาธไปยัง steamvr.vrsettings (ว่าง = ตรวจหาอัตโนมัติผ่าน Steam registry)",
        "مسار steamvr.vrsettings (فارغ = اكتشاف تلقائي عبر سجل Steam)"
    );
    public static string SettingsSteamVrProcesses => T(
        "Processus à tuer avant le merge (un nom par ligne, sans .exe). Vive Business Streaming doit être en tête car il relance SteamVR.",
        "Processes to kill before merging (one name per line, no .exe). Vive Business Streaming must be first because it auto-restarts SteamVR.",
        "合并前要终止的进程（每行一个名称，无 .exe）。Vive Business Streaming 必须放在第一位，因为它会自动重启 SteamVR。",
        "โพรเซสที่ต้องปิดก่อนรวม (หนึ่งชื่อต่อบรรทัด ไม่ใส่ .exe) Vive Business Streaming ต้องอยู่บนสุดเพราะมันรีสตาร์ท SteamVR อัตโนมัติ",
        "العمليات التي يجب إنهاؤها قبل الدمج (اسم واحد لكل سطر، بدون .exe). يجب أن يكون Vive Business Streaming في المقدمة لأنه يعيد تشغيل SteamVR تلقائياً."
    );

    // ---- Cache LAN P2P ----
    public static string StatusDownloadingFromPeer(string host, string version) => T(
        $"📡 Téléchargement de v{version} depuis {host} (LAN)…",
        $"📡 Downloading v{version} from {host} (LAN)…",
        $"📡 正在从 {host} 下载 v{version} (局域网)…",
        $"📡 กำลังดาวน์โหลด v{version} จาก {host} (LAN)…",
        $"📡 جارٍ تنزيل v{version} من {host} (LAN)…"
    );
    public static string SettingsLanCache => T("CACHE LAN P2P", "LAN P2P CACHE", "局域网 P2P 缓存", "แคช LAN P2P", "ذاكرة التخزين المؤقت LAN P2P");
    public static string SettingsLanCacheServerEnabled => T(
        "Activer le mode serveur (partager les ZIPs aux PCs du LAN)",
        "Enable server mode (share ZIPs to LAN PCs)",
        "启用服务器模式（与局域网 PC 共享 ZIP）",
        "เปิดใช้โหมดเซิร์ฟเวอร์ (แชร์ ZIP ไปยัง PC ใน LAN)",
        "تمكين وضع الخادم (مشاركة ZIPs مع أجهزة الكمبيوتر في LAN)"
    );
    public static string SettingsLanCacheClientEnabled => T(
        "Activer le mode client (chercher les ZIPs sur le LAN avant OVH)",
        "Enable client mode (look for ZIPs on the LAN before OVH)",
        "启用客户端模式（在 OVH 之前在局域网上查找 ZIP）",
        "เปิดใช้โหมดไคลเอนต์ (ค้นหา ZIP บน LAN ก่อน OVH)",
        "تمكين وضع العميل (البحث عن ZIPs على LAN قبل OVH)"
    );
    public static string SettingsLanCacheServerPort     => T("Port HTTP serveur",      "Server HTTP port",      "服务器 HTTP 端口",   "พอร์ต HTTP เซิร์ฟเวอร์", "منفذ HTTP الخادم");
    public static string SettingsLanCacheDiscoveryPort  => T("Port UDP découverte",   "Discovery UDP port",    "发现 UDP 端口",      "พอร์ต UDP การค้นพบ",  "منفذ UDP الاكتشاف");
    public static string SettingsLanCacheMaxCached      => T("ZIPs en cache (N)",     "Cached ZIPs (N)",       "缓存 ZIP (N)",       "ZIP ในแคช (N)",       "ZIPs المخزنة (N)");
    public static string SettingsLanCacheDiscoveredPeers => T("Peers découverts automatiquement", "Auto-discovered peers", "自动发现的 Peers", "Peers ที่ค้นพบอัตโนมัติ", "النظراء المكتشفون تلقائياً");
    public static string SettingsLanCacheManualPeers     => T("Peers manuels (1 URL par ligne, ex: http://10.0.0.5:47623)", "Manual peers (1 URL per line, e.g. http://10.0.0.5:47623)", "手动 Peers（每行 1 个 URL，例如 http://10.0.0.5:47623）", "Peers ด้วยตนเอง (1 URL ต่อบรรทัด เช่น http://10.0.0.5:47623)", "النظراء يدوياً (URL واحد لكل سطر، مثل http://10.0.0.5:47623)");
    public static string SettingsLanCacheNoDiscovered    => T(
        "Aucun peer détecté pour le moment (les beacons sont émis toutes les 15 s).",
        "No peer detected yet (beacons are sent every 15 s).",
        "目前未检测到任何 peer (信标每 15 秒发送一次).",
        "ยังไม่มี peer ตรวจพบ (สัญญาณถูกส่งทุก 15 วินาที).",
        "لم يتم اكتشاف أي peer بعد (يتم إرسال إشارات كل 15 ثانية)."
    );
    public static string SettingsLanCacheServerStatus(string endpoints, int versions) => T(
        $"✓ Serveur actif sur {endpoints} — {versions} version(s) en cache",
        $"✓ Server running on {endpoints} — {versions} version(s) cached",
        $"✓ 服务器正在 {endpoints} 上运行 — 缓存了 {versions} 个版本",
        $"✓ เซิร์ฟเวอร์ทำงานบน {endpoints} — แคช {versions} เวอร์ชัน",
        $"✓ الخادم يعمل على {endpoints} — {versions} نسخة مخزنة"
    );
    public static string SettingsLanCacheServerOff => T(
        "Mode serveur désactivé — ce PC ne sert pas les ZIPs aux peers du LAN.",
        "Server mode disabled — this PC does not share ZIPs to LAN peers.",
        "服务器模式已禁用 — 此 PC 不会与局域网 peers 共享 ZIP。",
        "โหมดเซิร์ฟเวอร์ถูกปิด — PC นี้จะไม่แชร์ ZIP ไปยัง LAN peers",
        "وضع الخادم معطل — هذا الكمبيوتر لا يشارك ZIPs مع نظراء LAN."
    );
    public static string MsgBoxLanCacheChange => T("Cache LAN P2P", "LAN P2P cache", "局域网 P2P 缓存", "แคช LAN P2P", "ذاكرة التخزين المؤقت LAN P2P");

    // ---- Redist Unreal (VC_redist, UEPrereqSetup, …) ----
    public static string StatusInstallingRedists(string version) => T(
        $"⚙️ Installation des composants requis pour v{version}…",
        $"⚙️ Installing required components for v{version}…",
        $"⚙️ 正在为 v{version} 安装必需组件…",
        $"⚙️ กำลังติดตั้งส่วนประกอบที่จำเป็นสำหรับ v{version}…",
        $"⚙️ جارٍ تثبيت المكونات المطلوبة للنسخة v{version}…"
    );
    public static string ProgressRedistInstalling(int done, int total, string filename) => T(
        $"⚙️ Redist {done}/{total} : {filename}",
        $"⚙️ Redist {done}/{total}: {filename}",
        $"⚙️ Redist {done}/{total}：{filename}",
        $"⚙️ Redist {done}/{total}: {filename}",
        $"⚙️ Redist {done}/{total}: {filename}"
    );

    // ---- Mode auto (auto-launch + auto-relaunch après exit) ----
    public static string ActionAuto => T("AUTO", "AUTO", "自动", "อัตโนมัติ", "تلقائي");
    public static string AutoModeTooltipActive => T(
        "Cette version est désignée pour le lancement automatique. Clique pour désactiver.",
        "This version is set for auto-launch. Click to disable.",
        "此版本设置为自动启动。点击以禁用。",
        "เวอร์ชันนี้ถูกตั้งให้เปิดอัตโนมัติ คลิกเพื่อปิด",
        "هذه النسخة معيّنة للتشغيل التلقائي. انقر للتعطيل."
    );
    public static string AutoModeTooltipInactive => T(
        "Clique pour désigner cette version comme lancée automatiquement au démarrage du launcher.",
        "Click to set this version as auto-launched on launcher startup.",
        "点击以设置此版本在启动器启动时自动启动。",
        "คลิกเพื่อตั้งเวอร์ชันนี้ให้เปิดอัตโนมัติเมื่อตัวเปิดเริ่มต้น",
        "انقر لتعيين هذه النسخة للتشغيل التلقائي عند بدء المشغل."
    );

    // ---- Modal de lancement auto (startup, post-exit, ou activation manuelle) ----
    public static string AutoRelaunchTitle => T("Lancement automatique", "Auto-launch", "自动启动", "เปิดอัตโนมัติ", "التشغيل التلقائي");
    public static string AutoRelaunchMessage(string version) => T(
        $"Lancement automatique de PROSERVE v{version} dans :",
        $"Auto-launching PROSERVE v{version} in:",
        $"PROSERVE v{version} 自动启动倒计时：",
        $"กำลังเปิด PROSERVE v{version} อัตโนมัติใน:",
        $"التشغيل التلقائي لـ PROSERVE v{version} خلال:"
    );
    public static string AutoRelaunchCancel => T(
        "Annuler (désactiver le mode auto)",
        "Cancel (disable auto-mode)",
        "取消（禁用自动模式）",
        "ยกเลิก (ปิดโหมดอัตโนมัติ)",
        "إلغاء (تعطيل الوضع التلقائي)"
    );

    // ---- Phase 1 du modal auto : attente santé système (option WaitForHealthChecks) ----
    public static string AutoLaunchHealthWaitTitle => T(
        "Vérification de la santé système",
        "System health check",
        "系统健康检查",
        "ตรวจสอบสุขภาพระบบ",
        "فحص صحة النظام"
    );
    public static string AutoLaunchHealthWaitMessage => T(
        "En attente que tous les indicateurs système passent au vert avant de lancer PROSERVE…",
        "Waiting for all system indicators to turn green before launching PROSERVE…",
        "等待所有系统指标变绿后再启动 PROSERVE…",
        "รอให้ตัวบ่งชี้ทั้งหมดเป็นสีเขียวก่อนเปิด PROSERVE…",
        "في انتظار أن تصبح جميع مؤشرات النظام خضراء قبل تشغيل PROSERVE…"
    );
    public static string AutoLaunchHealthWaitPending(int count) => T(
        $"Indicateurs en attente : {count}",
        $"Pending indicators: {count}",
        $"待处理指标：{count}",
        $"ตัวบ่งชี้ที่รอ: {count}",
        $"المؤشرات المعلقة: {count}"
    );

    // ---- Garde-fou contre double-lancement de PROSERVE ----
    public static string MsgAlreadyRunningTitle => T(
        "PROSERVE déjà lancé",
        "PROSERVE already running",
        "PROSERVE 已在运行",
        "PROSERVE กำลังทำงานอยู่แล้ว",
        "PROSERVE قيد التشغيل بالفعل"
    );
    public static string MsgAlreadyRunningBody => T(
        "Une instance de PROSERVE est déjà en cours d'exécution. Le launcher refuse de la dupliquer pour éviter les conflits (sessions, ports, fichiers verrouillés).\n\nFerme l'instance existante avant d'en lancer une autre.",
        "A PROSERVE instance is already running. The launcher refuses to start a second one to avoid conflicts (sessions, ports, locked files).\n\nClose the existing instance before starting another.",
        "PROSERVE 实例已在运行。启动器拒绝重复启动以避免冲突（会话、端口、锁定文件）。\n\n请先关闭现有实例再启动新的。",
        "มี PROSERVE ทำงานอยู่แล้ว ตัวเปิดปฏิเสธที่จะเริ่มอันที่สองเพื่อหลีกเลี่ยงความขัดแย้ง (เซสชัน, พอร์ต, ไฟล์ที่ถูกล็อก)\n\nปิดอันที่มีอยู่ก่อนเริ่มอันใหม่",
        "هناك نسخة من PROSERVE قيد التشغيل بالفعل. يرفض المشغل بدء نسخة ثانية لتجنب التعارضات (الجلسات، المنافذ، الملفات المقفلة).\n\nأغلق النسخة الحالية قبل بدء أخرى."
    );

    // ---- Section Settings → Avancés → Mode auto ----
    public static string SettingsAutoMode => T("MODE AUTO", "AUTO MODE", "自动模式", "โหมดอัตโนมัติ", "الوضع التلقائي");
    public static string SettingsAutoModeFeatureEnabled => T(
        "Activer la fonctionnalité mode auto (affiche les boutons AUTO sur la library)",
        "Enable auto mode feature (shows AUTO buttons on the library)",
        "启用自动模式功能（在库中显示 AUTO 按钮）",
        "เปิดใช้คุณสมบัติโหมดอัตโนมัติ (แสดงปุ่ม AUTO ในไลบรารี)",
        "تمكين ميزة الوضع التلقائي (إظهار أزرار AUTO في المكتبة)"
    );
    public static string SettingsAutoModeGrace => T("Délai avant lancement (s)", "Grace period before launch (s)", "启动延迟（秒）", "ดีเลย์ก่อนเปิด (วินาที)", "الفترة قبل التشغيل (ث)");
    public static string SettingsAutoModeWaitForHealth => T(
        "Attendre que tous les indicateurs de santé système soient OK avant de lancer",
        "Wait for all system health indicators to be OK before launching",
        "在启动前等待所有系统健康指标为 OK",
        "รอให้ตัวบ่งชี้สุขภาพระบบทั้งหมดเป็น OK ก่อนเปิด",
        "انتظر حتى تكون جميع مؤشرات صحة النظام بحالة OK قبل التشغيل"
    );
    public static string SettingsAutoModeArgs => T(
        "Arguments CLI passés à PROSERVE. Saisis juste le nom (ex. \"autoconnect\", \"login\") — le launcher ajoute automatiquement les \"-\", \"=\" et les guillemets autour des valeurs avec espaces.",
        "CLI arguments passed to PROSERVE. Just type the name (e.g. \"autoconnect\", \"login\") — the launcher auto-adds \"-\", \"=\" and quotes values containing spaces.",
        "传递给 PROSERVE 的 CLI 参数。只输入名称（例如 \"autoconnect\"、\"login\"），启动器自动添加 \"-\"、\"=\" 和引号。",
        "อาร์กิวเมนต์ CLI ที่ส่งไป PROSERVE พิมพ์เฉพาะชื่อ (เช่น \"autoconnect\", \"login\") — ตัวเปิดจะเพิ่ม \"-\", \"=\" และเครื่องหมายคำพูดให้อัตโนมัติ",
        "وسائط CLI الممررة إلى PROSERVE. اكتب الاسم فقط (مثل \"autoconnect\"، \"login\") — يضيف المشغل تلقائياً \"-\" و \"=\" وعلامات الاقتباس."
    );
    public static string SettingsAutoModeArgKey => T("Clé", "Key", "键", "คีย์", "المفتاح");
    public static string SettingsAutoModeArgValue => T("Valeur (optionnel)", "Value (optional)", "值（可选）", "ค่า (ตัวเลือก)", "القيمة (اختياري)");
    public static string SettingsAutoModeArgAdd => T("+ Ajouter un argument", "+ Add argument", "+ 添加参数", "+ เพิ่มอาร์กิวเมนต์", "+ إضافة وسيطة");
    public static string SettingsAutoModeArgRemove => T("Supprimer", "Remove", "删除", "ลบ", "إزالة");
    public static string SettingsAutoModeSelected(string version) => T(
        $"Version désignée : v{version}",
        $"Selected version: v{version}",
        $"已选版本：v{version}",
        $"เวอร์ชันที่เลือก: v{version}",
        $"النسخة المحددة: v{version}"
    );
    public static string SettingsAutoModeNoneSelected => T(
        "Aucune version désignée (clique le bouton AUTO sur une version installée pour la désigner).",
        "No version selected (click the AUTO button on an installed version to set it).",
        "未选版本（点击已安装版本上的 AUTO 按钮以选择）。",
        "ยังไม่ได้เลือกเวอร์ชัน (คลิกปุ่ม AUTO บนเวอร์ชันที่ติดตั้งแล้วเพื่อเลือก)",
        "لم يتم اختيار نسخة (انقر على زر AUTO في نسخة مثبتة لتحديدها)."
    );

    // ---- Settings lock (mot de passe pour déverrouiller la section Avancés) ----
    public static string SettingsLockedTitle => T("Paramètres avancés verrouillés", "Advanced settings locked", "高级设置已锁定", "การตั้งค่าขั้นสูงถูกล็อก", "الإعدادات المتقدمة مقفلة");
    public static string SettingsLockedBody => T(
        "Les paramètres avancés sont protégés par un mot de passe défini par l'administrateur. Saisis-le pour les déverrouiller (déverrouillage valable jusqu'à la fermeture du launcher).",
        "Advanced settings are protected by a password set by the administrator. Enter it to unlock (unlock persists until launcher restart).",
        "高级设置受管理员设置的密码保护。输入密码以解锁（解锁状态保持到启动器重新启动）。",
        "การตั้งค่าขั้นสูงถูกป้องกันด้วยรหัสผ่านที่ผู้ดูแลกำหนด ใส่รหัสเพื่อปลดล็อก (การปลดล็อกคงอยู่จนกว่าจะรีสตาร์ทตัวเปิด)",
        "الإعدادات المتقدمة محمية بكلمة مرور حددها المسؤول. أدخلها لإلغاء القفل (إلغاء القفل يستمر حتى إعادة تشغيل المشغل)."
    );
    public static string SettingsLockedUnlock => T("Déverrouiller", "Unlock", "解锁", "ปลดล็อก", "إلغاء القفل");
    public static string SettingsLockedWrongPassword => T(
        "Mot de passe incorrect.",
        "Incorrect password.",
        "密码错误。",
        "รหัสผ่านไม่ถูกต้อง",
        "كلمة المرور غير صحيحة."
    );

    // ---- Badge source du manifest (origine de la liste de versions affichée) ----
    public static string ManifestSourceOvh => T("🌐 OVH (en ligne)", "🌐 OVH (online)", "🌐 OVH（在线）", "🌐 OVH (ออนไลน์)", "🌐 OVH (متصل)");
    public static string ManifestSourcePeer => T("📡 Peer LAN (hors ligne)", "📡 LAN peer (offline)", "📡 局域网 Peer（离线）", "📡 LAN Peer (ออฟไลน์)", "📡 LAN Peer (غير متصل)");
    public static string ManifestSourceDiskCache => T("💾 Cache local (hors ligne)", "💾 Local cache (offline)", "💾 本地缓存（离线）", "💾 แคชในเครื่อง (ออฟไลน์)", "💾 ذاكرة محلية (غير متصل)");
    public static string MsgLanCacheRestart => T(
        "Le démarrage / arrêt du serveur ou client LAN nécessite un redémarrage du launcher pour prendre effet.\n\nRedémarrer maintenant ?",
        "Starting / stopping the LAN server or client requires a launcher restart to take effect.\n\nRestart now?",
        "启动/停止局域网服务器或客户端需要重新启动启动器才能生效。\n\n现在重新启动？",
        "การเริ่ม/หยุดเซิร์ฟเวอร์หรือไคลเอนต์ LAN ต้องรีสตาร์ทตัวเปิดเพื่อให้มีผล\n\nรีสตาร์ทตอนนี้?",
        "يتطلب بدء / إيقاف خادم أو عميل LAN إعادة تشغيل المشغل لتفعيل التغيير.\n\nإعادة التشغيل الآن؟"
    );

    // ---- Backups Report tool ----
    public static string SettingsReportBackups       => T("Backups disponibles", "Available backups", "可用备份", "สำรองข้อมูลที่มี", "النسخ الاحتياطية المتاحة");
    public static string SettingsReportMaxBackups    => T("Conserver N backups", "Keep N backups", "保留 N 个备份", "เก็บสำรอง N ชุด", "الاحتفاظ بـ N نسخة احتياطية");
    public static string SettingsReportRevert        => T("↶  Revert", "↶  Revert", "↶  还原", "↶  ย้อนกลับ", "↶  استرجاع");
    public static string SettingsReportNoBackups     => T(
        "Aucun backup pour l'instant. Le premier sera créé au prochain deploy.",
        "No backups yet. The first one will be created on the next deploy.",
        "暂无备份。下次部署时将创建第一个。",
        "ยังไม่มีสำรองข้อมูล จะสร้างครั้งแรกในการปรับใช้ครั้งถัดไป",
        "لا توجد نسخ احتياطية بعد. سيتم إنشاء أول نسخة عند النشر التالي."
    );
    public static string SettingsReportBackupSize(string size) => T(
        $"taille : {size}", $"size: {size}", $"大小：{size}", $"ขนาด: {size}", $"الحجم: {size}"
    );
    public static string MsgBoxRevertReport          => T("Revert Outil Report", "Revert Report tool", "还原报告工具", "ย้อนกลับเครื่องมือรายงาน", "استرجاع أداة التقارير");
    public static string MsgRevertReportConfirm(string date) => T(
        $"Revenir à la version du {date} ?\n\nLa version actuellement déployée sera elle-même sauvegardée comme nouveau backup, donc tu pourras y revenir si besoin.",
        $"Revert to the version from {date}?\n\nThe currently deployed version will itself be saved as a new backup, so you can switch back if needed.",
        $"还原到 {date} 的版本？\n\n当前已部署的版本将作为新备份保存，因此您可以在需要时切换回去。",
        $"ย้อนกลับไปยังเวอร์ชันของ {date}?\n\nเวอร์ชันที่ปรับใช้อยู่ในปัจจุบันจะถูกบันทึกเป็นสำรองใหม่ ดังนั้นคุณสามารถสลับกลับได้หากต้องการ",
        $"العودة إلى نسخة {date}؟\n\nسيتم حفظ النسخة المنشورة حالياً كنسخة احتياطية جديدة، لذا يمكنك التبديل إليها إذا لزم الأمر."
    );
    public static string MsgRevertReportSuccess(string date) => T(
        $"✓ Reverted vers la version du {date}",
        $"✓ Reverted to version from {date}",
        $"✓ 已还原到 {date} 的版本",
        $"✓ ย้อนกลับเป็นเวอร์ชันของ {date} แล้ว",
        $"✓ تم الاسترجاع إلى نسخة {date}"
    );
    public static string MsgMigrationFailed(string detail) => T(
        $"Une migration de la base de données a échoué :\n\n{detail}\n\nLa version a été extraite mais la base n'a PAS été modifiée (rollback automatique).\nVérifie que XAMPP est démarré et que les paramètres de connexion sont corrects (Settings → Base de données), puis clique « Rejouer les migrations » dans Settings.",
        $"A database migration failed:\n\n{detail}\n\nThe version was extracted but the database was NOT modified (automatic rollback).\nMake sure XAMPP is running and the connection settings are correct (Settings → Database), then click « Replay migrations » in Settings.",
        $"数据库迁移失败：\n\n{detail}\n\n版本已解压，但数据库未被修改（自动回滚）。\n请确保 XAMPP 正在运行并且连接设置正确（设置 → 数据库），然后点击设置中的「重新执行迁移」。",
        $"การโยกย้ายฐานข้อมูลล้มเหลว:\n\n{detail}\n\nเวอร์ชันถูกแตกแล้วแต่ฐานข้อมูลไม่ถูกแก้ไข (rollback อัตโนมัติ)\nตรวจสอบว่า XAMPP กำลังทำงานและการตั้งค่าการเชื่อมต่อถูกต้อง (ตั้งค่า → ฐานข้อมูล) จากนั้นคลิก « เรียกใช้การโยกย้ายอีกครั้ง » ในการตั้งค่า",
        $"فشلت ترقية قاعدة البيانات:\n\n{detail}\n\nتم استخراج النسخة لكن قاعدة البيانات لم تُعدَّل (rollback تلقائي).\nتأكد من تشغيل XAMPP وأن إعدادات الاتصال صحيحة (الإعدادات → قاعدة البيانات)، ثم انقر « إعادة تشغيل الترقيات » في الإعدادات."
    );

    public static string StatusPreparingDownload(string version) => T(
        $"Préparation du téléchargement v{version}…",
        $"Preparing download for v{version}…",
        $"正在准备 v{version} 的下载…",
        $"กำลังเตรียมการดาวน์โหลด v{version}…",
        $"جارٍ تحضير تنزيل v{version}…"
    );

    // ==================== CONFIRM DIALOGS ====================
    public static string MsgCancelDownloadConfirm(string version) => T(
        $"Annuler le téléchargement de v{version} ?\n\nLa progression sera conservée — tu pourras reprendre plus tard.",
        $"Cancel the download of v{version}?\n\nProgress will be kept — you'll be able to resume later.",
        $"取消 v{version} 的下载？\n\n进度将被保留，您稍后可以继续。",
        $"ยกเลิกการดาวน์โหลด v{version}?\n\nความคืบหน้าจะถูกบันทึกไว้ คุณสามารถดำเนินการต่อได้ภายหลัง",
        $"إلغاء تنزيل v{version}؟\n\nسيتم الاحتفاظ بالتقدم — يمكنك الاستئناف لاحقاً."
    );

    public static string MsgRestartDownloadConfirm(string version, string partialSize) => T(
        $"Recommencer le téléchargement de v{version} depuis zéro ?\n\nLe fichier partiel ({partialSize}) sera supprimé. Cette action est irréversible.",
        $"Restart the download of v{version} from scratch?\n\nThe partial file ({partialSize}) will be deleted. This is irreversible.",
        $"从头开始重新下载 v{version}？\n\n部分文件 ({partialSize}) 将被删除。此操作不可逆。",
        $"เริ่มดาวน์โหลด v{version} ใหม่ตั้งแต่ต้น?\n\nไฟล์บางส่วน ({partialSize}) จะถูกลบ การกระทำนี้ย้อนกลับไม่ได้",
        $"إعادة تنزيل v{version} من البداية؟\n\nسيتم حذف الملف الجزئي ({partialSize}). هذا الإجراء لا رجعة فيه."
    );

    public static string MsgQuitWhileDownloadingConfirm(string version) => T(
        $"Un téléchargement est en cours (v{version}).\n\nQuitter le launcher ? La progression sera conservée pour reprise au prochain lancement.",
        $"A download is in progress (v{version}).\n\nQuit the launcher? Progress will be kept for resume on next launch.",
        $"下载正在进行 (v{version})。\n\n要退出启动器吗？进度将被保留，下次启动时可继续。",
        $"การดาวน์โหลดกำลังดำเนินอยู่ (v{version})\n\nออกจาก Launcher? ความคืบหน้าจะถูกเก็บไว้สำหรับการดำเนินการต่อในการเปิดครั้งถัดไป",
        $"يوجد تنزيل قيد التنفيذ (v{version}).\n\nمغادرة المُشغِّل؟ سيتم الاحتفاظ بالتقدم للاستئناف في التشغيل التالي."
    );

    public static string MsgBoxQuit       => T("Quitter ?",        "Quit?",          "退出？",       "ออก?",         "خروج؟");
    public static string MsgBoxCancelDl   => T("Annuler le DL ?",  "Cancel download?", "取消下载？", "ยกเลิกการดาวน์โหลด?", "إلغاء التنزيل؟");
    public static string MsgBoxRestartDl  => T("Recommencer ?",    "Restart?",       "重新开始？",   "เริ่มใหม่?",    "إعادة البدء؟");

    public static string MenuRestartFromZero => T(
        "🔄  Recommencer depuis zéro…",
        "🔄  Restart from scratch…",
        "🔄  从头开始…",
        "🔄  เริ่มใหม่ตั้งแต่ต้น…",
        "🔄  البدء من جديد…"
    );

    // ==================== RESUME-OR-RESTART DIALOG ====================
    public static string ResumeDialogTitle => T(
        "Téléchargement interrompu",
        "Download interrupted",
        "下载已中断",
        "การดาวน์โหลดถูกขัดจังหวะ",
        "تم إيقاف التنزيل"
    );

    public static string ResumeDialogHeading(string version) => T(
        $"Que faire pour v{version} ?",
        $"What to do for v{version}?",
        $"v{version} 怎么办？",
        $"จะทำอะไรกับ v{version}?",
        $"ماذا تفعل لـ v{version}؟"
    );

    public static string ResumeDialogBody(string done, string total, int percent) => T(
        $"Un téléchargement précédent a été interrompu à {done} sur {total} ({percent}%).",
        $"A previous download was interrupted at {done} of {total} ({percent}%).",
        $"上一次下载在 {done}/{total} ({percent}%) 时中断。",
        $"การดาวน์โหลดครั้งก่อนถูกขัดจังหวะที่ {done}/{total} ({percent}%)",
        $"تم إيقاف تنزيل سابق عند {done}/{total} ({percent}%)."
    );

    public static string ResumeDialogResumeButton(int percent) => T(
        $"▶  Reprendre depuis {percent}%",
        $"▶  Resume from {percent}%",
        $"▶  从 {percent}% 继续",
        $"▶  ดำเนินการต่อจาก {percent}%",
        $"▶  استئناف من {percent}%"
    );

    public static string ResumeDialogResumeHint => T(
        "Le téléchargement reprendra là où il s'était arrêté. Aucun byte ne sera re-téléchargé.",
        "The download will continue from where it stopped. No bytes will be re-downloaded.",
        "下载将从中断处继续。不会重新下载任何字节。",
        "การดาวน์โหลดจะดำเนินต่อจากจุดที่หยุดไว้ จะไม่มีการดาวน์โหลดซ้ำ",
        "سيستمر التنزيل من حيث توقف. لن يتم إعادة تنزيل أي بايت."
    );

    public static string ResumeDialogRestartButton => T(
        "🔄  Recommencer depuis zéro",
        "🔄  Restart from scratch",
        "🔄  从头开始",
        "🔄  เริ่มใหม่ตั้งแต่ต้น",
        "🔄  البدء من جديد"
    );

    public static string ResumeDialogRestartHint(string done) => T(
        $"Le fichier partiel ({done}) sera supprimé et le téléchargement repartira de zéro.",
        $"The partial file ({done}) will be deleted and the download will restart from zero.",
        $"部分文件 ({done}) 将被删除，下载将从零开始。",
        $"ไฟล์บางส่วน ({done}) จะถูกลบและจะเริ่มดาวน์โหลดใหม่ตั้งแต่ต้น",
        $"سيتم حذف الملف الجزئي ({done}) وسيُعاد التنزيل من الصفر."
    );

    // ==================== LICENSE SUMMARY (top bar badge text) ====================
    public static string LicenseSummaryNone => LicenseNone;
    public static string LicenseSummaryRevoked => LicenseRevoked;

    public static string LicenseSummaryExpired(string date) => T(
        $"Expirée le {date}",
        $"Expired on {date}",
        $"于 {date} 过期",
        $"หมดอายุเมื่อ {date}",
        $"انتهت في {date}"
    );

    public static string LicenseSummaryValid(string owner, string date) => T(
        $"{owner} • exp. {date}",
        $"{owner} • exp. {date}",
        $"{owner} • 到期 {date}",
        $"{owner} • หมดอายุ {date}",
        $"{owner} • تنتهي {date}"
    );

    // ==================== ROW STATE LABELS ====================
    // (déjà définies plus haut : StatusInstalled, StatusAvailable, StatusDownloading, StatusInstalling, StatusUninstalling)

    // ==================== INSTALL BUTTON LABEL VARIANTS ====================
    public static string ResumeButton(int percent) => T(
        $"↻  Reprendre ({percent}%)",
        $"↻  Resume ({percent}%)",
        $"↻  恢复 ({percent}%)",
        $"↻  ดำเนินการต่อ ({percent}%)",
        $"↻  استئناف ({percent}%)"
    );
    public static string ResumeButtonNoPercent => T("↻  Reprendre", "↻  Resume", "↻  恢复", "↻  ดำเนินการต่อ", "↻  استئناف");

    // ==================== LICENSE DETAILS DIALOG ====================
    public static string LicenseDetailsValidTitle    => T("License de mise à jour valide", "Valid update license", "更新许可证有效", "ใบอนุญาตอัปเดตใช้ได้", "ترخيص التحديث صالح");
    public static string LicenseDetailsValidSubtitle => T(
        "Toutes les versions sont téléchargeables tant que ta license n'est pas dépassée.",
        "All versions are downloadable while your license is current.",
        "您的许可证有效期内可以下载所有版本。",
        "ดาวน์โหลดได้ทุกเวอร์ชันตราบใดที่ใบอนุญาตยังใช้งานได้",
        "يمكن تنزيل جميع النسخ طالما ترخيصك ساري."
    );
    public static string LicenseDetailsExpiringSoon(string date) => T(
        $"Expire bientôt — le {date}",
        $"Expiring soon — {date}",
        $"即将过期 — {date}",
        $"กำลังหมดอายุ — {date}",
        $"تنتهي قريباً — {date}"
    );
    public static string LicenseDetailsExpiredTitle    => T("License de mise à jour expirée", "Update license expired", "更新许可证已过期", "ใบอนุญาตอัปเดตหมดอายุ", "انتهى ترخيص التحديث");
    public static string LicenseDetailsExpiredSubtitle(string date) => T(
        $"Expirée le {date}. Tu peux encore télécharger les versions sorties avant cette date, et lancer toutes les versions installées.",
        $"Expired on {date}. You can still download versions released before that date and launch any installed version.",
        $"于 {date} 过期。您仍可下载该日期之前发布的版本，并运行任何已安装的版本。",
        $"หมดอายุเมื่อ {date} คุณยังคงดาวน์โหลดเวอร์ชันที่เผยแพร่ก่อนวันที่นั้นและเปิดเวอร์ชันที่ติดตั้งแล้วทุกเวอร์ชัน",
        $"انتهت في {date}. لا يزال بإمكانك تنزيل النسخ الصادرة قبل هذا التاريخ وتشغيل أي نسخة مثبتة."
    );
    public static string LicenseDetailsExpiredFallback => T(
        "Plus de nouveaux téléchargements possibles. Les versions installées restent utilisables.",
        "No new downloads available. Installed versions remain usable.",
        "无法下载新版本。已安装的版本仍可使用。",
        "ไม่สามารถดาวน์โหลดเวอร์ชันใหม่ได้ เวอร์ชันที่ติดตั้งแล้วยังใช้งานได้",
        "لا يمكن إجراء تنزيلات جديدة. تظل النسخ المثبتة قابلة للاستخدام."
    );
    public static string LicenseDetailsRevokedTitle    => T("License de mise à jour révoquée", "Update license revoked", "更新许可证已撤销", "ใบอนุญาตอัปเดตถูกเพิกถอน", "تم إلغاء ترخيص التحديث");
    public static string LicenseDetailsRevokedSubtitle => T("Contacte ASTERION pour plus d'informations", "Contact ASTERION for more information", "请联系 ASTERION 了解详情", "ติดต่อ ASTERION สำหรับข้อมูลเพิ่มเติม", "اتصل بـ ASTERION لمزيد من المعلومات");
    public static string LicenseDetailsUnknownTitle    => T("License inconnue", "Unknown license", "未知许可证", "ใบอนุญาตที่ไม่รู้จัก", "ترخيص غير معروف");

    // ==================== ONBOARDING STATUS ====================
    public static string OnboardingInvalidKey => T(
        "Saisis une clé valide au format PRSRV-XXXX-XXXX-XXXX-XXXX.",
        "Enter a valid key (format PRSRV-XXXX-XXXX-XXXX-XXXX).",
        "输入有效的密钥（格式 PRSRV-XXXX-XXXX-XXXX-XXXX）。",
        "ป้อนคีย์ที่ถูกต้อง (รูปแบบ PRSRV-XXXX-XXXX-XXXX-XXXX)",
        "أدخل مفتاحًا صالحًا (تنسيق PRSRV-XXXX-XXXX-XXXX-XXXX)."
    );
    public static string OnboardingValidating => T(
        "Validation en cours…", "Validating…", "正在验证…", "กำลังตรวจสอบ…", "جارٍ التحقق…"
    );
    public static string OnboardingActivated(string owner, string date) => T(
        $"License activée ({owner}). Téléchargements autorisés jusqu'au {date}.",
        $"License activated ({owner}). Downloads allowed until {date}.",
        $"许可证已激活（{owner}）。下载授权至 {date}。",
        $"เปิดใช้งานใบอนุญาต ({owner}) ดาวน์โหลดได้ถึง {date}",
        $"تم تنشيط الترخيص ({owner}). التنزيلات مسموحة حتى {date}."
    );
    public static string OnboardingExpired(string date) => T(
        $"License de mise à jour expirée le {date}. Tu peux encore télécharger les versions sorties avant cette date et lancer toutes les versions installées. Pour accéder aux versions plus récentes, contacte ASTERION pour renouveler.",
        $"Update license expired on {date}. You can still download versions released before that date and launch any installed version. To access newer versions, contact ASTERION to renew.",
        $"更新许可证于 {date} 过期。您仍可下载该日期之前发布的版本，并运行任何已安装的版本。如需访问更新版本，请联系 ASTERION 续订。",
        $"ใบอนุญาตอัปเดตหมดอายุเมื่อ {date} คุณยังคงดาวน์โหลดเวอร์ชันที่เผยแพร่ก่อนวันที่นั้นและเปิดเวอร์ชันที่ติดตั้งแล้วทุกเวอร์ชัน หากต้องการเข้าถึงเวอร์ชันใหม่กว่า ติดต่อ ASTERION เพื่อต่ออายุ",
        $"انتهى ترخيص التحديث في {date}. لا يزال بإمكانك تنزيل النسخ الصادرة قبل هذا التاريخ وتشغيل أي نسخة مثبتة. للوصول إلى نسخ أحدث، اتصل بـ ASTERION للتجديد."
    );
    public static string OnboardingRevoked => T(
        "Cette license a été révoquée. Contacte ASTERION.",
        "This license has been revoked. Contact ASTERION.",
        "此许可证已被撤销。请联系 ASTERION。",
        "ใบอนุญาตนี้ถูกเพิกถอน ติดต่อ ASTERION",
        "تم إلغاء هذا الترخيص. اتصل بـ ASTERION."
    );
    public static string OnboardingMachineLimit => T(
        "Cette license a atteint son nombre maximum de machines.",
        "This license has reached its maximum number of machines.",
        "此许可证已达到最大机器数。",
        "ใบอนุญาตถึงจำนวนเครื่องสูงสุดแล้ว",
        "وصل هذا الترخيص إلى الحد الأقصى لعدد الأجهزة."
    );
    public static string OnboardingInvalid => T(
        "Clé de license inconnue.",
        "Unknown license key.",
        "未知的许可证密钥。",
        "คีย์ใบอนุญาตที่ไม่รู้จัก",
        "مفتاح ترخيص غير معروف."
    );
    public static string OnboardingServerError(string detail) => T(
        $"Erreur de communication avec le serveur :\n{detail}",
        $"Server communication error:\n{detail}",
        $"与服务器通信错误：\n{detail}",
        $"การสื่อสารกับเซิร์ฟเวอร์ผิดพลาด:\n{detail}",
        $"خطأ في الاتصال بالخادم:\n{detail}"
    );

    // ==================== SETTINGS LABELS ====================
    public static string SettingsApiUrl       => T("URL de l'API",                       "API URL",                          "API 地址",          "URL ของ API",          "عنوان API");
    public static string SettingsInstallRoot  => T("Dossier où sont installées les versions", "Folder where versions are installed", "版本安装的文件夹",  "โฟลเดอร์สำหรับติดตั้งเวอร์ชัน", "المجلد الذي تُثبَّت فيه النسخ");
    public static string SettingsLicenseStatus  => T("Statut",                              "Status",                           "状态",              "สถานะ",                "الحالة");
    public static string SettingsMachineId      => T("Identifiant machine (anonyme)",       "Machine ID (anonymous)",           "机器 ID（匿名）",   "ID เครื่อง (ไม่ระบุตัวตน)", "معرّف الجهاز (مجهول)");
    public static string SettingsCopyMachineId  => T("📋  Copier",                          "📋  Copy",                          "📋  复制",          "📋  คัดลอก",            "📋  نسخ");
    public static string SettingsCacheSize      => T("Taille actuelle :",                   "Current size:",                    "当前大小：",        "ขนาดปัจจุบัน:",         "الحجم الحالي:");
    public static string SettingsCacheRotation  => T("Logs rotation quotidienne, 10 jours conservés", "Daily log rotation, 10 days kept", "日志每日轮换，保留 10 天", "หมุนเวียนบันทึกรายวัน เก็บ 10 วัน", "تدوير السجل يومياً، الاحتفاظ 10 أيام");
    public static string SettingsLauncherVersion => T("Version :",                          "Version:",                         "版本：",            "เวอร์ชัน:",            "النسخة:");
    public static string SettingsDeactivate     => T("Désactiver la license sur cette machine", "Deactivate license on this machine", "在此机器上停用许可证", "ปิดใช้งานใบอนุญาตบนเครื่องนี้", "إلغاء تنشيط الترخيص على هذا الجهاز");

    // ==================== LICENSE DETAILS DIALOG LABELS ====================
    public static string LicenseDetailsClient       => T("Client",                   "Customer",            "客户",        "ลูกค้า",        "العميل");
    public static string LicenseDetailsValidity     => T("Validité téléchargements", "Download validity",   "下载有效期",   "ระยะเวลาดาวน์โหลด", "صلاحية التنزيل");
    public static string LicenseDetailsActivatedOn  => T("Activée le",               "Activated on",        "激活日期",     "เปิดใช้งานเมื่อ", "تم التنشيط في");
    public static string LicenseDetailsMachineId    => T("ID machine",               "Machine ID",          "机器 ID",     "ID เครื่อง",     "معرّف الجهاز");
    public static string LicenseDetailsDeactivate   => T("🗑  Désactiver la license", "🗑  Deactivate license", "🗑  停用许可证", "🗑  ปิดใช้งานใบอนุญาต", "🗑  إلغاء تنشيط الترخيص");

    // ==================== EMPTY STATE ====================
    public static string EmptyHint(string installRoot) => T(
        $"Aucune version locale ni distante.\nClique « Vérifier les MAJ » pour récupérer le catalogue depuis le serveur, ou place un dossier « PROSERVE v{{X.Y.Z}} » contenant PROSERVE_UE_5_5.exe dans :\n{installRoot}",
        $"No local or remote version.\nClick « Check for updates » to fetch the catalog from the server, or place a folder « PROSERVE v{{X.Y.Z}} » containing PROSERVE_UE_5_5.exe in:\n{installRoot}",
        $"未找到本地或远程版本。\n点击「检查更新」以从服务器获取目录，或将包含 PROSERVE_UE_5_5.exe 的「PROSERVE v{{X.Y.Z}}」文件夹放置在：\n{installRoot}",
        $"ไม่พบเวอร์ชันในเครื่องหรือบนเซิร์ฟเวอร์\nคลิก « ตรวจสอบการอัปเดต » เพื่อดึงแคตตาล็อกจากเซิร์ฟเวอร์ หรือวางโฟลเดอร์ « PROSERVE v{{X.Y.Z}} » ที่มี PROSERVE_UE_5_5.exe ใน:\n{installRoot}",
        $"لا توجد نسخة محلية أو عن بُعد.\nانقر « التحقق من التحديثات » لجلب الكتالوج من الخادم، أو ضع مجلداً « PROSERVE v{{X.Y.Z}} » يحتوي على PROSERVE_UE_5_5.exe في:\n{installRoot}"
    );

    // ==================== TOAST NOTIFICATIONS ====================
    public static string ToastInstallSuccessTitle => T("Installation terminée", "Installation complete", "安装完成", "ติดตั้งเสร็จแล้ว", "اكتمل التثبيت");
    public static string ToastInstallSuccessBody(string version) => T(
        $"PROSERVE v{version} est prête à être lancée.",
        $"PROSERVE v{version} is ready to launch.",
        $"PROSERVE v{version} 已就绪。",
        $"PROSERVE v{version} พร้อมเปิดใช้งานแล้ว",
        $"PROSERVE v{version} جاهز للتشغيل."
    );
    public static string ToastInstallErrorTitle(string version) => T(
        $"Échec v{version}",
        $"v{version} failed",
        $"v{version} 失败",
        $"v{version} ล้มเหลว",
        $"v{version} فشل"
    );

    // ==================== RELEASE NOTES PLACEHOLDERS ====================
    public static string ReleaseNotesNone        => T("_Aucune release note fournie._", "_No release notes provided._", "_未提供版本说明。_", "_ไม่มีบันทึกการเปลี่ยนแปลง_", "_لا توجد ملاحظات إصدار._");
    public static string ReleaseNotesUnavailable => T("_Release notes indisponibles._", "_Release notes unavailable._", "_版本说明不可用。_",   "_บันทึกการเปลี่ยนแปลงไม่พร้อมใช้งาน_", "_ملاحظات الإصدار غير متاحة._");

    // ==================== MISC ====================
    public static string OpenButton  => T("📁  Ouvrir",   "📁  Open",     "📁  打开",   "📁  เปิด",     "📁  فتح");
    public static string CopyTooltip => T("Copier",       "Copy",         "复制",       "คัดลอก",     "نسخ");
    public static string Minimize    => T("Réduire",      "Minimize",     "最小化",     "ย่อลง",       "تصغير");
    public static string Maximize    => T("Agrandir",     "Maximize",     "最大化",     "ขยาย",        "تكبير");
    public static string Restore     => T("Restaurer",    "Restore",      "还原",       "คืนค่า",      "استعادة");
    public static string CloseTooltip=> T("Fermer",       "Close",        "关闭",       "ปิด",         "إغلاق");

    /// <summary>
    /// Unités de taille fichier localisées (octet, kilo-octet, méga-octet, giga-octet, téra-octet).
    /// FR : o/Ko/Mo/Go/To • EN : B/KB/MB/GB/TB • ZH : 字节/KB/MB/GB/TB • TH : ไบต์/KB/MB/GB/TB • AR : ب/ك.ب/م.ب/ج.ب/ت.ب
    /// </summary>
    private static string[] SizeUnits => _lang switch
    {
        "en" => new[] { "B",   "KB",   "MB",   "GB",   "TB"   },
        "zh" => new[] { "字节", "KB",   "MB",   "GB",   "TB"   },
        "th" => new[] { "ไบต์", "KB",   "MB",   "GB",   "TB"   },
        "ar" => new[] { "ب",   "ك.ب",  "م.ب",  "ج.ب",  "ت.ب"  },
        _    => new[] { "o",   "Ko",   "Mo",   "Go",   "To"   },
    };

    /// <summary>
    /// Format date court (ex. 29/04/2026 en FR, 4/29/2026 en US, 2026/4/29 en ZH).
    /// Utilise la culture courante (réglée par Init()).
    /// </summary>
    public static string FormatDate(DateTime dt) =>
        dt.ToLocalTime().ToString("d", CultureInfo.GetCultureInfo(_lang));

    /// <summary>Format date court — overload sur Nullable&lt;DateTime&gt; (renvoie « — » si null).</summary>
    public static string FormatDate(DateTime? dt) => dt is { } d ? FormatDate(d) : "—";

    /// <summary>
    /// Format date long (jour de la semaine + jour + mois en lettres + année).
    /// Ex. « lundi 29 avril 2026 » en FR, « Monday, April 29, 2026 » en EN.
    /// </summary>
    public static string FormatLongDate(DateTime dt) =>
        dt.ToLocalTime().ToString("D", CultureInfo.GetCultureInfo(_lang));

    /// <summary>Formatage canonique d'une taille en octets dans la langue active.</summary>
    public static string FormatSize(long bytes)
    {
        if (bytes <= 0) return $"0 {SizeUnits[0]}";
        var units = SizeUnits;
        double v = bytes; int u = 0;
        while (v >= 1024 && u < units.Length - 1) { v /= 1024; u++; }
        return $"{v:0.#} {units[u]}";
    }

    public static string SizeLabel(string size) => T(
        $"Taille : {size}", $"Size: {size}", $"大小：{size}", $"ขนาด: {size}", $"الحجم: {size}"
    );

    public static string SizeUnknown => T("taille inconnue", "unknown size", "未知大小", "ขนาดไม่ทราบ", "حجم غير معروف");

    public static string UpdateAvailableHeading(string version) => T(
        $"PROSERVE v{version} disponible",
        $"PROSERVE v{version} available",
        $"PROSERVE v{version} 可用",
        $"PROSERVE v{version} พร้อมใช้งาน",
        $"PROSERVE v{version} متاح"
    );

    public static string UpdateAvailableSubtitle(string date, string size) => T(
        $"Date de release : {date} • {size}",
        $"Release date: {date} • {size}",
        $"发布日期：{date} • {size}",
        $"วันเผยแพร่: {date} • {size}",
        $"تاريخ الإصدار: {date} • {size}"
    );

    // ==================== HEALTH BANNER ====================
    public static string SettingsHealthChecks => T(
        "BANDEAU DE SANTÉ SYSTÈME",
        "SYSTEM HEALTH BANNER",
        "系统健康状态栏",
        "แถบสถานะระบบ",
        "شريط حالة النظام"
    );
    public static string SettingsHealthChecksHint => T(
        "Pings et processus surveillés sous la top bar (Library uniquement). Édité ici, sauvegardé dans config.json — survit à un auto-update.",
        "Pings and processes monitored below the top bar (Library only). Edited here, saved to config.json — survives auto-updates.",
        "在顶部栏下监控的 Ping 和进程（仅库视图）。在此编辑，保存到 config.json — 在自动更新后保留。",
        "Ping และโปรเซสที่ติดตามใต้แถบบนสุด (เฉพาะ Library) แก้ไขที่นี่ บันทึกใน config.json — คงอยู่หลังการอัปเดตอัตโนมัติ",
        "Ping والعمليات المراقَبة أسفل الشريط العلوي (المكتبة فقط). يُحرَّر هنا، ويُحفَظ في config.json — يبقى بعد التحديث التلقائي."
    );
    public static string SettingsHealthAdd      => T("➕  Ajouter",       "➕  Add",       "➕  添加",     "➕  เพิ่ม",      "➕  إضافة");
    public static string SettingsHealthEdit     => T("✎  Éditer",         "✎  Edit",      "✎  编辑",     "✎  แก้ไข",     "✎  تحرير");
    public static string SettingsHealthDelete   => T("🗑  Supprimer",     "🗑  Delete",   "🗑  删除",     "🗑  ลบ",       "🗑  حذف");
    public static string SettingsHealthEmpty    => T(
        "Aucun check configuré. Le bandeau ne s'affichera pas tant qu'aucune entrée n'aura été ajoutée.",
        "No checks configured. The banner stays hidden until at least one entry is added.",
        "未配置任何检查。在添加至少一项之前，状态栏将保持隐藏。",
        "ยังไม่มีการตรวจสอบที่ตั้งค่าไว้ แถบจะถูกซ่อนจนกว่าจะมีการเพิ่มอย่างน้อยหนึ่งรายการ",
        "لم يُهيَّأ أي فحص. سيبقى الشريط مخفياً حتى تتم إضافة إدخال واحد على الأقل."
    );
    public static string SettingsHealthDeleteConfirm(string name) => T(
        $"Supprimer le check « {name} » ?",
        $"Delete check « {name} »?",
        $"删除检查 \"{name}\"？",
        $"ลบการตรวจสอบ « {name} »?",
        $"حذف الفحص « {name} »؟"
    );

    public static string HealthEditorTitle      => T("Éditer le check",   "Edit check",   "编辑检查",   "แก้ไขการตรวจสอบ", "تحرير الفحص");
    public static string HealthEditorTitleNew   => T("Nouveau check",     "New check",    "新建检查",   "การตรวจสอบใหม่",  "فحص جديد");
    public static string HealthEditorName       => T("NOM",               "NAME",         "名称",       "ชื่อ",            "الاسم");
    public static string HealthEditorNameHint   => T(
        "Libellé court affiché dans la pill du bandeau, ex. « SteamVR ».",
        "Short label shown in the banner pill, e.g. \"SteamVR\".",
        "状态栏胶囊中显示的简短标签，例如 \"SteamVR\"。",
        "ชื่อย่อที่แสดงในป้ายของแถบ เช่น \"SteamVR\"",
        "تسمية قصيرة تظهر في الشريط، مثل \"SteamVR\"."
    );
    public static string HealthEditorKind            => T("TYPE DE VÉRIFICATION", "CHECK TYPE",  "检查类型",  "ประเภทการตรวจสอบ", "نوع الفحص");
    public static string HealthEditorKindProcess     => T("Processus (Windows)",  "Process (Windows)", "进程 (Windows)", "โปรเซส (Windows)",  "عملية (Windows)");
    public static string HealthEditorKindPing        => T("Ping (réseau ICMP)",   "Ping (ICMP network)", "Ping (ICMP 网络)", "Ping (เครือข่าย ICMP)", "Ping (شبكة ICMP)");
    public static string HealthEditorKindVrDevice    => T(
        "Appareil SteamVR",
        "SteamVR device",
        "SteamVR 设备",
        "อุปกรณ์ SteamVR",
        "جهاز SteamVR"
    );
    public static string HealthEditorTargetVrDevice  => T("APPAREIL VR",          "VR DEVICE",        "VR 设备",       "อุปกรณ์ VR",        "جهاز VR");
    public static string HealthEditorTargetVrDeviceHint => T(
        "Phase 1 : détecte la présence de SteamVR et d'un HMD branché (vert si oui, rouge sinon). La batterie + l'info par-appareil arrive en Phase 1.5 via un sous-process séparé pour être robuste aux crashs natifs OpenVR.",
        "Phase 1: detects SteamVR running with an HMD plugged in (green if yes, red otherwise). Battery + per-device info coming in Phase 1.5 via a separate child process to be robust against OpenVR native crashes.",
        "第 1 阶段：检测 SteamVR 是否运行以及是否插入了 HMD（是则绿色，否则红色）。电池 + 每个设备的信息将在第 1.5 阶段通过单独的子进程提供，以增强对 OpenVR 原生崩溃的鲁棒性。",
        "เฟส 1: ตรวจจับว่า SteamVR กำลังทำงานและมี HMD เชื่อมต่ออยู่หรือไม่ (เขียวถ้าใช่ แดงถ้าไม่) ข้อมูลแบตเตอรี่ + ต่ออุปกรณ์จะมาในเฟส 1.5 ผ่านโปรเซสลูกแยกเพื่อความทนทานต่อข้อผิดพลาดเนทีฟ OpenVR",
        "المرحلة 1: تكتشف تشغيل SteamVR مع توصيل HMD (أخضر إذا نعم، أحمر خلاف ذلك). البطارية + المعلومات لكل جهاز ستأتي في المرحلة 1.5 عبر عملية فرعية منفصلة لتكون متينة ضد الأعطال الأصلية OpenVR."
    );
    public static string HealthEditorTargetProcess   => T("CIBLE — NOM DE PROCESSUS",  "TARGET — PROCESS NAME",  "目标 — 进程名",  "เป้าหมาย — ชื่อโปรเซส", "الهدف — اسم العملية");
    public static string HealthEditorTargetPing      => T("CIBLE — IP OU HOSTNAME",     "TARGET — IP OR HOSTNAME", "目标 — IP 或主机名", "เป้าหมาย — IP หรือชื่อโฮสต์", "الهدف — IP أو اسم المضيف");
    public static string HealthEditorTargetProcessHint => T(
        "Nom de l'exécutable sans .exe, ex. « vrserver » ou « HtcConnectionUtility ». Insensible à la casse.",
        "Executable name without .exe, e.g. \"vrserver\" or \"HtcConnectionUtility\". Case-insensitive.",
        "可执行文件名（不含 .exe），例如 \"vrserver\" 或 \"HtcConnectionUtility\"。不区分大小写。",
        "ชื่อไฟล์ปฏิบัติการโดยไม่มี .exe เช่น \"vrserver\" หรือ \"HtcConnectionUtility\" ไม่คำนึงถึงตัวพิมพ์",
        "اسم الملف التنفيذي بدون .exe، مثل \"vrserver\" أو \"HtcConnectionUtility\". غير حساس لحالة الأحرف."
    );
    public static string HealthEditorTargetPingHint  => T(
        "Adresse IP (ex. 192.168.1.42) ou hostname résolvable. Vide = check ignoré (statut Inconnu).",
        "IP address (e.g. 192.168.1.42) or resolvable hostname. Empty = check skipped (Unknown status).",
        "IP 地址（例如 192.168.1.42）或可解析的主机名。空 = 跳过检查（未知状态）。",
        "ที่อยู่ IP (เช่น 192.168.1.42) หรือชื่อโฮสต์ที่แก้ได้ ว่างเปล่า = ข้ามการตรวจสอบ (สถานะไม่ทราบ)",
        "عنوان IP (مثل 192.168.1.42) أو اسم مضيف قابل للحل. فارغ = تخطّي الفحص (الحالة غير معروفة)."
    );
    public static string HealthEditorRefresh        => T("INTERVALLE DE RAFRAÎCHISSEMENT", "REFRESH INTERVAL", "刷新间隔", "ช่วงเวลารีเฟรช", "فاصل التحديث");
    public static string HealthEditorRefreshHint    => T(
        "Délai entre 2 vérifications, en millisecondes. Process ~1 ms (1000 OK) ; Ping = round-trip réseau (5000-10000 conseillé). Min 200 ms.",
        "Delay between 2 checks, in milliseconds. Process ~1 ms (1000 OK); Ping = network round-trip (5000-10000 recommended). Min 200 ms.",
        "两次检查之间的延迟（毫秒）。进程约 1 毫秒（1000 即可）；Ping = 网络往返（建议 5000-10000）。最小 200 毫秒。",
        "ระยะเวลาระหว่าง 2 การตรวจสอบเป็นมิลลิวินาที โปรเซส ~1 ms (1000 OK); Ping = round-trip เครือข่าย (แนะนำ 5000-10000). ขั้นต่ำ 200 ms.",
        "التأخير بين فحصين بالمللي ثانية. عملية ~1 ms (1000 جيدة)؛ Ping = ذهاب وعودة الشبكة (يوصى 5000-10000). الحد الأدنى 200 ms."
    );
    public static string HealthEditorIcon       => T("PICTOGRAMME", "ICON", "图标", "ไอคอน", "أيقونة");
    public static string HealthEditorIconHint   => T(
        "Affiché à gauche du nom dans la pill. Choisis-en un dans la liste.",
        "Displayed to the left of the name in the pill. Pick one from the list.",
        "显示在胶囊中名称的左侧。从列表中选择一个。",
        "แสดงทางซ้ายของชื่อในป้าย เลือกหนึ่งจากรายการ",
        "تُعرض على يسار الاسم في الشارة. اختر واحدةً من القائمة."
    );
    public static string HealthEditorPingAdvanced => T(
        "Avancé — seuils Ping",
        "Advanced — Ping thresholds",
        "高级 — Ping 阈值",
        "ขั้นสูง — เกณฑ์ Ping",
        "متقدم — حدود Ping"
    );
    public static string HealthEditorPingWarn    => T("Warning ms — RTT au-dessus = orange", "Warning ms — RTT above = amber", "警告 ms — 高于此值 = 橙色", "Warning ms — RTT เกินค่านี้ = ส้ม", "تحذير ms — RTT فوق ذلك = برتقالي");
    public static string HealthEditorPingError   => T("Error ms — RTT au-dessus = rouge",    "Error ms — RTT above = red",     "错误 ms — 高于此值 = 红色", "Error ms — RTT เกินค่านี้ = แดง", "خطأ ms — RTT فوق ذلك = أحمر");
    public static string HealthEditorPingTimeout => T("Timeout ms — abandon de la requête",  "Timeout ms — abort the request", "超时 ms — 中止请求",   "Timeout ms — ยกเลิกคำขอ",          "مهلة ms — إلغاء الطلب");
    public static string HealthEditorErrorName    => T(
        "Le nom est requis.",
        "Name is required.",
        "需要名称。",
        "ต้องระบุชื่อ",
        "الاسم مطلوب."
    );
    public static string HealthEditorErrorRefresh => T(
        "Intervalle invalide. Saisis un entier ≥ 200 (millisecondes).",
        "Invalid interval. Enter an integer ≥ 200 (milliseconds).",
        "间隔无效。请输入 ≥ 200 的整数（毫秒）。",
        "ช่วงเวลาไม่ถูกต้อง ใส่จำนวนเต็ม ≥ 200 (มิลลิวินาที)",
        "فاصل غير صالح. أدخل عدداً صحيحاً ≥ 200 (مللي ثانية)."
    );

    // ==================== COPYRIGHT ====================
    public static string Copyright => T(
        "© 2026 ASTERION VR — Tous droits réservés",
        "© 2026 ASTERION VR — All rights reserved",
        "© 2026 ASTERION VR — 保留所有权利",
        "© 2026 ASTERION VR — สงวนลิขสิทธิ์",
        "© 2026 ASTERION VR — جميع الحقوق محفوظة"
    );
}
