Skip to content

Commit 1f82e1b

Browse files
committed
进一步汉化一些消息,并修复UT
1 parent f586eb5 commit 1f82e1b

22 files changed

+244
-215
lines changed

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
</p>
1111
<p align="center">面向AI编程助手的规范驱动开发框架</p>
1212
<p align="center">
13-
<a href="https://github.com/Fission-AI/OpenSpec/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Fission-AI/OpenSpec/actions/workflows/ci.yml/badge.svg" /></a>
14-
<a href="https://www.npmjs.com/package/@fission-ai/openspec"><img alt="npm version" src="https://img.shields.io/npm/v/@fission-ai/openspec?style=flat-square" /></a>
15-
<a href="https://nodejs.org/"><img alt="node version" src="https://img.shields.io/node/v/@fission-ai/openspec?style=flat-square" /></a>
13+
<a href="https://github.com/studyzy/OpenSpec-cn/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/studyzy/OpenSpec-cn/actions/workflows/ci.yml/badge.svg" /></a>
14+
<a href="https://www.npmjs.com/package/@studyzy/openspec-cn"><img alt="npm version" src="https://img.shields.io/npm/v/@studyzy/openspec-cn?style=flat-square" /></a>
15+
<a href="https://nodejs.org/"><img alt="node version" src="https://img.shields.io/node/v/@studyzy/openspec-cn?style=flat-square" /></a>
1616
<a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" /></a>
1717
<a href="https://conventionalcommits.org"><img alt="Conventional Commits" src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=flat-square" /></a>
1818
<a href="https://discord.gg/YctCnvvshC"><img alt="Discord" src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white&style=flat-square" /></a>
@@ -60,15 +60,15 @@ AI编程助手虽然功能强大,但当需求仅存在于聊天记录中时,
6060

6161
```
6262
┌────────────────────────┐
63-
│ 起草变更提案 │
63+
│ 起草变更提案
6464
└────────────┬───────────┘
6565
│ 与AI共享意图
6666
6767
┌────────────────────────┐
68-
│ 审查与对齐 │
69-
│ (编辑规范/任务) │◀──── 反馈循环 ──────┐
68+
│ 审查与对齐
69+
│ (编辑规范/任务) │◀──── 反馈循环 ────────────┐
7070
└────────────┬───────────┘ │
71-
│ 批准计划 │
71+
│ 批准计划
7272
▼ │
7373
┌────────────────────────┐ │
7474
│ 实施任务 │──────────────────────────┘
@@ -77,8 +77,8 @@ AI编程助手虽然功能强大,但当需求仅存在于聊天记录中时,
7777
│ 交付变更
7878
7979
┌────────────────────────┐
80-
│ 归档与更新 │
81-
│ 规范(真实来源) │
80+
│ 归档与更新
81+
│ 规范(真实来源)
8282
└────────────────────────┘
8383
```
8484

