@linxiraos/pi-tui 1.1.17 → 1.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +7 -3
  2. package/dist/types/i18n.d.ts +32 -0
  3. package/dist/types/index.d.ts +1 -0
  4. package/dist/types/overlays/agent-hub.d.ts +6 -1
  5. package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -1
  6. package/dist/types/overlays/btw-panel.d.ts +1 -0
  7. package/dist/types/overlays/composer-shape-registry.d.ts +11 -35
  8. package/dist/types/overlays/history-search.d.ts +2 -0
  9. package/dist/types/overlays/login-dialog.d.ts +1 -0
  10. package/dist/types/overlays/logout-account-selector.d.ts +1 -0
  11. package/dist/types/overlays/move-overlay.d.ts +0 -7
  12. package/dist/types/overlays/oauth-selector.d.ts +1 -0
  13. package/dist/types/overlays/queue-mode-selector.d.ts +1 -0
  14. package/dist/types/overlays/reset-usage-selector.d.ts +1 -0
  15. package/dist/types/overlays/settings-defs.d.ts +0 -1
  16. package/dist/types/overlays/show-images-selector.d.ts +1 -0
  17. package/dist/types/overlays/theme-selector.d.ts +1 -0
  18. package/dist/types/overlays/thinking-selector.d.ts +1 -0
  19. package/dist/types/overlays/tree-selector.d.ts +2 -0
  20. package/dist/types/setup/scenes/sign-in.d.ts +1 -1
  21. package/dist/types/setup/scenes/web-search.d.ts +1 -1
  22. package/package.json +10 -10
  23. package/src/apps/git/diff-pane.ts +28 -15
  24. package/src/apps/git/git-tui.ts +129 -34
  25. package/src/apps/git/sidebar.ts +34 -20
  26. package/src/apps/ps-top.ts +73 -23
  27. package/src/chat/compaction-summary-message.ts +32 -11
  28. package/src/chat/execution-shared.ts +11 -5
  29. package/src/chat/late-diagnostics-message.ts +2 -1
  30. package/src/chat/messages.ts +11 -3
  31. package/src/chat/skill-message.ts +18 -3
  32. package/src/chat/todo-reminder.ts +8 -2
  33. package/src/chat/tool-execution.ts +9 -1
  34. package/src/chat/user-message.ts +5 -2
  35. package/src/components/form.ts +3 -2
  36. package/src/components/select-list.ts +8 -3
  37. package/src/components/settings-list.ts +21 -6
  38. package/src/i18n.ts +46 -0
  39. package/src/index.ts +2 -0
  40. package/src/overlays/advisor-config.ts +124 -62
  41. package/src/overlays/agent-activity.ts +4 -3
  42. package/src/overlays/agent-hub.ts +189 -61
  43. package/src/overlays/agent-transcript-viewer.ts +32 -14
  44. package/src/overlays/agents-hub.ts +236 -76
  45. package/src/overlays/ask-dialog.ts +47 -25
  46. package/src/overlays/btw-history-panel.ts +101 -33
  47. package/src/overlays/btw-panel.ts +30 -12
  48. package/src/overlays/cleanse-panel.ts +19 -6
  49. package/src/overlays/codex-reset-fireworks.ts +12 -4
  50. package/src/overlays/composer-shape-preview.ts +6 -2
  51. package/src/overlays/composer-shape-registry.ts +58 -18
  52. package/src/overlays/copy-selector.ts +61 -27
  53. package/src/overlays/error-banner.ts +8 -1
  54. package/src/overlays/extensions/extension-dashboard.ts +7 -2
  55. package/src/overlays/extensions/extension-list.ts +20 -18
  56. package/src/overlays/extensions/inspector-model.ts +23 -16
  57. package/src/overlays/extensions/inspector-panel.ts +76 -35
  58. package/src/overlays/extensions/mcp-runtime.ts +29 -10
  59. package/src/overlays/extensions/state-manager.ts +12 -11
  60. package/src/overlays/history-search.ts +28 -7
  61. package/src/overlays/login-dialog.ts +46 -11
  62. package/src/overlays/logout-account-selector.ts +18 -4
  63. package/src/overlays/mcp-add-wizard.ts +190 -109
  64. package/src/overlays/model-browser.ts +5 -1
  65. package/src/overlays/model-hub.ts +188 -68
  66. package/src/overlays/model-picker.ts +39 -14
  67. package/src/overlays/move-overlay.ts +14 -5
  68. package/src/overlays/oauth-selector.ts +46 -12
  69. package/src/overlays/omfg-panel.ts +39 -13
  70. package/src/overlays/pause-screen.ts +19 -9
  71. package/src/overlays/plan-review-overlay.ts +49 -23
  72. package/src/overlays/plan-save-overlay.ts +8 -3
  73. package/src/overlays/plugin-selector.ts +9 -6
  74. package/src/overlays/plugin-settings.ts +127 -36
  75. package/src/overlays/queue-mode-selector.ts +13 -3
  76. package/src/overlays/reset-usage-selector.ts +28 -7
  77. package/src/overlays/rewind-selector.ts +18 -4
  78. package/src/overlays/session-info-overlay.ts +6 -2
  79. package/src/overlays/session-selector.ts +51 -26
  80. package/src/overlays/settings-defs.ts +61 -10
  81. package/src/overlays/settings-selector.ts +55 -25
  82. package/src/overlays/show-images-selector.ts +17 -3
  83. package/src/overlays/theme-selector.ts +8 -2
  84. package/src/overlays/thinking-selector.ts +7 -1
  85. package/src/overlays/tiny-title-download-progress.ts +10 -8
  86. package/src/overlays/tree-selector.ts +86 -33
  87. package/src/overlays/usage-dashboard.ts +38 -15
  88. package/src/prompt/prompt-action-autocomplete.ts +28 -9
  89. package/src/prompt/tools-markdown.ts +15 -4
  90. package/src/prompt/welcome.ts +5 -1
  91. package/src/setup/lazy.ts +2 -1
  92. package/src/setup/scenes/composer.ts +16 -5
  93. package/src/setup/scenes/glyph.ts +19 -4
  94. package/src/setup/scenes/model.ts +15 -6
  95. package/src/setup/scenes/outro.ts +5 -2
  96. package/src/setup/scenes/providers.ts +18 -5
  97. package/src/setup/scenes/sign-in.ts +70 -15
  98. package/src/setup/scenes/splash.ts +8 -5
  99. package/src/setup/scenes/theme.ts +87 -22
  100. package/src/setup/scenes/web-search.ts +25 -8
  101. package/src/setup/wizard-overlay.ts +13 -3
  102. package/src/status-line/context-usage.ts +32 -11
  103. package/src/status-line/footer.ts +2 -1
  104. package/src/status-line/loop.ts +2 -1
  105. package/src/tools/ask.ts +32 -12
  106. package/src/tools/ast-grep.ts +28 -13
  107. package/src/tools/glob.ts +62 -13
  108. package/src/tools/grep.ts +29 -10
  109. package/src/tools/hub.ts +47 -14
  110. package/src/tools/resolve.ts +19 -6
  111. package/src/tools/task.ts +9 -8
  112. package/src/tools/todo.ts +17 -4
  113. package/src/tools/web-search.ts +15 -5
