Skip to content

fix(macos): localize the app bundle's Info.plist strings - #1470

Open
eibidia wants to merge 1 commit into
AprilNEA:masterfrom
eibidia:fix/localize-macos-info-plist
Open

eibidia wants to merge 1 commit into
AprilNEA:masterfrom
eibidia:fix/localize-macos-info-plist

Conversation

@eibidia

@eibidia eibidia commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

OpenLogi.app declares no localizations, which leaks English into a localized app in two places the UI catalogs cannot reach:

  • The camera permission prompt. macOS draws the dialog chrome in the system language and the reason underneath from NSCameraUsageDescription, which only exists in English.
  • The menu items AppKit inserts itself. On a Spanish system, OpenLogi 0.8.5's Edit menu currently reads Deshacer · Rehacer · Cortar · Copiar · Pegar · Seleccionar todo · Writing Tools · AutoFill · Start Dictation · Emoji & Symbols. The first six come from the catalog; the last four are AppKit's, and they follow the bundle's localization, which is the English development region.

This moves the camera usage text into the shared catalogs and has xtask localize the bundle from them.

Changes

  • xtask
    • support/info_plist.rs: stamp_privacy_usage_descriptions, which stamped an English literal, is replaced by localize_app. It reads crates/openlogi-ui/locales, stamps the English value into Contents/Info.plist, declares every locale in openlogi_core::locale::SUPPORTED in CFBundleLocalizations, and writes Contents/Resources/<localization>.lproj/InfoPlist.strings only where a catalog's value differs from English. zh-CN and zh-TW map to Apple's zh-Hans and zh-Hant; every other code is already Apple's name. It refuses an empty English value, since a camera request from a bundle without a usage description kills the app.
    • The dev bundle is rebuilt in place, so strings files an earlier run left behind are removed first. An .lproj holding anything else is left alone.
    • commands/macos/bundle.rs and dev_bundle.rs call it where the old stamp ran, before identity stamping, verification and signing.
    • support/info_plist/tests.rs: every shipped locale maps to a distinct .lproj; the real catalogs carry every localized key; a missing key names the file and key; strings are written only for translated locales and CFBundleLocalizations matches SUPPORTED; stale strings are removed without touching foreign files; an empty English value is refused.
  • openlogi-ui — new permissions.camera_usage_description in all 23 catalogs, placed after camera_permission_description. The English value is byte-identical to the old literal; Spanish is translated; the rest carry the English fill-in until Crowdin covers them.
  • openlogi-desktopbundle/desktop-dev/Info.plist drops its copy of the English text, with a comment saying xtask stamps it, so the text has one owner.

Nothing here touches identity: identity::verify checks CFBundleIdentifier, the bundle names and CFBundleIconFile, none of which change, and the helpers are untouched.

Testing

