diff --git a/install.sh b/install.sh index 9cc5f4c..3c14a4b 100755 --- a/install.sh +++ b/install.sh @@ -41,6 +41,10 @@ source "$LIB_DIR/lib-profile.sh" source "$LIB_DIR/lib-args.sh" # shellcheck source=install/lib-menu.sh source "$LIB_DIR/lib-menu.sh" +# shellcheck source=install/lib-i18n.sh +source "$LIB_DIR/lib-i18n.sh" +# Загружаем английский словарь по умолчанию — welcome banner идёт до выбора языка. +i18n_load_default # shellcheck source=install/lib-onboarding.sh source "$LIB_DIR/lib-onboarding.sh" # shellcheck source=install/lib-plan.sh @@ -142,9 +146,13 @@ case "$PROFILE" in esac say "profile: $PROFILE" -# --- onboarding wizard (язык / транспорт / провайдер / модель / ключи) --- -# Запускается только в TTY и при отсутствии ~/.claude/.onboarded. +# --- welcome banner + onboarding wizard ---------------------------------- +# Banner всегда EN — пользователь ещё не выбрал язык. +# Wizard: TTY + нет ~/.claude/.onboarded + не задан KEISEI_SKIP_ONBOARD. # Skip: KEISEI_SKIP_ONBOARD=1 ./install.sh +if onboarding_should_run; then + i18n_print_welcome +fi onboarding_run # --- prerequisites ------------------------------------------------------- diff --git a/install/i18n/en.sh b/install/i18n/en.sh new file mode 100644 index 0000000..a5c4025 --- /dev/null +++ b/install/i18n/en.sh @@ -0,0 +1,33 @@ +# shellcheck shell=bash +# i18n/en.sh — English strings. Default before user picks language. + +# Welcome banner (always EN, shown before language picker). +STR_WELCOME_TITLE="KeiSeiKit · Exobrain installer" +STR_WELCOME_TAGLINE="Portable Rust agent substrate for AI coding tools" + +# Onboarding wizard steps +STR_ONBOARDING_INTRO="Onboarding wizard (5 steps)" +STR_PICK_LANGUAGE="Choose interface language:" +STR_PICK_TRANSPORT="Choose connection transport:" +STR_PICK_PROVIDER="Choose provider within" +STR_PICK_MODEL="Default model:" + +# Transport descriptions +STR_TR_DIRECT_API="Direct provider API (key)" +STR_TR_AWS_BEDROCK="AWS Bedrock (IAM/role)" +STR_TR_AZURE_OPENAI="Azure OpenAI (deployment+key)" +STR_TR_GOOGLE_VERTEX="Google Vertex AI (GCP)" +STR_TR_LOCAL="Local (Ollama/MLX/LMStudio)" +STR_TR_PROXY="Proxy (LiteLLM/OpenRouter)" +STR_TR_SUBSCRIPTION="OAuth subscription (ChatGPT)" + +# Auth collection +STR_AUTH_INTRO="Auth for" +STR_AUTH_PROMPT="Enter values (Enter — leave empty, fill later)." +STR_AUTH_CURRENT_HINT="(current: )" + +# Completion +STR_DONE_TITLE="Onboarding complete" +STR_DONE_CONFIG="config:" +STR_DONE_SECRETS="secrets:" +STR_DONE_NEXT="Next: run ./install.sh or restart this script to apply profile" diff --git a/install/i18n/ru.sh b/install/i18n/ru.sh new file mode 100644 index 0000000..cb45d03 --- /dev/null +++ b/install/i18n/ru.sh @@ -0,0 +1,33 @@ +# shellcheck shell=bash +# i18n/ru.sh — русские строки. Source'ится после выбора языка. +# Welcome-баннер всегда EN — на момент его показа выбор ещё не сделан. + +STR_WELCOME_TITLE="KeiSeiKit · Exobrain installer" +STR_WELCOME_TAGLINE="Portable Rust agent substrate for AI coding tools" + +# Шаги мастера +STR_ONBOARDING_INTRO="Мастер первичной настройки (5 шагов)" +STR_PICK_LANGUAGE="Выберите язык интерфейса:" +STR_PICK_TRANSPORT="Выберите способ подключения:" +STR_PICK_PROVIDER="Выберите провайдера в группе" +STR_PICK_MODEL="Модель по умолчанию:" + +# Описание транспортов +STR_TR_DIRECT_API="Прямой API провайдера (ключ)" +STR_TR_AWS_BEDROCK="AWS Bedrock (IAM/role)" +STR_TR_AZURE_OPENAI="Azure OpenAI (deployment+ключ)" +STR_TR_GOOGLE_VERTEX="Google Vertex AI (GCP)" +STR_TR_LOCAL="Локально (Ollama/MLX/LMStudio)" +STR_TR_PROXY="Прокси (LiteLLM/OpenRouter)" +STR_TR_SUBSCRIPTION="OAuth-подписка (ChatGPT)" + +# Сбор ключей +STR_AUTH_INTRO="Аутентификация для" +STR_AUTH_PROMPT="Введите значения (Enter — оставить пустым, заполните позже)." +STR_AUTH_CURRENT_HINT="(текущее: <скрыто>)" + +# Завершение +STR_DONE_TITLE="Первичная настройка завершена" +STR_DONE_CONFIG="конфиг:" +STR_DONE_SECRETS="секреты:" +STR_DONE_NEXT="Дальше: запустите ./install.sh или перезапустите этот скрипт для установки профиля" diff --git a/install/lib-i18n.sh b/install/lib-i18n.sh new file mode 100644 index 0000000..3ce29fe --- /dev/null +++ b/install/lib-i18n.sh @@ -0,0 +1,47 @@ +# shellcheck shell=bash +# lib-i18n.sh — лоадер локализаций. +# +# Контракт: +# 1. На старте всегда source install/i18n/en.sh — экран приветствия +# показывается ДО выбора языка пользователем. +# 2. После onboarding_pick_language вызывается i18n_load_lang "$lang" — +# перегружает строки выбранного словаря. +# 3. Любая строка отсутствующая в словаре — fallback на en.sh уже в +# памяти (мы не unset'им переменные, ru перезаписывает поверх). +# +# Используется install.sh и install/lib-onboarding.sh. + +# Корень i18n относительно LIB_DIR. +I18N_DIR="${LIB_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}/i18n" + +i18n_load_default() { + # shellcheck source=install/i18n/en.sh + source "$I18N_DIR/en.sh" +} + +i18n_load_lang() { + local lang="$1" + case "$lang" in + en) + i18n_load_default + ;; + ru) + i18n_load_default # base (fallback values) + # shellcheck source=install/i18n/ru.sh + [ -f "$I18N_DIR/ru.sh" ] && source "$I18N_DIR/ru.sh" + ;; + *) + i18n_load_default + ;; + esac +} + +# Welcome banner. Всегда EN. Запускается из install.sh до мастера. +i18n_print_welcome() { + echo "" + echo " ╔═══════════════════════════════════════════════════════╗" + echo " ║ ${STR_WELCOME_TITLE} ║" + echo " ║ ${STR_WELCOME_TAGLINE} ║" + echo " ╚═══════════════════════════════════════════════════════╝" + echo "" +} diff --git a/install/lib-onboarding.sh b/install/lib-onboarding.sh index 3be677b..e133a82 100644 --- a/install/lib-onboarding.sh +++ b/install/lib-onboarding.sh @@ -93,23 +93,28 @@ onboarding_models_for_provider() { # UI: язык # ─────────────────────────────────────────────────────────────────────── onboarding_pick_language() { + # На этом шаге язык ещё не выбран — экран на двух языках одновременно. if command -v whiptail >/dev/null 2>&1; then ONBOARDING_LANG=$(whiptail --title "KeiSei · Language / Язык" --radiolist \ "Choose interface language / Выберите язык:" 12 60 2 \ - "ru" "Русский" ON \ - "en" "English" OFF \ - 3>&1 1>&2 2>&3) || ONBOARDING_LANG="ru" + "en" "English" ON \ + "ru" "Русский" OFF \ + 3>&1 1>&2 2>&3) || ONBOARDING_LANG="en" else echo "" >&2 echo "Choose language / Выберите язык:" >&2 - echo " 1) ru — Русский (default)" >&2 - echo " 2) en — English" >&2 + echo " 1) en — English (default)" >&2 + echo " 2) ru — Русский" >&2 read -r -p "[1-2, default 1]: " ans case "$ans" in - 2) ONBOARDING_LANG="en" ;; - *) ONBOARDING_LANG="ru" ;; + 2) ONBOARDING_LANG="ru" ;; + *) ONBOARDING_LANG="en" ;; esac fi + # Перегружаем словарь — все последующие строки на выбранном языке. + if command -v i18n_load_lang >/dev/null 2>&1; then + i18n_load_lang "$ONBOARDING_LANG" + fi } # ─────────────────────────────────────────────────────────────────────── @@ -118,21 +123,20 @@ onboarding_pick_language() { onboarding_pick_transport() { local transports transports=$(onboarding_list_transports) - local title="${ONBOARDING_LANG:-ru}" - local prompt; [ "$title" = "ru" ] && prompt="Выберите способ подключения:" || prompt="Choose connection transport:" + local prompt="${STR_PICK_TRANSPORT:-Choose connection transport:}" if command -v whiptail >/dev/null 2>&1; then local args=() while IFS= read -r tr; do local desc case "$tr" in - direct-api) desc="Прямой API провайдера (ключ)" ;; - aws-bedrock) desc="AWS Bedrock (IAM/role)" ;; - azure-openai) desc="Azure OpenAI (deployment+key)" ;; - google-vertex) desc="Google Vertex AI (GCP)" ;; - local) desc="Локально (Ollama/MLX/LMStudio)" ;; - proxy) desc="Прокси (LiteLLM/OpenRouter)" ;; - subscription) desc="OAuth-подписка (ChatGPT)" ;; + direct-api) desc="${STR_TR_DIRECT_API:-Direct provider API}" ;; + aws-bedrock) desc="${STR_TR_AWS_BEDROCK:-AWS Bedrock}" ;; + azure-openai) desc="${STR_TR_AZURE_OPENAI:-Azure OpenAI}" ;; + google-vertex) desc="${STR_TR_GOOGLE_VERTEX:-Google Vertex AI}" ;; + local) desc="${STR_TR_LOCAL:-Local}" ;; + proxy) desc="${STR_TR_PROXY:-Proxy}" ;; + subscription) desc="${STR_TR_SUBSCRIPTION:-OAuth subscription}" ;; *) desc="$tr" ;; esac args+=("$tr" "$desc" "OFF") @@ -173,12 +177,13 @@ onboarding_pick_provider() { while IFS=$'\t' read -r id dn ae; do args+=("$id" "$dn" "OFF") done <<< "$rows" + local prompt="${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:" ONBOARDING_PROVIDER=$(whiptail --title "KeiSei · Provider" --radiolist \ - "Provider within $ONBOARDING_TRANSPORT:" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \ + "$prompt" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \ || ONBOARDING_PROVIDER=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}') else echo "" >&2 - echo "Providers within $ONBOARDING_TRANSPORT:" >&2 + echo "${STR_PICK_PROVIDER:-Providers within} $ONBOARDING_TRANSPORT:" >&2 declare -a ids=() local i=1 while IFS=$'\t' read -r id dn ae; do @@ -213,11 +218,11 @@ onboarding_pick_model() { args+=("$id" "$dn" "OFF") done <<< "$rows" ONBOARDING_MODEL=$(whiptail --title "KeiSei · Model" --radiolist \ - "Default model:" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \ + "${STR_PICK_MODEL:-Default model:}" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \ || ONBOARDING_MODEL=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}') else echo "" >&2 - echo "Models for $lookup:" >&2 + echo "${STR_PICK_MODEL:-Models for} $lookup:" >&2 declare -a ids=() local i=1 while IFS=$'\t' read -r id dn; do @@ -241,15 +246,15 @@ onboarding_collect_auth() { [ -z "$ae" ] || [ "$ae" = "_" ] && return # local / subscription — нет ключей echo "" >&2 - echo "Auth для $ONBOARDING_PROVIDER ($ae):" >&2 - echo "Введите значения (Enter — оставить пустым, заполним позже)." >&2 + echo "${STR_AUTH_INTRO:-Auth for} $ONBOARDING_PROVIDER ($ae):" >&2 + echo "${STR_AUTH_PROMPT:-Enter values (Enter — leave empty, fill later).}" >&2 local IFS_old="$IFS"; IFS=',' for key in $ae; do IFS="$IFS_old" local cur="${!key:-}" local prompt_msg="$key" - [ -n "$cur" ] && prompt_msg="$key (текущее: <скрыто>)" + [ -n "$cur" ] && prompt_msg="$key ${STR_AUTH_CURRENT_HINT:-(current: )}" # silent read — значение не светит в терминале read -r -s -p " $prompt_msg = " val echo "" >&2 @@ -305,9 +310,9 @@ onboarding_run() { onboarding_should_run || return 0 if command -v say >/dev/null 2>&1; then - say "onboarding wizard (5 шагов)" + say "${STR_ONBOARDING_INTRO:-Onboarding wizard (5 steps)}" else - echo "── KeiSei onboarding (5 шагов) ──" >&2 + echo "── KeiSei: ${STR_ONBOARDING_INTRO:-onboarding (5 steps)} ──" >&2 fi onboarding_pick_language @@ -319,8 +324,8 @@ onboarding_run() { onboarding_write_config if command -v say >/dev/null 2>&1; then - say "✓ onboarding: $ONBOARDING_TRANSPORT / $ONBOARDING_PROVIDER / $ONBOARDING_MODEL" - say " config: $ONBOARDING_CONFIG" - [ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" -gt 0 ] && say " secrets: $SECRETS_ENV (chmod 600)" + say "✓ ${STR_DONE_TITLE:-onboarding complete}: $ONBOARDING_TRANSPORT / $ONBOARDING_PROVIDER / $ONBOARDING_MODEL" + say " ${STR_DONE_CONFIG:-config:} $ONBOARDING_CONFIG" + [ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" -gt 0 ] && say " ${STR_DONE_SECRETS:-secrets:} $SECRETS_ENV (chmod 600)" fi }