package/CHANGELOG.md CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [1.1.16] - 2026-09-19
5
+ ## [1.1.18] - 2026-09-22
6
6
 
7
- - 上游 v18.2.5 同步:每击键/每帧渲染浪费削减、billing 摘要重构、stream 状态分段、paint-listener 通知管线。
8
- - 主题、覆盖层、chat、工具渲染器与 apps 组件自 coding-agent 迁入本包(模块迁移)。
7
+ - 新增宿主可注入文本层:`setTuiTextSource` / `tuiText` / `tuiTextFmt`——内联英文为永久 fallback,宿主(coding-agent)注入活翻译代理,面板文本随 `/language` 即时切换;pi-tui 独立使用零依赖。
9
8
 
10
9
  ## [9.8.0] - 2026-02-01
11
10
 
@@ -381,6 +380,11 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
381
380
 
382
381
  - Added `getText()` method to Text component for retrieving current text content
383
382
 
383
+ ## [1.1.16] - 2026-09-19
384
+
385
+ - 上游 v18.2.5 同步:每击键/每帧渲染浪费削减、billing 摘要重构、stream 状态分段、paint-listener 通知管线。
386
+ - 主题、覆盖层、chat、工具渲染器与 apps 组件自 coding-agent 迁入本包(模块迁移)。
387
+
384
388
  ## [0.50.0] - 2026-01-26
