Wizard Core Component – Language Bootstrap and GTK Application Startup

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.mo under LOCALEDIR.
  • 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:

  1. User preference (preferred): parse ~/.config/BastionGuard/lang.conf and extract LANGUAGE, LC_MESSAGES, or LANG.
  2. Validate against LOCALEDIR: only accept tokens that map to an existing BastionGuard.mo.
  3. Fallback normalization: if a regional locale is unavailable (e.g., it_IT), try the base language (e.g., it).
  4. Hard fallback: prefer en_US, then en, if available in LOCALEDIR.
  5. 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-8en_US)
  • Strips modifiers (en_US@fooen_US)
  • Supports LANGUAGE lists (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=VALUE pairs
  • 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 .mo availability, not on system locale generation.
  • Deterministic gettext behavior: forcing LANGUAGE and LC_MESSAGES prevents 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.