Repository navigation
feat(i18n): translate what git operations report - #92
noahbclarkson wants to merge 14 commits into
Conversation
Adds `rgitui_i18n` and wires it into settings, so a language can be chosen and applied without a restart. This is the infrastructure only — the Appearance settings panel is converted as the working example; the other ~1500 user-facing strings are still English literals. English is the source of truth in `src/en.rs` rather than a JSON asset, for the same reason the built-in themes are Rust: it is always present and cannot drift, so any missing translation has something to fall back to. Translations are JSON under `assets/locales/`, embedded at build time, with user overrides from the config directory so a translator can iterate against a release build. The catalogue is a process-wide RwLock rather than a GPUI global, because error strings are built on background threads and need translating too. The tradeoff is that GPUI cannot know a language change dirties the frame, so `set_language` must be followed by `cx.refresh_windows()`. `t!` and `tp!` return `SharedString`, which every `rgitui_ui` builder already accepts — converting a call site is a one-line change. Locale files are validated by tests: unknown keys fail (they would silently never render), placeholders must survive translation, and plural forms must be declared in pairs. Missing keys are deliberately allowed so partial translations are worth shipping. Simplified Chinese ships as the first translation and needs native review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the loader
Keys: t!, tp! and the new key! take literal keys and evaluate const
assertions against the English table, so a key missing from en.rs, or named
arguments that do not match the English message's placeholders, fail the
build at the call site instead of rendering the raw key. tp! fills {count}
itself. en.rs is one `key => message` line per entry, and an unsorted or
duplicated table fails the build naming the first key out of place, which is
what lets the lookup be a binary search. A test reports English keys that no
code uses.
Language changes: set_language takes the App, refreshes every window and
replaces an ActiveLanguage global, so callers no longer have to remember
refresh_windows, and views that keep translated text outside render can
observe it. The settings view reads the selection from rgitui_i18n instead of
keeping its own copy.
Matching: Traditional Chinese systems (zh-TW, zh-HK, zh-MO, zh-Hant) matched
the shipped zh-CN on the primary subtag alone and got Simplified characters;
matching now requires the same script, stated or implied. POSIX tags such as
zh_CN.UTF-8 are accepted. The system language and per-locale missing counts
are read once at init rather than on every render, and the count no longer
includes plural forms a locale's rule never selects, so a complete zh-CN
stops reporting one missing string.
Loader: user locale files must be regular files under 1 MiB, are read in path
order, and are layered over a shipped locale with the same id instead of
replacing it; keys English does not define are logged. An unknown plural_rule
is an error rather than silently becoming one_other. Shipped locales must use
exactly the English placeholders and only the plural forms their rule selects.
The UI font gains Simplified Chinese fallback families when the UI is
Simplified Chinese, and only then, so Japanese and Traditional Chinese text in
an English UI keeps the platform's glyph choice. The cached font is keyed on
the locale as well as the family.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports @VedaChen's translation of the interface from #89 onto rgitui_i18n. Their call-site work is kept file by file; the TrKey table, the settings language enum and the git-layer changes are not. - 469 strings carried over as written, under keys named by the panel that shows them (toolbar.push, confirm.force_push, branch_health.merged). - Every `tr(TrKey::X)` call is now a compile-checked t!("key"); keys held as data (graph menu rows, toolbar tooltips, the settings quick reference) use key!. Positional `{}` templates filled by chained replacen are named placeholders filled in one pass, so user text containing "{}" can no longer swallow the next value. - Sentence fragments joined with format! are whole sentences: "Pending changes on {branch}", "HEAD is detached at {commit}", "Return to {branch}", "Inspecting worktree: {name} ({branch})", "Failed to load comments: {error}". Counts are plurals ("1 file staged", "1 result", "1 commit"), and the four DescCount variants are one message built from two plural pieces. - English words no longer land in Chinese sentences: the PR and issue empty states have a message per filter (which also fixes "No all issues found"), and the AI section translates its credential store names, catalogue ages, "verified" suffix and model-list footer. - Relative dates in the graph, blame, history and detail panels go through shared time.* messages. - Pill groups select by value instead of comparing the clicked label against both languages. The confirm dialog returns its translated label directly. Dropped from #89: - The 27 observe_global::<SettingsState> redraw observers. A language change refreshes every window already, and those observers redrew the main window on every settings write, including each move of the settings window. - The placeholder resets in render. TextInput::set_placeholder_message takes a key and translates it when drawn, so inputs follow the language with no observer and no per-frame work. - The General-page language dropdown, which duplicated the Appearance picker. - The four #[allow(dead_code)] English label functions; the label functions that render are the ones tested now. - description_tr's hard-coded Chinese in the keymap registry; the settings quick reference has message keys instead, with a test that keeps their English equal to each command's doc comment. Terminology, from review of #89: rebase "pick" is 使用 (拣选 is cherry-pick), bisect good/bad is 正常/有问题 everywhere with the menu saying 二分查找, the "Open" filter is 打开, a stash message is 储藏说明, and the interface uses 你. The working-tree row's labels and badges and the diff header were English on the main screen in #89 too and are translated here. Co-authored-by: chenjunjie <zmiss2024@foxmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n English Retrying a failed push decided whether to force by searching the failure summary for "force push". The summary on failure is always "Push to '<remote>' failed", so a failed force push was retried as a plain push. Force pushes now carry their own GitOperationKind::ForcePush, the retry reads the kind, and the toolbar's pushing state covers both. Several outcomes are recognised by matching git's output: CONFLICT and "Automatic merge failed" after a pull, "could not apply" during a rebase, "Already up to date.", the SSH auth failures that trigger the HTTPS fallback, and the "[ahead N, behind M]" of %(upstream:track). A localized git translates all of them. git_command() now sets LC_MESSAGES=C. When LC_ALL is set it overrides every category, so its value moves to LANG and the per-category variables it was overriding are removed, which changes the message language and nothing else. git's stderr shown in failure toasts is English as a result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operation summaries and details are rgitui_i18n::Message values instead of English strings. The git layer builds them with msg!/msgp! — checked against en.rs at compile time, and safe to build on the background executor — and the workspace renders them in the active language where it shows them: toasts, the status line, the operation banner and the output bar. Display is English, so logs and anyhow chains are unchanged. Every operation's progress, success and failure message is covered, plus the user-facing errors #89 chose (about 80), with @VedaChen's Chinese for those. #89 translated failures only, so a running operation, its success toast and its failure read in three different mixes of languages. - Errors: messages::error(msg!(…)) wraps a Message in an anyhow error. error_message() uses it only when it is the text error.to_string() would show, so an outer .context() still wins — #89 took the innermost message in the chain and dropped any context around it. - Operation names inside messages are translated values rather than English words: GitOperationKind::name() and RepoState::label()/operation() return Messages, so "Checkout requires a clean working tree" and "Aborting merge…" come out whole in Chinese. ensure_clean_worktree and checkout_tree_safe take the operation instead of a &str. - Merge, pull and rebase signalled a conflict by prefixing "CONFLICT:" to the message and stripping it on the UI thread, and bisect skip detected exhaustion by searching its own English for "cannot continue". Each now returns a flag. - The path list in "would overwrite" errors, and its "and N more", is a message; plural counts are plurals ("1 conflict", "1 file", "1 commit"). - Raw git output stays verbatim. English changes: "Merging aborted"/"Aborting merging..." are now "Aborted merge"/"Aborting merge...", "Merging completed" is "Completed merge", and counts take the singular at one. Co-authored-by: chenjunjie <zmiss2024@foxmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1265385170
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pub fn is_push(&self) -> bool { | ||
| matches!(self, GitOperationKind::Push | GitOperationKind::ForcePush) | ||
| } |
There was a problem hiding this comment.
Treat both push variants as one failure category
When a force push fails and a later normal push succeeds, or vice versa, the success handler in workspace/events.rs compares operation kinds for exact equality, so it does not clear last_failed_git_operation. After the successful operation finishes, the UI falls back to the stale failure banner and can still offer a retry that force-pushes. Use the new is_push() equivalence when deciding whether a successful push clears a prior push failure.
Useful? React with 👍 / 👎.
| format!("{}: {}", update.summary, details) | ||
| } else { | ||
| update.summary.clone() | ||
| let summary = update.summary.translated(); |
There was a problem hiding this comment.
Keep operation messages untranslated until render
If the user changes languages after an operation event is handled, this eagerly translated value has already been copied into status_message, active_operations, and last_operation_output; changing the language only refreshes windows and does not rebuild those stored strings. Consequently, the status bar and operation output remain in the previous language even though the banner that retains the original Message updates correctly. Preserve the Message until rendering, or rebuild these caches when ActiveLanguage changes. CLAUDE.mdL59-L59
Useful? React with 👍 / 👎.
The picker keeps the footer and status notes it was handed, and the `ActiveLanguage` observer only re-translated the API-key placeholders. Opening the picker, switching language on the Appearance page and coming back left "updated 3 h ago" and the hidden-for-tools note in the old language. The observer now re-syncs the picker as well; `set_rows` and `set_chips` keep the query and chip, so an open picker is not reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operation handler translated each update as it arrived and stored the text in the status line, the running-operation label and the output bar. Switching language afterwards refreshed the windows but not those strings, so they stayed in the old language while the banner, which keeps the `GitOperationUpdate`, followed the switch. `status_message`, `ActiveOperation::label` and `OperationOutput`'s operation and output now hold the `Message` and translate in render. The failure line is built with `msg!` and nests the summary and details as messages. Only toasts are still translated when raised, since they are gone in seconds. Status lines that come from outside git go in as `Message::verbatim`, which is what they already were. `OperationState::is_loading` and `loading_message` were written on every update and never read, so they are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tle a failed push - Windows reads environment names without regard to case, so a lowercase `lc_all` is `LC_ALL` to git there. The exact-case check missed it, `LC_MESSAGES=C` lost to it, and git kept answering in the user's language. Names now match case-insensitively where the platform does (`cfg!(windows)`), and the variable is removed under the name it was found with. - Splitting `ForcePush` from `Push` meant a plain push succeeding after a failed force push (or the reverse) no longer cleared the failure: the success handler compared kinds for equality, so the stale banner came back and still offered a retry that force-pushes. `GitOperationKind::settles` treats both pushes as one for this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
# Conflicts: # CHANGELOG.md # CLAUDE.md # assets/locales/zh-CN.json # crates/rgitui_i18n/src/en.rs # crates/rgitui_i18n/src/lib.rs # crates/rgitui_workspace/src/settings_window/view.rs
The confirm dialog drew whatever title and warning its caller passed,
and every caller passed English, so a force push or a branch deletion
asked in English under translated buttons. The dialog now builds both
from the `ConfirmAction` in render: the actions already carry what the
text names (the branch, tag, remote, path, stash index or commit), so
`show_visible` takes only the action, and the text follows a language
change while the dialog is open.
- `AbortMerge` was used for every operation in progress and is now
`AbortOperation(RepoState)`, so the title says which one is being
aborted ("Abort Rebase") instead of "Abort Rebasing (interactive)".
- The two copies of the hard-reset warning, and the "Reset to Commit"
and "Reset (hard)" titles for the same action, are one message each.
- A worktree removal names the path; the worktree name was only in the
caller's text.
- `ConfirmDialog::show` had no callers and is gone, as is the commit id
`GraphViewEvent::ResetToCommit` carried beside the `Oid` it was
formatted from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # crates/rgitui_git/src/project/network.rs # crates/rgitui_git/src/types.rs
# Conflicts: # CHANGELOG.md # assets/locales/zh-CN.json # crates/rgitui_i18n/src/en.rs # crates/rgitui_workspace/src/workspace/layout.rs
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f76d3342a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if undoable { | ||
| cx.emit(GitProjectEvent::WorktreePatchApplied { | ||
| label: summary, | ||
| label: summary.translated().to_string(), |
There was a problem hiding this comment.
Keep the worktree undo label untranslated
When a user applies or reverts a worktree patch, changes the language, closes the originating repository, and invokes Undo within its 60-second lifetime, this translated string remains cached in UndoEntry and is interpolated into the closed-repository error toast by workspace/undo.rs:220-224, so that toast mixes the previous language with the current UI. Carry the Message through WorktreePatchApplied and the undo entry, and translate it only when the label is displayed. CLAUDE.mdL59-L59
Useful? React with 👍 / 👎.
Stacked on #91 (which is on #57). It also carries #90's commit so the push code is current; that commit drops out when #90 merges and this is rebased.
Translates what git operations report: the progress, success and failure message for every operation, plus the user-facing errors #89 picked out, with @VedaChen's Chinese for those. #89 translated failures only, so a running operation, its success toast and its failure toast came out in different mixes of languages.
How it works
rgitui_gitbuilds each summary and detail as anrgitui_i18n::Messagewithmsg!/msgp!. The key and argument names are checked againsten.rsat compile time, and messages can be built on the background executor.GitOperationUpdate.summaryand.detailsareMessages. The workspace calls.translated()where it shows them: toasts, status line, operation banner, output bar.Displayis English, so logs andanyhowchains are unchanged.Message::verbatim.messages::error(msg!(…))wraps aMessagein ananyhow::Error.error_message(&e)uses that message only when it is exactly the texte.to_string()would show, so an outer.context()still wins. feat(i18n): add Simplified Chinese (简体中文) language support #89 took the innermost message anywhere in the chain and dropped the context around it.GitOperationKind::name(),RepoState::label()andRepoState::operation()returnMessages.Bugs fixed on the way
"CONFLICT:"to the message, and the UI thread checked for the prefix and stripped it.English changes
Still English
Errors outside the set #89 picked: libgit2/filesystem failures,
git cleanand other internal CLI errors, and the worktree-patch errors that aren't user mistakes. These show as they are, so nothing goes blank, but they aren't translated.Verified
cargo clippy --workspace --all-targets -D warnings: clean.error_message: a plain message, an outer context winning, and a non-message error.Message: English rendering, plurals, nested messages, verbatim text, and values not being re-scanned..oneforms Chinese doesn't use."language": "zh-CN". The files staged, and the UI around the operation was in Chinese. The success toast itself fell outside the captured window area, so the toast text wasn't checked visually.🤖 Generated with Claude Code