385
389
 
386
390
  ### Added
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Host-injectable UI text source for @linxiraos/pi-tui.
3
+ *
4
+ * pi-tui must not depend on the coding-agent i18n bundle (dependency
5
+ * direction: coding-agent -> pi-tui). Instead, every user-visible string in
6
+ * a tui component goes through `tuiText(key, englishFallback)`: the inline
7
+ * English is the permanent fallback, and the host (coding-agent) registers
8
+ * a text source that resolves keys against its localised catalogue. The
9
+ * source is consulted on every call, so a runtime language switch takes
10
+ * effect immediately without rebuilding components.
11
+ *
12
+ * Key naming: reuse the coding-agent Messages key verbatim when one exists
13
+ * (e.g. `ssFooterSuffix`); otherwise add a new flat camelCase key namespaced
14
+ * by the panel (e.g. `mcpAddStepTransport`) to Messages/en/zh.
15
+ */
16
+ export type TuiTextSource = (key: string) => string | undefined;
17
+ /**
18
+ * Register the host text source. Pass `undefined` to reset to English
19
+ * fallbacks (the default; also the headless behavior — no rendering surface
20
+ * exists without the interactive CLI host).
21
+ */
22
+ export declare function setTuiTextSource(source: TuiTextSource | undefined): void;
23
+ /**
24
+ * Resolve a UI string: host catalogue first, inline English fallback second.
25
+ * Called per render, so language switches apply live.
26
+ */
27
+ export declare function tuiText(key: string, fallback: string): string;
28
+ /**
29
+ * Resolve a formatted UI string (`*Fmt` keys). `%s` / `%d` placeholders in
30
+ * the resolved text are substituted positionally with `args`.
31
+ */
32
+ export declare function tuiTextFmt(key: string, fallback: string, ...args: (string | number)[]): string;
@@ -1,4 +1,5 @@
1
1
  export * from "./autocomplete.js";
2
+ export * from "./i18n.js";
2
3
  export * from "./chat/transcript-browser.js";
3
4
  export * from "./components/box.js";
4
5
  export * from "./components/cancellable-loader.js";
@@ -20,7 +20,7 @@ import { type AgentActivitySource } from "./agent-activity.js";
20
20
  import type { KeyId } from "../app-keybindings.js";
21
21
  import type { MessageRenderer } from "../chat/extension-types.js";
22
22
  import type { AgentLifecycleLike, IrcBusLike } from "./agent-hub-types.js";
23
- import { type AgentRecordLike, type AgentHubRegistry } from "./agent-hub-types.js";
23
+ import { type AgentRecordLike, type AgentHubRegistry, type AgentStatus } from "./agent-hub-types.js";
24
24
  import type { SessionObserverRegistry } from "./session-observer-registry.js";
25
25
  import { type AgentTranscriptSource } from "./agent-transcript-viewer.js";
26
26
  import type { AgentRoleDisplay } from "./agent-hub-renderer.js";
@@ -40,6 +40,11 @@ export interface AgentHubRemote {
40
40
  /** Mirrors readFileIncremental: text from fromByte (complete JSONL lines), newSize = next fromByte base; null = temporarily unavailable. */
41
41
  readTranscript(id: string, fromByte: number): Promise<AgentHubRemoteTranscript | null>;
42
42
  }
