diff --git a/contact/ui/control_ui.py b/contact/ui/control_ui.py index 4145179..0f3a2fa 100644 --- a/contact/ui/control_ui.py +++ b/contact/ui/control_ui.py @@ -45,7 +45,7 @@ parent_dir = os.path.abspath(os.path.join(script_dir, os.pardir)) # Paths # locals_dir = os.path.dirname(os.path.abspath(sys.argv[0])) # Current script directory -translation_file = os.path.join(parent_dir, "localisations", "en.ini") +translation_file = config.get_localisation_file(config.language) # config_folder = os.path.join(locals_dir, "node-configs") config_folder = os.path.abspath(config.node_configs_file_path) @@ -54,6 +54,12 @@ config_folder = os.path.abspath(config.node_configs_file_path) field_mapping, help_text = parse_ini_file(translation_file) +def reload_translations() -> None: + global translation_file, field_mapping, help_text + translation_file = config.get_localisation_file(config.language) + field_mapping, help_text = parse_ini_file(translation_file) + + def display_menu() -> tuple[object, object]: # if help_win: # min_help_window_height = 6 @@ -411,6 +417,7 @@ def settings_menu(stdscr: object, interface: object) -> None: menu_state.menu_path.append("App Settings") menu_state.menu_index.append(menu_state.selected_index) json_editor(stdscr, menu_state) # Open the App Settings menu + reload_translations() menu_state.current_menu = menu["Main Menu"] menu_state.menu_path = ["Main Menu"] menu_state.start_index.pop() diff --git a/contact/ui/default_config.py b/contact/ui/default_config.py index 49644d4..6b56f57 100644 --- a/contact/ui/default_config.py +++ b/contact/ui/default_config.py @@ -1,7 +1,7 @@ import json import logging import os -from typing import Dict +from typing import Dict, List, Optional from contact.ui.colors import setup_colors # Get the parent directory of the script @@ -65,6 +65,44 @@ json_file_path = os.path.join(config_root, "config.json") log_file_path = os.path.join(config_root, "client.log") db_file_path = os.path.join(config_root, "client.db") node_configs_file_path = os.path.join(config_root, "node-configs/") +localisations_dir = os.path.join(parent_dir, "localisations") + + +def get_localisation_options(localisations_path: Optional[str] = None) -> List[str]: + """ + Return available localisation codes from the localisations folder. + """ + localisations_path = localisations_path or localisations_dir + if not os.path.isdir(localisations_path): + return [] + + options = [] + for filename in os.listdir(localisations_path): + if filename.startswith(".") or not filename.endswith(".ini"): + continue + options.append(os.path.splitext(filename)[0]) + + return sorted(options) + + +def get_localisation_file(language: str, localisations_path: Optional[str] = None) -> str: + """ + Return a valid localisation file path, falling back to a default when missing. + """ + localisations_path = localisations_path or localisations_dir + available = get_localisation_options(localisations_path) + if not available: + return os.path.join(localisations_path, "en.ini") + + normalized = (language or "").strip().lower() + if normalized.endswith(".ini"): + normalized = normalized[:-4] + + if normalized in available: + return os.path.join(localisations_path, f"{normalized}.ini") + + fallback = "en" if "en" in available else available[0] + return os.path.join(localisations_path, f"{fallback}.ini") def format_json_single_line_arrays(data: Dict[str, object], indent: int = 4) -> str: @@ -180,6 +218,8 @@ def initialize_config() -> Dict[str, object]: "node_favorite": ["cyan", "green"], "node_ignored": ["red", "black"], } + available_languages = get_localisation_options() + default_language = "en" if "en" in available_languages else (available_languages[0] if available_languages else "en") default_config_variables = { "channel_list_16ths": "3", "node_list_16ths": "5", @@ -187,6 +227,7 @@ def initialize_config() -> Dict[str, object]: "db_file_path": db_file_path, "log_file_path": log_file_path, "node_configs_file_path": node_configs_file_path, + "language": default_language, "message_prefix": ">>", "sent_message_prefix": ">> Sent", "notification_symbol": "*", @@ -230,7 +271,7 @@ def assign_config_variables(loaded_config: Dict[str, object]) -> None: global db_file_path, log_file_path, node_configs_file_path, message_prefix, sent_message_prefix global notification_symbol, ack_implicit_str, ack_str, nak_str, ack_unknown_str global node_list_16ths, channel_list_16ths, single_pane_mode - global theme, COLOR_CONFIG + global theme, COLOR_CONFIG, language global node_sort, notification_sound channel_list_16ths = loaded_config["channel_list_16ths"] @@ -239,6 +280,7 @@ def assign_config_variables(loaded_config: Dict[str, object]) -> None: db_file_path = loaded_config["db_file_path"] log_file_path = loaded_config["log_file_path"] node_configs_file_path = loaded_config.get("node_configs_file_path") + language = loaded_config["language"] message_prefix = loaded_config["message_prefix"] sent_message_prefix = loaded_config["sent_message_prefix"] notification_symbol = loaded_config["notification_symbol"] diff --git a/contact/ui/menus.py b/contact/ui/menus.py index b2451c8..7643305 100644 --- a/contact/ui/menus.py +++ b/contact/ui/menus.py @@ -1,6 +1,5 @@ import base64 import logging -import os from collections import OrderedDict from typing import Any, Union, Dict @@ -8,11 +7,6 @@ from typing import Any, Union, Dict from google.protobuf.message import Message from meshtastic.protobuf import channel_pb2, config_pb2, module_config_pb2 - -locals_dir = os.path.dirname(os.path.abspath(__file__)) -translation_file = os.path.join(locals_dir, "localisations", "en.ini") - - def encode_if_bytes(value: Any) -> str: """Encode byte values to base64 string.""" if isinstance(value, bytes): diff --git a/contact/ui/user_config.py b/contact/ui/user_config.py index 30f5875..5723481 100644 --- a/contact/ui/user_config.py +++ b/contact/ui/user_config.py @@ -66,6 +66,12 @@ def edit_value(key: str, current_value: str) -> str: ] return get_list_input("Select Theme", current_value, theme_options) + elif key == "language": + language_options = config.get_localisation_options() + if not language_options: + return current_value + return get_list_input("Select Language", current_value, language_options) + elif key == "node_sort": sort_options = ["lastHeard", "name", "hops"] return get_list_input("Sort By", current_value, sort_options)