Skip to content

feat(i18n): gettext layer and Spanish (Castilian) translation - #2

Open
gnacho wants to merge 5 commits into
superuser-miguel:mainfrom
gnacho:feat/i18n-es
Open

gnacho wants to merge 5 commits into
superuser-miguel:mainfrom
gnacho:feat/i18n-es

Conversation

@gnacho

@gnacho gnacho commented Aug 25, 2026

Copy link
Copy Markdown

What

This PR gives Foresight a real translation layer and ships the first catalogue: Spanish (Castilian). The UI is now fully translatable and translated — menus, dialogs, the capability registry, and the rsync exit-code messages.

Foresight had no i18n at all. This adds the standard GNOME/gettext setup and routes every user-facing string through it.

How

  • po/ catalogue — LINGUAS, POTFILES, meson.build with i18n.gettext, and foresight.pot. Meson compiles and installs the .mo files into localedir.
  • gettext-rs + domain setup — main() calls setlocale/bindtextdomain/textdomain before any widget is built, so the translatable="yes" strings in the compiled .ui (from the _("…") calls in window.blp) resolve through the same domain.
  • i18n helpers (crates/foresight/src/i18n.rs) — i18n, i18n_f (positional {}), i18n_k (named {name}), ni18n, and gettext_noop for the static capability table. Rust's format! can't take a translated string as its format, so i18n_f/i18n_k do placeholder substitution after gettext.
  • rsync-events now translates its exit-code messages through gettext-rs — a plain libc binding, not a GUI dependency, so the crate stays testable on any host.
  • es.po — full Castilian Spanish catalogue.
  • CONTRIBUTING.md — documents the translation workflow so new languages are easy to add.

Extraction

xgettext is configured with the keywords the code actually uses. The Blueprint source is extracted via --keyword=_; the Rust sources via the i18n* keywords. No --language is forced, so xgettext's C parser doesn't trip over Rust lifetimes in the .rs files (learned by measuring).

Verification

  • cargo test --workspace passes (the one timing-sensitive cancel test is flaky on fast machines — pre-existing, verified against the base commit).
  • cargo clippy --all-targets -- -D warnings and cargo fmt --check clean.
  • Meson build, meson test (metainfo validation) and install of the .mo file verified.
  • The catalogue loads at runtime: gettext resolves e.g. "Dry Run" → "Simulación", "Sync completed." → "Sincronización completada." with the installed es locale.

Suggested follow-ups (not in this PR)

These are the author's calls, so I left them out:

  • A LICENSE file (the Cargo.toml and meson.build already say GPL-3.0-or-later).
  • SECURITY.md, CODE_OF_CONDUCT.md and .github/ISSUE_TEMPLATE/ — standard community files for a public repo.

Closes #1

gnacho added 5 commits August 25, 2026 19:48
Adds a po/ catalogue compiled by i18n.gettext and a gettext-rs setup that
initialises the text domain in main() before any widget is built. The
translatable="yes" strings in the compiled .ui (from the _("...")
calls in window.blp) resolve through the same domain.

Extraction is configured in po/meson.build with the keywords the code
actually uses (i18n, i18n_f, i18n_k, ni18n, gettext_noop, gettext).
POTFILES lists both the Blueprint source and the Rust files that carry
translatable strings; no --language is forced so xgettext's C parser
doesn't choke on Rust lifetimes in the .rs files.
Wraps every string the user sees in i18n/i18n_f/i18n_k/ni18n so the
catalogue actually gets used, and marks the static capability-registry
strings with gettext_noop so xgettext extracts them (translated at
render time). rsync-events' exit-code messages now go through gettext
too — a plain libc binding, not a GUI dependency, so the crate stays
testable on any host.

Rust's format! cannot take a translated string as its format, so
i18n_f/i18n_k do the placeholder substitution after gettext.
Full es.po catalogue and an es entry in LINGUAS and the metainfo
<languages> block. Castilian Spanish throughout.
Uses ngettext for the change/file/rule counts instead of the hardcoded
(s) plural, and routes the remaining loose labels (Starting…, Scanning…,
Done, Choose a preset…, and the Exclude/Include dropdown) through
gettext. The dropdown strings in window.blp now use _("...") so
blueprint-compiler marks them translatable.
@superuser-miguel

Copy link
Copy Markdown
Owner

Thank you for this — a whole translation layer and a complete catalogue is a lot of work, and the approach (standard gettext through Meson, strings routed through helpers, CONTRIBUTING.md for the next translator) is the right one.

I want to review this a little more. There's a lot more that will be done, and it would be more work to add this now. Two things first, both my doing rather than yours:

  1. It conflicts with main now. 1.0.1 landed while this was waiting and touched window.rs, job.rs, capabilities.rs and rsync-events — the same files. It also added new user-facing strings (filter-rule warnings, a confirmation dialog) that aren't in the catalogue.
  2. 1.0.2 is about to touch them again with the fixes for your bug reports.

@gnacho

gnacho commented Sep 29, 2026

Copy link
Copy Markdown
Author

Works for me. I'll hold until 1.0.2 is out, then rebase and pick up the new strings (filter-rule warnings, the confirmation dialog, whatever else lands) in a single catalogue update.

This branch has not been deployed

No deployments
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.

Thank you!

2 participants