1. Overview
This source file implements a core bootstrap component for the BastionGuard Setup Wizard. Its primary responsibility is to initialize the GTK application with a reliable, self-contained language selection strategy, ensuring the wizard can present translated strings even on systems where the target locale is not generated yet (e.g., before locale-gen / localedef runs).
The wizard boot process is built around three design goals:
- Translation availability first: choose the wizard UI language based exclusively on the presence of
BastionGuard.mounderLOCALEDIR. - Locale independence: avoid relying on system-generated locales during early setup.
- Deterministic gettext behavior: explicitly force gettext language selection via environment variables.
2. Components and Dependencies
The file integrates the wizard UI and shared styling infrastructure:
- WizardWindow – primary GTK window for the setup wizard
- StyleProvider – global CSS/theme loader via
load_style() - resource – resource lookup helpers for runtime assets
- gettext – translation binding using
bindtextdomain()and related APIs - GTKmm (GTK4) – application entry and window orchestration via
Gtk::Application
3. Translation Discovery in LOCALEDIR
3.1 Translation Presence Check
The helper bastionguard_mo_exists(lang) verifies if the translation catalog is present:
LOCALEDIR/<lang>/LC_MESSAGES/BastionGuard.mo
It uses std::filesystem::exists() with std::error_code to avoid exceptions and remain safe on restricted or unusual filesystems.
3.2 Wizard Language Selection Strategy
pick_wizard_language_from_localedir() chooses the language token for gettext based on a strict order:
- User preference (preferred): parse
~/.config/BastionGuard/lang.confand extractLANGUAGE,LC_MESSAGES, orLANG. - Validate against LOCALEDIR: only accept tokens that map to an existing
BastionGuard.mo. - Fallback normalization: if a regional locale is unavailable (e.g.,
it_IT), try the base language (e.g.,it). - Hard fallback: prefer
en_US, thenen, if available in LOCALEDIR. - Ultimate fallback: return
"C"to run without translations (original strings).
Input normalization ensures gettext tokens remain stable and predictable:
- Trims whitespace
- Strips encoding suffixes (
en_US.UTF-8→en_US) - Strips modifiers (
en_US@foo→en_US) - Supports
LANGUAGElists (e.g.,it:en) by selecting the first token
4. Environment and Locale Bootstrapping
4.1 Forcing gettext Language Without System Locale
Once a valid wizard language is selected (not "C"), gettext is forced using:
setenv("LANGUAGE", wizard_lang, 1)setenv("LC_MESSAGES", wizard_lang, 1)
This ensures gettext message lookup uses BastionGuard’s own translation bundle regardless of the host OS locale configuration.
4.2 Safe Locale Choice for Early Setup
The wizard explicitly sets a locale that is guaranteed to exist on all systems:
std::setlocale(LC_ALL, "C")
This is a deliberate hardening decision: during installation, the desired locale may not exist yet in the OS. By using "C", the wizard avoids runtime errors and still obtains translations through gettext (because gettext language is forced independently).
4.3 gettext Domain Binding
Translation is initialized using the BastionGuard domain:
bindtextdomain("BastionGuard", LOCALEDIR)bind_textdomain_codeset("BastionGuard", "UTF-8")textdomain("BastionGuard")
This enforces UTF-8 decoding for translated strings, which is required for correct rendering in GTK.
5. Optional Language Config Loader
The helper load_lang_conf()~/.config/BastionGuard/lang.conf and applies environment variables defined in the file. It:
- Skips empty lines and comments (
#) - Parses
KEY=VALUEpairs - Applies values via
setenv()
In the wizard flow, this loader complements the LOCALEDIR-based selection by applying any user-configured environment variables that may be relevant for the application runtime.
6. GTK Application Startup
6.1 Application Creation and Styling
The wizard creates the GTK application instance with a dedicated application ID:
Gtk::Application::create("org.BastionGuard.wizard")
Global styling is loaded via:
load_style()
This ensures visual consistency with the BastionGuard UI ecosystem.
6.2 Wizard Window Registration and Run Loop
The main wizard window is created and registered with the application:
BastionGuard::wizard::WizardWindow win;app->add_window(win);win.show();return app->run(argc, argv);
This is the standard GTKmm model for application lifecycle and window ownership.
7. Runtime and Security Considerations
- Installer-safe language selection: translations are selected based on shipped
.moavailability, not on system locale generation. - Deterministic gettext behavior: forcing
LANGUAGEandLC_MESSAGESprevents unexpected translation mismatches. - Crash resistance: filesystem existence checks are non-throwing, and the locale is set to
"C"to avoid missing-locale failures. - Separation of responsibilities: the wizard is responsible for its own UI language; the installer or post-setup steps can later configure system locales formally.
- Operational transparency: stderr logs report which language token was selected and whether translations were found.