No Rust toolchain on the machine I did this on, so please treat CI as the gate.

  • Not run: cargo test -p xtask, cargo clippy -p xtask --all-targets -- -D warnings, cargo test -p openlogi-ui locale, cargo xtask macos bundle, cargo xtask macos dev-bundle.

  • Formatting: all four changed Rust files are byte-identical to stable rustfmt's output with edition 2024 and max width 100, checked through the Rust Playground formatter, with mod x; declarations stubbed so rustfmt would not try to open sibling files.

  • External APIs, checked against the published source of the versions in Cargo.lock rather than recalled: plist 1.10.0 (Dictionary: FromIterator<(K: Into<String>, V: Into<Value>)>, Value::from_file / to_file_xml / as_array / as_string), toml 0.8.23 (Table = Map<String, Value>, Map::get<Q>, Value::get<I: Index> with impl<T> Index for &T, Value::as_str), and Option::is_none_or (stable since 1.82; MSRV is 1.98).

  • Catalog parity: a Python port of catalog_parity.rs using the test's own line-based parser, calibrated against unmodified origin/master. Result: 22 catalogs, 516 keys; key set, order and placeholders match, and every file parses as TOML.

  • UI language is unaffected. openlogi_core::locale picks the language through sys-locale 0.3.2, whose Apple backend calls CFLocaleCopyPreferredLanguages (the user's preference list), not the bundle-negotiated CFLocaleCopyCurrent. Adding localizations does not change which catalog OpenLogi loads.

  • The artifact format, on macOS 26. A throwaway bundle, not OpenLogi, with CFBundleLocalizations and an es.lproj/InfoPlist.strings written as a UTF-8 XML property list (the format plist::Value::to_file_xml produces), queried through Foundation with -AppleLanguages set per process:

    preference negotiated NSCameraUsageDescription
    es-ES, es-MX es Spanish, from InfoPlist.strings
    pt-BR (declared, no strings file) pt-BR English base value
    zh-Hans-CN zh-Hans English base value
    zh-Hant-TW zh-Hant English base value
    zh-Hant-HK zh-HK English base value

    The same run showed that pt-PT negotiates pt-BR unless pt-PT is declared too; it is, since it is in SUPPORTED.

  • Not runtime-tested with a real OpenLogi build or camera. To check, with macOS set to Spanish: cargo run -p openlogi-desktop, then tccutil reset Camera org.openlogi.openlogi-dev and open a Logitech webcam's page, so the prompt appears with the Spanish reason. The Edit menu should also show AppKit's items in Spanish.

Note

Declaring localizations also makes OpenLogi selectable under System Settings → General → Language & Region → Applications. Since sys-locale reads the preference list, that setting takes effect for the GUI. The agent and overlay are separate bundles with their own defaults domains, so they keep following the system language there. OpenLogi's own Language setting remains the way to switch all three processes together.

The app bundle declared no localizations, so macOS showed the camera
usage description in English in every language, and AppKit kept the menu
items it inserts itself (Start Dictation, Emoji & Symbols, Writing Tools,
AutoFill) in English as well, since those follow the bundle's localization
rather than the UI catalog.

Move the camera usage text into the shared catalogs as
permissions.camera_usage_description, and have xtask localize the app from
them in both macos bundle and macos dev-bundle, before identity stamping
and signing: stamp the English value, declare every shipped locale in
CFBundleLocalizations (zh-CN and zh-TW map to Apple's zh-Hans and
zh-Hant), and write <localization>.lproj/InfoPlist.strings only where a
catalog's value differs from English. A declared locale without a strings
file falls back to the English value, so catalogs still carrying the
English fill-in need no file.

The dev bundle is rebuilt in place, so strings files from an earlier run
are removed first. The dev Info.plist template drops its copy of the
English text, which now has one owner.
@eibidia
eibidia requested a review from AprilNEA as a code owner September 17, 2026 09:13
@greptile-apps

greptile-apps Bot commented Sep 17, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with the bundle lifecycle, catalog coverage, fallback behavior, and generated localization cleanup coherently implemented.

Summary

This PR moves the macOS camera usage description into the shared locale catalogs and teaches xtask to generate bundle localization metadata before identity verification and signing.

  • Stamps the English fallback and CFBundleLocalizations into the main Info.plist.
  • Generates locale-specific InfoPlist.strings only when a catalog differs from English.
  • Removes stale generated strings from in-place development bundles while preserving unrelated localization resources.
  • Adds catalog coverage, localization mapping, stale cleanup, and invalid-English-value tests.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Read SUPPORTED locales] --> B[Read English catalog]
    B --> C{English usage text non-empty?}
    C -- No --> D[Fail bundle generation]
    C -- Yes --> E[Remove stale InfoPlist.strings]
    E --> F[Read each locale catalog]
    F --> G{Value differs from English?}
    G -- Yes --> H[Write locale lproj InfoPlist.strings]
    G -- No --> I[Use English Info.plist fallback]
    H --> J[Stamp NSCameraUsageDescription and CFBundleLocalizations]
    I --> J
    J --> K[Stamp identity and verify bundle]
    K --> L[Sign app bundle]
Loading

Reviews (1) · Last reviewed commit: "fix(macos): localize the app bundle's In..."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant