Skip to content

feat(i18n): CJK readiness — regional UI font fallbacks, grapheme-safe cuts, IME checks - #885

Merged
penso merged 5 commits into
mainfrom
i18n/phase-3-cjk
Oct 30, 2026
Merged

penso merged 5 commits into
mainfrom
i18n/phase-3-cjk

Conversation

@penso

@penso penso commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Phase 3 of docs/i18n.md: CJK readiness. Phase 2's remaining string migration is not part of this.

Regional font fallbacks for the interface faces. Han characters share codepoints across China, Taiwan, Hong Kong, Japan, and Korea, so the platform cascade draws them in whichever region's shapes it finds first. When a face's fallback is unset, the sidebar, tab, and interface faces now search the installed icon fonts, then one installed CJK family per script:

Platform Families (one per script, first installed)
macOS PingFang SC / TC / HK, Hiragino Sans, Apple SD Gothic Neo
Windows Microsoft YaHei UI, Microsoft JhengHei UI, Yu Gothic UI, Malgun Gothic
Linux Noto Sans CJK SC/TC/HK/JP/KR, then Source Han Sans, WenQuanYi, Droid Sans Fallback
  • herdr_i18n::CjkScript reads which glyphs a language wants from its script and region (zh-Hans, zh-TW, zh-HK, yue, ja, ko).
  • The script of the first CJK language in the language setting, or else in the system's languages, comes first. The setting counts even before a translation in that language ships, because the glyphs matter either way.
  • The terminal keeps its icon-only cascade (its cells need monospace widths), and an explicit fallback, even [], still opts a face out.

Grapheme-safe cuts. truncate.rs cuts between grapheme clusters, so a cut never splits an accented letter, an emoji sequence, a flag, or a Hangul syllable spelled as jamo. It is used for terminal, browser, and review notes, sidebar search excerpts, and the issue report's logs. A browser tab title and an orchestrator check pill have a width budget, so they count columns, and a CJK title is cut about as wide as a Latin one instead of twice as wide. Search fields now move the caret and delete by grapheme, as dialog fields already did.

IME. I audited the three input paths: SearchInput (palette, Settings, sidebar and theme search), DialogInput (rename and other dialogs), and the terminal. Composition, commit, and placement tests already covered dialogs, the terminal painter, the Settings fields, and the menus. SearchInput had no candidate-placement test, so this PR adds headless tests for Pinyin, a Japanese clause, and Zhuyin composed past a scrolled field's edge.

Known limitation: GPUI's own width-based text_ellipsis cuts between chars inside GPUI. CJK text is unaffected, but a combining mark or emoji sequence at that cut can lose its tail. This is recorded in docs/i18n.md.

Screenshots

Captured from the release build on macOS against an isolated daemon (system languages en-US, fr-FR), with a workspace named 直角骨 写次 中文工作区. These characters take visibly different shapes by region.

Before: platform cascade (also what a Japanese or Taiwanese reader got) After, language = "ja" After, language = "zh-TW" After, language = "zh-CN"
before ja zh-TW zh-CN

"Before" is this build with fallback = [], which gives the same cascade as main. On this machine it draws Simplified shapes whatever the language setting says.

Native Pinyin composition in the command palette, and the candidate list it produced:

composition
candidates

QA

  • just format: clean
  • just ci: passed on 78562ab (macOS arm64)
  • just check-commits: OK
  • just lint-linux and just lint-windows: passed (run on the same tree before it was split into commits)
  • New headless tests: herdr_i18n::tests::cjk (script by language), config::tests::cjk_fonts (per-platform selection, order, dedup, terminal kept separate), truncate::tests (clusters, CJK, column widths), search_input::tests::composition (Pinyin, Japanese clause, Zhuyin past the edge, grapheme deletion)
  • Native macOS, release build: CJK glyphs follow language (ja, zh-TW, zh-CN) on a fresh launch and on a live config reload
  • Native macOS: Pinyin composition shows inline and underlined in the palette and offers the expected candidates
  • Native macOS candidate-window placement. In my one composing run, the Pinyin candidate bar opened at the screen's bottom-left corner instead of under the palette field. Pinyin then stopped composing for synthetic keystrokes, so I could not reproduce it. A logging build showed the palette's SearchInput answering the OS's placement queries with the field's real bounds. To check: enable Pinyin, open the command palette, type zhongwen, and confirm the candidates open under the field. Repeat in Settings search, sidebar search, a rename dialog (Rename Workspace), and a terminal pane.
  • Windows: Chinese text in the sidebar and tabs draws with Microsoft YaHei UI (zh-CN) or JhengHei UI (zh-TW); Microsoft Pinyin candidates open under the field
  • Linux with Noto Sans CJK installed: same check with language = "ja" vs "zh-CN"; fcitx or ibus candidate placement

