#!/usr/bin/env python3
#
# Porteus Language Selection Tool

import os, sys, locale, threading, subprocess, urllib.request, re, shutil
from pathlib import Path

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib

# ============================================================
# Translations
# ============================================================
T = {
    'en': {
        "title": "Language Selection Tool",
        "header": "Porteus Language Selection Tool",
        "sysenc": "System encoding",
        "choose_lang": "Choose a system language",
        "choose_de": "Choose a desktop",
        "extras": "Extras",
        "devel_locales": "Development locales",
        "nonlatin_font": "Download a non-latin font",
        "nonlatin_tt": "Choose non-latin based fonts for webpages and text input",
        "scim": "Download scim input",
        "scim_tt": "Scim offers a user friendly input method for POSIX operating systems",
        "kbd_setup": "Keyboard setup",
        "kbd_q": "Do you want to change your keyboard configuration?",
        "kbd_config": "Keyboard configuration",
        "cancel": "Cancel",
        "ok": "OK",
        "error": "Error",
        "net_required": "An internet connection is required. Exiting.",
        "select_first": "Please select a language first.",
        "success": "Success",
        "files_info": "Your files: /tmp/language-files",
        "move_info": "Move them into your modules folder before you restart.",
        "root_required": "Root privileges required",
        "root_msg": "This tool must be run as root.",
        "downloading": "Downloading",
        "creating_module": "Creating language module",
        "iface_lang": "Interface language:",
        "loading": "Loading...",
        "downloading_f": "Downloading {}",
        "refresh": "Refresh list",
        "no_modules": "No modules found",
        "module_created": "Language module created successfully.",
        "cancelled": "Cancelled",
        "font_list": "Font list",
        "total_size": "Total size: {:.1f} MB",
        "downloaded": "Downloaded {:.1f} MB of {:.1f} MB",
    },
    'ru': {
        "title": "Выбор языка",
        "header": "Выбор языка Porteus",
        "sysenc": "Кодировка системы",
        "choose_lang": "Выберите язык системы",
        "choose_de": "Выберите окружение рабочего стола",
        "extras": "Дополнительно",
        "devel_locales": "Языковые пакеты для разработки",
        "nonlatin_font": "Скачать нелатинские шрифты",
        "nonlatin_tt": "Выберите нелатинские шрифты для веб-страниц и ввода текста",
        "scim": "Скачать метод ввода SCIM",
        "scim_tt": "SCIM предлагает удобный метод ввода для POSIX-систем",
        "kbd_setup": "Настройка клавиатуры",
        "kbd_q": "Хотите изменить конфигурацию клавиатуры?",
        "kbd_config": "Открыть настройки клавиатуры",
        "cancel": "Отмена",
        "ok": "ОК",
        "error": "Ошибка",
        "net_required": "Требуется подключение к интернету. Выход.",
        "select_first": "Пожалуйста, сначала выберите язык.",
        "success": "Успешно",
        "files_info": "Ваши файлы: /tmp/language-files",
        "move_info": "Переместите их в папку модулей перед перезагрузкой.",
        "root_required": "Требуются права суперпользователя",
        "root_msg": "Этот инструмент должен быть запущен от имени root.",
        "downloading": "Загрузка",
        "creating_module": "Создание языкового модуля",
        "iface_lang": "Язык интерфейса:",
        "loading": "Загрузка...",
        "downloading_f": "Загрузка {}",
        "refresh": "Обновить список",
        "no_modules": "Модули не найдены",
        "module_created": "Языковой модуль успешно создан.",
        "cancelled": "Отменено",
        "font_list": "Список шрифтов",
        "total_size": "Общий размер: {:.1f} МБ",
        "downloaded": "Загружено {:.1f} МБ из {:.1f} МБ",
    },
    'de': {
        "title": "Sprachauswahl", "header": "Porteus Sprachauswahl",
        "sysenc": "Systemkodierung", "choose_lang": "Wählen Sie eine Systemsprache",
        "choose_de": "Wählen Sie eine Desktop-Umgebung", "extras": "Extras",
        "devel_locales": "Entwickler-Lokalisierungen", "nonlatin_font": "Nicht-lateinische Schriftart herunterladen",
        "nonlatin_tt": "Wählen Sie nicht-lateinische Schriftarten", "scim": "SCIM-Eingabemethode herunterladen",
        "scim_tt": "SCIM bietet eine benutzerfreundliche Eingabemethode", "kbd_setup": "Tastatureinrichtung",
        "kbd_q": "Möchten Sie Ihre Tastaturkonfiguration ändern?", "kbd_config": "Tastaturkonfiguration",
        "cancel": "Abbrechen", "ok": "OK", "error": "Fehler",
        "net_required": "Eine Internetverbindung ist erforderlich.", "select_first": "Bitte wählen Sie zuerst eine Sprache.",
        "success": "Erfolg", "files_info": "Ihre Dateien: /tmp/language-files",
        "move_info": "Verschieben Sie sie vor dem Neustart in Ihren Module-Ordner.",
        "root_required": "Root-Rechte erforderlich", "root_msg": "Dieses Tool muss als Root ausgeführt werden.",
        "downloading": "Herunterladen", "creating_module": "Sprachmodul wird erstellt",
        "iface_lang": "Sprache der Oberfläche:", "loading": "Laden...",
        "downloading_f": "Herunterladen {}", "refresh": "Liste aktualisieren",
        "no_modules": "Keine Module gefunden", "module_created": "Sprachmodul erfolgreich erstellt.",
        "cancelled": "Abgebrochen", "font_list": "Schriftartenliste",
        "total_size": "Gesamtgröße: {:.1f} MB", "downloaded": "{:.1f} MB von {:.1f} MB heruntergeladen",
    },
    'es': {
        "title": "Selección de idioma", "header": "Selección de idioma de Porteus",
        "sysenc": "Codificación del sistema", "choose_lang": "Elija un idioma del sistema",
        "choose_de": "Elija un entorno de escritorio", "extras": "Extras",
        "devel_locales": "Locales de desarrollo", "nonlatin_font": "Descargar fuente no latina",
        "nonlatin_tt": "Elija fuentes no latinas", "scim": "Descargar método de entrada SCIM",
        "scim_tt": "SCIM ofrece un método de entrada fácil de usar", "kbd_setup": "Configuración del teclado",
        "kbd_q": "¿Desea cambiar la configuración de su teclado?", "kbd_config": "Configuración del teclado",
        "cancel": "Cancelar", "ok": "Aceptar", "error": "Error",
        "net_required": "Se requiere una conexión a Internet.", "select_first": "Por favor, seleccione un idioma primero.",
        "success": "Éxito", "files_info": "Sus archivos: /tmp/language-files",
        "move_info": "Muévalos a su carpeta de módulos antes de reiniciar.",
        "root_required": "Se requieren privilegios de root", "root_msg": "Esta herramienta debe ejecutarse como root.",
        "downloading": "Descargando", "creating_module": "Creando módulo de idioma",
        "iface_lang": "Idioma de la interfaz:", "loading": "Cargando...",
        "downloading_f": "Descargando {}", "refresh": "Actualizar lista",
        "no_modules": "No se encontraron módulos", "module_created": "Módulo de idioma creado con éxito.",
        "cancelled": "Cancelado", "font_list": "Lista de fuentes",
        "total_size": "Tamaño total: {:.1f} MB", "downloaded": "Descargado {:.1f} MB de {:.1f} MB",
    },
    'fr': {
        "title": "Sélection de la langue",
        "header": "Outil de sélection de la langue Porteus",
        "sysenc": "Encodage système",
        "choose_lang": "Choisissez une langue système",
        "choose_de": "Choisissez un environnement de bureau",
        "extras": "Extras",
        "devel_locales": "Locales de développement",
        "nonlatin_font": "Télécharger une police non latine",
        "nonlatin_tt": "Choisissez des polices non latines pour les pages web et la saisie de texte",
        "scim": "Télécharger la méthode de saisie SCIM",
        "scim_tt": "SCIM offre une méthode de saisie conviviale pour les systèmes POSIX",
        "kbd_setup": "Configuration du clavier",
        "kbd_q": "Voulez-vous modifier la configuration de votre clavier ?",
        "kbd_config": "Configuration du clavier",
        "cancel": "Annuler",
        "ok": "OK",
        "error": "Erreur",
        "net_required": "Une connexion Internet est requise. Fermeture.",
        "select_first": "Veuillez d'abord sélectionner une langue.",
        "success": "Succès",
        "files_info": "Vos fichiers : /tmp/language-files",
        "move_info": "Déplacez-les dans votre dossier de modules avant de redémarrer.",
        "root_required": "Privilèges root requis",
        "root_msg": "Cet outil doit être exécuté en tant que root.",
        "downloading": "Téléchargement",
        "creating_module": "Création du module de langue",
        "iface_lang": "Langue de l'interface :",
        "loading": "Chargement...",
        "downloading_f": "Téléchargement de {}",
        "refresh": "Actualiser la liste",
        "no_modules": "Aucun module trouvé",
        "module_created": "Module de langue créé avec succès.",
        "cancelled": "Annulé",
        "font_list": "Liste des polices",
        "total_size": "Taille totale : {:.1f} Mo",
        "downloaded": "{:.1f} Mo téléchargés sur {:.1f} Mo",
    },
    'ja': {
        "title": "言語選択ツール", "header": "Porteus 言語選択ツール",
        "sysenc": "システムエンコーディング", "choose_lang": "システム言語を選択",
        "choose_de": "デスクトップ環境を選択", "extras": "追加機能",
        "devel_locales": "開発用ロケール", "nonlatin_font": "非ラテン文字フォントをダウンロード",
        "nonlatin_tt": "ウェブページとテキスト入力用の非ラテン文字フォントを選択",
        "scim": "SCIM入力方法をダウンロード", "scim_tt": "SCIMはPOSIXオペレーティングシステム向けの使いやすい入力方法を提供します",
        "kbd_setup": "キーボード設定", "kbd_q": "キーボード設定を変更しますか？",
        "kbd_config": "キーボード設定", "cancel": "キャンセル", "ok": "OK", "error": "エラー",
        "net_required": "インターネット接続が必要です。", "select_first": "最初に言語を選択してください。",
        "success": "成功", "files_info": "ファイル：/tmp/language-files",
        "move_info": "再起動前にモジュールフォルダに移動してください。",
        "root_required": "root権限が必要です", "root_msg": "このツールはrootとして実行する必要があります。",
        "downloading": "ダウンロード中", "creating_module": "言語モジュールを作成中",
        "iface_lang": "インターフェース言語:", "loading": "読み込み中...",
        "downloading_f": "{} をダウンロード中", "refresh": "リストを更新",
        "no_modules": "モジュールが見つかりません", "module_created": "言語モジュールが正常に作成されました。",
        "cancelled": "キャンセルされました", "font_list": "フォント一覧",
        "total_size": "合計サイズ: {:.1f} MB", "downloaded": "{:.1f} MB / {:.1f} MB ダウンロード済み",
    },
    'nl_NL': {
        "title": "Taal Selectie Tool", "header": "Porteus Taal Selectie Tool",
        "sysenc": "Systeem codering", "choose_lang": "Kies een systeemtaal",
        "choose_de": "Kies een desktopomgeving", "extras": "Extra's",
        "devel_locales": "Ontwikkeling locales", "nonlatin_font": "Download een niet-Latijns lettertype",
        "nonlatin_tt": "Kies niet-Latijnse lettertypen", "scim": "Download SCIM invoermethode",
        "scim_tt": "SCIM biedt een gebruiksvriendelijke invoermethode", "kbd_setup": "Toetsenbord instellen",
        "kbd_q": "Wilt u uw toetsenbordconfiguratie wijzigen?", "kbd_config": "Toetsenbord configuratie",
        "cancel": "Annuleren", "ok": "OK", "error": "Fout",
        "net_required": "Een internetverbinding is vereist.", "select_first": "Selecteer eerst een taal.",
        "success": "Succes", "files_info": "Uw bestanden: /tmp/language-files",
        "move_info": "Verplaats ze naar uw modules map.",
        "root_required": "Root rechten vereist", "root_msg": "Deze tool moet als root worden uitgevoerd.",
        "downloading": "Downloaden", "creating_module": "Taalmodule aanmaken",
        "iface_lang": "Taal van de interface:", "loading": "Laden...",
        "downloading_f": "{} downloaden", "refresh": "Lijst vernieuwen",
        "no_modules": "Geen modules gevonden", "module_created": "Taalmodule succesvol aangemaakt.",
        "cancelled": "Geannuleerd", "font_list": "Lettertypelijst",
        "total_size": "Totale grootte: {:.1f} MB", "downloaded": "{:.1f} MB van {:.1f} MB gedownload",
    },
    'pl': {
        "title": "Wybór języka", "header": "Wybór języka Porteus",
        "sysenc": "Kodowanie systemu", "choose_lang": "Wybierz język systemu",
        "choose_de": "Wybierz środowisko graficzne", "extras": "Dodatki",
        "devel_locales": "Lokale programistyczne", "nonlatin_font": "Pobierz czcionkę niełacińską",
        "nonlatin_tt": "Wybierz czcionki niełacińskie", "scim": "Pobierz metodę wprowadzania SCIM",
        "scim_tt": "SCIM oferuje przyjazną metodę wprowadzania", "kbd_setup": "Konfiguracja klawiatury",
        "kbd_q": "Czy chcesz zmienić konfigurację klawiatury?", "kbd_config": "Konfiguracja klawiatury",
        "cancel": "Anuluj", "ok": "OK", "error": "Błąd",
        "net_required": "Wymagane połączenie z internetem.", "select_first": "Najpierw wybierz język.",
        "success": "Sukces", "files_info": "Twoje pliki: /tmp/language-files",
        "move_info": "Przenieś je do folderu modułów przed restartem.",
        "root_required": "Wymagane uprawnienia roota", "root_msg": "To narzędzie musi być uruchomione jako root.",
        "downloading": "Pobieranie", "creating_module": "Tworzenie modułu językowego",
        "iface_lang": "Język interfejsu:", "loading": "Wczytywanie...",
        "downloading_f": "Pobieranie {}", "refresh": "Odśwież listę",
        "no_modules": "Nie znaleziono modułów", "module_created": "Moduł językowy został pomyślnie utworzony.",
        "cancelled": "Anulowano", "font_list": "Lista czcionek",
        "total_size": "Całkowity rozmiar: {:.1f} MB", "downloaded": "Pobrano {:.1f} MB z {:.1f} MB",
    },
    'pt_BR': {
        "title": "Seleção de Idioma", "header": "Seleção de Idioma do Porteus",
        "sysenc": "Codificação do sistema", "choose_lang": "Escolha um idioma do sistema",
        "choose_de": "Escolha um ambiente de desktop", "extras": "Extras",
        "devel_locales": "Locais de desenvolvimento", "nonlatin_font": "Baixar fonte não-latina",
        "nonlatin_tt": "Escolha fontes não-latinas", "scim": "Baixar método de entrada SCIM",
        "scim_tt": "SCIM oferece um método de entrada amigável", "kbd_setup": "Configuração do teclado",
        "kbd_q": "Deseja alterar a configuração do seu teclado?", "kbd_config": "Configuração do teclado",
        "cancel": "Cancelar", "ok": "OK", "error": "Erro",
        "net_required": "É necessária uma conexão com a Internet.", "select_first": "Por favor, selecione um idioma primeiro.",
        "success": "Sucesso", "files_info": "Seus arquivos: /tmp/language-files",
        "move_info": "Mova-os para sua pasta de módulos antes de reiniciar.",
        "root_required": "Privilégios de root necessários", "root_msg": "Esta ferramenta deve ser executada como root.",
        "downloading": "Baixando", "creating_module": "Criando módulo de idioma",
        "iface_lang": "Idioma da interface:", "loading": "Carregando...",
        "downloading_f": "Baixando {}", "refresh": "Atualizar lista",
        "no_modules": "Nenhum módulo encontrado", "module_created": "Módulo de idioma criado com sucesso.",
        "cancelled": "Cancelado", "font_list": "Lista de fontes",
        "total_size": "Tamanho total: {:.1f} MB", "downloaded": "Baixado {:.1f} MB de {:.1f} MB",
    },
    'zh_CN': {
        "title": "语言选择工具", "header": "Porteus 语言选择工具",
        "sysenc": "系统编码", "choose_lang": "选择系统语言",
        "choose_de": "选择桌面环境", "extras": "附加功能",
        "devel_locales": "开发语言包", "nonlatin_font": "下载非拉丁字体",
        "nonlatin_tt": "为网页和文本输入选择非拉丁字体", "scim": "下载 SCIM 输入法",
        "scim_tt": "SCIM 为 POSIX 操作系统提供用户友好的输入法", "kbd_setup": "键盘设置",
        "kbd_q": "您想更改键盘配置吗？", "kbd_config": "键盘配置",
        "cancel": "取消", "ok": "确定", "error": "错误",
        "net_required": "需要互联网连接。", "select_first": "请先选择语言。",
        "success": "成功", "files_info": "您的文件：/tmp/language-files",
        "move_info": "重启前请将它们移动到模块文件夹。",
        "root_required": "需要 root 权限", "root_msg": "此工具必须以 root 身份运行。",
        "downloading": "下载中", "creating_module": "创建语言模块",
        "iface_lang": "界面语言:", "loading": "加载中...",
        "downloading_f": "正在下载 {}", "refresh": "刷新列表",
        "no_modules": "未找到模块", "module_created": "语言模块创建成功。",
        "cancelled": "已取消", "font_list": "字体列表",
        "total_size": "总大小: {:.1f} MB", "downloaded": "已下载 {:.1f} MB / {:.1f} MB",
    },
    'zh_TW': {
        "title": "語言選擇工具", "header": "Porteus 語言選擇工具",
        "sysenc": "系統編碼", "choose_lang": "選擇系統語言",
        "choose_de": "選擇桌面環境", "extras": "附加功能",
        "devel_locales": "開發語言包", "nonlatin_font": "下載非拉丁字體",
        "nonlatin_tt": "為網頁和文字輸入選擇非拉丁字體", "scim": "下載 SCIM 輸入法",
        "scim_tt": "SCIM 為 POSIX 作業系統提供使用者友善的輸入法", "kbd_setup": "鍵盤設定",
        "kbd_q": "您想更改鍵盤設定嗎？", "kbd_config": "鍵盤設定",
        "cancel": "取消", "ok": "確定", "error": "錯誤",
        "net_required": "需要網際網路連線。", "select_first": "請先選擇語言。",
        "success": "成功", "files_info": "您的檔案：/tmp/language-files",
        "move_info": "重新啟動前請將它們移動到模組資料夾。",
        "root_required": "需要 root 權限", "root_msg": "此工具必須以 root 身份執行。",
        "downloading": "下載中", "creating_module": "建立語言模組",
        "iface_lang": "介面語言:", "loading": "載入中...",
        "downloading_f": "正在下載 {}", "refresh": "重新整理清單",
        "no_modules": "找不到模組", "module_created": "語言模組建立成功。",
        "cancelled": "已取消", "font_list": "字型列表",
        "total_size": "總大小: {:.1f} MB", "downloaded": "已下載 {:.1f} MB / {:.1f} MB",
    }
}

