feat(cli): migrate TUI board strings to i18n (U7)
Establish the terminal-UI migration pattern: route BoardView's loading/empty
labels through useTranslation("cli"), keeping single-letter keybinding
accelerators literal via interpolation ([{{key}}]). Add the keys to the en cli
catalog, sync the four locales, and regenerate the CLI import map. Tests cover
the migrated label and accelerator preservation. The remaining TUI labels are
deferred long-tail work; the pattern is in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState, useSyncExternalStore, useCallback, useEffect, useRef }
|
||||
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
||||
import Spinner from "ink-spinner";
|
||||
import TextInput from "ink-text-input";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { spawn } from "node:child_process";
|
||||
import { appendFileSync } from "node:fs";
|
||||
|
||||
@@ -1695,6 +1696,7 @@ function groupTasksByColumn(tasks: TaskItem[]): Record<KanbanColumn, TaskItem[]>
|
||||
}
|
||||
|
||||
function BoardView({ state, controller }: { state: DashboardState; controller: DashboardTUI }) {
|
||||
const { t } = useTranslation("cli");
|
||||
const { stdout } = useStdout();
|
||||
const cols = stdout?.columns ?? 80;
|
||||
const rows = stdout?.rows ?? 24;
|
||||
@@ -1937,12 +1939,13 @@ function BoardView({ state, controller }: { state: DashboardState; controller: D
|
||||
) : tasksState.loading ? (
|
||||
<Box justifyContent="center" alignItems="center" flexGrow={1} gap={1}>
|
||||
<Text color="white"><Spinner type="dots" /></Text>
|
||||
<Text dimColor>Loading tasks…</Text>
|
||||
<Text dimColor>{t("tui.loadingTasks", "Loading tasks…")}</Text>
|
||||
</Box>
|
||||
) : tasksState.tasks.length === 0 ? (
|
||||
<Box justifyContent="center" alignItems="center" flexGrow={1} flexDirection="column">
|
||||
<Text dimColor>No tasks in this project.</Text>
|
||||
<Text dimColor>Press [p] to switch projects.</Text>
|
||||
<Text dimColor>{t("tui.noTasks", "No tasks in this project.")}</Text>
|
||||
{/* Keybinding accelerator [{{key}}] stays literal — never translated. */}
|
||||
<Text dimColor>{t("tui.switchProjectsHint", { key: "p", defaultValue: "Press [{{key}}] to switch projects." })}</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<Box flexDirection="row" flexGrow={1} overflow="hidden">
|
||||
|
||||
@@ -67,3 +67,36 @@ describe("react-i18next under the Ink reconciler", () => {
|
||||
expect(lastFrame()).toContain("加载中…");
|
||||
});
|
||||
});
|
||||
|
||||
// Keybinding accelerators must stay literal even inside translated hints.
|
||||
function ProjectHint() {
|
||||
const { t } = useTranslation("cli");
|
||||
return createElement(
|
||||
Text,
|
||||
null,
|
||||
t("tui.switchProjectsHint", { key: "p", defaultValue: "Press [{{key}}] to switch projects." }),
|
||||
);
|
||||
}
|
||||
|
||||
describe("CLI string migration", () => {
|
||||
it("renders the migrated en label", () => {
|
||||
const i18n = initCliI18n("en");
|
||||
function Tasks() {
|
||||
const { t } = useTranslation("cli");
|
||||
return createElement(Text, null, t("tui.loadingTasks", "Loading tasks…"));
|
||||
}
|
||||
const { lastFrame } = render(
|
||||
createElement(I18nextProvider, { i18n }, createElement(Tasks)),
|
||||
);
|
||||
expect(lastFrame()).toContain("Loading tasks…");
|
||||
});
|
||||
|
||||
it("keeps the [p] accelerator literal in an interpolated hint", () => {
|
||||
const i18n = initCliI18n("en");
|
||||
const { lastFrame } = render(
|
||||
createElement(I18nextProvider, { i18n }, createElement(ProjectHint)),
|
||||
);
|
||||
expect(lastFrame()).toContain("[p]");
|
||||
expect(lastFrame()).toContain("Press");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"tui": {
|
||||
"loading": "Loading…",
|
||||
"loadingTasks": "Loading tasks…",
|
||||
"noTasks": "No tasks in this project.",
|
||||
"switchProjectsHint": "Press [{{key}}] to switch projects.",
|
||||
"quit": "Quit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"tui": {
|
||||
"loading": "",
|
||||
"quit": ""
|
||||
"quit": "",
|
||||
"loadingTasks": "",
|
||||
"noTasks": "",
|
||||
"switchProjectsHint": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"tui": {
|
||||
"loading": "",
|
||||
"quit": ""
|
||||
"quit": "",
|
||||
"loadingTasks": "",
|
||||
"noTasks": "",
|
||||
"switchProjectsHint": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"tui": {
|
||||
"loading": "",
|
||||
"quit": ""
|
||||
"quit": "",
|
||||
"loadingTasks": "",
|
||||
"noTasks": "",
|
||||
"switchProjectsHint": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"tui": {
|
||||
"loading": "",
|
||||
"quit": ""
|
||||
"quit": "",
|
||||
"loadingTasks": "",
|
||||
"noTasks": "",
|
||||
"switchProjectsHint": ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user