fix(i18n): translate Git operation titles and their notifications (#1154)

Git operations passed their title, success and failure text to runOp / runGitOperation as plain strings the text catalog check doesn't see, so notifications after a commit, merge, rebase, stash, shelve, tag, patch or rollback stayed in English, several splicing plurals or English words. Call sites now tag the text with tx, each count and variant has its own sentence, and a failure is titled "{title} failed" from the translated title.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-04 10:57:10 +08:00
1 джерело c86928eee7
коміт e88e7e6459
15 files changed
+143 -66

No files matched your search

+1 -1
Переглянути файл
@@ -724,7 +724,7 @@ export function DiffEditor({ input, toolbarActions }: { input: EditorInput; tool
logConsole("info", "Nothing to stage for this change");
return;
}
await runOp(`Stage hunk of ${path}`, () => call(commands.vcsStageHunks(path, indices)));
await runOp(tx`Stage hunk of ${path}`, () => call(commands.vcsStageHunks(path, indices)));
await refreshNow();
} finally {
setBusy(false);
+3 -2
Переглянути файл
@@ -97,12 +97,13 @@ export function LocalHistoryEditor({ input }: { input: EditorInput }) {
const ok = await confirmDialog({ title: "Revert", message: question, okLabel: "Revert" });
if (!ok) return;
await runOp(
"Revert from Local History",
tx("Revert from Local History"),
async () => {
for (const e of targets) await call(commands.historyRestore(e.path, e.id));
},
{
success: targets.length === 1 ? `${baseName(targets[0]!.path)} reverted` : `${targets.length} files reverted`,
success:
targets.length === 1 ? tx`${baseName(targets[0]!.path)} reverted` : tx`${targets.length} files reverted`,
refresh: true,
},
);
+2 -2
Переглянути файл
@@ -259,14 +259,14 @@ export function MergeEditor({ input }: { input: EditorInput }) {
/** Saves `text` as the resolved file and stages it; like IDEA, that closes the merge editor. */
const writeResolved = async (text: string | null) => {
const ok = await runOp(
`Resolve ${baseName(path)}`,
tx`Resolve ${baseName(path)}`,
async () => {
if (text === null) await call(commands.fsDelete(path));
else await writeFileText(path, text, { save: true, exact: true });
await call(commands.vcsStage([path]));
return true;
},
{ success: `${baseName(path)}: conflicts resolved` },
{ success: tx`${baseName(path)}: conflicts resolved` },
);
if (ok) {
setResolved(true);
+21 -17
Переглянути файл
@@ -79,7 +79,7 @@ async function performCheckout(
let options: CheckoutOptions = { mode: "normal", diverged: null };
for (;;) {
const opts = options;
const outcome = await runOp(`Checkout ${rev}`, () => call(commands.vcsCheckout(repo, rev, false, opts)));
const outcome = await runOp(tx`Checkout ${rev}`, () => call(commands.vcsCheckout(repo, rev, false, opts)));
if (!outcome) return false;
// `success` may name the branch now checked out.
if (outcome.kind === "done" || outcome.kind === "conflicts") await refreshNow();
@@ -167,7 +167,7 @@ export async function newBranch(from?: string) {
const previous = useVcs.getState().branch?.head;
if (checkout) await saveAll();
const create = () => call(commands.vcsCreateBranch(repo, r.value, from ?? null, checkout));
const ok = await runOp(`Create branch ${r.value}`, create, {
const ok = await runOp(tx`Create branch ${r.value}`, create, {
success: checkout ? `Created and checked out ${r.value}` : `Created branch ${r.value}`,
});
if (ok !== undefined && checkout) {
@@ -208,8 +208,8 @@ export async function openConflictsInstead(): Promise<boolean> {
export async function mergeIntoCurrent(branch: string, repo: string | null = currentRepo()) {
const current = (repo === currentRepo() ? useVcs.getState().branch?.head : null) ?? "HEAD";
await saveAll();
await runOp(`Merge ${branch} into ${current}`, () => call(commands.vcsMerge(repo, branch)), {
success: `Merged ${branch} into ${current}`,
await runOp(tx`Merge ${branch} into ${current}`, () => call(commands.vcsMerge(repo, branch)), {
success: tx`Merged ${branch} into ${current}`,
errorActions: conflictActions,
onError: openConflictsInstead,
});
@@ -219,8 +219,8 @@ export async function rebaseOnto(branch: string) {
const repo = currentRepo();
const current = useVcs.getState().branch?.head ?? "HEAD";
await saveAll();
await runOp(`Rebase ${current} onto ${branch}`, () => call(commands.vcsRebase(repo, branch)), {
success: `Rebased ${current} onto ${branch}`,
await runOp(tx`Rebase ${current} onto ${branch}`, () => call(commands.vcsRebase(repo, branch)), {
success: tx`Rebased ${current} onto ${branch}`,
errorActions: conflictActions,
onError: openConflictsInstead,
});
@@ -238,8 +238,8 @@ export async function renameBranch(name: string) {
});
if (!r) return;
const to = r.value.trim();
const ok = await runOp(`Rename branch ${name}`, () => call(commands.vcsRenameBranch(repo, name, to)), {
success: `Renamed branch ${name} to ${to}`,
const ok = await runOp(tx`Rename branch ${name}`, () => call(commands.vcsRenameBranch(repo, name, to)), {
success: tx`Renamed branch ${name} to ${to}`,
});
if (ok !== undefined) {
try {
@@ -257,7 +257,7 @@ export async function renameBranch(name: string) {
*/
export async function deleteBranch(name: string) {
const repo = currentRepo();
const deleted = await runOp(`Delete branch ${name}`, () => call(commands.vcsDeleteBranch(repo, name)));
const deleted = await runOp(tx`Delete branch ${name}`, () => call(commands.vcsDeleteBranch(repo, name)));
if (!deleted) return;
const tracked = deleted.upstream;
const otherTracking = useVcs.getState().branches.some((b) => !b.remote && b.name !== name && b.upstream === tracked);
@@ -274,8 +274,8 @@ export async function deleteBranch(name: string) {
label: "Restore",
run: () => {
done();
void runOp(`Restore branch ${name}`, () => call(commands.vcsRestoreBranch(repo, deleted)), {
success: `Restored branch ${name}`,
void runOp(tx`Restore branch ${name}`, () => call(commands.vcsRestoreBranch(repo, deleted)), {
success: tx`Restored branch ${name}`,
});
},
},
@@ -379,9 +379,13 @@ export async function newBranchInRepos(roots: readonly string[], from?: string)
await eachRepo(
roots,
async (root) =>
(await runOp(`Create branch ${name}`, () => call(commands.vcsCreateBranch(root, name, from ?? null, r.checked)), {
refresh: false,
})) !== undefined,
(await runOp(
tx`Create branch ${name}`,
() => call(commands.vcsCreateBranch(root, name, from ?? null, r.checked)),
{
refresh: false,
},
)) !== undefined,
r.checked ? `Created and checked out ${name}` : `Created branch ${name}`,
);
}
@@ -392,7 +396,7 @@ export async function mergeInRepos(branch: string, roots: readonly string[]) {
await eachRepo(
roots,
async (root) =>
(await runOp(`Merge ${branch} (${repoName(root)})`, () => call(commands.vcsMerge(root, branch)), {
(await runOp(tx`Merge ${branch} (${repoName(root)})`, () => call(commands.vcsMerge(root, branch)), {
refresh: false,
errorActions: conflictActions,
onError: openConflictsInstead,
@@ -407,7 +411,7 @@ export async function rebaseInRepos(branch: string, roots: readonly string[]) {
await eachRepo(
roots,
async (root) =>
(await runOp(`Rebase onto ${branch} (${repoName(root)})`, () => call(commands.vcsRebase(root, branch)), {
(await runOp(tx`Rebase onto ${branch} (${repoName(root)})`, () => call(commands.vcsRebase(root, branch)), {
refresh: false,
errorActions: conflictActions,
onError: openConflictsInstead,
@@ -428,7 +432,7 @@ export async function deleteInRepos(name: string, roots: readonly string[]) {
await eachRepo(
roots,
async (root) =>
(await runOp(`Delete branch ${name} (${repoName(root)})`, () => call(commands.vcsDeleteBranch(root, name)), {
(await runOp(tx`Delete branch ${name} (${repoName(root)})`, () => call(commands.vcsDeleteBranch(root, name)), {
refresh: false,
})) !== undefined,
`Deleted branch ${name}`,
+27 -12
Переглянути файл
@@ -90,8 +90,23 @@ export async function commitChanges(): Promise<string | undefined> {
success: () => {
const n = plan.included.length;
const summary = firstLine(message);
const where = repos.length > 1 ? ` in ${repos.length} repositories` : "";
return `${n} file${n === 1 ? "" : "s"} ${amend ? "amended" : "committed"}${where}${summary ? `: ${summary}` : ""}`;
const several = repos.length;
const done = amend
? several > 1
? n === 1
? tx`1 file amended in ${several} repositories`
: tx`${n} files amended in ${several} repositories`
: n === 1
? tx("1 file amended")
: tx`${n} files amended`
: several > 1
? n === 1
? tx`1 file committed in ${several} repositories`
: tx`${n} files committed in ${several} repositories`
: n === 1
? tx("1 file committed")
: tx`${n} files committed`;
return summary ? `${done}: ${summary}` : done;
},
},
);
@@ -199,12 +214,12 @@ function RollbackDialog({ files, onClose }: { files: FileStatus[]; onClose: () =
await saveAll();
const { unstage, discard } = rollbackPlan(chosen, deleteAdded);
await runOp(
"Rollback",
tx("Rollback"),
async () => {
if (unstage.length) await call(commands.vcsUnstage(unstage));
if (discard.length) await call(commands.vcsDiscard(discard));
},
{ success: `Rolled back ${chosen.length} file${chosen.length === 1 ? "" : "s"}` },
{ success: chosen.length === 1 ? tx("Rolled back 1 file") : tx`Rolled back ${chosen.length} files` },
);
};
return (
@@ -284,7 +299,7 @@ export async function addToVcs(paths: readonly string[]) {
});
return;
}
await runOp(`Add ${targets.length} file${targets.length === 1 ? "" : "s"} to Git`, () =>
await runOp(targets.length === 1 ? tx("Add 1 file to Git") : tx`Add ${targets.length} files to Git`, () =>
call(commands.vcsStage(targets.map(stripSlash))),
);
}
@@ -299,9 +314,9 @@ export function stashChanges(repo = currentRepo()): Promise<void> {
if (r) {
await saveAll();
await runOp(
"Stash changes",
tx("Stash changes"),
() => call(commands.vcsStashPush(repo, r.message || null, r.includeUntracked, r.keepIndex)),
{ success: "Changes stashed" },
{ success: tx("Changes stashed") },
);
void loadStashes().catch(() => undefined);
}
@@ -372,10 +387,10 @@ export interface UnstashOptions {
export function unstashTitles(o: UnstashOptions): { title: string; success: string } {
const ref = `stash@{${o.index}}`;
if (o.branch)
return { title: tx`Unstash ${ref} as branch ${o.branch}`, success: `Unstashed ${ref} on branch ${o.branch}` };
return { title: tx`Unstash ${ref} as branch ${o.branch}`, success: tx`Unstashed ${ref} on branch ${o.branch}` };
return o.pop
? { title: tx`Pop ${ref}`, success: `Unstashed ${ref}` }
: { title: tx`Apply ${ref}`, success: `Applied ${ref}` };
? { title: tx`Pop ${ref}`, success: tx`Unstashed ${ref}` }
: { title: tx`Apply ${ref}`, success: tx`Applied ${ref}` };
}
export async function unstash(o: UnstashOptions, repo = currentRepo()) {
@@ -482,8 +497,8 @@ export async function dropStash(index: number, message: string, repo = currentRe
danger: true,
});
if (!ok) return;
await runOp(`Drop stash@{${index}}`, () => call(commands.vcsStashDrop(repo, index)), {
success: `Dropped stash@{${index}}`,
await runOp(tx`Drop ${`stash@{${index}}`}`, () => call(commands.vcsStashDrop(repo, index)), {
success: tx`Dropped ${`stash@{${index}}`}`,
});
void loadStashes().catch((e) => logConsole("err", String(e)));
}
+7 -2
Переглянути файл
@@ -4,6 +4,7 @@
* the deletion staged (`git rm`), with the same three choices.
*/
import { onFileOperation, type FileOperation } from "@/platform/fileOps";
import { tx } from "@/platform/i18n";
import { call, commands, isTauri } from "@/platform/ipc";
import { loadSettings } from "@/platform/project";
import { workflowSettings } from "@/platform/workflowSettings";
@@ -80,8 +81,12 @@ async function handle(op: FileOperation) {
const n = files.length;
await runOp(
op.kind === "created"
? `Add ${n} file${n === 1 ? "" : "s"} to Git`
: `Remove ${n} file${n === 1 ? "" : "s"} from Git`,
? n === 1
? tx("Add 1 file to Git")
: tx`Add ${n} files to Git`
: n === 1
? tx("Remove 1 file from Git")
: tx`Remove ${n} files from Git`,
() => call(commands.vcsStage(files)),
);
}
+2 -2
Переглянути файл
@@ -31,7 +31,7 @@ export function openMergeEditor(path: string) {
async function acceptSide(f: FileStatus, side: "ours" | "theirs") {
const path = stripSlash(f.path);
await runOp(
`Accept ${side === "ours" ? "yours" : "theirs"} for ${baseName(path)}`,
side === "ours" ? tx`Accept yours for ${baseName(path)}` : tx`Accept theirs for ${baseName(path)}`,
async () => {
const v = await call(commands.vcsConflictVersions(path));
const text = side === "ours" ? v.ours : v.theirs;
@@ -43,7 +43,7 @@ async function acceptSide(f: FileStatus, side: "ours" | "theirs") {
}
await call(commands.vcsStage([path]));
},
{ success: `${baseName(path)} resolved` },
{ success: tx`${baseName(path)} resolved` },
);
}
+4 -4
Переглянути файл
@@ -238,9 +238,9 @@ function MergeDialog({ onClose }: { onClose: () => void }) {
await saveAll();
const target = head ?? "HEAD";
await runOp(
`Merge ${branch} into ${target}`,
tx`Merge ${branch} into ${target}`,
() => call(commands.vcsMergeWith(repo, [branch], mergeOptionsOf(flags, withMessage ? message : null))),
{ success: `Merged ${branch} into ${target}`, errorActions: conflictActions, onError: openConflictsInstead },
{ success: tx`Merged ${branch} into ${target}`, errorActions: conflictActions, onError: openConflictsInstead },
);
await refreshNow();
};
@@ -313,7 +313,7 @@ function RebaseDialog({ onClose }: { onClose: () => void }) {
const target = useOnto && onto ? onto : upstream;
const current = head ?? "HEAD";
await runOp(
`Rebase ${current} onto ${target}`,
tx`Rebase ${current} onto ${target}`,
() =>
call(
commands.vcsRebaseWith(repo, upstream, {
@@ -322,7 +322,7 @@ function RebaseDialog({ onClose }: { onClose: () => void }) {
keepEmpty,
}),
),
{ success: `Rebased ${current} onto ${target}`, errorActions: conflictActions, onError: openConflictsInstead },
{ success: tx`Rebased ${current} onto ${target}`, errorActions: conflictActions, onError: openConflictsInstead },
);
await refreshNow();
};
+2 -2
Переглянути файл
@@ -184,7 +184,7 @@ export async function getFromRevision(changes: readonly LogChange[]) {
if (!ok) return;
await saveAll();
await runOp(
"Get from Revision",
tx("Get from Revision"),
async () => {
for (const c of changes) {
const path = absolutePath(c.path, c.repo);
@@ -211,7 +211,7 @@ export async function revertSelectedChanges(changes: readonly LogChange[], commi
const ids = [...commits].reverse().map((c) => c.id);
const paths = new Set(changes.flatMap((c) => (c.oldPath ? [c.path, c.oldPath] : [c.path])));
const outcome = await runOp(
"Revert selected changes",
tx("Revert selected changes"),
async () => {
const patch = filterPatch(
await call(
+10 -10
Переглянути файл
@@ -165,7 +165,7 @@ export async function controlOperation(control: "continue" | "skip" | "abort", e
{
progress,
success,
failure: `${controlLabel(op, control)} failed`,
failure: tx`${controlLabel(op, control)} failed`,
onError: control === "abort" ? undefined : conflictsInstead,
},
);
@@ -241,8 +241,8 @@ export async function cherryPick(commits: Commit[]) {
{ op: "cherryPick", commits: ordered.map((c) => c.id) },
{
progress: "Cherry-picking",
success: `Cherry-picked ${label}`,
failure: "Cherry-pick failed",
success: tx`Cherry-picked ${label}`,
failure: tx("Cherry-pick failed"),
onError: conflictsInstead,
},
commitRepo(commits[0]!) ?? currentRepo(),
@@ -256,7 +256,7 @@ export async function revertCommits(commits: Commit[]) {
ordered.length === 1 ? `${short(ordered[0]!.id)} “${ordered[0]!.summary}”` : `${ordered.length} commits`;
await runGitOperation(
{ op: "revert", commits: ordered.map((c) => c.id) },
{ progress: "Reverting", success: `Reverted ${label}`, failure: "Revert failed", onError: conflictsInstead },
{ progress: "Reverting", success: tx`Reverted ${label}`, failure: tx("Revert failed"), onError: conflictsInstead },
commitRepo(commits[0]!) ?? currentRepo(),
);
}
@@ -294,8 +294,8 @@ function ResetDialog({ commit, onClose }: { commit?: Commit; onClose: () => void
{ op: "reset", commit: rev, mode },
{
progress: "Resetting",
success: `Reset ${head} to ${commit ? short(commit.id) : rev} (${mode})`,
failure: "Reset failed",
success: tx`Reset ${head} to ${commit ? short(commit.id) : rev} (${mode})`,
failure: tx("Reset failed"),
},
repo,
);
@@ -374,8 +374,8 @@ export async function newTagOn(commit: Commit) {
});
if (!r) return;
const create = () => call(commands.vcsCreateTag(repo, r.value, commit.id, null, false));
const ok = await runOp(`Create tag ${r.value}`, create, {
success: `Created tag ${r.value} on ${short(commit.id)}`,
const ok = await runOp(tx`Create tag ${r.value}`, create, {
success: tx`Created tag ${r.value} on ${short(commit.id)}`,
});
// Tags are not part of the refs signature that reloads the log.
if (ok !== undefined) void reloadLog();
@@ -402,9 +402,9 @@ function TagDialog({ onClose }: { onClose: () => void }) {
onClose();
const tag = name.trim();
const ok = await runOp(
`Create tag ${tag}`,
tx`Create tag ${tag}`,
() => call(commands.vcsCreateTag(repo, tag, commit.trim() || "HEAD", message || null, force)),
{ success: `Created tag ${tag}` },
{ success: tx`Created tag ${tag}` },
);
if (ok !== undefined && useGitLog.getState().commits.length) void reloadLog();
};
+1 -1
Переглянути файл
@@ -89,7 +89,7 @@ export async function runOp<T>(title: string, fn: () => Promise<T>, opts: OpOpti
notifications().notify({
group: "vcs",
level: "error",
title: `${title} failed`,
title: tx`${title} failed`,
body: message,
actions: opts.errorActions?.(message),
});
+2 -2
Переглянути файл
@@ -116,7 +116,7 @@ function CreatePatchDialog({ origin, onClose }: { origin: PatchOrigin; onClose:
reverse,
};
const done = await runOp(
"Create patch",
tx("Create patch"),
async () => {
const patch =
origin.kind === "shelf"
@@ -250,7 +250,7 @@ function ApplyPatchDialog({ patch, source, onClose }: { patch: string; source: s
const run = async () => {
onClose();
await saveAll();
const outcome = await runOp(`Apply patch (${source})`, () => call(commands.vcsApplyPatch(repo, patch)), {
const outcome = await runOp(tx`Apply patch (${source})`, () => call(commands.vcsApplyPatch(repo, patch)), {
refresh: false,
});
if (outcome) await reportApply(outcome, "Patch applied");
+3 -3
Переглянути файл
@@ -62,8 +62,8 @@ export async function undoCommit(commit: Commit) {
if (!ok) return;
}
const undo = () => call(commands.vcsUndoCommit(repo, commit.id));
const message = await runOp(`Undo commit ${short(commit.id)}`, undo, {
success: `Undid commit ${short(commit.id)} “${commit.summary}”; its changes are staged`,
const message = await runOp(tx`Undo commit ${short(commit.id)}`, undo, {
success: tx`Undid commit ${short(commit.id)} “${commit.summary}”; its changes are staged`,
});
// IDEA offers the undone message for the next commit.
if (message !== undefined && !useCommit.getState().message.trim()) useCommit.setState({ message });
@@ -123,7 +123,7 @@ async function runRebase(base: string | null, entries: RebaseEntry[], done: stri
{
progress: "Rebasing",
success: () => (inProgress() === "rebase" ? "Rebase stopped for editing; continue the rebase when done" : done),
failure: "Rebase failed",
failure: tx("Rebase failed"),
onError: conflictsInstead,
},
repo,
+5 -5
Переглянути файл
@@ -144,7 +144,7 @@ function ShelveDialog({
if (!chosen.length || !name.trim()) return;
onClose();
await saveAll();
const entry = await runOp(`Shelve changes “${name.trim()}”`, () => call(commands.vcsShelve(name.trim(), chosen)));
const entry = await runOp(tx`Shelve changes “${name.trim()}”`, () => call(commands.vcsShelve(name.trim(), chosen)));
if (!entry) return;
void loadShelves().catch(() => undefined);
notifications().notify({
@@ -218,7 +218,7 @@ function UnshelveDialog({ entry, path, onClose }: { entry: ShelvedChangeList; pa
onClose();
await saveAll();
const outcome = await runOp(
`Unshelve “${entry.name}”`,
tx`Unshelve “${entry.name}”`,
() => call(commands.vcsUnshelve(entry.id, remove, path ?? null)),
{
refresh: false,
@@ -272,7 +272,7 @@ export async function deleteShelf(entry: ShelvedChangeList, path?: string) {
danger: true,
});
if (!ok) return;
await runOp(`Delete shelved changes “${entry.name}”`, () => call(commands.vcsShelfDelete(entry.id, path ?? null)), {
await runOp(tx`Delete shelved changes “${entry.name}”`, () => call(commands.vcsShelfDelete(entry.id, path ?? null)), {
refresh: false,
});
void loadShelves().catch(() => undefined);
@@ -286,7 +286,7 @@ export async function renameShelf(entry: ShelvedChangeList) {
okLabel: "Rename",
});
if (!r || r.value === entry.name) return;
await runOp("Rename shelved changes", () => call(commands.vcsShelfRename(entry.id, r.value)), { refresh: false });
await runOp(tx("Rename shelved changes"), () => call(commands.vcsShelfRename(entry.id, r.value)), { refresh: false });
void loadShelves().catch(() => undefined);
}
@@ -388,7 +388,7 @@ export async function importShelfPatches(paths?: string[]) {
if (!picked) return;
for (const path of typeof picked === "string" ? [picked] : picked) {
await runOp(
"Import patches",
tx("Import patches"),
async () => call(commands.vcsShelfImport(repo, baseName(path), await call(commands.fsReadText(path)))),
{ refresh: false },
);
+53 -1
Переглянути файл
@@ -5858,5 +5858,57 @@
"Could not start Compose project {project}": "无法启动 Compose 项目 {project}",
"Could not stop Compose project {project}": "无法停止 Compose 项目 {project}",
"Could not restart Compose project {project}": "无法重启 Compose 项目 {project}",
"Could not take down Compose project {project}": "无法停止并移除 Compose 项目 {project}"
"Could not take down Compose project {project}": "无法停止并移除 Compose 项目 {project}",
"Stage hunk of {path}": "暂存 {path} 的块",
"{path} reverted": "已还原 {path}",
"{length} files reverted": "已还原 {length} 个文件",
"Resolve {path}": "解决 {path}",
"{path}: conflicts resolved": "{path}:冲突已解决",
"Create branch {value}": "创建分支 {value}",
"Merge {branch} into {current}": "将 {branch} 合并到 {current}",
"Merged {branch} into {current}": "已将 {branch} 合并到 {current}",
"Rebase {current} onto {branch}": "将 {current} 变基到 {branch}",
"Rebased {current} onto {branch}": "已将 {current} 变基到 {branch}",
"Rename branch {name}": "重命名分支 {name}",
"Renamed branch {name} to {to}": "已将分支 {name} 重命名为 {to}",
"Delete branch {name}": "删除分支 {name}",
"Restore branch {name}": "恢复分支 {name}",
"Merge {branch} ({root})": "合并 {branch}({root})",
"Rebase onto {branch} ({root})": "变基到 {branch}({root})",
"Delete branch {name} ({root})": "删除分支 {name}({root})",
"1 file amended in {several} repositories": "已在 {several} 个仓库中修改 1 个文件的提交",
"{n} files amended in {several} repositories": "已在 {several} 个仓库中修改 {n} 个文件的提交",
"1 file amended": "已修改 1 个文件的提交",
"{n} files amended": "已修改 {n} 个文件的提交",
"1 file committed in {several} repositories": "已在 {several} 个仓库中提交 1 个文件",
"{n} files committed in {several} repositories": "已在 {several} 个仓库中提交 {n} 个文件",
"1 file committed": "已提交 1 个文件",
"{n} files committed": "已提交 {n} 个文件",
"Rolled back 1 file": "已回滚 1 个文件",
"Rolled back {length} files": "已回滚 {length} 个文件",
"Add 1 file to Git": "将 1 个文件添加到 Git",
"Add {length} files to Git": "将 {length} 个文件添加到 Git",
"Stash changes": "储藏更改",
"Changes stashed": "已储藏更改",
"Unstashed {ref} on branch {branch}": "已在分支 {branch} 上取消储藏 {ref}",
"Unstashed {ref}": "已取消储藏 {ref}",
"Applied {ref}": "已应用 {ref}",
"Dropped {ref}": "已删除 {ref}",
"Remove 1 file from Git": "从 Git 中移除 1 个文件",
"Remove {n} files from Git": "从 Git 中移除 {n} 个文件",
"Accept yours for {path}": "对 {path} 接受你的版本",
"Accept theirs for {path}": "对 {path} 接受他们的版本",
"{path} resolved": "{path} 已解决",
"Revert selected changes": "还原所选更改",
"Create tag {value}": "创建标签 {value}",
"Created tag {value} on {id}": "已在 {id} 上创建标签 {value}",
"Created tag {tag}": "已创建标签 {tag}",
"Create patch": "创建补丁",
"Apply patch ({source})": "应用补丁({source})",
"Undo commit {id}": "撤销提交 {id}",
"Undid commit {id} “{summary}”; its changes are staged": "已撤销提交 {id}“{summary}”,其更改已暂存",
"Shelve changes “{trim}”": "搁置更改“{trim}”",
"Unshelve “{name}”": "取消搁置“{name}”",
"Delete shelved changes “{name}”": "删除搁置的更改“{name}”",
"Rename shelved changes": "重命名搁置的更改"
}