43
+ /** Localizable status words; keys resolve per render so /language switches live. */
44
+ export declare const AGENT_STATUS_TEXT: Record<AgentStatus, {
45
+ key: string;
46
+ fallback: string;
47
+ }>;
43
48
  export interface AgentHubDeps<TRecord extends AgentRecordLike = AgentRecordLike> {
44
49
  /** Progress/status snapshot source (task lifecycle + progress channels). */
45
50
  observers: SessionObserverRegistry;
@@ -22,7 +22,7 @@ import type { AgentLifecycleLike } from "./agent-hub-types.js";
22
22
  import type { AgentHubRegistry } from "./agent-hub-types.js";
23
23
  import type { SessionMessageEntryLike } from "../chat/transcript-entry.js";
24
24
  import type { SessionObserverRegistry } from "./session-observer-registry.js";
25
- import type { AgentHubRemote } from "./agent-hub.js";
25
+ import { type AgentHubRemote } from "./agent-hub.js";
26
26
  /** Parsed message and model metadata relevant to a transcript viewer. */
27
27
  export type AgentTranscriptEntry = SessionMessageEntryLike | {
28
28
  type: "model_change";
@@ -14,6 +14,7 @@ export declare class BtwPanelComponent extends OverlayPanel {
14
14
  markComplete(): void;
15
15
  /** Visual confirmation that `c` copied the answer to the clipboard. */
16
16
  markCopied(): void;
17
+ render(width: number): readonly string[];
17
18
  /** Shows that the completed answer is being promoted into the chat session. */
18
19
  markBranching(): void;
19
20
  markAborted(): void;
@@ -2,42 +2,18 @@ import { type ComposerStyle } from "../index.js";
2
2
  import type { SubmenuOption } from "./settings-defs.js";
3
3
  /** Composer shape id; extensions may register additional values at runtime. */
4
4
  export type ComposerShape = string;
5
- /** Built-in composer choices and their shared settings/setup copy. */
6
- export declare const BUILTIN_COMPOSER_SHAPES: readonly [{
7
- readonly value: "band";
8
- readonly label: "Status Band (Default)";
9
- readonly description: "Flush soft-capped status band above a curved prompt, no frame";
10
- }, {
11
- readonly value: "box";
12
- readonly label: "Rounded Box";
13
- readonly description: "Status line embedded in top border, compact 2-line prompt";
14
- }, {
15
- readonly value: "claude";
16
- readonly label: "Claude Code";
17
- readonly description: "Full-width horizontal rules above and below, status line at bottom";
18
- }, {
19
- readonly value: "pi";
20
- readonly label: "Pi";
21
- readonly description: "Framed horizontal rules with status line at bottom";
22
- }, {
23
- readonly value: "borderless";
24
- readonly label: "Borderless";
25
- readonly description: "Clean prompt glyph with status line at bottom, no box borders";
26
- }, {
27
- readonly value: "rule";
28
- readonly label: "Top Rule Dock";
29
- readonly description: "Single top rule with status docked onto it and below";
30
- }, {
31
- readonly value: "field";
32
- readonly label: "Compact Field";
33
- readonly description: "Filled one-row field with accent end caps";
34
- }, {
35
- readonly value: "rail";
36
- readonly label: "Accent Rail";
37
- readonly description: "Filled one-row field anchored by a single accent rail";
38
- }];
5
+ /**
6
+ * Built-in composer choices and their shared settings/setup copy. Labels and
7
+ * descriptions resolve through `tuiText` on every read (getter, not cached
8
+ * field) so a runtime language switch applies to the next rendered selector.
9
+ */
10
+ export declare const BUILTIN_COMPOSER_SHAPES: {
11
+ value: string;
12
+ readonly label: string;
13
+ readonly description: string;
14
+ }[];
39
15
  /** Built-in composer ids used by tests and non-runtime consumers. */
40
- export declare const COMPOSER_SHAPE_VALUES: ("band" | "borderless" | "box" | "claude" | "field" | "pi" | "rail" | "rule")[];
16
+ export declare const COMPOSER_SHAPE_VALUES: string[];
41
17
  /** Visual composer style and selector copy registered by an extension. */
42
18
  export interface ComposerShapeDefinition {
43
19
  label: string;
@@ -12,5 +12,7 @@ import { OverlayPanel } from "../chrome/overlay-box.js";
12
12
  export declare class HistorySearchComponent extends OverlayPanel {
13
13
  #private;
14
14
  constructor(historyStorage: HistorySource, onSelect: (prompt: string) => void, onCancel: () => void);
15
+ /** Re-resolve the panel title every render so a live language switch applies. */
16
+ render(width: number): readonly string[];
15
17
  handleInput(keyData: string): void;
16
18
  }
@@ -40,4 +40,5 @@ export declare class LoginDialogComponent extends OverlayPanel {
40
40
  /** Route non-bracketed paste transports into the active login input. */
41
41
  pasteText(text: string): void;
42
42
  handleInput(data: string): void;
43
+ render(width: number): readonly string[];
43
44
  }
@@ -12,4 +12,5 @@ export declare class LogoutAccountSelectorComponent extends OverlayPanel {
12
12
  #private;
13
13
  constructor(providerName: string, accounts: LogoutAccount[], onSelect: (account: LogoutAccount) => void, onCancel: () => void);
14
14
  handleInput(keyData: string): void;
15
+ render(width: number): readonly string[];
15
16
  }
@@ -1,10 +1,3 @@
1
- /**
2
- * `/move` overlay: a path input with live directory autocomplete.
3
- *
4
- * Rendered as a centered modal via `showHookCustom(..., { overlay: true })`.
5
- * The user types a path, Tab autocompletes the highlighted directory, and Enter
6
- * confirms — yielding the resolved directory string (or `undefined` on cancel).
7
- */
8
1
  import { type Component, type Focusable } from "../index.js";
9
2
  export interface MoveOverlayResult {
10
3
  directory: string;
@@ -38,4 +38,5 @@ export declare class OAuthSelectorComponent extends OverlayPanel {
38
38
  * drives the hover band, and a left click selects and confirms like Enter.
39
39
  */
40
40
  routeMouse(event: SgrMouseEvent, line: number, _col: number): void;
41
+ render(width: number): readonly string[];
41
42
  }
@@ -8,4 +8,5 @@ export declare class QueueModeSelectorComponent extends OverlayPanel {
8
8
  constructor(currentMode: "all" | "one-at-a-time", onSelect: (mode: "all" | "one-at-a-time") => void, onCancel: () => void);
9
9
  getSelectList(): SelectList;
10
10
  routeMouse(event: SgrMouseEvent, line: number, col: number): void;
11
+ render(width: number): readonly string[];
11
12
  }
@@ -20,4 +20,5 @@ export declare class ResetUsageSelectorComponent extends OverlayPanel {
20
20
  #private;
21
21
  constructor(accounts: ResetUsageAccount[], onSelect: (account: ResetUsageAccount) => void, onCancel: () => void);
22
22
  handleInput(keyData: string): void;
23
+ render(width: number): readonly string[];
23
24
  }
@@ -1,4 +1,3 @@
1
- /** Schema-independent display definitions for the settings overlay. */
2
1
  import type { SymbolKey } from "../theme/symbols.js";
3
2
  export type SettingTab = "appearance" | "model" | "interaction" | "context" | "memory" | "files" | "shell" | "tools" | "tasks" | "providers";
4
3
  /** Tab display metadata - icon is resolved via theme.symbol() */
@@ -8,4 +8,5 @@ export declare class ShowImagesSelectorComponent extends OverlayPanel {
8
8
  constructor(currentValue: boolean, onSelect: (show: boolean) => void, onCancel: () => void);
9
9
  getSelectList(): SelectList;
10
10
  routeMouse(event: SgrMouseEvent, line: number, col: number): void;
11
+ render(width: number): readonly string[];
11
12
  }
@@ -9,4 +9,5 @@ export declare class ThemeSelectorComponent extends OverlayPanel {
9
9
  constructor(currentTheme: string, themes: string[], onSelect: (themeName: string) => void, onCancel: () => void, onPreview: (themeName: string) => void);
10
10
  getSelectList(): SelectList;
11
11
  routeMouse(event: SgrMouseEvent, line: number, col: number): void;
12
+ render(width: number): readonly string[];
12
13
  }
@@ -9,4 +9,5 @@ export declare class ThinkingSelectorComponent extends OverlayPanel {
9
9
  constructor(currentLevel: Effort, availableLevels: Effort[], onSelect: (level: Effort) => void, onCancel: () => void);
10
10
  getSelectList(): SelectList;
11
11
  routeMouse(event: SgrMouseEvent, line: number, col: number): void;
12
+ render(width: number): readonly string[];
12
13
  }
@@ -86,6 +86,8 @@ export declare class TreeSelectorComponent extends OverlayPanel {
86
86
  constructor(tree: TreeSelectorNode[], currentLeafId: string | null, terminalHeight: number, onSelect: (entryId: string, options: {
87
87
  summarize: boolean;
88
88
  }) => void, onCancel: () => void, onLabelChangeCallback?: ((entryId: string, label: string | undefined) => void) | undefined, initialFilterMode?: FilterMode);
89
+ /** Re-resolve the panel title every render so a live language switch applies. */
90
+ render(width: number): readonly string[];
89
91
  handleInput(keyData: string): void;
90
92
  getTreeList(): TreeList;
91
93
  }
@@ -8,7 +8,7 @@ import type { SetupSceneHost, SetupTab } from "./types.js";
8
8
  export declare class SignInTab implements SetupTab {
9
9
  #private;
10
10
  readonly id = "sign-in";
11
- readonly label = "Sign in";
11
+ get label(): string;
12
12
  constructor(host: SetupSceneHost);
13
13
  /** Modal while an OAuth flow is running so the scene won't switch tabs or finish. */
14
14
  get modal(): boolean;
@@ -9,7 +9,7 @@ import type { SetupSceneHost, SetupTab } from "./types.js";
9
9
  export declare class WebSearchTab implements SetupTab {
10
10
  #private;
11
11
  readonly id = "web-search";
12
- readonly label = "Web search";
12
+ get label(): string;
13
13
  readonly modal = false;
14
14
  constructor(host: SetupSceneHost);
15
15
  onActivate(): void;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@linxiraos/pi-tui",
4
- "version": "1.1.17",
4
+ "version": "1.1.18",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://linxira-os.github.io/zeta/",
7
- "author": "Stencil Labs, Inc.",
7
+ "author": "Linxira-OS",
8
8
  "contributors": [
9
9
  "Mario Zechner"
10
10
  ],
@@ -37,14 +37,14 @@
37
37
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
38
38
  },
39
39
  "dependencies": {
40
- "@linxiraos/pi-natives": "1.1.17",
41
- "@linxiraos/pi-utils": "1.1.17",
42
- "@linxiraos/pi-omptype": "1.1.17",
43
- "@linxiraos/pi-agent-core": "1.1.17",
44
- "@linxiraos/pi-ai": "1.1.17",
45
- "@linxiraos/pi-catalog": "1.1.17",
46
- "@linxiraos/pi-wire": "1.1.17",
47
- "@linxiraos/pi-snapcompact": "1.1.17"
40
+ "@linxiraos/pi-natives": "1.1.18",
41
+ "@linxiraos/pi-utils": "1.1.18",
42
+ "@linxiraos/pi-omptype": "1.1.18",
43
+ "@linxiraos/pi-agent-core": "1.1.18",
44
+ "@linxiraos/pi-ai": "1.1.18",
45
+ "@linxiraos/pi-catalog": "1.1.18",
46
+ "@linxiraos/pi-wire": "1.1.18",
47
+ "@linxiraos/pi-snapcompact": "1.1.18"
48
48
  },
49
49
  "devDependencies": {
50
50
  "kitty-vt-wasm": "^0.2.0"
@@ -24,6 +24,7 @@ import { getLanguageFromPath } from "../../lang-from-path";
24
24
  import { createHighlightStream, theme } from "../../theme/theme";
25
25
  import { bgAnsiHex, canvasHex, fgAnsiHex, mixHex, pill, selectionBgAnsi, textHex, withBg } from "./colors";
26
26
  import { DIFF_CONTEXT_LINES, type FileAssetSide, type FileStreamUpdate } from "./state";
27
+ import { tuiText } from "../../i18n";
27
28
 
28
29
  /** Column ranges (inclusive start, exclusive end) carrying intraline emphasis. */
29
30
  type MarkRanges = readonly (readonly [number, number])[];
@@ -1033,7 +1034,7 @@ export class DiffPane {
1033
1034
  if (this.state === "streaming" && this.#streaming) return this.#renderStreaming(width, height);
1034
1035
  if (this.state === "asset" && this.#asset) return this.#renderAsset(width, height);
1035
1036
  if (!doc || this.state !== "ready") {
1036
- const message = this.state === "loading" ? "Loading diff…" : this.emptyMessage;
1037
+ const message = this.state === "loading" ? tuiText("gitLoadingDiff", "Loading diff…") : this.emptyMessage;
1037
1038
  const lines: string[] = [];
1038
1039
  for (let i = 0; i < height; i++) {
1039
1040
  lines.push(
@@ -1084,11 +1085,17 @@ export class DiffPane {
1084
1085
  for (let index = 0; index < height; index++) {
1085
1086
  const leftSource =
1086
1087
  index === 0
1087
- ? centerLine(theme.bold(this.#assetTitle("Before", asset.old)), leftWidth).trimEnd()
1088
+ ? centerLine(
1089
+ theme.bold(this.#assetTitle(tuiText("gitAssetBefore", "Before"), asset.old)),
1090
+ leftWidth,
1091
+ ).trimEnd()
1088
1092
  : (oldLines[index - 1] ?? "");
1089
1093
  const rightSource =
1090
1094
  index === 0
1091
- ? centerLine(theme.bold(this.#assetTitle("After", asset.new)), rightWidth).trimEnd()
1095
+ ? centerLine(
1096
+ theme.bold(this.#assetTitle(tuiText("gitAssetAfter", "After"), asset.new)),
1097
+ rightWidth,
1098
+ ).trimEnd()
1092
1099
  : (newLines[index - 1] ?? "");
1093
1100
  const left = truncateToWidth(leftSource, leftWidth);
1094
1101
  const right = truncateToWidth(rightSource, rightWidth);
@@ -1121,26 +1128,30 @@ export class DiffPane {
1121
1128
  let details: string[];
1122
1129
  switch (side.kind) {
1123
1130
  case "empty":
1124
- details = ["No file"];
1131
+ details = [tuiText("gitNoFile", "No file")];
1125
1132
  break;
1126
1133
  case "text":
1127
- details = ["Text object", formatBytes(side.byteLength)];
1134
+ details = [tuiText("gitTextObject", "Text object"), formatBytes(side.byteLength)];
1128
1135
  break;
1129
1136
  case "binary":
1130
1137
  details = [
1131
- "Binary object",
1132
- side.byteLength === undefined ? "Size unavailable" : formatBytes(side.byteLength),
1138
+ tuiText("gitBinaryObject", "Binary object"),
1139
+ side.byteLength === undefined
1140
+ ? tuiText("gitSizeUnavailable", "Size unavailable")
1141
+ : formatBytes(side.byteLength),
1133
1142
  ];
1134
1143
  break;
1135
1144
  case "tooLarge":
1136
1145
  details = [
1137
- "Object too large to preview",
1138
- side.byteLength === undefined ? "Exceeds preview limit" : formatBytes(side.byteLength),
1146
+ tuiText("gitObjectTooLarge", "Object too large to preview"),
1147
+ side.byteLength === undefined
1148
+ ? tuiText("gitExceedsPreviewLimit", "Exceeds preview limit")
1149
+ : formatBytes(side.byteLength),
1139
1150
  ];
1140
1151
  break;
1141
1152
  case "lfsMissing":
1142
1153
  details = [
1143
- "Git LFS object unavailable",
1154
+ tuiText("gitLfsObjectUnavailable", "Git LFS object unavailable"),
1144
1155
  `sha256:${side.oid.slice(0, 12)}… · ${formatBytes(side.byteLength)}`,
1145
1156
  ];
1146
1157
  break;
@@ -1165,19 +1176,19 @@ export class DiffPane {
1165
1176
  lfs = side.image.lfsOid !== undefined;
1166
1177
  break;
1167
1178
  case "text":
1168
- kind = "Text";
1179
+ kind = tuiText("gitKindText", "Text");
1169
1180
  lfs = side.lfsOid !== undefined;
1170
1181
  break;
1171
1182
  case "binary":
1172
- kind = "Binary";
1183
+ kind = tuiText("gitKindBinary", "Binary");
1173
1184
  lfs = side.lfsOid !== undefined;
1174
1185
  break;
1175
1186
  case "tooLarge":
1176
- kind = "Too large";
1187
+ kind = tuiText("gitKindTooLarge", "Too large");
1177
1188
  lfs = side.lfsOid !== undefined;
1178
1189
  break;
1179
1190
  case "lfsMissing":
1180
- kind = "LFS missing";
1191
+ kind = tuiText("gitKindLfsMissing", "LFS missing");
1181
1192
  lfs = true;
1182
1193
  break;
1183
1194
  }
@@ -1190,7 +1201,9 @@ export class DiffPane {
1190
1201
  const total = this.#total();
1191
1202
  if (total === 0) {
1192
1203
  return Array.from({ length: height }, (_, index) =>
1193
- index === Math.floor(height / 2) ? centerLine(theme.fg("dim", "Streaming file…"), width).trimEnd() : "",
1204
+ index === Math.floor(height / 2)
1205
+ ? centerLine(theme.fg("dim", tuiText("gitStreamingFile", "Streaming file…")), width).trimEnd()
1206
+ : "",
1194
1207
  );
1195
1208
  }
1196
1209
  this.#clampScroll();