# How Locale Negotiation Works in OpenLogi: Reading Host Language Preferences

> Learn how OpenLogi's locale negotiation reads host language preferences by checking user settings and querying the OS. Discover the fallback to English and resolution logic.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-08-30

---

**OpenLogi determines the host’s language preference by first checking explicit user settings, then querying the OS via `sys_locale::get_locale()`, and falling back to English, with all resolution logic centralized in the `openlogi-core` crate.**

OpenLogi implements a deterministic locale negotiation system that ensures consistent language selection across its desktop UI, background agent, and overlay components. According to the AprilNEA/OpenLogi source code, the resolution pipeline lives entirely within [`crates/openlogi-core/src/locale.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/locale.rs) and relies on a strict priority order: stored preferences override system detection, while unrecognized codes gracefully degrade to the default English catalog.

## The Six-Step Locale Resolution Pipeline

The `resolve()` function (lines 56–68 in [`locale.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/locale.rs)) implements a cascading fallback strategy:

1. **Explicit stored setting** – The optional `language` argument passed to `activate()` typically originates from `AppSettings` in `crate::config::AppSettings`.
2. **OS locale detection** – If no valid setting exists, the system calls `sys_locale::get_locale()` to retrieve the host’s BCP‑47 language tag.
3. **String normalization** – Raw locale strings have underscores converted to dashes (e.g., `en_US` → `en-US`) before parsing.
4. **Special-case handling** – Language families like Chinese (`zh`), Portuguese (`pt`), and Norwegian receive custom mapping rules via `special_locale()`.
5. **Supported catalog matching** – For standard cases, `lookup_supported()` uses `fluent_langneg::negotiate_languages` with a lookup strategy against the `SUPPORTED` constant containing all shipped locales.
6. **Global activation** – The final resolved code is passed to `rust_i18n::set_locale()`, which stores it in a process‑wide atomic for UI and agent consumption.

## Core Resolution Logic in [`openlogi-core/src/locale.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/locale.rs)

The heart of the system is the `resolve()` function, which chains optionals to enforce the priority order:

```rust
/// Resolve the locale to apply, preferring an explicit stored `setting`,
/// then the system locale, and finally `"en"`.
fn resolve(setting: Option<&str>) -> &'static str {
    setting
        .and_then(match_supported)               // 1️⃣ explicit setting
        .or_else(|| {
            sys_locale::get_locale()              // 2️⃣ OS locale
                .as_deref()
                .and_then(match_supported)       // 3️⃣ match against SUPPORTED
        })
        .unwrap_or("en")                          // 4️⃣ fallback
}

```

When `match_supported()` (lines 81–112) receives a locale string, it first attempts to parse it into a `LanguageIdentifier` from the `fluent_langneg` crate. The matcher validates the identifier against the catalog returned by `supported_langids()`, which derives from the internal `SUPPORTED` constant listing all available translation files in `crates/openlogi-ui/locales/`.

### Special Locale Handling

The `special_locale()` helper applies deterministic rules for edge cases:

- **Chinese variants** map traditional scripts to specific region codes (`zh-Hant-HK` → `zh-HK`).
- **Portuguese** distinguishes between Brazil and Portugal (`pt-BR` vs `pt`).
- **Norwegian** resolves `nb-NO` and `nn-NO` to the unified `nb` catalog.

## Activating Locales Across Application Components

All OpenLogi processes—GUI, agent, and overlay—call `openlogi_core::locale::activate()` at startup to synchronize language state.

To activate based on stored settings (or system default if none):

```rust
// In the GUI or agent startup
openlogi_core::locale::activate(None);

```

To explicitly override from a command‑line argument or settings picker:

```rust
// User selected “Français” in the Settings pane
openlogi_core::locale::activate(Some("fr"));

```

### Integration Points

- **[`crates/openlogi-desktop/src/services/i18n.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/i18n.rs)** initializes the UI locale during application boot.
- **[`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs)** sets the background agent’s menu‑bar language before tray initialization.
- **[`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs)** passes the invoking process language to ensure the overlay matches the active application context.

## Testing Locale Matching

You can validate the normalization and matching logic directly:

```rust
use openlogi_core::locale::match_supported;

assert_eq!(match_supported("zh-Hant-HK"), Some("zh-HK"));
assert_eq!(match_supported("pt-BR"), Some("pt-BR"));
assert_eq!(match_supported("nb-NO"), Some("nb"));

```

This demonstrates how underspecified or overly specific locale strings collapse to the nearest supported catalog.

## Summary

- OpenLogi centralizes all **locale negotiation** in [`crates/openlogi-core/src/locale.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/locale.rs) to guarantee consistency across processes.
- The resolution order is: **stored user setting** → **OS locale via `sys_locale::get_locale()`** → **English fallback**.
- Special rules handle complex language families (Chinese, Portuguese, Norwegian) before standard lookup.
- The `match_supported()` function validates locales against the `SUPPORTED` constant using `fluent_langneg` negotiation.
- All components call `openlogi_core::locale::activate()` to apply the resolved locale via `rust_i18n::set_locale()`.

## Frequently Asked Questions

### How does OpenLogi prioritize multiple language sources?

OpenLogi uses a strict cascading fallback. It first checks the explicit `language` parameter passed to `activate()`, which typically comes from `AppSettings`. If that is `None` or unrecognized, it queries the host OS via `sys_locale::get_locale()`. Only if both sources fail to produce a match does it default to `"en"` (English).

### What happens if my system locale is not in the supported list?

The `match_supported()` function attempts to negotiate the closest match using the `fluent_langneg` crate. For example, `zh-Hant-HK` resolves to the `zh-HK` catalog. If no reasonable match exists (e.g., an obscure language code), the system falls back to English to ensure the UI remains functional.

### Why are Chinese, Portuguese, and Norwegian handled specially?

These languages have complex regional or script variants that standard BCP‑47 negotiation cannot map cleanly to OpenLogi’s shipped catalogs. The `special_locale()` function encodes explicit business logic—such as mapping `nb-NO` to `nb`—to ensure users receive the most appropriate translation file available.

### Can I force a specific locale for testing or debugging?

Yes. By calling `openlogi_core::locale::activate(Some("fr"))` (or any valid code) before the UI initializes, you override both stored settings and OS detection. This is commonly used in the desktop entry points and integration tests to verify specific localization paths.