Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Internationalization (i18n)

The cgd1-rs-controller GTK 4 application supports UI translation via the Fluent localization system. Translation files (.ftl) are embedded into the binary at compile time using rust-embed, so no external files are needed at runtime.

Supported Languages

LanguageCodeStatus
EnglishenFallback (all keys present)
GermandeComplete

The application auto-detects the system language at startup via i18n_embed::DesktopLanguageRequester. If the requested language is not available, it falls back to English.

Selecting a Language

The language is determined by the system locale. You can override it via environment variables:

# English
LANG=en ./target/debug/cgd1-controller

# German
LANG=de_DE.UTF-8 ./target/debug/cgd1-controller

# Unsupported locale -> falls back to English
LANG=fr_FR.UTF-8 ./target/debug/cgd1-controller

Architecture

File Layout

cgd1-rs-controller/
├── i18n.toml                          # i18n-embed configuration
├── i18n/
│   ├── en/
│   │   └── cgd1-rs-controller.ftl     # English (fallback)
│   └── de/
│       └── cgd1-rs-controller.ftl     # German
└── src/
    └── i18n.rs                        # Loader, macro, helpers

The .ftl filename must match the crate name (cgd1-rs-controller) — this is how the fl! macro resolves the domain at compile time.

Core Module (src/i18n.rs)

The i18n module provides:

  • fl! macro — Compile-time-validated translation lookup. Missing keys cause build failures.
  • get() — Runtime lookup for dynamic keys (e.g. weekday names selected by index).
  • get_str() / get_int() — Runtime lookup with a single string/integer argument.
  • init() — Forces lazy initialization of the language loader. Called early in main().

Initialization

In src/main.rs:

mod i18n;

fn main() {
    // ... tracing setup ...
    i18n::init();
    let app = app::ClockControllerApp::new(cli.backend);
    app.run();
}

Adding a New Language

  1. Create a new directory: cgd1-rs-controller/i18n/{lang}/ (e.g. fr/).
  2. Copy cgd1-rs-controller/i18n/en/cgd1-rs-controller.ftl to the new directory.
  3. Translate all values to the target language.
  4. Rebuild — the new language is automatically embedded via rust-embed and selected when the system locale matches.

No changes to i18n.toml or source code are needed. The rust-embed macro scans the i18n/ directory at compile time and includes all subdirectories.

Translation Key Conventions

  • Use kebab-case for message IDs (e.g. status-no-device-connected).
  • Arguments use { $name } syntax in .ftl files.
  • Group keys by feature area with comment headers (e.g. # Alarm editor, # Audio editor).
  • Shared keys (e.g. status-no-device-connected, button-read) are defined once in the “Common status messages” section and reused across dialogs.

Using the fl! Macro (Contributors)

Static strings

For strings with known keys at compile time:

#![allow(unused)]
fn main() {
use crate::fl;

let label = Label::builder().label(&fl!("status-disconnected")).build();
}

Strings with arguments

#![allow(unused)]
fn main() {
status.set_label(&fl!("status-connected", addr = addr.to_string()));
}

Cast expressions must be parenthesized:

#![allow(unused)]
fn main() {
// Correct
status.set_label(&fl!("status-devices-found", count = (found.len() as i64)));

// Wrong — won't compile
status.set_label(&fl!("status-devices-found", count = found.len() as i64));
}

Dynamic lookups

For runtime-determined keys (e.g. weekday names looked up by index), use get():

#![allow(unused)]
fn main() {
use crate::i18n::get;

let key = match weekday {
    Mon => "weekday-mon",
    Tue => "weekday-tue",
    // ...
};
let day_name = get(key);
}

What not to translate

  • Log messages (tracing::debug!, warn!, info!, error!) — developer-facing.
  • Error strings in ClockError::Parse("device not found") — internal error handling.
  • Ringtone names from RingtoneSignature::name() — device firmware identifiers.
  • Timezone labels in common.rs — technical identifiers (UTC offsets).
  • Sensor units (°C, %) — universal.
  • CLI help text — could be translated in a future phase using clap’s i18n support.