Skip to content

feat(i18n): port the Simplified Chinese translation from #89 - #91

Merged
noahbclarkson merged 8 commits into
mainfrom
feat/i18n-zh-port
Oct 19, 2026
Merged

noahbclarkson merged 8 commits into
mainfrom
feat/i18n-zh-port

Conversation

@noahbclarkson

Copy link
Copy Markdown
Owner

Ports @VedaChen's Simplified Chinese translation from #89 onto the rgitui_i18n foundation in #57. Stacked on #57; merge that first. Git operation messages follow in a separate PR stacked on this one.

@VedaChen, your call-site work is kept file by file and you're credited as co-author. About 50 strings were reworded on the way: fragments became whole sentences, counts became plurals, and English words no longer get pasted into Chinese. Those, and the terminology changes below, are where your review would help most.

What carried over

  • 469 strings as written, under keys named for the panel that shows them (toolbar.push, confirm.force_push, branch_health.merged).
  • Compile-checked keys everywhere. Every tr(TrKey::X) is now t!("key"). Keys held as data (graph menu rows, toolbar tooltips, the settings quick reference) use key!.

What changed in the move

  • Named placeholders. Positional {} filled by chained replacen became named placeholders filled in one pass. User text containing {}, like the AI base-URL host, can no longer swallow the next value.
  • Whole sentences instead of fragments:
    • "Pending changes on {branch}"
    • "HEAD is detached at {commit}"
    • "Return to {branch}"
    • "Inspecting worktree: {name} ({branch})"
    • "Failed to load comments: {error}"
    • "More actions: {hint}"
  • Plurals:
    • "1 file staged", "1 result", "1 commit", "1 model", "1 repository".
    • The four DescCount variants are now one message built from two plural pieces.
  • No English inside Chinese sentences:
    • Each PR/issue filter has its own empty-state message, which also fixes "No all issues found".
    • The AI section translates the credential store names, catalogue ages, the "verified" suffix and the 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.

Left out from #89

  • The 27 observe_global::<SettingsState> redraw observers. A language change already refreshes every window, and these redrew the main window on every settings write, including each move of the settings window.
  • Placeholder resets in render (about 15 views). TextInput::set_placeholder_message(key!(…)) translates when drawn, so inputs follow the language with no observers and no per-frame work. Only the AI key placeholders, which name their provider, still use an ActiveLanguage observer.
  • The General-page language dropdown, which duplicated feat(i18n): localization foundation and language picker #57's 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 quick reference has message keys instead, plus a test that keeps their English equal to each command's doc comment.
  • The git-layer changes (op_i18n.rs). They're redone in the follow-up PR so that progress and success messages are translated too, not only failures.

Terminology

Open for discussion.

English #89 Here Why
Rebase "Pick" 拣选 使用 拣选 is cherry-pick; git's own zh_CN todo help says pick <提交> = 使用提交
Bisect good / bad 良好 / 不良 in the panel 正常 / 有问题 everywhere One wording; the menu says 二分查找 like the panel
"Open" filter 开启 打开 开启 reads as "enable"
Stash message 留言 储藏说明 留言 reads as "leave a comment"
Form of address 你 and 您 你 Consistency

Coverage

  • Complete: 593 English entries and 574 Chinese messages. The 19 without Chinese are the .one plural forms Chinese doesn't use, so the picker reports zh-CN as fully translated.
  • Also translated here: the working-tree row's "working" label and staged/unstaged badges, and the diff header. They were English on the main screen in feat(i18n): add Simplified Chinese (简体中文) language support #89 too.
  • Still English: toast messages, confirm-dialog bodies, the command palette, the GitHub sign-in flow and the conflict resolver's actions.

Verified

  • cargo clippy --workspace --all-targets -D warnings: clean.
  • Tests: rgitui_i18n 50, rgitui_workspace 772, rgitui_graph 37, rgitui_diff 99, rgitui_ui 66, rgitui_settings 31, all passing. This includes the locale checks: no unknown keys, matching placeholders, and no unused English keys.
  • Ran the app under the perf-harness replay with "language": "zh-CN" against a scratch repo and checked a screenshot. CJK text renders through the fallback fonts, and the toolbar, sidebar, graph columns, relative dates and the pending-changes row all show in Chinese.

