mirror of
https://github.com/pdxlocations/contact.git
synced 2026-08-11 03:13:11 +02:00
Add internationalization support and Russian translations
- Introduced a new i18n module for handling translations. - Added Russian translations for various UI prompts, error messages, and confirmations in the `ru.ini` localization file. - Updated multiple UI components to utilize the new translation functions, ensuring messages are displayed in the user's selected language. - Enhanced user input validation messages to be translatable. - Refactored dialog and input handling functions to support dynamic text translation.
This commit is contained in:
@@ -13,6 +13,79 @@ Reset Node DB, "Reset Node DB", ""
|
||||
Shutdown, "Shutdown", ""
|
||||
Factory Reset, "Factory Reset", ""
|
||||
Exit, "Exit", ""
|
||||
Yes, "Yes", ""
|
||||
No, "No", ""
|
||||
Cancel, "Cancel", ""
|
||||
|
||||
[ui]
|
||||
save_changes, "Save Changes", ""
|
||||
dialog.invalid_input, "Invalid Input", ""
|
||||
prompt.enter_new_value, "Enter new value: ", ""
|
||||
error.value_empty, "Value cannot be empty.", ""
|
||||
error.value_exact_length, "Value must be exactly {length} characters long.", ""
|
||||
error.value_min_length, "Value must be at least {length} characters long.", ""
|
||||
error.value_max_length, "Value must be no more than {length} characters long.", ""
|
||||
error.digits_only, "Only numeric digits (0-9) allowed.", ""
|
||||
error.number_range, "Enter a number between {min_value} and {max_value}.", ""
|
||||
error.float_invalid, "Must be a valid floating point number.", ""
|
||||
prompt.edit_admin_keys, "Edit up to 3 Admin Keys:", ""
|
||||
label.admin_key, "Admin Key", ""
|
||||
error.admin_key_invalid, "Error: Each key must be valid Base64 and 32 bytes long!", ""
|
||||
prompt.edit_values, "Edit up to 3 Values:", ""
|
||||
label.value, "Value", ""
|
||||
prompt.enter_ip, "Enter an IP address (xxx.xxx.xxx.xxx):", ""
|
||||
label.current, "Current", ""
|
||||
label.new_value, "New value", ""
|
||||
label.editing, "Editing {label}", ""
|
||||
label.current_value, "Current Value:", ""
|
||||
error.ip_invalid, "Invalid IP address. Try again.", ""
|
||||
prompt.select_foreground_color, "Select Foreground Color for {label}", ""
|
||||
prompt.select_background_color, "Select Background Color for {label}", ""
|
||||
prompt.select_value, "Select {label}", ""
|
||||
confirm.save_before_exit, "You have unsaved changes. Save before exiting?", ""
|
||||
prompt.config_filename, "Enter a filename for the config file", ""
|
||||
confirm.overwrite_file, "{filename} already exists. Overwrite?", ""
|
||||
dialog.config_saved_title, "Config File Saved:", ""
|
||||
dialog.no_config_files, " No config files found. Export a config first.", ""
|
||||
prompt.choose_config_file, "Choose a config file", ""
|
||||
confirm.load_config_file, "Are you sure you want to load {filename}?", ""
|
||||
prompt.config_url_current, "Config URL is currently: {value}", ""
|
||||
confirm.load_config_url, "Are you sure you want to load this config?", ""
|
||||
confirm.reboot, "Are you sure you want to Reboot?", ""
|
||||
confirm.reset_node_db, "Are you sure you want to Reset Node DB?", ""
|
||||
confirm.shutdown, "Are you sure you want to Shutdown?", ""
|
||||
confirm.factory_reset, "Are you sure you want to Factory Reset?", ""
|
||||
confirm.save_before_exit_section, "You have unsaved changes in {section}. Save before exiting?", ""
|
||||
prompt.select_region, "Select your region:", ""
|
||||
dialog.slow_down_title, "Slow down", ""
|
||||
dialog.slow_down_body, "Please wait 2 seconds between messages.", ""
|
||||
dialog.node_details_title, "📡 Node Details: {name}", ""
|
||||
dialog.traceroute_not_sent_title, "Traceroute Not Sent", ""
|
||||
dialog.traceroute_not_sent_body, "Please wait {seconds} seconds before sending another traceroute.", ""
|
||||
dialog.traceroute_sent_title, "Traceroute Sent To: {name}", ""
|
||||
dialog.traceroute_sent_body, "Results will appear in messages window.", ""
|
||||
dialog.help_title, "Help - Shortcut Keys", ""
|
||||
help.scroll, "Up/Down = Scroll", ""
|
||||
help.switch_window, "Left/Right = Switch window", ""
|
||||
help.jump_windows, "F1/F2/F3 = Jump to Channel/Messages/Nodes", ""
|
||||
help.enter, "ENTER = Send / Select", ""
|
||||
help.settings, "` or F12 = Settings", ""
|
||||
help.quit, "ESC = Quit", ""
|
||||
help.packet_log, "Ctrl+P = Toggle Packet Log", ""
|
||||
help.traceroute, "Ctrl+T or F4 = Traceroute", ""
|
||||
help.node_info, "F5 = Full node info", ""
|
||||
help.archive_chat, "Ctrl+D = Archive chat / remove node", ""
|
||||
help.favorite, "Ctrl+F = Favorite", ""
|
||||
help.ignore, "Ctrl+G = Ignore", ""
|
||||
help.search, "Ctrl+/ = Search", ""
|
||||
help.help, "Ctrl+K = Help", ""
|
||||
help.no_help, "No help available.", ""
|
||||
confirm.remove_from_nodedb, "Remove {name} from nodedb?", ""
|
||||
confirm.set_favorite, "Set {name} as Favorite?", ""
|
||||
confirm.remove_favorite, "Remove {name} from Favorites?", ""
|
||||
confirm.set_ignored, "Set {name} as Ignored?", ""
|
||||
confirm.remove_ignored, "Remove {name} from Ignored?", ""
|
||||
confirm.region_unset, "Your region is UNSET. Set it now?", ""
|
||||
|
||||
[User Settings]
|
||||
user, "User"
|
||||
@@ -33,10 +106,10 @@ message_prefix, "Message prefix", ""
|
||||
sent_message_prefix, "Sent message prefix", ""
|
||||
notification_symbol, "Notification symbol", ""
|
||||
notification_sound, "Notification sound", ""
|
||||
ack_implicit_str, "Ack (implicit)", ""
|
||||
ack_str, "Ack", ""
|
||||
ack_implicit_str, "ACK (implicit)", ""
|
||||
ack_str, "ACK", ""
|
||||
nak_str, "NAK", ""
|
||||
ack_unknown_str, "Ack (unknown)", ""
|
||||
ack_unknown_str, "ACK (unknown)", ""
|
||||
node_sort, "Node sort", ""
|
||||
theme, "Theme", ""
|
||||
COLOR_CONFIG_DARK, "Theme colors (dark)", ""
|
||||
@@ -320,6 +393,7 @@ power_screen_enabled, "Power screen enabled", "Show the power telemetry data on
|
||||
health_measurement_enabled, "Health telemetry interval", "This option is used to configure the interval in seconds that should be used to send health data from an attached supported sensor over the mesh network in seconds."
|
||||
health_update_interval, "Health telemetry enabled", "This option is used to enable/disable the sending of health data from an attached supported sensor over the mesh network."
|
||||
health_screen_enabled, "Health screen enabled", "Show the health telemetry data on the device display."
|
||||
device_telemetry_enabled, "Device telemetry enabled", "Enable the Device Telemetry"
|
||||
|
||||
[module.canned_message]
|
||||
title, "Canned Message"
|
||||
|
||||
@@ -13,6 +13,79 @@ Reset Node DB, "Сбросить БД узлов", ""
|
||||
Shutdown, "Выключить", ""
|
||||
Factory Reset, "Сброс до заводских", ""
|
||||
Exit, "Выход", ""
|
||||
Yes, "Да", ""
|
||||
No, "Нет", ""
|
||||
Cancel, "Отмена", ""
|
||||
|
||||
[ui]
|
||||
save_changes, "Сохранить изменения", ""
|
||||
dialog.invalid_input, "Некорректный ввод", ""
|
||||
prompt.enter_new_value, "Введите новое значение: ", ""
|
||||
error.value_empty, "Значение не может быть пустым.", ""
|
||||
error.value_exact_length, "Значение должно быть длиной ровно {length} символов.", ""
|
||||
error.value_min_length, "Значение должно быть не короче {length} символов.", ""
|
||||
error.value_max_length, "Значение должно быть не длиннее {length} символов.", ""
|
||||
error.digits_only, "Разрешены только цифры (0-9).", ""
|
||||
error.number_range, "Введите число между {min_value} и {max_value}.", ""
|
||||
error.float_invalid, "Введите корректное число с плавающей точкой.", ""
|
||||
prompt.edit_admin_keys, "Редактировать до 3 ключей администратора:", ""
|
||||
label.admin_key, "Ключ администратора", ""
|
||||
error.admin_key_invalid, "Ошибка: каждый ключ должен быть Base64 и длиной 32 байта.", ""
|
||||
prompt.edit_values, "Редактировать до 3 значений:", ""
|
||||
label.value, "Значение", ""
|
||||
prompt.enter_ip, "Введите IP-адрес (xxx.xxx.xxx.xxx):", ""
|
||||
label.current, "Текущее", ""
|
||||
label.new_value, "Новое значение", ""
|
||||
label.editing, "Редактирование {label}", ""
|
||||
label.current_value, "Текущее значение:", ""
|
||||
error.ip_invalid, "Неверный IP-адрес. Попробуйте еще раз.", ""
|
||||
prompt.select_foreground_color, "Выберите цвет текста для {label}", ""
|
||||
prompt.select_background_color, "Выберите цвет фона для {label}", ""
|
||||
prompt.select_value, "Выберите {label}", ""
|
||||
confirm.save_before_exit, "Есть несохраненные изменения. Сохранить перед выходом?", ""
|
||||
prompt.config_filename, "Введите имя файла конфигурации", ""
|
||||
confirm.overwrite_file, "Файл {filename} уже существует. Перезаписать?", ""
|
||||
dialog.config_saved_title, "Файл конфигурации сохранен:", ""
|
||||
dialog.no_config_files, " Нет файлов конфигурации. Сначала экспортируйте конфигурацию.", ""
|
||||
prompt.choose_config_file, "Выберите файл конфигурации", ""
|
||||
confirm.load_config_file, "Загрузить файл {filename}?", ""
|
||||
prompt.config_url_current, "Текущий URL конфигурации: {value}", ""
|
||||
confirm.load_config_url, "Загрузить эту конфигурацию?", ""
|
||||
confirm.reboot, "Перезагрузить устройство?", ""
|
||||
confirm.reset_node_db, "Сбросить БД узлов?", ""
|
||||
confirm.shutdown, "Выключить устройство?", ""
|
||||
confirm.factory_reset, "Сбросить до заводских настроек?", ""
|
||||
confirm.save_before_exit_section, "Есть несохраненные изменения в {section}. Сохранить перед выходом?", ""
|
||||
prompt.select_region, "Выберите ваш регион:", ""
|
||||
dialog.slow_down_title, "Подождите", ""
|
||||
dialog.slow_down_body, "Подождите 2 секунды между сообщениями.", ""
|
||||
dialog.node_details_title, "📡 Информация об узле: {name}", ""
|
||||
dialog.traceroute_not_sent_title, "Traceroute не отправлен", ""
|
||||
dialog.traceroute_not_sent_body, "Подождите {seconds} секунд перед повторной отправкой traceroute.", ""
|
||||
dialog.traceroute_sent_title, "Traceroute отправлен: {name}", ""
|
||||
dialog.traceroute_sent_body, "Результаты появятся в окне сообщений.", ""
|
||||
dialog.help_title, "Справка - горячие клавиши", ""
|
||||
help.scroll, "Вверх/Вниз = Прокрутка", ""
|
||||
help.switch_window, "Влево/Вправо = Переключить окно", ""
|
||||
help.jump_windows, "F1/F2/F3 = Каналы/Сообщения/Узлы", ""
|
||||
help.enter, "ENTER = Отправить / Выбрать", ""
|
||||
help.settings, "` или F12 = Настройки", ""
|
||||
help.quit, "ESC = Выход", ""
|
||||
help.packet_log, "Ctrl+P = Журнал пакетов", ""
|
||||
help.traceroute, "Ctrl+T или F4 = Traceroute", ""
|
||||
help.node_info, "F5 = Полная информация об узле", ""
|
||||
help.archive_chat, "Ctrl+D = Архив чата / удалить узел", ""
|
||||
help.favorite, "Ctrl+F = Избранное", ""
|
||||
help.ignore, "Ctrl+G = Игнорировать", ""
|
||||
help.search, "Ctrl+/ = Поиск", ""
|
||||
help.help, "Ctrl+K = Справка", ""
|
||||
help.no_help, "Нет справки.", ""
|
||||
confirm.remove_from_nodedb, "Удалить {name} из базы узлов?", ""
|
||||
confirm.set_favorite, "Добавить {name} в избранное?", ""
|
||||
confirm.remove_favorite, "Удалить {name} из избранного?", ""
|
||||
confirm.set_ignored, "Игнорировать {name}?", ""
|
||||
confirm.remove_ignored, "Убрать {name} из игнорируемых?", ""
|
||||
confirm.region_unset, "Ваш регион НЕ ЗАДАН. Установить сейчас?", ""
|
||||
|
||||
[User Settings]
|
||||
user, "Пользователь"
|
||||
@@ -320,6 +393,7 @@ power_screen_enabled, "Power screen enabled", "Show the power telemetry data on
|
||||
health_measurement_enabled, "Health telemetry interval", "This option is used to configure the interval in seconds that should be used to send health data from an attached supported sensor over the mesh network in seconds."
|
||||
health_update_interval, "Health telemetry enabled", "This option is used to enable/disable the sending of health data from an attached supported sensor over the mesh network."
|
||||
health_screen_enabled, "Health screen enabled", "Show the health telemetry data on the device display."
|
||||
device_telemetry_enabled, "Device telemetry enabled", "Enable the Device Telemetry"
|
||||
|
||||
[module.canned_message]
|
||||
title, "Canned Message"
|
||||
|
||||
+6
-1
@@ -7,6 +7,7 @@ import traceback
|
||||
|
||||
import contact.ui.default_config as config
|
||||
from contact.utilities.input_handlers import get_list_input
|
||||
from contact.utilities.i18n import t
|
||||
from contact.ui.colors import setup_colors
|
||||
from contact.ui.splash import draw_splash
|
||||
from contact.ui.control_ui import set_region, settings_menu
|
||||
@@ -28,7 +29,11 @@ def main(stdscr: curses.window) -> None:
|
||||
interface = initialize_interface(args)
|
||||
|
||||
if interface.localNode.localConfig.lora.region == 0:
|
||||
confirmation = get_list_input("Your region is UNSET. Set it now?", "Yes", ["Yes", "No"])
|
||||
confirmation = get_list_input(
|
||||
t("ui.confirm.region_unset", default="Your region is UNSET. Set it now?"),
|
||||
"Yes",
|
||||
["Yes", "No"],
|
||||
)
|
||||
if confirmation == "Yes":
|
||||
set_region(interface)
|
||||
interface.close()
|
||||
|
||||
+65
-25
@@ -11,6 +11,7 @@ from contact.utilities.utils import parse_protobuf
|
||||
from contact.ui.colors import get_color
|
||||
from contact.utilities.db_handler import get_name_from_database, update_node_info_in_db, is_chat_archived
|
||||
from contact.utilities.input_handlers import get_list_input
|
||||
from contact.utilities.i18n import t
|
||||
import contact.ui.default_config as config
|
||||
import contact.ui.dialog
|
||||
from contact.ui.nav_utils import move_main_highlight, draw_main_arrows, get_msg_window_lines, wrap_text
|
||||
@@ -433,7 +434,10 @@ def handle_enter(input_text: str) -> str:
|
||||
# TODO: This is a hack to prevent sending messages too quickly. Let's get errors from the node.
|
||||
now = time.monotonic()
|
||||
if now - ui_state.last_sent_time < 2.5:
|
||||
contact.ui.dialog.dialog("Slow down", "Please wait 2 seconds between messages.")
|
||||
contact.ui.dialog.dialog(
|
||||
t("ui.dialog.slow_down_title", default="Slow down"),
|
||||
t("ui.dialog.slow_down_body", default="Please wait 2 seconds between messages."),
|
||||
)
|
||||
return input_text
|
||||
# Enter key pressed, send user input as message
|
||||
send_message(input_text, channel=ui_state.selected_channel)
|
||||
@@ -525,7 +529,14 @@ def handle_f5_key(stdscr: curses.window) -> None:
|
||||
|
||||
message = "\n".join(message_parts)
|
||||
|
||||
contact.ui.dialog.dialog(f"📡 Node Details: {node.get('user', {}).get('shortName', 'Unknown')}", message)
|
||||
contact.ui.dialog.dialog(
|
||||
t(
|
||||
"ui.dialog.node_details_title",
|
||||
default="📡 Node Details: {name}",
|
||||
name=node.get("user", {}).get("shortName", "Unknown"),
|
||||
),
|
||||
message,
|
||||
)
|
||||
curses.curs_set(1) # Show cursor again
|
||||
handle_resize(stdscr, False)
|
||||
|
||||
@@ -542,7 +553,12 @@ def handle_ctrl_t(stdscr: curses.window) -> None:
|
||||
if remaining > 0:
|
||||
curses.curs_set(0) # Hide cursor
|
||||
contact.ui.dialog.dialog(
|
||||
"Traceroute Not Sent", f"Please wait {int(remaining)} seconds before sending another traceroute."
|
||||
t("ui.dialog.traceroute_not_sent_title", default="Traceroute Not Sent"),
|
||||
t(
|
||||
"ui.dialog.traceroute_not_sent_body",
|
||||
default="Please wait {seconds} seconds before sending another traceroute.",
|
||||
seconds=int(remaining),
|
||||
),
|
||||
)
|
||||
curses.curs_set(1) # Show cursor again
|
||||
handle_resize(stdscr, False)
|
||||
@@ -552,8 +568,12 @@ def handle_ctrl_t(stdscr: curses.window) -> None:
|
||||
ui_state.last_traceroute_time = now
|
||||
curses.curs_set(0) # Hide cursor
|
||||
contact.ui.dialog.dialog(
|
||||
f"Traceroute Sent To: {get_name_from_database(ui_state.node_list[ui_state.selected_node])}",
|
||||
"Results will appear in messages window.",
|
||||
t(
|
||||
"ui.dialog.traceroute_sent_title",
|
||||
default="Traceroute Sent To: {name}",
|
||||
name=get_name_from_database(ui_state.node_list[ui_state.selected_node]),
|
||||
),
|
||||
t("ui.dialog.traceroute_sent_body", default="Results will appear in messages window."),
|
||||
)
|
||||
curses.curs_set(1) # Show cursor again
|
||||
handle_resize(stdscr, False)
|
||||
@@ -601,23 +621,23 @@ def handle_ctrl_k(stdscr: curses.window) -> None:
|
||||
curses.curs_set(0)
|
||||
|
||||
cmds = [
|
||||
"↑/↓ = Scroll",
|
||||
"←/→ = Switch window",
|
||||
"F1/F2/F3 = Jump to Channel/Messages/Nodes",
|
||||
"ENTER = Send / Select",
|
||||
"` or F12 = Settings",
|
||||
"ESC = Quit",
|
||||
"Ctrl+P = Toggle Packet Log",
|
||||
"Ctrl+T or F4 = Traceroute",
|
||||
"F5 = Full node info",
|
||||
"Ctrl+D = Archive chat / remove node",
|
||||
"Ctrl+F = Favorite",
|
||||
"Ctrl+G = Ignore",
|
||||
"Ctrl+/ = Search",
|
||||
"Ctrl+K = Help",
|
||||
t("ui.help.scroll", default="Up/Down = Scroll"),
|
||||
t("ui.help.switch_window", default="Left/Right = Switch window"),
|
||||
t("ui.help.jump_windows", default="F1/F2/F3 = Jump to Channel/Messages/Nodes"),
|
||||
t("ui.help.enter", default="ENTER = Send / Select"),
|
||||
t("ui.help.settings", default="` or F12 = Settings"),
|
||||
t("ui.help.quit", default="ESC = Quit"),
|
||||
t("ui.help.packet_log", default="Ctrl+P = Toggle Packet Log"),
|
||||
t("ui.help.traceroute", default="Ctrl+T or F4 = Traceroute"),
|
||||
t("ui.help.node_info", default="F5 = Full node info"),
|
||||
t("ui.help.archive_chat", default="Ctrl+D = Archive chat / remove node"),
|
||||
t("ui.help.favorite", default="Ctrl+F = Favorite"),
|
||||
t("ui.help.ignore", default="Ctrl+G = Ignore"),
|
||||
t("ui.help.search", default="Ctrl+/ = Search"),
|
||||
t("ui.help.help", default="Ctrl+K = Help"),
|
||||
]
|
||||
|
||||
contact.ui.dialog.dialog("Help — Shortcut Keys", "\n".join(cmds))
|
||||
contact.ui.dialog.dialog(t("ui.dialog.help_title", default="Help - Shortcut Keys"), "\n".join(cmds))
|
||||
|
||||
curses.curs_set(1)
|
||||
handle_resize(stdscr, False)
|
||||
@@ -642,7 +662,11 @@ def handle_ctrl_d() -> None:
|
||||
if ui_state.current_window == 2:
|
||||
curses.curs_set(0)
|
||||
confirmation = get_list_input(
|
||||
f"Remove {get_name_from_database(ui_state.node_list[ui_state.selected_node])} from nodedb?",
|
||||
t(
|
||||
"ui.confirm.remove_from_nodedb",
|
||||
default="Remove {name} from nodedb?",
|
||||
name=get_name_from_database(ui_state.node_list[ui_state.selected_node]),
|
||||
),
|
||||
"No",
|
||||
["Yes", "No"],
|
||||
)
|
||||
@@ -680,7 +704,11 @@ def handle_ctrl_f(stdscr: curses.window) -> None:
|
||||
|
||||
if "isFavorite" not in selectedNode or selectedNode["isFavorite"] == False:
|
||||
confirmation = get_list_input(
|
||||
f"Set {get_name_from_database(ui_state.node_list[ui_state.selected_node])} as Favorite?",
|
||||
t(
|
||||
"ui.confirm.set_favorite",
|
||||
default="Set {name} as Favorite?",
|
||||
name=get_name_from_database(ui_state.node_list[ui_state.selected_node]),
|
||||
),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
@@ -693,7 +721,11 @@ def handle_ctrl_f(stdscr: curses.window) -> None:
|
||||
|
||||
else:
|
||||
confirmation = get_list_input(
|
||||
f"Remove {get_name_from_database(ui_state.node_list[ui_state.selected_node])} from Favorites?",
|
||||
t(
|
||||
"ui.confirm.remove_favorite",
|
||||
default="Remove {name} from Favorites?",
|
||||
name=get_name_from_database(ui_state.node_list[ui_state.selected_node]),
|
||||
),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
@@ -716,7 +748,11 @@ def handle_ctlr_g(stdscr: curses.window) -> None:
|
||||
|
||||
if "isIgnored" not in selectedNode or selectedNode["isIgnored"] == False:
|
||||
confirmation = get_list_input(
|
||||
f"Set {get_name_from_database(ui_state.node_list[ui_state.selected_node])} as Ignored?",
|
||||
t(
|
||||
"ui.confirm.set_ignored",
|
||||
default="Set {name} as Ignored?",
|
||||
name=get_name_from_database(ui_state.node_list[ui_state.selected_node]),
|
||||
),
|
||||
"No",
|
||||
["Yes", "No"],
|
||||
)
|
||||
@@ -725,7 +761,11 @@ def handle_ctlr_g(stdscr: curses.window) -> None:
|
||||
interface_state.interface.nodesByNum[ui_state.node_list[ui_state.selected_node]]["isIgnored"] = True
|
||||
else:
|
||||
confirmation = get_list_input(
|
||||
f"Remove {get_name_from_database(ui_state.node_list[ui_state.selected_node])} from Ignored?",
|
||||
t(
|
||||
"ui.confirm.remove_ignored",
|
||||
default="Remove {name} from Ignored?",
|
||||
name=get_name_from_database(ui_state.node_list[ui_state.selected_node]),
|
||||
),
|
||||
"No",
|
||||
["Yes", "No"],
|
||||
)
|
||||
|
||||
+71
-17
@@ -16,6 +16,7 @@ from contact.utilities.input_handlers import (
|
||||
get_list_input,
|
||||
get_admin_key_input,
|
||||
)
|
||||
from contact.utilities.i18n import t
|
||||
from contact.ui.colors import get_color
|
||||
from contact.ui.dialog import dialog
|
||||
from contact.ui.menus import generate_menu_from_protobuf
|
||||
@@ -134,10 +135,11 @@ def display_menu() -> tuple[object, object]:
|
||||
|
||||
if menu_state.show_save_option:
|
||||
save_position = menu_height - 2
|
||||
save_label = t("ui.save_changes", default=save_option)
|
||||
menu_win.addstr(
|
||||
save_position,
|
||||
(w - len(save_option)) // 2,
|
||||
save_option,
|
||||
(w - len(save_label)) // 2,
|
||||
save_label,
|
||||
get_color("settings_save", reverse=(menu_state.selected_index == len(menu_state.current_menu))),
|
||||
)
|
||||
|
||||
@@ -322,7 +324,11 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
|
||||
elif selected_option == "Export Config File":
|
||||
|
||||
filename = get_text_input("Enter a filename for the config file", None, None)
|
||||
filename = get_text_input(
|
||||
t("ui.prompt.config_filename", default="Enter a filename for the config file"),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
if not filename:
|
||||
logging.info("Export aborted: No filename provided.")
|
||||
menu_state.start_index.pop()
|
||||
@@ -335,7 +341,15 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
yaml_file_path = os.path.join(config_folder, filename)
|
||||
|
||||
if os.path.exists(yaml_file_path):
|
||||
overwrite = get_list_input(f"{filename} already exists. Overwrite?", None, ["Yes", "No"])
|
||||
overwrite = get_list_input(
|
||||
t(
|
||||
"ui.confirm.overwrite_file",
|
||||
default="{filename} already exists. Overwrite?",
|
||||
filename=filename,
|
||||
),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
if overwrite == "No":
|
||||
logging.info("Export cancelled: User chose not to overwrite.")
|
||||
menu_state.start_index.pop()
|
||||
@@ -344,7 +358,7 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
with open(yaml_file_path, "w", encoding="utf-8") as file:
|
||||
file.write(config_text)
|
||||
logging.info(f"Config file saved to {yaml_file_path}")
|
||||
dialog("Config File Saved:", yaml_file_path)
|
||||
dialog(t("ui.dialog.config_saved_title", default="Config File Saved:"), yaml_file_path)
|
||||
menu_state.need_redraw = True
|
||||
menu_state.start_index.pop()
|
||||
continue
|
||||
@@ -361,7 +375,7 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
|
||||
# Check if folder exists and is not empty
|
||||
if not os.path.exists(config_folder) or not any(os.listdir(config_folder)):
|
||||
dialog("", " No config files found. Export a config first.")
|
||||
dialog("", t("ui.dialog.no_config_files", default=" No config files found. Export a config first."))
|
||||
menu_state.need_redraw = True
|
||||
continue # Return to menu
|
||||
|
||||
@@ -369,14 +383,24 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
|
||||
# Ensure file_list is not empty before proceeding
|
||||
if not file_list:
|
||||
dialog("", " No config files found. Export a config first.")
|
||||
dialog("", t("ui.dialog.no_config_files", default=" No config files found. Export a config first."))
|
||||
menu_state.need_redraw = True
|
||||
continue
|
||||
|
||||
filename = get_list_input("Choose a config file", None, file_list)
|
||||
filename = get_list_input(
|
||||
t("ui.prompt.choose_config_file", default="Choose a config file"), None, file_list
|
||||
)
|
||||
if filename:
|
||||
file_path = os.path.join(config_folder, filename)
|
||||
overwrite = get_list_input(f"Are you sure you want to load {filename}?", None, ["Yes", "No"])
|
||||
overwrite = get_list_input(
|
||||
t(
|
||||
"ui.confirm.load_config_file",
|
||||
default="Are you sure you want to load {filename}?",
|
||||
filename=filename,
|
||||
),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
if overwrite == "Yes":
|
||||
config_import(interface, file_path)
|
||||
menu_state.start_index.pop()
|
||||
@@ -384,10 +408,22 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
|
||||
elif selected_option == "Config URL":
|
||||
current_value = interface.localNode.getURL()
|
||||
new_value = get_text_input(f"Config URL is currently: {current_value}", None, str)
|
||||
new_value = get_text_input(
|
||||
t(
|
||||
"ui.prompt.config_url_current",
|
||||
default="Config URL is currently: {value}",
|
||||
value=current_value,
|
||||
),
|
||||
None,
|
||||
str,
|
||||
)
|
||||
if new_value is not None:
|
||||
current_value = new_value
|
||||
overwrite = get_list_input(f"Are you sure you want to load this config?", None, ["Yes", "No"])
|
||||
overwrite = get_list_input(
|
||||
t("ui.confirm.load_config_url", default="Are you sure you want to load this config?"),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
if overwrite == "Yes":
|
||||
interface.localNode.setURL(new_value)
|
||||
logging.info(f"New Config URL sent to node")
|
||||
@@ -395,7 +431,9 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
continue
|
||||
|
||||
elif selected_option == "Reboot":
|
||||
confirmation = get_list_input("Are you sure you want to Reboot?", None, ["Yes", "No"])
|
||||
confirmation = get_list_input(
|
||||
t("ui.confirm.reboot", default="Are you sure you want to Reboot?"), None, ["Yes", "No"]
|
||||
)
|
||||
if confirmation == "Yes":
|
||||
interface.localNode.reboot()
|
||||
logging.info(f"Node Reboot Requested by menu")
|
||||
@@ -403,7 +441,11 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
continue
|
||||
|
||||
elif selected_option == "Reset Node DB":
|
||||
confirmation = get_list_input("Are you sure you want to Reset Node DB?", None, ["Yes", "No"])
|
||||
confirmation = get_list_input(
|
||||
t("ui.confirm.reset_node_db", default="Are you sure you want to Reset Node DB?"),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
if confirmation == "Yes":
|
||||
interface.localNode.resetNodeDb()
|
||||
logging.info(f"Node DB Reset Requested by menu")
|
||||
@@ -411,7 +453,9 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
continue
|
||||
|
||||
elif selected_option == "Shutdown":
|
||||
confirmation = get_list_input("Are you sure you want to Shutdown?", None, ["Yes", "No"])
|
||||
confirmation = get_list_input(
|
||||
t("ui.confirm.shutdown", default="Are you sure you want to Shutdown?"), None, ["Yes", "No"]
|
||||
)
|
||||
if confirmation == "Yes":
|
||||
interface.localNode.shutdown()
|
||||
logging.info(f"Node Shutdown Requested by menu")
|
||||
@@ -419,7 +463,11 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
continue
|
||||
|
||||
elif selected_option == "Factory Reset":
|
||||
confirmation = get_list_input("Are you sure you want to Factory Reset?", None, ["Yes", "No"])
|
||||
confirmation = get_list_input(
|
||||
t("ui.confirm.factory_reset", default="Are you sure you want to Factory Reset?"),
|
||||
None,
|
||||
["Yes", "No"],
|
||||
)
|
||||
if confirmation == "Yes":
|
||||
interface.localNode.factoryReset()
|
||||
logging.info(f"Factory Reset Requested by menu")
|
||||
@@ -570,7 +618,11 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
||||
|
||||
current_section = menu_state.menu_path[-1]
|
||||
save_prompt = get_list_input(
|
||||
f"You have unsaved changes in {current_section}. Save before exiting?",
|
||||
t(
|
||||
"ui.confirm.save_before_exit_section",
|
||||
default="You have unsaved changes in {section}. Save before exiting?",
|
||||
section=current_section,
|
||||
),
|
||||
None,
|
||||
["Yes", "No", "Cancel"],
|
||||
mandatory=True,
|
||||
@@ -637,7 +689,9 @@ def set_region(interface: object) -> None:
|
||||
|
||||
regions = list(region_name_to_number.keys())
|
||||
|
||||
new_region_name = get_list_input("Select your region:", "UNSET", regions)
|
||||
new_region_name = get_list_input(
|
||||
t("ui.prompt.select_region", default="Select your region:"), "UNSET", regions
|
||||
)
|
||||
|
||||
# Convert region name to corresponding enum number
|
||||
new_region_number = region_name_to_number.get(new_region_name, 0) # Default to 0 if not found
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import curses
|
||||
|
||||
from contact.utilities.i18n import t_text
|
||||
from contact.ui.colors import get_color
|
||||
from contact.ui.nav_utils import draw_main_arrows
|
||||
from contact.utilities.singleton import menu_state, ui_state
|
||||
|
||||
|
||||
def dialog(title: str, message: str) -> None:
|
||||
title = t_text(title)
|
||||
message = t_text(message)
|
||||
"""Display a dialog with a title and message."""
|
||||
|
||||
previous_window = ui_state.current_window
|
||||
|
||||
+18
-6
@@ -3,6 +3,7 @@ import re
|
||||
from unicodedata import east_asian_width
|
||||
|
||||
from contact.ui.colors import get_color
|
||||
from contact.utilities.i18n import t
|
||||
from contact.utilities.control_utils import transform_menu_path
|
||||
from typing import Any, Optional, List, Dict
|
||||
from contact.utilities.singleton import interface_state, ui_state
|
||||
@@ -24,6 +25,10 @@ WrappedLine = List[Segment]
|
||||
|
||||
sensitive_settings = ["Reboot", "Reset Node DB", "Shutdown", "Factory Reset"]
|
||||
save_option = "Save Changes"
|
||||
|
||||
|
||||
def get_save_option_label() -> str:
|
||||
return t("ui.save_changes", default=save_option)
|
||||
MIN_HEIGHT_FOR_HELP = 20
|
||||
|
||||
|
||||
@@ -54,6 +59,9 @@ def move_highlight(
|
||||
|
||||
if "max_help_lines" in kwargs:
|
||||
max_help_lines = kwargs["max_help_lines"]
|
||||
if not options:
|
||||
return
|
||||
|
||||
if old_idx == new_idx: # No-op
|
||||
return
|
||||
|
||||
@@ -74,8 +82,11 @@ def move_highlight(
|
||||
# Clear old selection
|
||||
if show_save_option and old_idx == max_index:
|
||||
win_h, win_w = menu_win.getmaxyx()
|
||||
menu_win.chgat(win_h - 2, (win_w - len(save_option)) // 2, len(save_option), get_color("settings_save"))
|
||||
else:
|
||||
save_label = get_save_option_label()
|
||||
menu_win.chgat(
|
||||
win_h - 2, (win_w - len(save_label)) // 2, len(save_label), get_color("settings_save")
|
||||
)
|
||||
elif 0 <= old_idx < len(options):
|
||||
menu_pad.chgat(
|
||||
old_idx,
|
||||
0,
|
||||
@@ -90,13 +101,14 @@ def move_highlight(
|
||||
# Highlight new selection
|
||||
if show_save_option and new_idx == max_index:
|
||||
win_h, win_w = menu_win.getmaxyx()
|
||||
save_label = get_save_option_label()
|
||||
menu_win.chgat(
|
||||
win_h - 2,
|
||||
(win_w - len(save_option)) // 2,
|
||||
len(save_option),
|
||||
(win_w - len(save_label)) // 2,
|
||||
len(save_label),
|
||||
get_color("settings_save", reverse=True),
|
||||
)
|
||||
else:
|
||||
elif 0 <= new_idx < len(options):
|
||||
menu_pad.chgat(
|
||||
new_idx,
|
||||
0,
|
||||
@@ -233,7 +245,7 @@ def get_wrapped_help_text(
|
||||
"""Fetches and formats help text for display, ensuring it fits within the allowed lines."""
|
||||
|
||||
full_help_key = ".".join(transformed_path + [selected_option]) if selected_option else None
|
||||
help_content = help_text.get(full_help_key, "No help available.")
|
||||
help_content = help_text.get(full_help_key, t("ui.help.no_help", default="No help available."))
|
||||
|
||||
wrap_width = max(width - 6, 10) # Ensure a valid wrapping width
|
||||
|
||||
|
||||
+56
-16
@@ -8,6 +8,7 @@ import contact.ui.default_config as config
|
||||
from contact.ui.nav_utils import move_highlight, draw_arrows, update_help_window
|
||||
from contact.utilities.control_utils import parse_ini_file
|
||||
from contact.utilities.input_handlers import get_list_input
|
||||
from contact.utilities.i18n import t
|
||||
from contact.utilities.singleton import menu_state
|
||||
|
||||
|
||||
@@ -90,8 +91,24 @@ def edit_color_pair(key: str, display_label: str, current_value: List[str]) -> L
|
||||
Allows the user to select a foreground and background color for a key.
|
||||
"""
|
||||
color_list = [" "] + list(COLOR_MAP.keys())
|
||||
fg_color = get_list_input(f"Select Foreground Color for {display_label}", current_value[0], color_list)
|
||||
bg_color = get_list_input(f"Select Background Color for {display_label}", current_value[1], color_list)
|
||||
fg_color = get_list_input(
|
||||
t(
|
||||
"ui.prompt.select_foreground_color",
|
||||
default="Select Foreground Color for {label}",
|
||||
label=display_label,
|
||||
),
|
||||
current_value[0],
|
||||
color_list,
|
||||
)
|
||||
bg_color = get_list_input(
|
||||
t(
|
||||
"ui.prompt.select_background_color",
|
||||
default="Select Background Color for {label}",
|
||||
label=display_label,
|
||||
),
|
||||
current_value[1],
|
||||
color_list,
|
||||
)
|
||||
|
||||
return [fg_color, bg_color]
|
||||
|
||||
@@ -111,8 +128,13 @@ def edit_value(key: str, display_label: str, current_value: str) -> str:
|
||||
edit_win.border()
|
||||
|
||||
# Display instructions
|
||||
edit_win.addstr(1, 2, f"Editing {display_label}", get_color("settings_default", bold=True))
|
||||
edit_win.addstr(3, 2, "Current Value:", get_color("settings_default"))
|
||||
edit_win.addstr(
|
||||
1,
|
||||
2,
|
||||
t("ui.label.editing", default="Editing {label}", label=display_label),
|
||||
get_color("settings_default", bold=True),
|
||||
)
|
||||
edit_win.addstr(3, 2, t("ui.label.current_value", default="Current Value:"), get_color("settings_default"))
|
||||
|
||||
wrap_width = w - 4 # Account for border and padding
|
||||
wrapped_lines = [current_value[i : i + wrap_width] for i in range(0, len(current_value), wrap_width)]
|
||||
@@ -128,28 +150,36 @@ def edit_value(key: str, display_label: str, current_value: str) -> str:
|
||||
theme_options = [
|
||||
k.split("_", 2)[2].lower() for k in config.loaded_config.keys() if k.startswith("COLOR_CONFIG")
|
||||
]
|
||||
return get_list_input(f"Select {display_label}", current_value, theme_options)
|
||||
return get_list_input(
|
||||
t("ui.prompt.select_value", default="Select {label}", label=display_label),
|
||||
current_value,
|
||||
theme_options,
|
||||
)
|
||||
|
||||
elif key == "language":
|
||||
language_options = config.get_localisation_options()
|
||||
if not language_options:
|
||||
return current_value
|
||||
return get_list_input(f"Select {display_label}", current_value, language_options)
|
||||
return get_list_input(
|
||||
t("ui.prompt.select_value", default="Select {label}", label=display_label),
|
||||
current_value,
|
||||
language_options,
|
||||
)
|
||||
|
||||
elif key == "node_sort":
|
||||
sort_options = ["lastHeard", "name", "hops"]
|
||||
return get_list_input(f"{display_label}", current_value, sort_options)
|
||||
return get_list_input(display_label, current_value, sort_options)
|
||||
|
||||
elif key == "notification_sound":
|
||||
sound_options = ["True", "False"]
|
||||
return get_list_input(f"{display_label}", current_value, sound_options)
|
||||
return get_list_input(display_label, current_value, sound_options)
|
||||
|
||||
elif key == "single_pane_mode":
|
||||
sound_options = ["True", "False"]
|
||||
return get_list_input(f"{display_label}", current_value, sound_options)
|
||||
return get_list_input(display_label, current_value, sound_options)
|
||||
|
||||
# Standard Input Mode (Scrollable)
|
||||
edit_win.addstr(7, 2, "New Value: ", get_color("settings_default"))
|
||||
edit_win.addstr(7, 2, t("ui.label.new_value", default="New Value: "), get_color("settings_default"))
|
||||
curses.curs_set(1)
|
||||
|
||||
scroll_offset = 0 # Determines which part of the text is visible
|
||||
@@ -170,11 +200,20 @@ def edit_value(key: str, display_label: str, current_value: str) -> str:
|
||||
edit_win.border()
|
||||
|
||||
# Redraw static content
|
||||
edit_win.addstr(1, 2, f"Editing {display_label}", get_color("settings_default", bold=True))
|
||||
edit_win.addstr(3, 2, "Current Value:", get_color("settings_default"))
|
||||
edit_win.addstr(
|
||||
1,
|
||||
2,
|
||||
t("ui.label.editing", default="Editing {label}", label=display_label),
|
||||
get_color("settings_default", bold=True),
|
||||
)
|
||||
edit_win.addstr(
|
||||
3, 2, t("ui.label.current_value", default="Current Value:"), get_color("settings_default")
|
||||
)
|
||||
for i, line in enumerate(wrapped_lines[:4]):
|
||||
edit_win.addstr(4 + i, 2, line, get_color("settings_default"))
|
||||
edit_win.addstr(7, 2, "New Value: ", get_color("settings_default"))
|
||||
edit_win.addstr(
|
||||
7, 2, t("ui.label.new_value", default="New Value: "), get_color("settings_default")
|
||||
)
|
||||
|
||||
visible_text = user_input[scroll_offset : scroll_offset + input_width]
|
||||
edit_win.addstr(row, col, " " * input_width, get_color("settings_default"))
|
||||
@@ -278,10 +317,11 @@ def display_menu() -> tuple[Any, Any, List[str]]:
|
||||
# Add Save button to the main window
|
||||
if menu_state.show_save_option:
|
||||
save_position = menu_height - 2
|
||||
save_label = t("ui.save_changes", default=save_option)
|
||||
menu_win.addstr(
|
||||
save_position,
|
||||
(w - len(save_option)) // 2,
|
||||
save_option,
|
||||
(w - len(save_label)) // 2,
|
||||
save_label,
|
||||
get_color("settings_save", reverse=(menu_state.selected_index == len(menu_state.current_menu))),
|
||||
)
|
||||
|
||||
@@ -505,7 +545,7 @@ def json_editor(stdscr: curses.window, menu_state: Any) -> None:
|
||||
# Exit the editor
|
||||
if made_changes:
|
||||
save_prompt = get_list_input(
|
||||
"You have unsaved changes. Save before exiting?",
|
||||
t("ui.confirm.save_before_exit", default="You have unsaved changes. Save before exiting?"),
|
||||
None,
|
||||
["Yes", "No", "Cancel"],
|
||||
mandatory=True,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from typing import Optional
|
||||
|
||||
import contact.ui.default_config as config
|
||||
from contact.utilities.control_utils import parse_ini_file
|
||||
|
||||
_translations = {}
|
||||
_language = None
|
||||
|
||||
|
||||
def _load_translations() -> None:
|
||||
global _translations, _language
|
||||
language = config.language
|
||||
if _translations and _language == language:
|
||||
return
|
||||
|
||||
translation_file = config.get_localisation_file(language)
|
||||
_translations, _ = parse_ini_file(translation_file)
|
||||
_language = language
|
||||
|
||||
|
||||
def t(key: str, default: Optional[str] = None, **kwargs: object) -> str:
|
||||
_load_translations()
|
||||
text = _translations.get(key, default if default is not None else key)
|
||||
try:
|
||||
return text.format(**kwargs)
|
||||
except Exception:
|
||||
return text
|
||||
|
||||
|
||||
def t_text(text: str, **kwargs: object) -> str:
|
||||
return t(text, default=text, **kwargs)
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Optional, List
|
||||
from contact.ui.colors import get_color
|
||||
from contact.ui.nav_utils import move_highlight, draw_arrows, wrap_text
|
||||
from contact.ui.dialog import dialog
|
||||
from contact.utilities.i18n import t, t_text
|
||||
from contact.utilities.validation_rules import get_validation_for
|
||||
from contact.utilities.singleton import menu_state
|
||||
|
||||
@@ -28,7 +29,7 @@ def invalid_input(window: curses.window, message: str, redraw_func: Optional[cal
|
||||
"""Displays an invalid input message in the given window and redraws if needed."""
|
||||
cursor_y, cursor_x = window.getyx()
|
||||
curses.curs_set(0)
|
||||
dialog("Invalid Input", message)
|
||||
dialog(t("ui.dialog.invalid_input", default="Invalid Input"), t_text(message))
|
||||
if redraw_func:
|
||||
redraw_func() # Redraw the original window content that got obscured
|
||||
else:
|
||||
@@ -72,6 +73,7 @@ def get_text_input(prompt: str, selected_config: str, input_type: str) -> Option
|
||||
input_win.attrset(get_color("window_frame"))
|
||||
input_win.border()
|
||||
|
||||
prompt = t_text(prompt)
|
||||
# Wrap the prompt text
|
||||
wrapped_prompt = wrap_text(prompt, wrap_width=input_width)
|
||||
row = 1
|
||||
@@ -82,7 +84,7 @@ def get_text_input(prompt: str, selected_config: str, input_type: str) -> Option
|
||||
if row >= height - 3: # Prevent overflow
|
||||
break
|
||||
|
||||
prompt_text = "Enter new value: "
|
||||
prompt_text = t("ui.prompt.enter_new_value", default="Enter new value: ")
|
||||
input_win.addstr(row + 1, margin, prompt_text, get_color("settings_default"))
|
||||
|
||||
input_win.refresh()
|
||||
@@ -125,41 +127,58 @@ def get_text_input(prompt: str, selected_config: str, input_type: str) -> Option
|
||||
menu_state.need_redraw = True
|
||||
|
||||
if not user_input.strip():
|
||||
invalid_input(input_win, "Value cannot be empty.", redraw_func=redraw_input_win)
|
||||
invalid_input(
|
||||
input_win,
|
||||
t("ui.error.value_empty", default="Value cannot be empty."),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
|
||||
length = len(user_input)
|
||||
if min_length == max_length and max_length is not None:
|
||||
if length != min_length:
|
||||
invalid_input(
|
||||
input_win, f"Value must be exactly {min_length} characters long.", redraw_func=redraw_input_win
|
||||
input_win,
|
||||
t("ui.error.value_exact_length", default="Value must be exactly {length} characters long.", length=min_length),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
if length < min_length:
|
||||
invalid_input(
|
||||
input_win,
|
||||
f"Value must be at least {min_length} characters long.",
|
||||
t("ui.error.value_min_length", default="Value must be at least {length} characters long.", length=min_length),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
if max_length is not None and length > max_length:
|
||||
invalid_input(
|
||||
input_win,
|
||||
f"Value must be no more than {max_length} characters long.",
|
||||
t("ui.error.value_max_length", default="Value must be no more than {length} characters long.", length=max_length),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
|
||||
if input_type is int:
|
||||
if not user_input.isdigit():
|
||||
invalid_input(input_win, "Only numeric digits (0–9) allowed.", redraw_func=redraw_input_win)
|
||||
invalid_input(
|
||||
input_win,
|
||||
t("ui.error.digits_only", default="Only numeric digits (0-9) allowed."),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
|
||||
int_val = int(user_input)
|
||||
if not (min_value <= int_val <= max_value):
|
||||
invalid_input(
|
||||
input_win, f"Enter a number between {min_value} and {max_value}.", redraw_func=redraw_input_win
|
||||
input_win,
|
||||
t(
|
||||
"ui.error.number_range",
|
||||
default="Enter a number between {min_value} and {max_value}.",
|
||||
min_value=min_value,
|
||||
max_value=max_value,
|
||||
),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -172,12 +191,21 @@ def get_text_input(prompt: str, selected_config: str, input_type: str) -> Option
|
||||
if not (min_value <= float_val <= max_value):
|
||||
invalid_input(
|
||||
input_win,
|
||||
f"Enter a number between {min_value} and {max_value}.",
|
||||
t(
|
||||
"ui.error.number_range",
|
||||
default="Enter a number between {min_value} and {max_value}.",
|
||||
min_value=min_value,
|
||||
max_value=max_value,
|
||||
),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
except ValueError:
|
||||
invalid_input(input_win, "Must be a valid floating point number.", redraw_func=redraw_input_win)
|
||||
invalid_input(
|
||||
input_win,
|
||||
t("ui.error.float_invalid", default="Must be a valid floating point number."),
|
||||
redraw_func=redraw_input_win,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
curses.curs_set(0)
|
||||
@@ -276,13 +304,21 @@ def get_admin_key_input(current_value: List[bytes]) -> Optional[List[str]]:
|
||||
while True:
|
||||
admin_key_win.erase()
|
||||
admin_key_win.border()
|
||||
admin_key_win.addstr(1, 2, "Edit up to 3 Admin Keys:", get_color("settings_default", bold=True))
|
||||
admin_key_win.addstr(
|
||||
1,
|
||||
2,
|
||||
t("ui.prompt.edit_admin_keys", default="Edit up to 3 Admin Keys:"),
|
||||
get_color("settings_default", bold=True),
|
||||
)
|
||||
|
||||
# Display current values, allowing editing
|
||||
for i, line in enumerate(user_values):
|
||||
prefix = "→ " if i == cursor_pos else " " # Highlight the current line
|
||||
admin_key_win.addstr(
|
||||
3 + i, 2, f"{prefix}Admin Key {i + 1}: ", get_color("settings_default", bold=(i == cursor_pos))
|
||||
3 + i,
|
||||
2,
|
||||
f"{prefix}{t('ui.label.admin_key', default='Admin Key')} {i + 1}: ",
|
||||
get_color("settings_default", bold=(i == cursor_pos)),
|
||||
)
|
||||
admin_key_win.addstr(3 + i, 18, line) # Align text for easier editing
|
||||
|
||||
@@ -292,7 +328,7 @@ def get_admin_key_input(current_value: List[bytes]) -> Optional[List[str]]:
|
||||
|
||||
# Show error message if needed
|
||||
if invalid_input:
|
||||
admin_key_win.addstr(7, 2, invalid_input, get_color("settings_default", bold=True))
|
||||
admin_key_win.addstr(7, 2, t_text(invalid_input), get_color("settings_default", bold=True))
|
||||
|
||||
admin_key_win.refresh()
|
||||
key = admin_key_win.getch()
|
||||
@@ -312,7 +348,10 @@ def get_admin_key_input(current_value: List[bytes]) -> Optional[List[str]]:
|
||||
curses.curs_set(0)
|
||||
return user_values # Return the edited Base64 values
|
||||
else:
|
||||
invalid_input = "Error: Each key must be valid Base64 and 32 bytes long!"
|
||||
invalid_input = t(
|
||||
"ui.error.admin_key_invalid",
|
||||
default="Error: Each key must be valid Base64 and 32 bytes long!",
|
||||
)
|
||||
elif key == curses.KEY_UP: # Move cursor up
|
||||
cursor_pos = (cursor_pos - 1) % len(user_values)
|
||||
elif key == curses.KEY_DOWN: # Move cursor down
|
||||
@@ -353,13 +392,21 @@ def get_repeated_input(current_value: List[str]) -> Optional[str]:
|
||||
def redraw():
|
||||
repeated_win.erase()
|
||||
repeated_win.border()
|
||||
repeated_win.addstr(1, 2, "Edit up to 3 Values:", get_color("settings_default", bold=True))
|
||||
repeated_win.addstr(
|
||||
1,
|
||||
2,
|
||||
t("ui.prompt.edit_values", default="Edit up to 3 Values:"),
|
||||
get_color("settings_default", bold=True),
|
||||
)
|
||||
|
||||
win_h, win_w = repeated_win.getmaxyx()
|
||||
for i, line in enumerate(user_values):
|
||||
prefix = "→ " if i == cursor_pos else " "
|
||||
repeated_win.addstr(
|
||||
3 + i, 2, f"{prefix}Value{i + 1}: ", get_color("settings_default", bold=(i == cursor_pos))
|
||||
3 + i,
|
||||
2,
|
||||
f"{prefix}{t('ui.label.value', default='Value')}{i + 1}: ",
|
||||
get_color("settings_default", bold=(i == cursor_pos)),
|
||||
)
|
||||
repeated_win.addstr(3 + i, 18, line[: max(0, win_w - 20)]) # Prevent overflow
|
||||
|
||||
@@ -434,9 +481,21 @@ def get_fixed32_input(current_value: int) -> int:
|
||||
def redraw():
|
||||
fixed32_win.erase()
|
||||
fixed32_win.border()
|
||||
fixed32_win.addstr(1, 2, "Enter an IP address (xxx.xxx.xxx.xxx):", get_color("settings_default", bold=True))
|
||||
fixed32_win.addstr(3, 2, f"Current: {ip_string}", get_color("settings_default"))
|
||||
fixed32_win.addstr(5, 2, f"New value: {user_input}", get_color("settings_default"))
|
||||
fixed32_win.addstr(
|
||||
1,
|
||||
2,
|
||||
t("ui.prompt.enter_ip", default="Enter an IP address (xxx.xxx.xxx.xxx):"),
|
||||
get_color("settings_default", bold=True),
|
||||
)
|
||||
fixed32_win.addstr(
|
||||
3, 2, f"{t('ui.label.current', default='Current')}: {ip_string}", get_color("settings_default")
|
||||
)
|
||||
fixed32_win.addstr(
|
||||
5,
|
||||
2,
|
||||
f"{t('ui.label.new_value', default='New value')}: {user_input}",
|
||||
get_color("settings_default"),
|
||||
)
|
||||
fixed32_win.refresh()
|
||||
|
||||
while True:
|
||||
@@ -467,7 +526,12 @@ def get_fixed32_input(current_value: int) -> int:
|
||||
curses.curs_set(0)
|
||||
return int(ipaddress.ip_address(user_input))
|
||||
else:
|
||||
fixed32_win.addstr(7, 2, "Invalid IP address. Try again.", get_color("settings_default", bold=True))
|
||||
fixed32_win.addstr(
|
||||
7,
|
||||
2,
|
||||
t("ui.error.ip_invalid", default="Invalid IP address. Try again."),
|
||||
get_color("settings_default", bold=True),
|
||||
)
|
||||
fixed32_win.refresh()
|
||||
curses.napms(1500)
|
||||
user_input = ""
|
||||
@@ -513,15 +577,17 @@ def get_list_input(
|
||||
visible_height = list_win.getmaxyx()[0] - 5
|
||||
|
||||
def redraw_list_ui():
|
||||
translated_prompt = t_text(prompt)
|
||||
list_win.erase()
|
||||
list_win.border()
|
||||
list_win.addstr(1, 2, prompt, get_color("settings_default", bold=True))
|
||||
list_win.addstr(1, 2, translated_prompt, get_color("settings_default", bold=True))
|
||||
|
||||
win_h, win_w = list_win.getmaxyx()
|
||||
pad_w = max(1, win_w - 8)
|
||||
for idx, item in enumerate(list_options):
|
||||
color = get_color("settings_default", reverse=(idx == selected_index))
|
||||
list_pad.addstr(idx, 0, item[:pad_w].ljust(pad_w), color)
|
||||
display_item = t_text(item)
|
||||
list_pad.addstr(idx, 0, display_item[:pad_w].ljust(pad_w), color)
|
||||
|
||||
list_win.refresh()
|
||||
list_pad.refresh(
|
||||
|
||||
Reference in New Issue
Block a user