From f6b98b8ec3afad8e29aaa686406b66ca4591e6c5 Mon Sep 17 00:00:00 2001 From: ochenstarik-ui Date: Wed, 15 Jul 2026 14:04:01 +0700 Subject: [PATCH] Add multilingual dialog to step one --- README.md | 6 +- ochenstarik-server-1.sh | 418 +++++++++++++++++++++++++++++----- tests/test-step1-selection.sh | 8 + 3 files changed, 376 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 699d151..9301c4e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ sudo ./ochenstarik-server-install.sh | Файл | Назначение | Можно запускать отдельно | | --- | --- | --- | | `ochenstarik-server-install.sh` | Единый пошаговый мастер: установить, пропустить или завершить на каждом этапе | Да; рекомендуемый способ полной настройки | -| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, язык терминала и наборы программ, создаёт swap-файл 2 ГБ | Да | +| `ochenstarik-server-1.sh` | Предлагает выбрать язык диалога, часовой пояс, язык терминала и наборы программ, создаёт swap-файл 2 ГБ | Да | | `ochenstarik-server-2.sh` | Предлагает будущий SSH-порт и IPv4/IPv6, обновляет систему, ставит обязательные пакеты и настраивает UFW | Да | | `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 | | `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 | @@ -115,7 +115,9 @@ bash -n ./ИМЯ-СКРИПТА.sh Файл: `ochenstarik-server-1.sh`. -Сначала показывает нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется. +Самый первый запрос выбирает язык диалога установщика: English, Русский, Español, Deutsch, Français, Português, 中文, 日本語, العربية или हिन्दी. Начальное меню выводится на английском, а пустой ввод оставляет English. Этот выбор меняет вопросы и сообщения скрипта, но не системную локаль сервера. + +Затем показывается нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется. После выбора времени скрипт показывает меню языков терминала: оставить текущий, русский, английский, немецкий, французский, испанский, итальянский, португальский, польский, украинский, турецкий, китайский, японский, корейский либо выбрать другую локаль из полного системного списка. Выбранный язык применяется к новым терминальным сессиям. Исходный `/etc/default/locale` сохраняется для последующего восстановления. diff --git a/ochenstarik-server-1.sh b/ochenstarik-server-1.sh index 51fd1ab..d3be6c5 100644 --- a/ochenstarik-server-1.sh +++ b/ochenstarik-server-1.sh @@ -14,28 +14,28 @@ readonly LEGACY_LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent readonly LEGACY_LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list" readonly PROGRAM_PACKAGES="${CONFIG_DIR}/step1-installed-packages.list" -readonly -a PROGRAM_GROUP_TITLES=( - "Терминал и повседневная работа" - "Диагностика сети" - "Мониторинг системы и дисков" - "Безопасность и обслуживание Ubuntu" - "Разработка и автоматизация" - "Архивы, таблицы и локальные данные" - "Документы, PDF и OCR" - "Изображения, аудио и видео" - "Резервное копирование и синхронизация" +declare -a PROGRAM_GROUP_TITLES=( + "Terminal and everyday tools" + "Network diagnostics" + "System and disk monitoring" + "Ubuntu security and maintenance" + "Development and automation" + "Archives, tables, and local data" + "Documents, PDF, and OCR" + "Images, audio, and video" + "Backup and synchronization" ) -readonly -a PROGRAM_GROUP_DESCRIPTIONS=( - "git, wget, curl, jq/yq, ripgrep/fd/fzf, lnav, редакторы, mc, tmux, htop/btop, ncdu, rsync" - "DNS/ping/MTR/traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool и socat" - "iotop, sysstat, SMART, датчики, atop, nethogs, vnStat и инструменты процессов" - "auditd, AppArmor tools, Lynis, needrestart, автоматические обновления, debsums, ACL/xattr" - "компилятор, ShellCheck, Python pip/venv и pipx" - "7-Zip/ZIP/RAR, SQLite, csvkit, клиенты PostgreSQL и Redis" +declare -a PROGRAM_GROUP_DESCRIPTIONS=( + "git, wget, curl, jq/yq, ripgrep/fd/fzf, lnav, editors, mc, tmux, htop/btop, ncdu, rsync" + "DNS/ping/MTR/traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool, socat" + "iotop, sysstat, SMART, sensors, atop, nethogs, vnStat, process tools" + "auditd, AppArmor tools, Lynis, needrestart, automatic updates, debsums, ACL/xattr" + "compiler, ShellCheck, Python pip/venv, pipx" + "7-Zip/ZIP/RAR, SQLite, csvkit, PostgreSQL and Redis clients" "Poppler, qpdf, Ghostscript, OCRmyPDF/Tesseract, LibreOffice, Pandoc, antiword/catdoc" - "ImageMagick, ExifTool, WebP, FFmpeg и MediaInfo" - "restic, BorgBackup и rclone" + "ImageMagick, ExifTool, WebP, FFmpeg, MediaInfo" + "restic, BorgBackup, rclone" ) readonly -a PROGRAM_GROUP_PACKAGES=( @@ -51,6 +51,315 @@ readonly -a PROGRAM_GROUP_PACKAGES=( ) APT_UPDATED=no +UI_LANG=en + +declare -A UI_TEXT=() + +load_ui_text() { + UI_TEXT=( + [input_interrupted]="Input was interrupted" + [invalid_number]="Enter a number from %s to %s" + [no_options]="No options are available for: %s" + [select_option]="Enter the option number: " + [select_region]="Select a region:" + [select_timezone]="Select a timezone in %s:" + [timezone_heading]="Select the server timezone (current: %s):" + [other_timezone]="Other timezone (select by region)" + [unknown_timezone]="Unknown timezone: %s" + [timezone_set]="Timezone set to %s" + [terminal_heading]="Select the language for new terminal sessions (current: %s):" + [terminal_keep]="Keep the current terminal language" + [terminal_other]="Other locale from the full list" + [terminal_prompt]="Language number [1 — keep current]: " + [terminal_unchanged]="Terminal language was not changed" + [terminal_full_list]="Select a locale from the full list:" + [locale_package_missing]="Language package is unavailable and will be skipped: %s" + [locale_selected]="Locale %s selected. It will apply at the next login" + [program_heading]="Select program groups to install:" + [program_none]="Do not install additional programs" + [program_example]="Enter numbers separated by spaces, for example: 1 2 4 9." + [program_prompt]="Selection [Enter — install all groups]: " + [program_skipped]="Installation of additional programs was skipped" + [program_bad_number]="Invalid group number: %s" + [program_out_of_range]="Group number is out of range: %s" + [program_selected]="Selected group: %s" + [package_missing]="Package is unavailable in the configured repositories and will be skipped: %s" + [no_packages]="None of the selected packages were found" + [programs_installed]="Selected programs have been installed" + [apt_update]="Updating the package list" + [root_required]="Run this script as root" + [required_missing]="Required command not found: %s" + [swap_configuring]="Configuring %s swap file" + [setup_complete]="Server memory setup is complete" + [next_script]="Now run ochenstarik-server-2.sh as root." + ) + + case "$UI_LANG" in + ru) UI_TEXT+=( + [input_interrupted]="Ввод прерван" [invalid_number]="Введите номер от %s до %s" + [select_option]="Введите номер варианта: " [select_region]="Выберите регион:" + [select_timezone]="Выберите часовой пояс в регионе %s:" + [timezone_heading]="Выберите часовой пояс сервера (сейчас: %s):" + [other_timezone]="Другой часовой пояс (выбор по региону)" + [unknown_timezone]="Неизвестный часовой пояс: %s" [timezone_set]="Установлен часовой пояс %s" + [terminal_heading]="Выберите язык новых терминальных сессий (сейчас: %s):" + [terminal_keep]="Не менять язык терминала" [terminal_other]="Другая локаль из полного списка" + [terminal_prompt]="Номер языка [1 — не менять]: " [terminal_unchanged]="Язык терминала не изменён" + [terminal_full_list]="Выберите локаль из полного списка:" + [locale_package_missing]="Языковой пакет недоступен и будет пропущен: %s" + [locale_selected]="Выбрана локаль %s. Она применится при следующем входе" + [program_heading]="Выберите наборы программ для установки:" + [program_none]="Не устанавливать дополнительные программы" + [program_example]="Введите номера через пробел, например: 1 2 4 9." + [program_prompt]="Выбор [Enter — установить все наборы]: " + [program_skipped]="Установка дополнительных программ пропущена" + [program_bad_number]="Некорректный номер набора: %s" + [program_out_of_range]="Номер набора вне диапазона: %s" + [program_selected]="Выбран набор: %s" + [package_missing]="Пакет отсутствует в подключённых репозиториях и будет пропущен: %s" + [no_packages]="Ни один выбранный пакет не найден" + [programs_installed]="Выбранные программы установлены" [apt_update]="Обновление списка пакетов" + [root_required]="Запустите этот скрипт от root" + [required_missing]="Не найдена обязательная команда: %s" + [swap_configuring]="Настройка файла подкачки размером %s" + [setup_complete]="Настройка памяти сервера завершена" + [next_script]="Теперь запустите ochenstarik-server-2.sh от root." + ) ;; + es) UI_TEXT+=( + [input_interrupted]="Entrada interrumpida" [invalid_number]="Introduzca un número del %s al %s" + [select_option]="Introduzca el número de opción: " [select_region]="Seleccione una región:" + [select_timezone]="Seleccione una zona horaria en %s:" + [timezone_heading]="Seleccione la zona horaria del servidor (actual: %s):" + [other_timezone]="Otra zona horaria (seleccionar por región)" [timezone_set]="Zona horaria configurada: %s" + [terminal_heading]="Seleccione el idioma para nuevas sesiones de terminal (actual: %s):" + [terminal_keep]="Mantener el idioma actual" [terminal_other]="Otra configuración regional de la lista completa" + [terminal_prompt]="Número de idioma [1 — mantener]: " [terminal_unchanged]="El idioma del terminal no se cambió" + [terminal_full_list]="Seleccione una configuración regional de la lista completa:" + [program_heading]="Seleccione los grupos de programas que desea instalar:" + [program_none]="No instalar programas adicionales" + [program_example]="Introduzca números separados por espacios, por ejemplo: 1 2 4 9." + [program_prompt]="Selección [Enter — instalar todos]: " [program_skipped]="Se omitió la instalación de programas adicionales" + [program_bad_number]="Número de grupo no válido: %s" [program_out_of_range]="Número de grupo fuera de rango: %s" + [program_selected]="Grupo seleccionado: %s" [programs_installed]="Los programas seleccionados se instalaron" + [apt_update]="Actualizando la lista de paquetes" [root_required]="Ejecute este script como root" + [swap_configuring]="Configurando un archivo swap de %s" [setup_complete]="Configuración de memoria completada" + [next_script]="Ahora ejecute ochenstarik-server-2.sh como root." + ) ;; + de) UI_TEXT+=( + [input_interrupted]="Eingabe wurde unterbrochen" [invalid_number]="Geben Sie eine Zahl von %s bis %s ein" + [select_option]="Optionsnummer eingeben: " [select_region]="Region auswählen:" + [select_timezone]="Zeitzone in %s auswählen:" + [timezone_heading]="Server-Zeitzone auswählen (aktuell: %s):" + [other_timezone]="Andere Zeitzone (nach Region auswählen)" [timezone_set]="Zeitzone auf %s gesetzt" + [terminal_heading]="Sprache für neue Terminalsitzungen auswählen (aktuell: %s):" + [terminal_keep]="Aktuelle Terminalsprache beibehalten" [terminal_other]="Andere Locale aus der vollständigen Liste" + [terminal_prompt]="Sprachnummer [1 — beibehalten]: " [terminal_unchanged]="Terminalsprache wurde nicht geändert" + [terminal_full_list]="Locale aus der vollständigen Liste auswählen:" + [program_heading]="Zu installierende Programmgruppen auswählen:" + [program_none]="Keine zusätzlichen Programme installieren" + [program_example]="Nummern durch Leerzeichen getrennt eingeben, zum Beispiel: 1 2 4 9." + [program_prompt]="Auswahl [Enter — alle installieren]: " [program_skipped]="Installation zusätzlicher Programme übersprungen" + [program_bad_number]="Ungültige Gruppennummer: %s" [program_out_of_range]="Gruppennummer außerhalb des Bereichs: %s" + [program_selected]="Ausgewählte Gruppe: %s" [programs_installed]="Ausgewählte Programme wurden installiert" + [apt_update]="Paketliste wird aktualisiert" [root_required]="Führen Sie dieses Skript als root aus" + [swap_configuring]="%s-Swap-Datei wird eingerichtet" [setup_complete]="Speichereinrichtung abgeschlossen" + [next_script]="Führen Sie jetzt ochenstarik-server-2.sh als root aus." + ) ;; + fr) UI_TEXT+=( + [input_interrupted]="Saisie interrompue" [invalid_number]="Saisissez un nombre de %s à %s" + [select_option]="Saisissez le numéro de l’option : " [select_region]="Sélectionnez une région :" + [select_timezone]="Sélectionnez un fuseau horaire dans %s :" + [timezone_heading]="Sélectionnez le fuseau horaire du serveur (actuel : %s) :" + [other_timezone]="Autre fuseau horaire (sélection par région)" [timezone_set]="Fuseau horaire défini sur %s" + [terminal_heading]="Sélectionnez la langue des nouvelles sessions (actuelle : %s) :" + [terminal_keep]="Conserver la langue actuelle" [terminal_other]="Autre locale de la liste complète" + [terminal_prompt]="Numéro de langue [1 — conserver] : " [terminal_unchanged]="La langue du terminal n’a pas été modifiée" + [terminal_full_list]="Sélectionnez une locale dans la liste complète :" + [program_heading]="Sélectionnez les groupes de programmes à installer :" + [program_none]="Ne pas installer de programmes supplémentaires" + [program_example]="Saisissez les numéros séparés par des espaces, par exemple : 1 2 4 9." + [program_prompt]="Sélection [Entrée — tout installer] : " [program_skipped]="Installation des programmes supplémentaires ignorée" + [program_bad_number]="Numéro de groupe incorrect : %s" [program_out_of_range]="Numéro de groupe hors plage : %s" + [program_selected]="Groupe sélectionné : %s" [programs_installed]="Les programmes sélectionnés ont été installés" + [apt_update]="Mise à jour de la liste des paquets" [root_required]="Exécutez ce script en tant que root" + [swap_configuring]="Configuration d’un fichier swap de %s" [setup_complete]="Configuration de la mémoire terminée" + [next_script]="Exécutez maintenant ochenstarik-server-2.sh en tant que root." + ) ;; + pt) UI_TEXT+=( + [input_interrupted]="Entrada interrompida" [invalid_number]="Digite um número de %s a %s" + [select_option]="Digite o número da opção: " [select_region]="Selecione uma região:" + [select_timezone]="Selecione um fuso horário em %s:" + [timezone_heading]="Selecione o fuso horário do servidor (atual: %s):" + [other_timezone]="Outro fuso horário (selecionar por região)" [timezone_set]="Fuso horário definido como %s" + [terminal_heading]="Selecione o idioma das novas sessões de terminal (atual: %s):" + [terminal_keep]="Manter o idioma atual" [terminal_other]="Outra localidade da lista completa" + [terminal_prompt]="Número do idioma [1 — manter]: " [terminal_unchanged]="O idioma do terminal não foi alterado" + [terminal_full_list]="Selecione uma localidade na lista completa:" + [program_heading]="Selecione os grupos de programas para instalar:" + [program_none]="Não instalar programas adicionais" + [program_example]="Digite números separados por espaços, por exemplo: 1 2 4 9." + [program_prompt]="Seleção [Enter — instalar todos]: " [program_skipped]="Instalação de programas adicionais ignorada" + [program_bad_number]="Número de grupo inválido: %s" [program_out_of_range]="Número de grupo fora do intervalo: %s" + [program_selected]="Grupo selecionado: %s" [programs_installed]="Os programas selecionados foram instalados" + [apt_update]="Atualizando a lista de pacotes" [root_required]="Execute este script como root" + [swap_configuring]="Configurando arquivo swap de %s" [setup_complete]="Configuração de memória concluída" + [next_script]="Agora execute ochenstarik-server-2.sh como root." + ) ;; + zh) UI_TEXT+=( + [input_interrupted]="输入已中断" [invalid_number]="请输入 %s 到 %s 之间的数字" + [select_option]="请输入选项编号:" [select_region]="请选择地区:" [select_timezone]="请选择 %s 的时区:" + [timezone_heading]="请选择服务器时区(当前:%s):" [other_timezone]="其他时区(按地区选择)" [timezone_set]="时区已设置为 %s" + [terminal_heading]="请选择新终端会话的语言(当前:%s):" [terminal_keep]="保留当前终端语言" + [terminal_other]="从完整列表选择其他区域设置" [terminal_prompt]="语言编号 [1 — 保留当前设置]:" + [terminal_unchanged]="终端语言未更改" [terminal_full_list]="请从完整列表选择区域设置:" + [program_heading]="请选择要安装的程序组:" [program_none]="不安装其他程序" + [program_example]="请输入用空格分隔的编号,例如:1 2 4 9。" [program_prompt]="选择 [Enter — 全部安装]:" + [program_skipped]="已跳过其他程序的安装" [program_bad_number]="无效的程序组编号:%s" + [program_out_of_range]="程序组编号超出范围:%s" [program_selected]="已选择程序组:%s" + [programs_installed]="已安装所选程序" [apt_update]="正在更新软件包列表" [root_required]="请以 root 身份运行此脚本" + [swap_configuring]="正在配置 %s 交换文件" [setup_complete]="服务器内存设置完成" + [next_script]="现在请以 root 身份运行 ochenstarik-server-2.sh。" + ) ;; + ja) UI_TEXT+=( + [input_interrupted]="入力が中断されました" [invalid_number]="%s から %s の番号を入力してください" + [select_option]="オプション番号を入力してください: " [select_region]="地域を選択してください:" + [select_timezone]="%s のタイムゾーンを選択してください:" + [timezone_heading]="サーバーのタイムゾーンを選択してください(現在: %s):" + [other_timezone]="その他のタイムゾーン(地域から選択)" [timezone_set]="タイムゾーンを %s に設定しました" + [terminal_heading]="新しいターミナルセッションの言語を選択してください(現在: %s):" + [terminal_keep]="現在の言語を維持" [terminal_other]="完全なリストから別のロケールを選択" + [terminal_prompt]="言語番号 [1 — 変更しない]: " [terminal_unchanged]="ターミナルの言語は変更されませんでした" + [terminal_full_list]="完全なリストからロケールを選択してください:" + [program_heading]="インストールするプログラムグループを選択してください:" + [program_none]="追加プログラムをインストールしない" + [program_example]="番号を空白で区切って入力してください。例: 1 2 4 9。" + [program_prompt]="選択 [Enter — すべてインストール]: " [program_skipped]="追加プログラムのインストールをスキップしました" + [program_bad_number]="無効なグループ番号: %s" [program_out_of_range]="グループ番号が範囲外です: %s" + [program_selected]="選択したグループ: %s" [programs_installed]="選択したプログラムをインストールしました" + [apt_update]="パッケージリストを更新しています" [root_required]="このスクリプトを root で実行してください" + [swap_configuring]="%s のスワップファイルを設定しています" [setup_complete]="メモリ設定が完了しました" + [next_script]="次に ochenstarik-server-2.sh を root で実行してください。" + ) ;; + ar) UI_TEXT+=( + [input_interrupted]="تم إيقاف الإدخال" [invalid_number]="أدخل رقماً من %s إلى %s" + [select_option]="أدخل رقم الخيار: " [select_region]="اختر المنطقة:" [select_timezone]="اختر منطقة زمنية في %s:" + [timezone_heading]="اختر المنطقة الزمنية للخادم (الحالية: %s):" [other_timezone]="منطقة زمنية أخرى (حسب المنطقة)" + [timezone_set]="تم ضبط المنطقة الزمنية على %s" [terminal_heading]="اختر لغة جلسات الطرفية الجديدة (الحالية: %s):" + [terminal_keep]="الاحتفاظ باللغة الحالية" [terminal_other]="إعداد محلي آخر من القائمة الكاملة" + [terminal_prompt]="رقم اللغة [1 — الاحتفاظ]: " [terminal_unchanged]="لم تتغير لغة الطرفية" + [terminal_full_list]="اختر إعداداً محلياً من القائمة الكاملة:" + [program_heading]="اختر مجموعات البرامج المراد تثبيتها:" [program_none]="عدم تثبيت برامج إضافية" + [program_example]="أدخل الأرقام مفصولة بمسافات، مثال: 1 2 4 9." [program_prompt]="الاختيار [Enter — تثبيت الكل]: " + [program_skipped]="تم تخطي تثبيت البرامج الإضافية" [program_bad_number]="رقم مجموعة غير صالح: %s" + [program_out_of_range]="رقم المجموعة خارج النطاق: %s" [program_selected]="المجموعة المحددة: %s" + [programs_installed]="تم تثبيت البرامج المحددة" [apt_update]="جارٍ تحديث قائمة الحزم" + [root_required]="شغّل هذا البرنامج النصي بصلاحية root" [swap_configuring]="جارٍ إعداد ملف swap بحجم %s" + [setup_complete]="اكتمل إعداد ذاكرة الخادم" [next_script]="شغّل الآن ochenstarik-server-2.sh بصلاحية root." + ) ;; + hi) UI_TEXT+=( + [input_interrupted]="इनपुट बाधित हुआ" [invalid_number]="%s से %s तक की संख्या दर्ज करें" + [select_option]="विकल्प संख्या दर्ज करें: " [select_region]="क्षेत्र चुनें:" [select_timezone]="%s में समय क्षेत्र चुनें:" + [timezone_heading]="सर्वर का समय क्षेत्र चुनें (वर्तमान: %s):" [other_timezone]="अन्य समय क्षेत्र (क्षेत्र के अनुसार)" + [timezone_set]="समय क्षेत्र %s पर सेट किया गया" [terminal_heading]="नई टर्मिनल सत्रों की भाषा चुनें (वर्तमान: %s):" + [terminal_keep]="वर्तमान भाषा बनाए रखें" [terminal_other]="पूरी सूची से अन्य लोकेल" + [terminal_prompt]="भाषा संख्या [1 — वर्तमान रखें]: " [terminal_unchanged]="टर्मिनल भाषा नहीं बदली गई" + [terminal_full_list]="पूरी सूची से लोकेल चुनें:" + [program_heading]="इंस्टॉल करने के लिए प्रोग्राम समूह चुनें:" [program_none]="अतिरिक्त प्रोग्राम इंस्टॉल न करें" + [program_example]="खाली स्थान से अलग संख्याएँ दर्ज करें, उदाहरण: 1 2 4 9।" [program_prompt]="चयन [Enter — सभी इंस्टॉल करें]: " + [program_skipped]="अतिरिक्त प्रोग्राम की स्थापना छोड़ दी गई" [program_bad_number]="अमान्य समूह संख्या: %s" + [program_out_of_range]="समूह संख्या सीमा से बाहर है: %s" [program_selected]="चयनित समूह: %s" + [programs_installed]="चयनित प्रोग्राम इंस्टॉल हो गए" [apt_update]="पैकेज सूची अपडेट की जा रही है" + [root_required]="इस स्क्रिप्ट को root के रूप में चलाएँ" [swap_configuring]="%s swap फ़ाइल कॉन्फ़िगर की जा रही है" + [setup_complete]="सर्वर मेमोरी सेटअप पूरा हुआ" [next_script]="अब ochenstarik-server-2.sh को root के रूप में चलाएँ।" + ) ;; + esac +} + +msg() { + local key="$1" + shift + printf "${UI_TEXT[$key]}" "$@" +} + +localize_program_catalog() { + case "$UI_LANG" in + ru) PROGRAM_GROUP_TITLES=( + "Терминал и повседневная работа" "Диагностика сети" "Мониторинг системы и дисков" + "Безопасность и обслуживание Ubuntu" "Разработка и автоматизация" + "Архивы, таблицы и локальные данные" "Документы, PDF и OCR" + "Изображения, аудио и видео" "Резервное копирование и синхронизация" + ) ;; + es) PROGRAM_GROUP_TITLES=( + "Terminal y herramientas cotidianas" "Diagnóstico de red" "Supervisión del sistema y discos" + "Seguridad y mantenimiento de Ubuntu" "Desarrollo y automatización" + "Archivos, tablas y datos locales" "Documentos, PDF y OCR" + "Imágenes, audio y vídeo" "Copias de seguridad y sincronización" + ) ;; + de) PROGRAM_GROUP_TITLES=( + "Terminal und Alltagswerkzeuge" "Netzwerkdiagnose" "System- und Datenträgerüberwachung" + "Ubuntu-Sicherheit und Wartung" "Entwicklung und Automatisierung" + "Archive, Tabellen und lokale Daten" "Dokumente, PDF und OCR" + "Bilder, Audio und Video" "Sicherung und Synchronisierung" + ) ;; + fr) PROGRAM_GROUP_TITLES=( + "Terminal et outils quotidiens" "Diagnostic réseau" "Surveillance du système et des disques" + "Sécurité et maintenance d’Ubuntu" "Développement et automatisation" + "Archives, tableaux et données locales" "Documents, PDF et OCR" + "Images, audio et vidéo" "Sauvegarde et synchronisation" + ) ;; + pt) PROGRAM_GROUP_TITLES=( + "Terminal e ferramentas diárias" "Diagnóstico de rede" "Monitoramento do sistema e discos" + "Segurança e manutenção do Ubuntu" "Desenvolvimento e automação" + "Arquivos, tabelas e dados locais" "Documentos, PDF e OCR" + "Imagens, áudio e vídeo" "Backup e sincronização" + ) ;; + zh) PROGRAM_GROUP_TITLES=( + "终端与日常工具" "网络诊断" "系统与磁盘监控" "Ubuntu 安全与维护" "开发与自动化" + "压缩包、表格与本地数据" "文档、PDF 与 OCR" "图像、音频与视频" "备份与同步" + ) ;; + ja) PROGRAM_GROUP_TITLES=( + "ターミナルと日常ツール" "ネットワーク診断" "システムとディスクの監視" + "Ubuntu のセキュリティと保守" "開発と自動化" "アーカイブ、表、ローカルデータ" + "文書、PDF、OCR" "画像、音声、動画" "バックアップと同期" + ) ;; + ar) PROGRAM_GROUP_TITLES=( + "الطرفية والأدوات اليومية" "تشخيص الشبكة" "مراقبة النظام والأقراص" + "أمان Ubuntu وصيانته" "التطوير والأتمتة" "الأرشيفات والجداول والبيانات المحلية" + "المستندات وPDF وOCR" "الصور والصوت والفيديو" "النسخ الاحتياطي والمزامنة" + ) ;; + hi) PROGRAM_GROUP_TITLES=( + "टर्मिनल और दैनिक उपकरण" "नेटवर्क निदान" "सिस्टम और डिस्क निगरानी" + "Ubuntu सुरक्षा और रखरखाव" "विकास और स्वचालन" "अभिलेख, तालिकाएँ और स्थानीय डेटा" + "दस्तावेज़, PDF और OCR" "चित्र, ऑडियो और वीडियो" "बैकअप और सिंक्रोनाइज़ेशन" + ) ;; + esac +} + +choose_ui_language() { + local answer + printf '\nSelect the installer language / Выберите язык установщика:\n' + printf '%s\n' \ + ' 1) English' ' 2) Русский' ' 3) Español' ' 4) Deutsch' \ + ' 5) Français' ' 6) Português' ' 7) 中文' ' 8) 日本語' \ + ' 9) العربية' ' 10) हिन्दी' + while :; do + read -rp 'Language [1 — English]: ' answer || { printf '[x] Input was interrupted\n' >&2; exit 1; } + answer="${answer:-1}" + case "$answer" in + 1) UI_LANG=en ;; 2) UI_LANG=ru ;; 3) UI_LANG=es ;; 4) UI_LANG=de ;; + 5) UI_LANG=fr ;; 6) UI_LANG=pt ;; 7) UI_LANG=zh ;; 8) UI_LANG=ja ;; + 9) UI_LANG=ar ;; 10) UI_LANG=hi ;; + *) printf '[!] Enter a number from 1 to 10.\n' >&2; continue ;; + esac + break + done + load_ui_text + localize_program_catalog +} + +# English is active until the user answers the first prompt. This also keeps +# sourced helper functions usable by the automated tests. +load_ui_text log() { printf '[+] %s\n' "$*"; } warn() { printf '[!] %s\n' "$*" >&2; } @@ -64,14 +373,14 @@ ensure_package_tools() { local command_name for command_name in apt-get apt-cache dpkg-query; do command -v "$command_name" >/dev/null 2>&1 \ - || die "Required command not found: $command_name" + || die "$(msg required_missing "$command_name")" done } ensure_apt_updated() { [[ "$APT_UPDATED" == no ]] || return 0 ensure_package_tools - log "Обновление списка пакетов" + log "$(msg apt_update)" apt-get update APT_UPDATED=yes } @@ -131,7 +440,7 @@ choose_terminal_language() { local answer selected_locale selected_language primary_language package_name index local current_locale="${LANG:-не задана}" local -a titles=( - "Не менять язык терминала" + "$(msg terminal_keep)" "Русский — ru_RU.UTF-8" "English (US) — en_US.UTF-8" "Deutsch — de_DE.UTF-8" @@ -145,7 +454,7 @@ choose_terminal_language() { "简体中文 — zh_CN.UTF-8" "日本語 — ja_JP.UTF-8" "한국어 — ko_KR.UTF-8" - "Другая локаль из полного списка" + "$(msg terminal_other)" ) local -a locales=( "" ru_RU.UTF-8 en_US.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 es_ES.UTF-8 @@ -166,17 +475,17 @@ choose_terminal_language() { newly_installed=() extra_packages=() while :; do - printf '\nВыберите язык новых терминальных сессий (сейчас: %s):\n' "$current_locale" + printf '\n%s\n' "$(msg terminal_heading "$current_locale")" for index in "${!titles[@]}"; do printf ' %d) %s\n' "$((index + 1))" "${titles[index]}" done - read -rp 'Номер языка [1 — не менять]: ' answer || die "Ввод прерван" + read -rp "$(msg terminal_prompt)" answer || die "$(msg input_interrupted)" answer="${answer:-1}" [[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#titles[@]})) && break - warn "Введите номер от 1 до ${#titles[@]}" + warn "$(msg invalid_number 1 "${#titles[@]}")" done - ((answer == 1)) && { log "Язык терминала не изменён"; return 0; } + ((answer == 1)) && { log "$(msg terminal_unchanged)"; return 0; } ensure_apt_updated save_original_locale @@ -184,7 +493,7 @@ choose_terminal_language() { package_is_installed locales || newly_installed+=(locales) apt-get install -y locales mapfile -t supported_locales < <(awk '{print $1}' /usr/share/i18n/SUPPORTED | sort -u) - choose_numbered_option "Выберите локаль из полного списка:" selected_locale \ + choose_numbered_option "$(msg terminal_full_list)" selected_locale \ "${supported_locales[@]}" primary_language="${selected_locale%%_*}" selected_language="${selected_locale%%.*}:${primary_language}" @@ -202,7 +511,7 @@ choose_terminal_language() { available_packages+=("$package_name") package_is_installed "$package_name" || newly_installed+=("$package_name") else - warn "Языковой пакет недоступен и будет пропущен: $package_name" + warn "$(msg locale_package_missing "$package_name")" fi done @@ -213,7 +522,7 @@ choose_terminal_language() { LC_MESSAGES="$selected_locale" record_new_packages "$LOCALE_PACKAGES" "${newly_installed[@]}" - log "Выбрана локаль ${selected_locale}. Она применится при следующем входе" + log "$(msg locale_selected "$selected_locale")" } choose_and_install_programs() { @@ -221,14 +530,14 @@ choose_and_install_programs() { local -a selected_groups=() group_packages=() requested=() available=() newly_installed=() local -A selected=() seen=() - printf '\nВыберите наборы программ для установки:\n' + printf '\n%s\n' "$(msg program_heading)" for group_index in "${!PROGRAM_GROUP_TITLES[@]}"; do printf ' %d) %s\n %s\n' "$((group_index + 1))" \ "${PROGRAM_GROUP_TITLES[group_index]}" "${PROGRAM_GROUP_DESCRIPTIONS[group_index]}" done - printf ' 0) Не устанавливать дополнительные программы\n' - printf '\nВведите номера через пробел, например: 1 2 4 9.\n' - read -rp 'Выбор [Enter — установить все наборы]: ' answer || die "Ввод прерван" + printf ' 0) %s\n' "$(msg program_none)" + printf '\n%s\n' "$(msg program_example)" + read -rp "$(msg program_prompt)" answer || die "$(msg input_interrupted)" answer="${answer//,/ }" if [[ -z "${answer//[[:space:]]/}" ]]; then @@ -236,14 +545,14 @@ choose_and_install_programs() { selected_groups+=("$group_index") done elif [[ "$answer" =~ ^[[:space:]]*0[[:space:]]*$ ]]; then - log "Установка дополнительных программ пропущена" + log "$(msg program_skipped)" return 0 else for token in $answer; do [[ "$token" =~ ^[0-9]+$ ]] \ - || die "Некорректный номер набора: $token" + || die "$(msg program_bad_number "$token")" ((token >= 1 && token <= ${#PROGRAM_GROUP_TITLES[@]})) \ - || die "Номер набора вне диапазона: $token" + || die "$(msg program_out_of_range "$token")" group_index="$((token - 1))" [[ -n "${selected[$group_index]:-}" ]] || selected_groups+=("$group_index") selected[$group_index]=yes @@ -252,7 +561,7 @@ choose_and_install_programs() { ensure_apt_updated for group_index in "${selected_groups[@]}"; do - log "Выбран набор: ${PROGRAM_GROUP_TITLES[group_index]}" + log "$(msg program_selected "${PROGRAM_GROUP_TITLES[group_index]}")" read -r -a group_packages <<< "${PROGRAM_GROUP_PACKAGES[group_index]}" for package_name in "${group_packages[@]}"; do [[ -n "${seen[$package_name]:-}" ]] && continue @@ -266,15 +575,15 @@ choose_and_install_programs() { available+=("$package_name") package_is_installed "$package_name" || newly_installed+=("$package_name") else - warn "Пакет отсутствует в подключённых репозиториях и пропущен: $package_name" + warn "$(msg package_missing "$package_name")" fi done - ((${#available[@]} > 0)) || die "Ни один выбранный пакет не найден" + ((${#available[@]} > 0)) || die "$(msg no_packages)" export DEBIAN_FRONTEND=noninteractive apt-get install -y "${available[@]}" record_new_packages "$PROGRAM_PACKAGES" "${newly_installed[@]}" - log "Выбранные программы установлены" + log "$(msg programs_installed)" } choose_numbered_option() { @@ -282,18 +591,18 @@ choose_numbered_option() { shift 2 local -a options=("$@") - ((${#options[@]} > 0)) || die "No options are available for: ${prompt}" + ((${#options[@]} > 0)) || die "$(msg no_options "$prompt")" while :; do printf '\n%s\n' "$prompt" for index in "${!options[@]}"; do printf ' %d) %s\n' "$((index + 1))" "${options[index]}" done - read -rp "Enter the option number: " answer || die "Input was interrupted" + read -rp "$(msg select_option)" answer || die "$(msg input_interrupted)" if [[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#options[@]})); then printf -v "$result_variable" '%s' "${options[answer - 1]}" return 0 fi - warn "Enter a number from 1 to ${#options[@]}" + warn "$(msg invalid_number 1 "${#options[@]}")" done } @@ -302,15 +611,15 @@ choose_timezone_from_full_list() { local -a regions region_timezones mapfile -t regions < <(timedatectl list-timezones | awk -F/ 'NF > 1 && !seen[$1]++ { print $1 }') - choose_numbered_option "Select a region:" region "${regions[@]}" + choose_numbered_option "$(msg select_region)" region "${regions[@]}" mapfile -t region_timezones < <(timedatectl list-timezones | awk -F/ -v selected_region="$region" '$1 == selected_region') - choose_numbered_option "Select a timezone in ${region}:" selected_timezone "${region_timezones[@]}" + choose_numbered_option "$(msg select_timezone "$region")" selected_timezone "${region_timezones[@]}" printf -v "$result_variable" '%s' "$selected_timezone" } choose_timezone() { local current_timezone timezone candidate - local full_list_option="Other timezone (select by region)" + local full_list_option="$(msg other_timezone)" local -a timezone_options=() current_timezone="$(timedatectl show --property=Timezone --value 2>/dev/null || true)" @@ -337,26 +646,27 @@ choose_timezone() { done timezone_options+=("$full_list_option") - choose_numbered_option "Select the server timezone (current: ${current_timezone}):" timezone "${timezone_options[@]}" + choose_numbered_option "$(msg timezone_heading "$current_timezone")" timezone "${timezone_options[@]}" if [[ "$timezone" == "$full_list_option" ]]; then choose_timezone_from_full_list timezone fi - timedatectl list-timezones | grep -Fqx -- "$timezone" || die "Unknown timezone: $timezone" + timedatectl list-timezones | grep -Fqx -- "$timezone" || die "$(msg unknown_timezone "$timezone")" timedatectl set-timezone "$timezone" - log "Timezone set to ${timezone}" + log "$(msg timezone_set "$timezone")" } [[ "$EUID" -eq 0 ]] || die "Run this script as root" +choose_ui_language for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; do - command -v "$command_name" >/dev/null 2>&1 || die "Required command not found: $command_name" + command -v "$command_name" >/dev/null 2>&1 || die "$(msg required_missing "$command_name")" done choose_timezone choose_terminal_language choose_and_install_programs -log "Configuring ${SWAPSIZE} swap file" +log "$(msg swap_configuring "$SWAPSIZE")" if [[ ! -e "$SWAPFILE" ]]; then fallocate -l "$SWAPSIZE" "$SWAPFILE" chmod 600 "$SWAPFILE" @@ -385,7 +695,7 @@ EOF chmod 644 "$SYSCTL_FILE" sysctl -p "$SYSCTL_FILE" >/dev/null -log "Server memory setup is complete" +log "$(msg setup_complete)" printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)" swapon --show -printf '\nNow run ochenstarik-server-2.sh as root.\n' +printf '\n%s\n' "$(msg next_script)" diff --git a/tests/test-step1-selection.sh b/tests/test-step1-selection.sh index 59bae7c..fd24e74 100644 --- a/tests/test-step1-selection.sh +++ b/tests/test-step1-selection.sh @@ -32,6 +32,14 @@ contains_package() { return 1 } +declare -a EXPECTED_UI_LANGUAGES=(en ru es de fr pt zh ja ar hi) +for index in "${!EXPECTED_UI_LANGUAGES[@]}"; do + choose_ui_language <<< "$((index + 1))" >/dev/null + [[ "$UI_LANG" == "${EXPECTED_UI_LANGUAGES[index]}" ]] +done +choose_ui_language <<< '' >/dev/null +[[ "$UI_LANG" == en ]] + choose_and_install_programs <<< '1 3' >/dev/null contains_package git contains_package iotop -- 2.45.2