The co-author trailer currently uses the identity on #89's commits (chenjunjie <zmiss2024@foxmail.com>). It can be changed at squash time if a different identity is preferred.

🤖 Generated with Claude Code

noahbclarkson and others added 3 commits October 15, 2026 21:17
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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T09:31:15.559206Z b87d256 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b50f114b5e

ℹ️ 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".

Comment on lines +486 to 488
footer.push(t!("settings.ai.hidden_for_tools", count = hidden_for_tools).to_string());
}
let footer = (!footer.is_empty()).then(|| SharedString::from(footer.join(" · ")));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebuild the model picker when the language changes

If a user opens the AI model picker, navigates to Appearance to switch languages, and then returns to AI, this translated footer remains in the previous language because sync_model_picker stores the resulting SharedString in the picker entity and is not rerun by the ActiveLanguage observer (which only rebuilds API-key placeholders). Re-sync the picker from that observer, or store translation keys/data and translate during picker rendering. CLAUDE.mdL59-L59

Useful? React with 👍 / 👎.

@VedaChen

Copy link
Copy Markdown
Contributor

good thanks

@VedaChen

Copy link
Copy Markdown
Contributor

I manually reviewed your changes. The translation is fine. Thanks for your contribution.

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>
@noahbclarkson

Copy link
Copy Markdown
Owner Author

@codex review

noahbclarkson added a commit that referenced this pull request Oct 19, 2026
Adds `rgitui_i18n` and wires it into settings, so the interface language can
be chosen under Settings → Appearance → Language and applied without a
restart. Simplified Chinese ships first. The Appearance panel is the only view
converted here; the rest of the interface follows in #91.

- English is the source of truth in `src/en.rs`, one `key => message` line
  per entry, always present so a missing translation has a fallback.
  Translations are JSON under `assets/locales/`, embedded at build time, and a
  file in `<config>/rgitui/locales/` is layered over the shipped one so a
  translator can work against a release build.
- `t!`, `tp!` and `key!` take literal keys and check them, and their named
  arguments, against the English table at compile time. `tp!` fills
  `{count}`. An unsorted or duplicated table fails the build.
- The catalogue is a process-wide RwLock, so messages can be built off the UI
  thread. `set_language` refreshes every window and replaces an
  `ActiveLanguage` global that views keeping translated text can observe.
- Matching needs the same script, so a Traditional Chinese system stays in
  English instead of being given Simplified characters. POSIX tags such as
  `zh_CN.UTF-8` are accepted.
- User locale files must be regular files under 1 MiB, are read in path order
  and overlay a shipped locale with the same id. Unknown keys are logged and
  an unknown plural rule is an error.
- The UI font gains Simplified Chinese fallbacks only when the UI is
  Simplified Chinese.
- Tests reject locale files with keys or placeholders English lacks, plural
  forms a rule never selects, and English keys no code uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7e78dc9cf

ℹ️ 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".

.gap_2()
.child(
Label::new("Enter to confirm | Esc to cancel")
Label::new(t!("common.confirm_hint"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Translate confirmation titles and warnings

When Simplified Chinese is active, confirmations such as force push, discard-all, remote removal, and branch deletion still render their title and warning in English because ConfirmDialog displays the caller-provided strings unchanged, while call sites such as workspace/commands.rs:591-617 and workspace/events.rs:1589-1608 continue passing English literals. Translating only the hint and buttons leaves the most important destructive-operation text untranslated; add message keys with arguments for these titles and warnings, or store keys/data for translation during rendering. CLAUDE.mdL59-L59

Useful? React with 👍 / 👎.

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>
@noahbclarkson
noahbclarkson changed the base branch from feat/i18n-foundation to main October 19, 2026 09:00
@noahbclarkson

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 09f5ca2cc3

ℹ️ 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".

@noahbclarkson

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: b87d256031

ℹ️ 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".

@noahbclarkson
noahbclarkson merged commit 0544df3 into main Oct 19, 2026
4 checks passed
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.

2 participants