Complete client UI i18n migration

Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
This commit is contained in:
chyendongnhanh338
2026-06-11 14:15:35 +08:00
committed by GitHub
parent 41cf070fd8
commit 08ebe5e7e1
199 changed files with 16426 additions and 1821 deletions
@@ -1,4 +1,5 @@
import { confirmDialog } from "@/utils/confirm-dialog";
import { i18n } from "@/i18n/i18next";
export interface WorktreeArchiveRisk {
isDirty?: boolean | null;
@@ -10,27 +11,61 @@ export interface WorktreeArchiveConfirmationInput extends WorktreeArchiveRisk {
worktreeName: string;
}
function pluralize(count: number, singular: string, plural = `${singular}s`): string {
return count === 1 ? singular : plural;
export interface WorktreeArchiveWarningLabels {
title: (worktreeName: string) => string;
confirm: string;
cancel: string;
uncommittedChanges: string;
uncommittedChangesWithDiff: (diffStat: string) => string;
addedLine: (count: number) => string;
deletedLine: (count: number) => string;
unpushedCommit: (count: number) => string;
}
function formatDiffStat(diffStat: WorktreeArchiveRisk["diffStat"]): string | null {
export const DEFAULT_WORKTREE_ARCHIVE_WARNING_LABELS: WorktreeArchiveWarningLabels = {
title: (worktreeName) => i18n.t("workspace.git.actions.archiveWarning.title", { worktreeName }),
confirm: i18n.t("workspace.git.actions.archiveWarning.confirm"),
cancel: i18n.t("workspace.git.actions.archiveWarning.cancel"),
uncommittedChanges: i18n.t("workspace.git.actions.archiveWarning.uncommittedChanges"),
uncommittedChangesWithDiff: (diffStat) =>
i18n.t("workspace.git.actions.archiveWarning.uncommittedChangesWithDiff", { diffStat }),
addedLine: (count) =>
count === 1
? i18n.t("workspace.git.actions.archiveWarning.addedLine", { count })
: i18n.t("workspace.git.actions.archiveWarning.addedLines", { count }),
deletedLine: (count) =>
count === 1
? i18n.t("workspace.git.actions.archiveWarning.deletedLine", { count })
: i18n.t("workspace.git.actions.archiveWarning.deletedLines", { count }),
unpushedCommit: (count) =>
count === 1
? i18n.t("workspace.git.actions.archiveWarning.unpushedCommit", { count })
: i18n.t("workspace.git.actions.archiveWarning.unpushedCommits", { count }),
};
function formatDiffStat(
diffStat: WorktreeArchiveRisk["diffStat"],
labels: WorktreeArchiveWarningLabels,
): string | null {
if (!diffStat) {
return null;
}
const parts: string[] = [];
if (diffStat.additions > 0) {
parts.push(`${diffStat.additions} added ${pluralize(diffStat.additions, "line")}`);
parts.push(labels.addedLine(diffStat.additions));
}
if (diffStat.deletions > 0) {
parts.push(`${diffStat.deletions} deleted ${pluralize(diffStat.deletions, "line")}`);
parts.push(labels.deletedLine(diffStat.deletions));
}
return parts.length > 0 ? parts.join(", ") : null;
}
export function buildWorktreeArchiveRiskReasons(input: WorktreeArchiveRisk): string[] {
export function buildWorktreeArchiveRiskReasons(
input: WorktreeArchiveRisk,
labels: WorktreeArchiveWarningLabels = DEFAULT_WORKTREE_ARCHIVE_WARNING_LABELS,
): string[] {
const reasons: string[] = [];
const diffStat = input.diffStat;
const hasDiffStatChanges = diffStat ? diffStat.additions > 0 || diffStat.deletions > 0 : false;
@@ -38,13 +73,15 @@ export function buildWorktreeArchiveRiskReasons(input: WorktreeArchiveRisk): str
input.isDirty === true || (input.isDirty == null && hasDiffStatChanges);
if (hasUncommittedChanges) {
const diffStatLabel = formatDiffStat(diffStat);
reasons.push(diffStatLabel ? `Uncommitted changes (${diffStatLabel})` : "Uncommitted changes");
const diffStatLabel = formatDiffStat(diffStat, labels);
reasons.push(
diffStatLabel ? labels.uncommittedChangesWithDiff(diffStatLabel) : labels.uncommittedChanges,
);
}
if ((input.aheadOfOrigin ?? 0) > 0) {
const aheadOfOrigin = input.aheadOfOrigin ?? 0;
reasons.push(`${aheadOfOrigin} unpushed ${pluralize(aheadOfOrigin, "commit")}`);
reasons.push(labels.unpushedCommit(aheadOfOrigin));
}
return reasons;
@@ -52,8 +89,9 @@ export function buildWorktreeArchiveRiskReasons(input: WorktreeArchiveRisk): str
export function buildWorktreeArchiveConfirmationMessage(
input: WorktreeArchiveConfirmationInput,
labels: WorktreeArchiveWarningLabels = DEFAULT_WORKTREE_ARCHIVE_WARNING_LABELS,
): string | null {
const reasons = buildWorktreeArchiveRiskReasons(input);
const reasons = buildWorktreeArchiveRiskReasons(input, labels);
if (reasons.length === 0) {
return null;
}
@@ -63,17 +101,18 @@ export function buildWorktreeArchiveConfirmationMessage(
export async function confirmRiskyWorktreeArchive(
input: WorktreeArchiveConfirmationInput,
labels: WorktreeArchiveWarningLabels = DEFAULT_WORKTREE_ARCHIVE_WARNING_LABELS,
): Promise<boolean> {
const message = buildWorktreeArchiveConfirmationMessage(input);
const message = buildWorktreeArchiveConfirmationMessage(input, labels);
if (!message) {
return true;
}
return await confirmDialog({
title: `Archive "${input.worktreeName}"?`,
title: labels.title(input.worktreeName),
message,
confirmLabel: "Archive",
cancelLabel: "Cancel",
confirmLabel: labels.confirm,
cancelLabel: labels.cancel,
destructive: true,
});
}