@@ -144,6 +144,12 @@ Kilo Code会自动发现团队工作流。将生成的文件保存在 `.kilocode
144144
```bash
145145
npm install -g @studyzy/openspec-cn@latest
146146
```
147+
或者通过源码安装
148+
```bash
149+
git clone https://github.com/studyzy/OpenSpec-cn.git
150+
cd OpenSpec-cn
151+
make install
152+
```
147153

148154
验证安装:
149155
```bash
@@ -392,7 +398,7 @@ OpenSpec将每个功能的变更分组到一个文件夹中(`openspec/changes/
392398

393399
1. **升级包**
394400
```bash
395-
npm install -g @fission-ai/openspec@latest
401+
npm install -g @studyzy/openspec-cn@latest
396402
```
397403
2. **刷新代理指令**
398404
- 在每个项目中运行 `openspec-cn update`,重新生成AI指导并确保最新的斜杠命令处于活动状态。

src/cli/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ program
5151
try {
5252
const stats = await fs.stat(resolvedPath);
5353
if (!stats.isDirectory()) {
54-
throw new Error(`Path "${targetPath}" is not a directory`);
54+
throw new Error(`路径 "${targetPath}" 不是一个目录`);
5555
}
5656
} catch (error: any) {
5757
if (error.code === 'ENOENT') {
@@ -60,7 +60,7 @@ program
6060
} else if (error.message && error.message.includes('not a directory')) {
6161
throw error;
6262
} else {
63-
throw new Error(`Cannot access path "${targetPath}": ${error.message}`);
63+
throw new Error(`无法访问路径 "${targetPath}": ${error.message}`);
6464
}
6565
}
6666

src/core/archive.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export class ArchiveCommand {
239239
// Check if archive already exists
240240
try {
241241
await fs.access(archivePath);
242-
throw new Error(`Archive '${archiveName}' already exists.`);
242+
throw new Error(`归档 '${archiveName}' 已经存在。`);
243243
} catch (error: any) {
244244
if (error.code !== 'ENOENT') {
245245
throw error;
@@ -359,7 +359,7 @@ export class ArchiveCommand {
359359
const name = normalizeRequirementName(add.name);
360360
if (addedNames.has(name)) {
361361
throw new Error(
362-
`${specName} validation failed - duplicate requirement in ADDED for header "### Requirement: ${add.name}"`
362+
`${specName} 验证失败 - ADDED 中的重复需求,标题为 "███ Requirement: ${add.name}"`
363363
);
364364
}
365365
addedNames.add(name);
@@ -369,7 +369,7 @@ export class ArchiveCommand {
369369
const name = normalizeRequirementName(mod.name);
370370
if (modifiedNames.has(name)) {
371371
throw new Error(
372-
`${specName} validation failed - duplicate requirement in MODIFIED for header "### Requirement: ${mod.name}"`
372+
`${specName} 验证失败 - MODIFIED 中的重复需求,标题为 "### Requirement: ${mod.name}"`
373373
);
374374
}
375375
modifiedNames.add(name);
@@ -379,7 +379,7 @@ export class ArchiveCommand {
379379
const name = normalizeRequirementName(rem);
380380
if (removedNamesSet.has(name)) {
381381
throw new Error(
382-
`${specName} validation failed - duplicate requirement in REMOVED for header "### Requirement: ${rem}"`
382+
`${specName} 验证失败 - REMOVED 中的重复需求,标题为 "### Requirement: ${rem}"`
383383
);
384384
}
385385
removedNamesSet.add(name);
@@ -391,12 +391,12 @@ export class ArchiveCommand {
391391
const toNorm = normalizeRequirementName(to);
392392
if (renamedFromSet.has(fromNorm)) {
393393
throw new Error(
394-
`${specName} validation failed - duplicate FROM in RENAMED for header "### Requirement: ${from}"`
394+
`${specName} 验证失败 - RENAMED 中的重复 FROM,标题为 "### Requirement: ${from}"`
395395
);
396396
}
397397
if (renamedToSet.has(toNorm)) {
398398
throw new Error(
399-
`${specName} validation failed - duplicate TO in RENAMED for header "### Requirement: ${to}"`
399+
`${specName} 验证失败 - RENAMED 中的重复 TO,标题为 "### Requirement: ${to}"`
400400
);
401401
}
402402
renamedFromSet.add(fromNorm);
@@ -418,27 +418,27 @@ export class ArchiveCommand {
418418
const toNorm = normalizeRequirementName(to);
419419
if (modifiedNames.has(fromNorm)) {
420420
throw new Error(
421-
`${specName} validation failed - when a rename exists, MODIFIED must reference the NEW header "### Requirement: ${to}"`
421+
`${specName} 验证失败 - 当存在重命名时,MODIFIED 必须引用新标题 "### Requirement: ${to}"`
422422
);
423423
}
424424
// Detect ADDED colliding with a RENAMED TO
425425
if (addedNames.has(toNorm)) {
426426
throw new Error(
427-
`${specName} validation failed - RENAMED TO header collides with ADDED for "### Requirement: ${to}"`
427+
`${specName} 验证失败 - RENAMED TO 标题与 ADDED 冲突,标题为 "### Requirement: ${to}"`
428428
);
429429
}
430430
}
431431
if (conflicts.length > 0) {
432432
const c = conflicts[0];
433433
throw new Error(
434-
`${specName} validation failed - requirement present in multiple sections (${c.a} and ${c.b}) for header "### Requirement: ${c.name}"`
434+
`${specName} 验证失败 - 需求出现在多个部分中(${c.a} ${c.b}),标题为 "### Requirement: ${c.name}"`
435435
);
436436
}
437437
const hasAnyDelta = (plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length) > 0;
438438
if (!hasAnyDelta) {
439439
throw new Error(
440-
`Delta parsing found no operations for ${path.basename(path.dirname(update.source))}. ` +
441-
`Provide ADDED/MODIFIED/REMOVED/RENAMED sections in change spec.`
440+
`未找到 ${path.basename(path.dirname(update.source))} 的任何操作。` +
441+
`请在变更规范中提供 ADDED/MODIFIED/REMOVED/RENAMED 部分。`
442442
);
443443
}
444444

@@ -450,7 +450,7 @@ export class ArchiveCommand {
450450
// Target spec does not exist; only ADDED operations are permitted
451451
if (plan.modified.length > 0 || plan.removed.length > 0 || plan.renamed.length > 0) {
452452
throw new Error(
453-
`${specName}: target spec does not exist; only ADDED requirements are allowed for new specs.`
453+
`${specName}: 目标规范不存在;新规范仅允许 ADDED 需求。`
454454
);
455455
}
456456
targetContent = this.buildSpecSkeleton(specName, changeName);
@@ -470,12 +470,12 @@ export class ArchiveCommand {
470470
const to = normalizeRequirementName(r.to);
471471
if (!nameToBlock.has(from)) {
472472
throw new Error(
473-
`${specName} RENAMED failed for header "### Requirement: ${r.from}" - source not found`
473+
`${specName} RENAMED 失败,标题 "### Requirement: ${r.from}" - 未找到源`
474474
);
475475
}
476476
if (nameToBlock.has(to)) {
477477
throw new Error(
478-
`${specName} RENAMED failed for header "### Requirement: ${r.to}" - target already exists`
478+
`${specName} RENAMED 失败,标题 "### Requirement: ${r.to}" - 目标已经存在`
479479
);
480480
}
481481
const block = nameToBlock.get(from)!;
@@ -497,7 +497,7 @@ export class ArchiveCommand {
497497
const key = normalizeRequirementName(name);
498498
if (!nameToBlock.has(key)) {
499499
throw new Error(
500-
`${specName} REMOVED failed for header "### Requirement: ${name}" - not found`
500+
`${specName} REMOVED 失败,标题 "### Requirement: ${name}" - 未找到`
501501
);
502502
}
503503
nameToBlock.delete(key);
@@ -508,15 +508,15 @@ export class ArchiveCommand {
508508
const key = normalizeRequirementName(mod.name);
509509
if (!nameToBlock.has(key)) {
510510
throw new Error(
511-
`${specName} MODIFIED failed for header "### Requirement: ${mod.name}" - not found`
511+
`${specName} MODIFIED 失败,标题 "### Requirement: ${mod.name}" - 未找到`
512512
);
513513
}
514514
// Replace block with provided raw (ensure header line matches key)
515515
const REQUIREMENT_KEYWORD_PATTERN = '(?:Requirement|需求)';
516516
const modHeaderMatch = mod.raw.split('\n')[0].match(new RegExp(`^###\\s*${REQUIREMENT_KEYWORD_PATTERN}:\\s*(.+)\\s*$`));
517517
if (!modHeaderMatch || normalizeRequirementName(modHeaderMatch[1]) !== key) {
518518
throw new Error(
519-
`${specName} MODIFIED failed for header "### Requirement: ${mod.name}" - header mismatch in content`
519+
`${specName} MODIFIED 失败,标题 "### Requirement: ${mod.name}" - 内容中的标题不匹配`
520520
);
521521
}
522522
nameToBlock.set(key, mod);

src/core/configurators/slash/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export abstract class SlashCommandConfigurator {
8383
const endIndex = content.indexOf(OPENSPEC_MARKERS.end);
8484

8585
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
86-
throw new Error(`Missing OpenSpec markers in ${filePath}`);
86+
throw new Error(`${filePath} 中缺少 OpenSpec 标记`);
8787
}
8888

8989
const before = content.slice(0, startIndex + OPENSPEC_MARKERS.start.length);

src/core/configurators/slash/codex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ $ARGUMENTS`,
105105
const startIndex = content.indexOf(OPENSPEC_MARKERS.start);
106106

107107
if (startIndex === -1) {
108-
throw new Error(`Missing OpenSpec start marker in ${filePath}`);
108+
throw new Error(`${filePath} 中缺少 OpenSpec 开始标记`);
109109
}
110110

111111
// Replace everything before the start marker with the new frontmatter

src/core/configurators/slash/toml-base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ ${OPENSPEC_MARKERS.end}
5454
const endIndex = content.indexOf(OPENSPEC_MARKERS.end);
5555

5656
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
57-
throw new Error(`Missing OpenSpec markers in ${filePath}`);
57+
throw new Error(`${filePath} 中缺少 OpenSpec 标记`);
5858
}
5959

6060
const before = content.slice(0, startIndex + OPENSPEC_MARKERS.start.length);

src/core/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export class InitCommand {
467467

468468
// Check write permissions
469469
if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
470-
throw new Error(`Insufficient permissions to write to ${projectPath}`);
470+
throw new Error(`没有权限写入 ${projectPath}`);
471471
}
472472
return extendMode;
473473
}

src/core/parsers/change-parser.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ export class ChangeParser extends MarkdownParser {
1919

2020
async parseChangeWithDeltas(name: string): Promise<Change> {
2121
const sections = this.parseSections();
22-
const why = this.findSection(sections, 'Why')?.content || '';
23-
const whatChanges = this.findSection(sections, 'What Changes')?.content || '';
22+
const why = this.findSection(sections, 'Why')?.content || this.findSection(sections, '为什么')?.content || '';
23+
const whatChanges = this.findSection(sections, 'What Changes')?.content || this.findSection(sections, '变更内容')?.content || '';
2424

2525
if (!why) {
26-
throw new Error('Change must have a Why section');
26+
throw new Error('变更必须有为什么部分');
2727
}
2828

2929
if (!whatChanges) {
30-
throw new Error('Change must have a What Changes section');
30+
throw new Error('变更必须有变更内容部分');
3131
}
3232

3333
// Parse deltas from the What Changes section (simple format)
@@ -85,8 +85,8 @@ export class ChangeParser extends MarkdownParser {
8585
const deltas: Delta[] = [];
8686
const sections = this.parseSectionsFromContent(content);
8787

88-
// Parse ADDED requirements
89-
const addedSection = this.findSection(sections, 'ADDED Requirements');
88+
// 解析新增需求
89+
const addedSection = this.findSection(sections, '新增需求') || this.findSection(sections, 'ADDED Requirements');
9090
if (addedSection) {
9191
const requirements = this.parseRequirements(addedSection);
9292
requirements.forEach(req => {
@@ -101,8 +101,8 @@ export class ChangeParser extends MarkdownParser {
101101
});
102102
}
103103

104-
// Parse MODIFIED requirements
105-
const modifiedSection = this.findSection(sections, 'MODIFIED Requirements');
104+
// 解析修改需求
105+
const modifiedSection = this.findSection(sections, '修改需求') || this.findSection(sections, 'MODIFIED Requirements');
106106
if (modifiedSection) {
107107
const requirements = this.parseRequirements(modifiedSection);
108108
requirements.forEach(req => {
@@ -116,8 +116,8 @@ export class ChangeParser extends MarkdownParser {
116116
});
117117
}
118118

119-
// Parse REMOVED requirements
120-
const removedSection = this.findSection(sections, 'REMOVED Requirements');
119+
// 解析移除需求
120+
const removedSection = this.findSection(sections, '移除需求') || this.findSection(sections, 'REMOVED Requirements');
121121
if (removedSection) {
122122
const requirements = this.parseRequirements(removedSection);
123123
requirements.forEach(req => {
@@ -131,8 +131,8 @@ export class ChangeParser extends MarkdownParser {
131131
});
132132
}
133133

134-
// Parse RENAMED requirements
135-
const renamedSection = this.findSection(sections, 'RENAMED Requirements');
134+
// 解析重命名需求
135+
const renamedSection = this.findSection(sections, '重命名需求') || this.findSection(sections, 'RENAMED Requirements');
136136
if (renamedSection) {
137137
const renames = this.parseRenames(renamedSection.content);
138138
renames.forEach(rename => {

src/core/parsers/markdown-parser.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ export class MarkdownParser {
2323

2424
parseSpec(name: string): Spec {
2525
const sections = this.parseSections();
26-
const purpose = this.findSection(sections, 'Purpose')?.content || '';
26+
const purpose = this.findSection(sections, 'Purpose')?.content || this.findSection(sections, '目的')?.content || '';
2727

28-
const requirementsSection = this.findSection(sections, 'Requirements');
28+
const requirementsSection = this.findSection(sections, 'Requirements') || this.findSection(sections, '需求');
2929

3030
if (!purpose) {
31-
throw new Error('Spec must have a Purpose section');
31+
throw new Error('规范必须有目的部分');
3232
}
3333

3434
if (!requirementsSection) {
35-
throw new Error('Spec must have a Requirements section');
35+
throw new Error('规范必须有需求部分');
3636
}
3737

3838
const requirements = this.parseRequirements(requirementsSection);
@@ -50,15 +50,15 @@ export class MarkdownParser {
5050

5151
parseChange(name: string): Change {
5252
const sections = this.parseSections();
53-
const why = this.findSection(sections, 'Why')?.content || '';
54-
const whatChanges = this.findSection(sections, 'What Changes')?.content || '';
53+
const why = this.findSection(sections, 'Why')?.content || this.findSection(sections, '为什么')?.content || '';
54+
const whatChanges = this.findSection(sections, 'What Changes')?.content || this.findSection(sections, '变更内容')?.content || '';
5555

5656
if (!why) {
57-
throw new Error('Change must have a Why section');
57+
throw new Error('变更必须有为什么部分');
5858
}
5959

6060
if (!whatChanges) {
61-
throw new Error('Change must have a What Changes section');
61+
throw new Error('变更必须有变更内容部分');
6262
}
6363

6464
const deltas = this.parseDeltas(whatChanges);

0 commit comments

Comments
 (0)