# ============================================================
# Helpers
# ============================================================
def detect_lang():
    full = os.environ.get('LANG', 'en_US.UTF-8').split('.')[0]
    if full in T:
        return full
    short = full.split('_')[0]
    return short if short in T else 'en'

_ = lambda key: T[detect_lang()].get(key, key)

FONTS = [
    "freefont-ttf", "IPA_Japanese", "KhmerOS", "Myanmar3",
    "odosung", "PakType", "sazanami", "sinhala_lklug",
    "tibmachuni", "ttf_indic", "unifont", "wqy_zenhei"
]
DESKTOPS = [
    "cinnamon", "cosmic", "gnome", "kde", "lxde",
    "lxqt", "mate", "openbox", "xfce"
]

# ============================================================
# Main application class
# ============================================================
class App(Gtk.Window):
    def __init__(self):
        self.lang = self._load_ui_lang() or detect_lang()
        super().__init__(
            title=self.tr("title"),
            default_width=660,
            default_height=660,
            border_width=12,
            icon_name="preferences-desktop-locale"
        )
        self.set_position(Gtk.WindowPosition.CENTER)

        self.arch = "x86_64" if os.uname().machine == "x86_64" else "i586"
        try:
            self.version = Path("/etc/porteus-version").read_text().strip()
        except Exception:
            self.version = "5.0"

        conf = Path("/etc/porteus.conf").read_text() if Path("/etc/porteus.conf").exists() else ""
        server = "http://build.porteus.org"
        for line in conf.splitlines():
            if line.startswith("SERVER="):
                server = line.split("=", 1)[1].strip()
        if not server.startswith("http"):
            server = "http://" + server

        self.base_url = f"{server.rstrip('/')}/{self.arch}/{self.version}/language"
        self.modules = []
        self.available_langs = []
        self.processing = False
        self.cancel_requested = False
        self._status_key = None
        self._status_args = ()

        self.build_ui()
        self.load_modules()

    def tr(self, key, *args):
        s = T.get(self.lang, T['en']).get(key, key)
        if args:
            s = s.format(*args)
        return s

    def _config_dir(self):
        d = Path.home() / ".config" / "porteus"
        d.mkdir(parents=True, exist_ok=True)
        return d

    def _load_ui_lang(self):
        f = self._config_dir() / "language-gui.conf"
        if f.exists():
            saved = f.read_text().strip()
            if saved in T:
                return saved
        return None

    def _save_ui_lang(self, code):
        (self._config_dir() / "language-gui.conf").write_text(code)

    def build_ui(self):
        v = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        self.add(v)

        h = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
        h.set_halign(Gtk.Align.CENTER)
        h.pack_start(
            Gtk.Image.new_from_icon_name("preferences-desktop-locale", Gtk.IconSize.DIALOG),
            False, False, 0
        )
        self.header_lbl = Gtk.Label(use_markup=True, valign=Gtk.Align.CENTER)
        self.header_lbl.set_markup(
            f'<span size="x-large" weight="bold">{self.tr("header")}</span>'
        )
        h.pack_start(self.header_lbl, False, False, 0)
        v.pack_start(h, False, False, 0)

        ib = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        ib.set_halign(Gtk.Align.END)
        self.ilbl = Gtk.Label(label=self.tr("iface_lang"), xalign=0.0)
        ib.pack_start(self.ilbl, False, False, 0)

        self.icombo = Gtk.ComboBoxText()
        self.lang_map = {
            'en': 'English', 'ru': 'Русский', 'de': 'Deutsch', 'es': 'Español',
            'fr': 'Français', 'ja': '日本語', 'nl_NL': 'Nederlands', 'pl': 'Polski',
            'pt_BR': 'Português (BR)', 'zh_CN': '简体中文', 'zh_TW': '繁體中文'
        }
        for code, name in self.lang_map.items():
            self.icombo.append_text(name)
        codes = list(self.lang_map.keys())
        self.icombo.set_active(codes.index(self.lang) if self.lang in codes else 0)
        self.icombo.connect("changed", self.on_ui_lang_changed)
        ib.pack_start(self.icombo, False, False, 0)
        v.pack_start(ib, False, False, 0)

        scroll = Gtk.ScrolledWindow()
        scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        v.pack_start(scroll, True, True, 0)

        c = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        c.set_margin_start(8); c.set_margin_end(8); c.set_margin_bottom(8)
        scroll.add(c)

        def add_frame(title_key, inner_box):
            frame = Gtk.Frame(label=self.tr(title_key))
            frame.set_label_align(0.02, 0.5)
            frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
            inner_box.set_margin_start(10); inner_box.set_margin_end(10)
            inner_box.set_margin_top(10); inner_box.set_margin_bottom(10)
            frame.add(inner_box)
            c.pack_start(frame, False, False, 0)
            c.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL),
                         False, False, 4)
            return frame

        b1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)

        self.l_syslang = Gtk.Label()
        self.l_syslang.set_markup(f'<b>{self.tr("choose_lang")}</b>')
        self.l_syslang.set_xalign(0.0)
        b1.pack_start(self.l_syslang, False, False, 0)

        row = Gtk.Box(spacing=5)
        self.combo_lang = Gtk.ComboBoxText()
        row.pack_start(self.combo_lang, True, True, 0)
        self.refresh_btn = Gtk.Button(label="⟳", tooltip_text=self.tr("refresh"))
        self.refresh_btn.connect("clicked", lambda b: self.load_modules())
        row.pack_start(self.refresh_btn, False, False, 0)
        b1.pack_start(row, False, False, 0)

        self.l_choose_de = Gtk.Label()
        self.l_choose_de.set_markup(f'<b>{self.tr("choose_de")}</b>')
        self.l_choose_de.set_xalign(0.0)
        b1.pack_start(self.l_choose_de, False, False, 0)

        self.combo_de = Gtk.ComboBoxText()
        for dt in DESKTOPS:
            self.combo_de.append_text(dt)
        det = self._detect_de()
        self.combo_de.set_active(det if det is not None else 0)
        b1.pack_start(self.combo_de, False, False, 0)

        self.frame1 = add_frame("sysenc", b1)

        b2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)

        self.chk_dev = Gtk.CheckButton(label=self.tr("devel_locales"))
        b2.pack_start(self.chk_dev, False, False, 0)

        self.chk_fonts = Gtk.CheckButton(label=self.tr("nonlatin_font"))
        self.chk_fonts.set_tooltip_text(self.tr("nonlatin_tt"))
        b2.pack_start(self.chk_fonts, False, False, 0)

        self.font_expander = Gtk.Expander()
        self.font_expander.set_sensitive(False)
        self.font_expander.set_expanded(False)
        self.font_expander.set_label(self.tr("font_list"))

        self.font_checks = {}
        fb = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
        fb.set_margin_start(20)
        for font in FONTS:
            chk = Gtk.CheckButton(label=font, sensitive=False)
            self.font_checks[font] = chk
            fb.pack_start(chk, False, False, 0)
        self.font_expander.add(fb)
        b2.pack_start(self.font_expander, False, False, 0)

        def on_fonts_toggled(button):
            active = button.get_active()
            self.font_expander.set_sensitive(active)
            if active:
                self.font_expander.set_expanded(True)
            else:
                for chk in self.font_checks.values():
                    chk.set_active(False)
                self.font_expander.set_expanded(False)
            for chk in self.font_checks.values():
                chk.set_sensitive(active)

        self.chk_fonts.connect("toggled", on_fonts_toggled)

        self.chk_scim = Gtk.CheckButton(label=self.tr("scim"))
        self.chk_scim.set_tooltip_text(self.tr("scim_tt"))
        b2.pack_start(self.chk_scim, False, False, 0)

        self.frame2 = add_frame("extras", b2)

        b3 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)

        self.l_kbdq = Gtk.Label(label=self.tr("kbd_q"), xalign=0.0, wrap=True)
        b3.pack_start(self.l_kbdq, False, False, 0)
        self.btn_kbd = Gtk.Button(label=self.tr("kbd_config"), halign=Gtk.Align.START)
        self.btn_kbd.connect("clicked",
                             lambda _: subprocess.Popen(["/opt/porteus-scripts/gtk-xkb-select"]))
        b3.pack_start(self.btn_kbd, False, False, 0)

        self.frame3 = add_frame("kbd_setup", b3)

        self.progress = Gtk.ProgressBar(visible=False)
        v.pack_start(self.progress, False, False, 0)
        self.status = Gtk.Label(xalign=0.0, visible=False)
        v.pack_start(self.status, False, False, 0)

        bb = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        bb.set_layout(Gtk.ButtonBoxStyle.END)
        bb.set_margin_top(6)
        self.btn_cancel = Gtk.Button(label=self.tr("cancel"))
        self.btn_cancel.connect("clicked", self.on_cancel)
        bb.pack_end(self.btn_cancel, False, False, 0)
        self.btn_ok = Gtk.Button(label=self.tr("ok"))
        self.btn_ok.get_style_context().add_class("suggested-action")
        self.btn_ok.connect("clicked", self.on_apply)
        bb.pack_end(self.btn_ok, False, False, 0)
        v.pack_end(bb, False, False, 0)

    def _detect_de(self):
        xdg = os.environ.get('XDG_CURRENT_DESKTOP', '').lower()
        ds = os.environ.get('DESKTOP_SESSION', '').lower()
        gdm = os.environ.get('GDMSESSION', '').lower()
        for de in DESKTOPS:
            if de in xdg or de in ds or de in gdm:
                return DESKTOPS.index(de)
        if 'x-cinnamon' in xdg:
            return DESKTOPS.index('cinnamon')
        if 'plasma' in xdg:
            return DESKTOPS.index('kde')
        return None

    def on_ui_lang_changed(self, combo):
        new_code = list(self.lang_map.keys())[combo.get_active()]
        if new_code != self.lang:
            self.lang = new_code
            self._save_ui_lang(new_code)
            self._retranslate()

    def _retranslate(self):
        self.set_title(self.tr("title"))
        self.header_lbl.set_markup(f'<span size="x-large" weight="bold">{self.tr("header")}</span>')
        self.ilbl.set_text(self.tr("iface_lang"))
        self.l_syslang.set_markup(f'<b>{self.tr("choose_lang")}</b>')
        self.l_choose_de.set_markup(f'<b>{self.tr("choose_de")}</b>')
        self.chk_dev.set_label(self.tr("devel_locales"))
        self.chk_fonts.set_label(self.tr("nonlatin_font"))
        self.chk_fonts.set_tooltip_text(self.tr("nonlatin_tt"))
        self.font_expander.set_label(self.tr("font_list"))
        self.chk_scim.set_label(self.tr("scim"))
        self.chk_scim.set_tooltip_text(self.tr("scim_tt"))
        self.l_kbdq.set_text(self.tr("kbd_q"))
        self.btn_kbd.set_label(self.tr("kbd_config"))
        self.btn_cancel.set_label(self.tr("cancel"))
        self.btn_ok.set_label(self.tr("ok"))
        self.refresh_btn.set_tooltip_text(self.tr("refresh"))

        self.frame1.set_label(self.tr("sysenc"))
        self.frame2.set_label(self.tr("extras"))
        self.frame3.set_label(self.tr("kbd_setup"))

        if self.available_langs:
            old = self.combo_lang.get_active_text()
            self.combo_lang.remove_all()
            idx = 0
            for i, l in enumerate(self.available_langs):
                self.combo_lang.append_text(l)
                if l == old:
                    idx = i
            self.combo_lang.set_active(idx)

        if self._status_key is not None:
            self._set_status(self._status_key, *self._status_args)

    def _set_status(self, key, *args):
        self._status_key = key
        self._status_args = args
        if key is None:
            GLib.idle_add(lambda: self.status.hide())
            return
        t = self.tr(key, *args)
        GLib.idle_add(lambda: (self.status.set_text(t), self.status.show(), False))

    def load_modules(self):
        self._set_status("loading")
        threading.Thread(target=self._fetch, daemon=True).start()

    def _fetch(self):
        try:
            with urllib.request.urlopen(self.base_url + "/", timeout=10) as r:
                self.modules = re.findall(r'href="([^"]+\.xzm)"', r.read().decode())
            langs = sorted(set(m.split("-")[0] for m in self.modules if "_core_locales.xzm" in m))
            GLib.idle_add(self._fill_langs, langs)
        except Exception:
            GLib.idle_add(self._error, self.tr("net_required"))

    def _fill_langs(self, langs):
        self.available_langs = langs
        self.combo_lang.remove_all()
        cur = os.environ.get('LANG', 'en_US').split('_')[0]
        idx = 0
        for i, l in enumerate(langs):
            self.combo_lang.append_text(l)
            if cur == l:
                idx = i
        if langs:
            self.combo_lang.set_active(idx)
        self.status.hide()
        self._status_key = None

    def _error(self, msg):
        self.status.hide()
        d = Gtk.MessageDialog(
            transient_for=self, flags=0,
            message_type=Gtk.MessageType.WARNING,
            buttons=Gtk.ButtonsType.OK,
            text=self.tr("error")
        )
        d.format_secondary_text(msg)
        d.run()
        d.destroy()
        self.btn_ok.set_sensitive(True)
        self.processing = False

    def on_apply(self, btn):
        if self.processing:
            return
        lang = self.combo_lang.get_active_text()
        if not lang:
            self._error(self.tr("select_first"))
            return
        self.processing = True
        self.cancel_requested = False
        self.btn_ok.set_sensitive(False)
        self.btn_cancel.set_sensitive(True)
        self.progress.show()
        threading.Thread(target=self._download, args=(lang,), daemon=True).start()

    def on_cancel(self, btn):
        if self.processing:
            self.cancel_requested = True
            btn.set_sensitive(False)
        else:
            Gtk.main_quit()

    def _download(self, lang_code):
        de = self.combo_de.get_active_text()
        urls = []

        def find(pat):
            for m in self.modules:
                if pat in m:
                    return m
            return None

        g = find("glibc-i18n")
        if g:
            urls.append(f"{self.base_url}/{g}")
        c = find("console-cyrillic")
        if c:
            urls.append(f"{self.base_url}/{c}")

        urls += [
            f"{self.base_url}/{lang_code}-core_locales.xzm",
            f"{self.base_url}/{lang_code}-{de}_locales.xzm"
        ]

        if self.chk_dev.get_active():
            d = find(f"{lang_code}-devel_locales")
            if d:
                urls.append(f"{self.base_url}/{d}")

        if self.chk_scim.get_active():
            for m in self.modules:
                if "scim" in m and self.arch in m:
                    urls.append(f"{self.base_url}/{m}")

        if self.chk_fonts.get_active():
            for font, chk in self.font_checks.items():
                if chk.get_active():
                    for m in self.modules:
                        if font in m:
                            urls.append(f"{self.base_url}/{m}")

        if not urls:
            GLib.idle_add(self._error, self.tr("no_modules"))
            return

        tmp = Path("/tmp/lang_build")
        if tmp.exists():
            shutil.rmtree(tmp)
        tmp.mkdir(parents=True)

        total_bytes = 0
        file_sizes = []
        for u in urls:
            try:
                req = urllib.request.Request(u, method='HEAD')
                with urllib.request.urlopen(req) as resp:
                    length = int(resp.getheader('content-length', '0'))
                    total_bytes += length
                    file_sizes.append((u, u.split('/')[-1], length))
            except Exception:
                file_sizes.append((u, u.split('/')[-1], 0))

        total_mb = total_bytes / (1024 * 1024) if total_bytes > 0 else 0
        if total_mb > 0:
            GLib.idle_add(lambda: self._set_status("total_size", total_mb))
        else:
            GLib.idle_add(lambda: self._set_status("downloading"))

        downloaded_bytes = 0
        for url, filename, size in file_sizes:
            if self.cancel_requested:
                break
            dest = tmp / filename
            try:
                req = urllib.request.Request(url)
                with urllib.request.urlopen(req) as resp:
                    with open(dest, 'wb') as f:
                        while True:
                            if self.cancel_requested:
                                break
                            chunk = resp.read(8192)
                            if not chunk:
                                break
                            f.write(chunk)
                            downloaded_bytes += len(chunk)
                            if total_bytes > 0:
                                GLib.idle_add(
                                    lambda: self.progress.set_fraction(downloaded_bytes / total_bytes)
                                )
                                down_mb = downloaded_bytes / (1024 * 1024)
                                GLib.idle_add(
                                    lambda: self.status.set_text(
                                        self.tr("downloaded", down_mb, total_mb) + f" ({filename})"
                                    )
                                )
                            else:
                                GLib.idle_add(lambda: self.progress.pulse())
                if self.cancel_requested:
                    break
            except Exception as e:
                print(f"Failed {url}: {e}")

        if self.cancel_requested:
            GLib.idle_add(
                lambda: shutil.rmtree(tmp, ignore_errors=True) or
                        self.progress.hide() or
                        self._set_status("cancelled")
            )
            GLib.idle_add(
                lambda: (self.btn_ok.set_sensitive(True),
                         self.btn_cancel.set_sensitive(True),
                         setattr(self, 'processing', False))
            )
            return

        (tmp / "etc" / "profile.d").mkdir(parents=True, exist_ok=True)
        (tmp / "etc" / "profile.d" / "lang.sh").write_text(
            f"#!/bin/bash\nexport LANG={lang_code}.utf8\nexport LC_COLLATE=C\n"
        )

        GLib.idle_add(lambda: self._set_status("creating_module"))
        try:
            out = tmp / f"{lang_code}-locales-files.xzm"
            subprocess.run(["dir2xzm", str(tmp), str(out)], check=True, capture_output=True)
            out_dir = Path("/tmp/language-files")
            out_dir.mkdir(exist_ok=True)
            shutil.move(str(out), str(out_dir / out.name))
            shutil.rmtree(tmp, ignore_errors=True)
            GLib.idle_add(self._success)
        except Exception as e:
            shutil.rmtree(tmp, ignore_errors=True)
            GLib.idle_add(self._error, f"dir2xzm failed: {e}")

    def _success(self):
        self.progress.hide()
        self._set_status("success")
        try:
            subprocess.run(
                ["notify-send", self.tr("header"), self.tr("module_created")],
                timeout=2
            )
        except Exception:
            pass
        d = Gtk.MessageDialog(
            transient_for=self, flags=0,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.OK,
            text=self.tr("success")
        )
        d.format_secondary_text(f"{self.tr('files_info')}\n{self.tr('move_info')}")
        d.run()
        d.destroy()
        Gtk.main_quit()


if __name__ == "__main__":
    if os.geteuid() != 0:
        d = Gtk.MessageDialog(
            message_type=Gtk.MessageType.ERROR,
            buttons=Gtk.ButtonsType.OK,
            text=_("root_required")
        )
        d.format_secondary_text(_("root_msg"))
        d.run()
        d.destroy()
        sys.exit(1)

    win = App()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()