penso added 3 commits October 28, 2026 23:51
Phase 3 of docs/i18n.md. Han characters share codepoints across China,
Taiwan, Hong Kong, Japan, and Korea, so the platform cascade draws them
in whichever region's shapes it finds first. The sidebar, tab, and
interface faces now name each platform's CJK families after the icon
fonts: PingFang and Hiragino on macOS, Microsoft YaHei/JhengHei UI and
Yu Gothic UI on Windows, Noto Sans CJK (or Source Han Sans, WenQuanYi,
Droid) on Linux, one installed family per script.

- herdr_i18n::CjkScript reads which glyphs a language wants from its
  script and region (zh-Hans, zh-TW, zh-HK, yue, ja, ko).
- The script of the first CJK language in the `language` setting, else
  in the system's languages, goes first; the setting counts before any
  translation in that language ships.
- The terminal keeps the icon cascade alone, and an explicit `fallback`
  still opts a face out.
The app's own ellipses cut text after a count of `char`s, which can
split what reads as one character: a letter and its combining accent,
an emoji joined by zero-width joiners, a flag, or a Hangul syllable
spelled as jamo. truncate.rs cuts between grapheme clusters instead.

- Terminal and browser notes, review notes, sidebar search excerpts, and
  the issue report's logs count grapheme clusters.
- A browser tab's title and an orchestrator check pill have a width
  budget, so they count columns: a CJK title is cut about as wide as a
  Latin one, not twice as wide.

GPUI's width-based text_ellipsis still cuts between chars inside GPUI;
docs/i18n.md records that.
…elds

Search fields moved the caret and deleted by `char`, so Backspace after
an accented letter, an emoji sequence, or a jamo-spelled Hangul syllable
left part of it behind. They now move by grapheme cluster, as dialog
fields already did.

Headless tests compose Pinyin, a Japanese clause, and Zhuyin past the
field's edge, and check where the candidate window is asked to open.
docs/i18n.md records what Phase 3 built.
@penso

penso commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner Author

just ci ran locally and passed on d018477 (macOS arm64). just lint-linux and just lint-windows passed on 78562ab; the follow-up commits touch no cfg gates.

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with a non-blocking issue in blank-comment checks.

Findings

  1. P2 Blank comments become ellipses ▶

Summary

This PR adds regional CJK font fallbacks and cuts text without splitting visible characters.

  • Interface text uses regional CJK fonts that match the preferred language.
  • Shortened text stays whole and fits the width its label gets.
  • Search fields and dialogs keep edits on whole graphemes.

Reviews (2) · Last reviewed commit: "fix: keep cut notes bounded and the care..." · Reviewed by Greptile

Comment thread crates/herdr-gpui/src/terminal_notes.rs Resolved
Comment thread crates/herdr-gpui/src/review/notes.rs Resolved
Comment thread crates/herdr-gpui/src/search_input.rs Resolved
Follow-up to review on #885.

- truncate::cut_chars cuts at the last whole grapheme cluster within a
  character budget. A quoted terminal line uses it, so a letter carrying
  thousands of combining marks is capped at 300 characters again.
- The three identical one-line note helpers (terminal, browser, review)
  become truncate::one_line. Its safety cut now ends on a whole cluster
  and marks the cut, so a run of emoji sequences no longer ends in half
  of one.
- Search and dialog fields find caret boundaries in the whole text, and
  an edit whose text joins the cluster after it (a letter typed before an
  accent) leaves the caret after that cluster, so Delete and Backspace
  never split it.
@penso

penso commented Oct 29, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review

Comment thread crates/herdr-gpui/src/truncate.rs Resolved
one_line marked its safety cut even when nothing but whitespace was left,
so a blank comment longer than the budget became "…" and was kept as a
note. Text that is all breaks and controls now stays empty and is refused;
text whose first character alone exceeds the budget still shows the
ellipsis.
@penso
penso merged commit 4843151 into main Oct 30, 2026
19 checks passed
@penso
penso deleted the i18n/phase-3-cjk branch October 30, 2026 00:54
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