feat(install): i18n модуль + welcome banner
Структура локализации:
install/i18n/en.sh — английский словарь (дефолт, fallback)
install/i18n/ru.sh — русский словарь
install/lib-i18n.sh — лоадер + welcome banner
Поток:
1. install.sh source'ит lib-i18n.sh и зовёт i18n_load_default →
все строки на английском.
2. Если onboarding нужен — печатается welcome banner ASCII-рамка
на английском (язык ещё не выбран).
3. onboarding_pick_language — единственный двуязычный шаг
("Choose language / Выберите язык"). По выбору вызывает
i18n_load_lang ru|en — перегружает словарь.
4. Все последующие шаги (transport / provider / model / auth /
completion) идут на выбранном языке.
Fallback: если ru-словарь не имеет ключа — используется английское
значение (load_default вызывается до загрузки ru.sh, переменные
перезаписываются поверх).
lib-onboarding.sh переведён со смешанных hardcoded строк на
${STR_*} placeholders.
Тесты: bash -n всех 5 файлов чисто, i18n loader unit-тест показывает
EN/RU перегрузку, non-TTY smoke install --no-execute проходит.
This commit is contained in:
parent
c844524f68
commit
ab260f429e
5 changed files with 156 additions and 30 deletions
12
install.sh
12
install.sh
|
|
@ -41,6 +41,10 @@ source "$LIB_DIR/lib-profile.sh"
|
||||||
source "$LIB_DIR/lib-args.sh"
|
source "$LIB_DIR/lib-args.sh"
|
||||||
# shellcheck source=install/lib-menu.sh
|
# shellcheck source=install/lib-menu.sh
|
||||||
source "$LIB_DIR/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
|
# shellcheck source=install/lib-onboarding.sh
|
||||||
source "$LIB_DIR/lib-onboarding.sh"
|
source "$LIB_DIR/lib-onboarding.sh"
|
||||||
# shellcheck source=install/lib-plan.sh
|
# shellcheck source=install/lib-plan.sh
|
||||||
|
|
@ -142,9 +146,13 @@ case "$PROFILE" in
|
||||||
esac
|
esac
|
||||||
say "profile: $PROFILE"
|
say "profile: $PROFILE"
|
||||||
|
|
||||||
# --- onboarding wizard (язык / транспорт / провайдер / модель / ключи) ---
|
# --- welcome banner + onboarding wizard ----------------------------------
|
||||||
# Запускается только в TTY и при отсутствии ~/.claude/.onboarded.
|
# Banner всегда EN — пользователь ещё не выбрал язык.
|
||||||
|
# Wizard: TTY + нет ~/.claude/.onboarded + не задан KEISEI_SKIP_ONBOARD.
|
||||||
# Skip: KEISEI_SKIP_ONBOARD=1 ./install.sh
|
# Skip: KEISEI_SKIP_ONBOARD=1 ./install.sh
|
||||||
|
if onboarding_should_run; then
|
||||||
|
i18n_print_welcome
|
||||||
|
fi
|
||||||
onboarding_run
|
onboarding_run
|
||||||
|
|
||||||
# --- prerequisites -------------------------------------------------------
|
# --- prerequisites -------------------------------------------------------
|
||||||
|
|
|
||||||
33
install/i18n/en.sh
Normal file
33
install/i18n/en.sh
Normal file
|
|
@ -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: <hidden>)"
|
||||||
|
|
||||||
|
# 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"
|
||||||
33
install/i18n/ru.sh
Normal file
33
install/i18n/ru.sh
Normal file
|
|
@ -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 или перезапустите этот скрипт для установки профиля"
|
||||||
47
install/lib-i18n.sh
Normal file
47
install/lib-i18n.sh
Normal file
|
|
@ -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 ""
|
||||||
|
}
|
||||||
|
|
@ -93,23 +93,28 @@ onboarding_models_for_provider() {
|
||||||
# UI: язык
|
# UI: язык
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
# ───────────────────────────────────────────────────────────────────────
|
||||||
onboarding_pick_language() {
|
onboarding_pick_language() {
|
||||||
|
# На этом шаге язык ещё не выбран — экран на двух языках одновременно.
|
||||||
if command -v whiptail >/dev/null 2>&1; then
|
if command -v whiptail >/dev/null 2>&1; then
|
||||||
ONBOARDING_LANG=$(whiptail --title "KeiSei · Language / Язык" --radiolist \
|
ONBOARDING_LANG=$(whiptail --title "KeiSei · Language / Язык" --radiolist \
|
||||||
"Choose interface language / Выберите язык:" 12 60 2 \
|
"Choose interface language / Выберите язык:" 12 60 2 \
|
||||||
"ru" "Русский" ON \
|
"en" "English" ON \
|
||||||
"en" "English" OFF \
|
"ru" "Русский" OFF \
|
||||||
3>&1 1>&2 2>&3) || ONBOARDING_LANG="ru"
|
3>&1 1>&2 2>&3) || ONBOARDING_LANG="en"
|
||||||
else
|
else
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Choose language / Выберите язык:" >&2
|
echo "Choose language / Выберите язык:" >&2
|
||||||
echo " 1) ru — Русский (default)" >&2
|
echo " 1) en — English (default)" >&2
|
||||||
echo " 2) en — English" >&2
|
echo " 2) ru — Русский" >&2
|
||||||
read -r -p "[1-2, default 1]: " ans
|
read -r -p "[1-2, default 1]: " ans
|
||||||
case "$ans" in
|
case "$ans" in
|
||||||
2) ONBOARDING_LANG="en" ;;
|
2) ONBOARDING_LANG="ru" ;;
|
||||||
*) ONBOARDING_LANG="ru" ;;
|
*) ONBOARDING_LANG="en" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
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() {
|
onboarding_pick_transport() {
|
||||||
local transports
|
local transports
|
||||||
transports=$(onboarding_list_transports)
|
transports=$(onboarding_list_transports)
|
||||||
local title="${ONBOARDING_LANG:-ru}"
|
local prompt="${STR_PICK_TRANSPORT:-Choose connection transport:}"
|
||||||
local prompt; [ "$title" = "ru" ] && prompt="Выберите способ подключения:" || prompt="Choose connection transport:"
|
|
||||||
|
|
||||||
if command -v whiptail >/dev/null 2>&1; then
|
if command -v whiptail >/dev/null 2>&1; then
|
||||||
local args=()
|
local args=()
|
||||||
while IFS= read -r tr; do
|
while IFS= read -r tr; do
|
||||||
local desc
|
local desc
|
||||||
case "$tr" in
|
case "$tr" in
|
||||||
direct-api) desc="Прямой API провайдера (ключ)" ;;
|
direct-api) desc="${STR_TR_DIRECT_API:-Direct provider API}" ;;
|
||||||
aws-bedrock) desc="AWS Bedrock (IAM/role)" ;;
|
aws-bedrock) desc="${STR_TR_AWS_BEDROCK:-AWS Bedrock}" ;;
|
||||||
azure-openai) desc="Azure OpenAI (deployment+key)" ;;
|
azure-openai) desc="${STR_TR_AZURE_OPENAI:-Azure OpenAI}" ;;
|
||||||
google-vertex) desc="Google Vertex AI (GCP)" ;;
|
google-vertex) desc="${STR_TR_GOOGLE_VERTEX:-Google Vertex AI}" ;;
|
||||||
local) desc="Локально (Ollama/MLX/LMStudio)" ;;
|
local) desc="${STR_TR_LOCAL:-Local}" ;;
|
||||||
proxy) desc="Прокси (LiteLLM/OpenRouter)" ;;
|
proxy) desc="${STR_TR_PROXY:-Proxy}" ;;
|
||||||
subscription) desc="OAuth-подписка (ChatGPT)" ;;
|
subscription) desc="${STR_TR_SUBSCRIPTION:-OAuth subscription}" ;;
|
||||||
*) desc="$tr" ;;
|
*) desc="$tr" ;;
|
||||||
esac
|
esac
|
||||||
args+=("$tr" "$desc" "OFF")
|
args+=("$tr" "$desc" "OFF")
|
||||||
|
|
@ -173,12 +177,13 @@ onboarding_pick_provider() {
|
||||||
while IFS=$'\t' read -r id dn ae; do
|
while IFS=$'\t' read -r id dn ae; do
|
||||||
args+=("$id" "$dn" "OFF")
|
args+=("$id" "$dn" "OFF")
|
||||||
done <<< "$rows"
|
done <<< "$rows"
|
||||||
|
local prompt="${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:"
|
||||||
ONBOARDING_PROVIDER=$(whiptail --title "KeiSei · Provider" --radiolist \
|
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}')
|
|| ONBOARDING_PROVIDER=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}')
|
||||||
else
|
else
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Providers within $ONBOARDING_TRANSPORT:" >&2
|
echo "${STR_PICK_PROVIDER:-Providers within} $ONBOARDING_TRANSPORT:" >&2
|
||||||
declare -a ids=()
|
declare -a ids=()
|
||||||
local i=1
|
local i=1
|
||||||
while IFS=$'\t' read -r id dn ae; do
|
while IFS=$'\t' read -r id dn ae; do
|
||||||
|
|
@ -213,11 +218,11 @@ onboarding_pick_model() {
|
||||||
args+=("$id" "$dn" "OFF")
|
args+=("$id" "$dn" "OFF")
|
||||||
done <<< "$rows"
|
done <<< "$rows"
|
||||||
ONBOARDING_MODEL=$(whiptail --title "KeiSei · Model" --radiolist \
|
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}')
|
|| ONBOARDING_MODEL=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}')
|
||||||
else
|
else
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Models for $lookup:" >&2
|
echo "${STR_PICK_MODEL:-Models for} $lookup:" >&2
|
||||||
declare -a ids=()
|
declare -a ids=()
|
||||||
local i=1
|
local i=1
|
||||||
while IFS=$'\t' read -r id dn; do
|
while IFS=$'\t' read -r id dn; do
|
||||||
|
|
@ -241,15 +246,15 @@ onboarding_collect_auth() {
|
||||||
[ -z "$ae" ] || [ "$ae" = "_" ] && return # local / subscription — нет ключей
|
[ -z "$ae" ] || [ "$ae" = "_" ] && return # local / subscription — нет ключей
|
||||||
|
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "Auth для $ONBOARDING_PROVIDER ($ae):" >&2
|
echo "${STR_AUTH_INTRO:-Auth for} $ONBOARDING_PROVIDER ($ae):" >&2
|
||||||
echo "Введите значения (Enter — оставить пустым, заполним позже)." >&2
|
echo "${STR_AUTH_PROMPT:-Enter values (Enter — leave empty, fill later).}" >&2
|
||||||
|
|
||||||
local IFS_old="$IFS"; IFS=','
|
local IFS_old="$IFS"; IFS=','
|
||||||
for key in $ae; do
|
for key in $ae; do
|
||||||
IFS="$IFS_old"
|
IFS="$IFS_old"
|
||||||
local cur="${!key:-}"
|
local cur="${!key:-}"
|
||||||
local prompt_msg="$key"
|
local prompt_msg="$key"
|
||||||
[ -n "$cur" ] && prompt_msg="$key (текущее: <скрыто>)"
|
[ -n "$cur" ] && prompt_msg="$key ${STR_AUTH_CURRENT_HINT:-(current: <hidden>)}"
|
||||||
# silent read — значение не светит в терминале
|
# silent read — значение не светит в терминале
|
||||||
read -r -s -p " $prompt_msg = " val
|
read -r -s -p " $prompt_msg = " val
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
|
|
@ -305,9 +310,9 @@ onboarding_run() {
|
||||||
onboarding_should_run || return 0
|
onboarding_should_run || return 0
|
||||||
|
|
||||||
if command -v say >/dev/null 2>&1; then
|
if command -v say >/dev/null 2>&1; then
|
||||||
say "onboarding wizard (5 шагов)"
|
say "${STR_ONBOARDING_INTRO:-Onboarding wizard (5 steps)}"
|
||||||
else
|
else
|
||||||
echo "── KeiSei onboarding (5 шагов) ──" >&2
|
echo "── KeiSei: ${STR_ONBOARDING_INTRO:-onboarding (5 steps)} ──" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
onboarding_pick_language
|
onboarding_pick_language
|
||||||
|
|
@ -319,8 +324,8 @@ onboarding_run() {
|
||||||
onboarding_write_config
|
onboarding_write_config
|
||||||
|
|
||||||
if command -v say >/dev/null 2>&1; then
|
if command -v say >/dev/null 2>&1; then
|
||||||
say "✓ onboarding: $ONBOARDING_TRANSPORT / $ONBOARDING_PROVIDER / $ONBOARDING_MODEL"
|
say "✓ ${STR_DONE_TITLE:-onboarding complete}: $ONBOARDING_TRANSPORT / $ONBOARDING_PROVIDER / $ONBOARDING_MODEL"
|
||||||
say " config: $ONBOARDING_CONFIG"
|
say " ${STR_DONE_CONFIG:-config:} $ONBOARDING_CONFIG"
|
||||||
[ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" -gt 0 ] && say " secrets: $SECRETS_ENV (chmod 600)"
|
[ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" -gt 0 ] && say " ${STR_DONE_SECRETS:-secrets:} $SECRETS_ENV (chmod 600)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue