fix(i18n): Translate the remaining UI text (Git, hierarchy, pull requests, dialogs, aria labels) (#114)

Hard-coded UI text, titles and aria-labels in about 110 components (Git panels, hierarchy, pull requests, dialogs, settings, welcome) now go through tx; spliced fragments became whole sentences (fileCount, noneFoundLabel, hierarchyTitle) and pull request states use keyed messages. 225 zh entries added, stale fragment entries removed.

Also: tx used as a tag without interpolations (tx`No checks`) now looks the text up exactly instead of as a template, so it is translated like tx("No checks"); the text-catalog test checks such tags as plain literals. Check and test counts reuse the existing "{count} passed" / "{failed} failing" entries.

Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Co-authored-by: jaw52 <[email protected]>
Цей коміт міститься в:
Jaw authored and GitHub committed 2026-10-28 12:18:46 +08:00
1 джерело faf666d125
коміт 70f10e741c
121 files changed
+854 -457

No files matched your search

+1 -1
Переглянути файл
@@ -225,7 +225,7 @@ export function ImportDialog({ id, schema, table }: { id: string; schema: string
<T>Columns:</T>
</span>
<span className={styles.formHint}>
{mapped.length ? mapped.join(", ") : "No column of the file matches the table"}
{mapped.length ? mapped.join(", ") : tx("No column of the file matches the table")}
</span>
</div>
{preview.length > 0 && (
+3 -3
Переглянути файл
@@ -148,7 +148,7 @@ export function DataView(props: DataViewProps) {
/>
{filter.size > 0 && (
<div className={styles.filterPill} role="status">
{shown} of {rowCount} rows
{tx`${shown} of ${rowCount} rows`}
<button type="button" className={styles.filterPillClear} onClick={() => setFilter(new Map())}>
<T>Clear local filter</T>
</button>
@@ -219,7 +219,7 @@ function FilterPopup(props: {
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<div className={styles.filterValues} role="group" aria-label="Values">
<div className={styles.filterValues} role="group" aria-label={tx("Values")}>
{list.map(({ value: v, count }) => (
<div key={v ?? "\u0000null"} className={styles.filterValue}>
<Checkbox
@@ -270,7 +270,7 @@ function SidePanel(props: {
}) {
const { range, value, columns } = props;
return (
<aside className={styles.sidePanel} aria-label="Value panel">
<aside className={styles.sidePanel} aria-label={tx("Value panel")}>
<div className={styles.sidePanelHead}>
<Tabs
variant="compact"
+4 -3
Переглянути файл
@@ -42,6 +42,7 @@ import {
import { DB_VIEW_FOCUS, openTableData } from "./treeActions";
import { newMenu, nodeMenu } from "./treeMenu";
import styles from "./Database.module.css";
import { tx } from "@/platform/i18n";
const GROUP_LABELS: Record<Group, string> = {
columns: "columns",
@@ -427,7 +428,7 @@ export function DatabaseView() {
<span className={styles.nodeIcon}>
<Icon name={i} size={16} />
{/* IDEA: a green dot on the data source icon while connected. */}
{live && <span className={styles.connectedDot} title="Connected" />}
{live && <span className={styles.connectedDot} title={tx("Connected")} />}
</span>
)}
<span className={labelClass}>{label(n)}</span>
@@ -490,7 +491,7 @@ function DatabaseToolbar() {
const active = !!target && [...connected].some((t) => splitTarget(t).id === splitTarget(target).id);
const button = { size: "small", iconSize: 16 } as const;
return (
<div className={styles.dbToolbar} role="toolbar" aria-label="Database actions">
<div className={styles.dbToolbar} role="toolbar" aria-label={tx("Database actions")}>
<IconButton
ref={plus}
{...button}
@@ -520,7 +521,7 @@ function DatabaseToolbar() {
<button
type="button"
className={styles.textTool}
aria-label="Go to DDL"
aria-label={tx("Go to DDL")}
disabled={!table}
onClick={() => void executeAction("db.goToDdl")}
{...tooltipAttrs("Go to DDL", shortcutLabel("db.goToDdl"))}
+2 -1
Переглянути файл
@@ -9,6 +9,7 @@ import clsx from "clsx";
import { Icon, showContextMenu, type MenuItem } from "@/ui";
import { toCsv, toTsv, type Cell } from "./model";
import styles from "./Database.module.css";
import { tx } from "@/platform/i18n";
export interface GridColumn {
name: string;
@@ -291,7 +292,7 @@ export function Grid(props: GridProps) {
type="button"
className={clsx(styles.headerFilter, props.filtered?.(ci) && styles.headerFilterOn)}
aria-label={`Filter ${c.name}`}
title="Local filter"
title={tx("Local filter")}
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation();
+5 -4
Переглянути файл
@@ -120,7 +120,7 @@ function RenameDialog({ id, object }: { id: string; object: DbObjectRef | null }
>
<div className={styles.dialogBody}>
<label className={styles.formLabelLeft} htmlFor="db-rename">
Rename {what} '{initial}' to:
{tx`Rename ${what} '${initial}' to:`}
</label>
<Input
id="db-rename"
@@ -156,7 +156,8 @@ function DropDialog({ id, object }: { id: string; object: DbObjectRef }) {
>
<div className={styles.dialogBody}>
<div className={styles.dialogLead}>
Drop {KIND_LABEL[object.kind]} <strong>{reference(driver, object)}</strong>? This cannot be undone.
{tx`Drop ${KIND_LABEL[object.kind]}`} <strong>{reference(driver, object)}</strong>
{tx`? This cannot be undone.`}
</div>
<Checkbox
checked={ifExists}
@@ -387,7 +388,7 @@ function ModifyTableForm({
onClick={remove}
/>
</div>
<div role="listbox" aria-label="Columns" className={styles.modifyList}>
<div role="listbox" aria-label={tx("Columns")} className={styles.modifyList}>
{columns.map((c) => (
<div
key={c.key}
@@ -521,7 +522,7 @@ function SourceUrlDialog() {
/>
<span className={styles.formHint}>
{url.trim() && !parsed
? "Not a URL of PostgreSQL, MySQL, SQLite or a compatible database"
? tx("Not a URL of PostgreSQL, MySQL, SQLite or a compatible database")
: "postgresql://, mysql://, mariadb://, redshift://, tidb://…, jdbc:…, sqlite:path"}
</span>
</div>
+5 -4
Переглянути файл
@@ -24,6 +24,7 @@ import {
type ResultTab,
} from "./store";
import styles from "./Database.module.css";
import { tx } from "@/platform/i18n";
function time(t: number): string {
const d = new Date(t);
@@ -44,7 +45,7 @@ function Output() {
<span className={styles.spacer} />
<IconButton icon="trash" title="Clear All" disabled={!lines.length} onClick={clearOutput} />
</div>
<div ref={ref} className={styles.output} role="log" aria-label="Database output">
<div ref={ref} className={styles.output} role="log" aria-label={tx("Database output")}>
{lines.map((l: OutputLine) => (
<div key={l.id} className={clsx(styles.outputLine, l.kind === "error" && styles.outputError)}>
<span className={styles.outputTime}>[{time(l.time)}]</span>{" "}
@@ -115,8 +116,8 @@ function Result({ tab }: { tab: ResultTab }) {
aria-label={tab.title}
/>
<div className={styles.rowPill} role="status" title={outcomeSummary(tab.outcome)}>
{r.rows.length} row{r.rows.length === 1 ? "" : "s"}
{r.hasMore ? " (limit reached)" : ""}
{r.rows.length === 1 ? tx`${r.rows.length} row` : tx`${r.rows.length} rows`}
{r.hasMore ? ` ${tx("(limit reached)")}` : ""}
</div>
</div>
)}
@@ -208,7 +209,7 @@ function PlanResult({ tab }: { tab: ResultTab }) {
<EmptyState title="Empty plan" />
)}
</div>
<table className={styles.planProperties} aria-label="Plan node properties">
<table className={styles.planProperties} aria-label={tx("Plan node properties")}>
<thead>
<tr>
<th>
+5 -3
Переглянути файл
@@ -534,7 +534,7 @@ export function SourceDialog({
{sshOn && (
<>
<span />
<span className={styles.formHint}>Reached from the SSH host {sshLabel({ ssh: sshUsed })}.</span>
<span className={styles.formHint}>{tx`Reached from the SSH host ${sshLabel({ ssh: sshUsed })}.`}</span>
</>
)}
<label className={styles.formLabel} htmlFor="db-user">
@@ -916,8 +916,10 @@ export function SourceDialog({
</Button>
<span className={styles.formHint}>
{initial?.id
? "The Database tree shows only the checked names and patterns (* and ?); the connected database always shows."
: "Save the data source to pick from the server's names; patterns (* and ?) work any time."}
? tx(
"The Database tree shows only the checked names and patterns (* and ?); the connected database always shows.",
)
: tx("Save the data source to pick from the server's names; patterns (* and ?) work any time.")}
</span>
</div>
</div>
+1 -1
Переглянути файл
@@ -297,7 +297,7 @@ export function TableEditor({ input }: { input: EditorInput }) {
</div>
{page && !editable && (
<div className={styles.notice}>
{pk.length ? "Read-only data source." : "Read-only: the table has no primary key to identify rows."}
{pk.length ? tx("Read-only data source.") : tx("Read-only: the table has no primary key to identify rows.")}
</div>
)}
{error ? (