Skip to content

Commit 35bb2d0

Browse files
committed
feat: I18n --story=1
1 parent 1b1e279 commit 35bb2d0

32 files changed

Lines changed: 3700 additions & 101 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
name: OpenSpec: 实施
3+
description: 实施已批准的OpenSpec变更并保持任务同步。
4+
argument-hint: "[change-id]"
5+
---
6+
<!-- OPENSPEC:START -->
7+
**护栏规则**
8+
- 优先使用简单、最小的实现,仅在请求或明确需要时才添加复杂性。
9+
- 将变更紧密限制在请求的结果范围内。
10+
- 如果需要额外的OpenSpec约定或澄清,请参考`openspec/AGENTS.md`(位于`openspec/`目录中—如果看不到,请运行`ls openspec``openspec-cn update`)。
11+
12+
**步骤**
13+
将这些步骤作为TODO跟踪并逐一完成。
14+
1. 阅读`openspec/changes/<id>/proposal.md``design.md`(如果存在)和`tasks.md`以确认范围和验收标准。
15+
2. 按顺序完成任务,保持编辑最小化并专注于请求的变更。
16+
3. 在更新状态前确认完成—确保`tasks.md`中的每个项目都已完成。
17+
4. 所有工作完成后更新清单,使每个任务标记为`- [x]`并反映实际情况。
18+
5. 需要额外上下文时参考`openspec-cn list``openspec-cn show <item>`
19+
20+
**参考**
21+
- 如果在实施过程中需要提案的额外上下文,请使用`openspec-cn show <id> --json --deltas-only`
22+
<!-- OPENSPEC:END -->
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
name: OpenSpec: 归档
3+
description: 归档已部署的OpenSpec变更并更新规范。
4+
argument-hint: "[change-id]"
5+
---
6+
<!-- OPENSPEC:START -->
7+
**护栏规则**
8+
- 优先使用简单、最小的实现,仅在请求或明确需要时才添加复杂性。
9+
- 将变更紧密限制在请求的结果范围内。
10+
- 如果需要额外的OpenSpec约定或澄清,请参考`openspec/AGENTS.md`(位于`openspec/`目录中—如果看不到,请运行`ls openspec``openspec-cn update`)。
11+
12+
**步骤**
13+
1. 确定要归档的变更ID:
14+
- 如果此提示已包含特定变更ID(例如在由斜杠命令参数填充的`<ChangeId>`块内),在修剪空格后使用该值。
15+
- 如果对话松散地引用变更(例如通过标题或摘要),运行`openspec-cn list`以显示可能的ID,分享相关候选者,并确认用户意图归档哪一个。
16+
- 否则,查看对话,运行`openspec-cn list`,并询问用户要归档哪个变更;在继续前等待确认的变更ID。
17+
- 如果仍然无法识别单个变更ID,停止并告诉用户您还无法归档任何内容。
18+
2. 通过运行`openspec-cn list`(或`openspec-cn show <id>`)验证变更ID,如果变更缺失、已归档或尚未准备好归档,则停止。
19+
3. 运行`openspec-cn archive <id> --yes`,以便CLI在没有提示的情况下移动变更并应用规范更新(仅对仅工具工作使用`--skip-specs`)。
20+
4. 查看命令输出以确认目标规范已更新且变更已放入`openspec/changes/archive/`
21+
5. 使用`openspec-cn validate --strict`验证,如果出现异常,使用`openspec-cn show <id>`检查。
22+
23+
**参考**
24+
- 在归档前使用`openspec-cn list`确认变更ID。
25+
- 使用`openspec-cn list --specs`检查刷新的规范,并在交前解决任何验证问题。
26+
<!-- OPENSPEC:END -->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
name: OpenSpec: 提案
3+
description: 搭建新的OpenSpec变更提案并进行严格验证。
4+
argument-hint: "[feature description or request]"
5+
---
6+
<!-- OPENSPEC:START -->
7+
**护栏规则**
8+
- 优先使用简单、最小的实现,仅在请求或明确需要时才添加复杂性。
9+
- 将变更紧密限制在请求的结果范围内。
10+
- 如果需要额外的OpenSpec约定或澄清,请参考`openspec/AGENTS.md`(位于`openspec/`目录中—如果看不到,请运行`ls openspec``openspec-cn update`)。
11+
- 识别任何模糊或不明确的细节,并在编辑文件前询问必要的后续问题。
12+
- 在提案阶段不要编写任何代码。仅创建设计文档(proposal.md、tasks.md、design.md和规范增量)。实施在批准后的apply阶段进行。
13+
14+
**步骤**
15+
1. 查看`openspec/project.md`,运行`openspec-cn list``openspec-cn list --specs`,并检查相关代码或文档(例如通过`rg`/`ls`)以使提案基于当前行为;注意任何需要澄清的空白。
16+
2. 选择唯一的动词开头`change-id`并在`openspec/changes/<id>/`下创建`proposal.md``tasks.md``design.md`(需要时)。
17+
3. 将变更映射为具体功能或需求,将多范围工作分解为具有明确关系和顺序的独立规范增量。
18+
4. 当解决方案跨越多个系统、引入新模式或在提交规范前需要权衡讨论时,在`design.md`中记录架构推理。
19+
5.`openspec/changes/<id>/specs/<capability>/spec.md`中起草规范增量(每个功能一个文件夹),使用`## 新增需求|修改需求|移除需求|重命名需求`,每个需求至少有一个`#### 场景:`,并在相关时交叉引用相关功能。
20+
6.`tasks.md`起草为有序的小型可验证工作项列表,这些项提供用户可见的进展,包括验证(测试、工具),并突出显示依赖关系或可并行工作。
21+
7. 使用`openspec-cn validate <id> --strict`验证并在分享提案前解决每个问题。
22+
23+
**参考**
24+
- 当验证失败时,使用`openspec-cn show <id> --json --deltas-only``openspec-cn show <spec> --type spec`检查详情。
25+
- 在编写新需求前,使用`rg -n "需求:|场景:" openspec/specs`搜索现有需求。
26+
- 使用`rg <keyword>``ls`或直接文件读取探索代码库,以便提案与当前实现现实保持一致。
27+
<!-- OPENSPEC:END -->

AGENTS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!-- OPENSPEC:START -->
2+
# OpenSpec 使用说明
3+
4+
这些说明适用于在此项目中工作的AI助手。
5+
6+
## 语言偏好设置
7+
8+
**默认使用中文**:除非明确说明使用英文,否则所有输出都应使用中文,包括:
9+
- 文档内容
10+
- 代码注释
11+
- 提交信息
12+
- 规范说明
13+
14+
## 工作流程
15+
16+
当请求满足以下条件时,始终打开`@/openspec/AGENTS.md`
17+
- 提及规划或提案(如提案、规范、变更、计划等词语)
18+
- 引入新功能、重大变更、架构变更或大型性能/安全工作时
19+
- 听起来不明确,需要在编码前了解权威规范时
20+
21+
使用`@/openspec/AGENTS.md`了解:
22+
- 如何创建和应用变更提案
23+
- 规范格式和约定
24+
- 项目结构和指南
25+
26+
保持此托管块,以便'openspec-cn update'可以刷新说明。
27+
28+
<!-- OPENSPEC:END -->

CODEBUDDY.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!-- OPENSPEC:START -->
2+
# OpenSpec 使用说明
3+
4+
这些说明适用于在此项目中工作的AI助手。
5+
6+
## 语言偏好设置
7+
8+
**默认使用中文**:除非明确说明使用英文,否则所有输出都应使用中文,包括:
9+
- 文档内容
10+
- 代码注释
11+
- 提交信息
12+
- 规范说明
13+
14+
## 工作流程
15+
16+
当请求满足以下条件时,始终打开`@/openspec/AGENTS.md`
17+
- 提及规划或提案(如提案、规范、变更、计划等词语)
18+
- 引入新功能、重大变更、架构变更或大型性能/安全工作时
19+
- 听起来不明确,需要在编码前了解权威规范时
20+
21+
使用`@/openspec/AGENTS.md`了解:
22+
- 如何创建和应用变更提案
23+
- 规范格式和约定
24+
- 项目结构和指南
25+
26+
保持此托管块,以便'openspec-cn update'可以刷新说明。
27+
28+
<!-- OPENSPEC:END -->

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ AICLI is a Go tool that brings natural-language operations to your terminal. You
1515
- **Safety confirmations**: detects risky commands (e.g., bulk delete/format) and asks before executing
1616
- **Command history**: stores past prompts/commands and supports retry
1717
- **Multiple LLM providers**: OpenAI, Anthropic, local models, and other OpenAI-compatible APIs
18+
- **Internationalization (i18n)**: supports Chinese and English with automatic detection from OS locale
1819
- **Cross-platform**: Linux, macOS, and Windows
1920

2021
## Quick start
@@ -51,6 +52,7 @@ Create `~/.aicli.json`:
5152
```json
5253
{
5354
"version": "1.0",
55+
"language": "en",
5456
"llm": {
5557
"provider": "openai",
5658
"api_key": "your-api-key-here",
@@ -68,6 +70,12 @@ Create `~/.aicli.json`:
6870
}
6971
```
7072

73+
**Language setting**: The `language` field is optional. Supported values:
74+
- `"zh"` - Chinese (中文)
75+
- `"en"` - English
76+
77+
If not set, AICLI automatically detects your system locale from `LANG` or `LC_ALL` environment variables. Default is Chinese.
78+
7179
You can also set the API key via environment variable:
7280

7381
```bash
@@ -218,6 +226,7 @@ go test ./...
218226

219227
- [Architecture](docs/architecture.md)
220228
- [Configuration](docs/configuration.md)
229+
- [Internationalization Guide](docs/i18n-guide.md)
221230
- [Development guide](docs/development.md)
222231

223232
## Security & privacy

README_CN.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- 🛡️ **安全确认机制**:自动检测危险命令(删除、格式化等),执行前需要用户确认
1616
- 📜 **历史记录**:保存命令历史,支持查看和重新执行
1717
- 🔌 **多 LLM 提供商**:支持 OpenAI、Anthropic、本地模型等多种 LLM 服务
18+
- 🌐 **国际化支持**:支持中文和英文,自动检测操作系统语言
1819
- 🌍 **跨平台**:支持 Linux、macOS 和 Windows 系统
1920

2021
## 🚀 快速开始
@@ -51,6 +52,7 @@ aicli init
5152
```json
5253
{
5354
"version": "1.0",
55+
"language": "zh",
5456
"llm": {
5557
"provider": "openai",
5658
"api_key": "your-api-key-here",
@@ -68,6 +70,12 @@ aicli init
6870
}
6971
```
7072

73+
**语言设置**`language` 字段可选,支持以下值:
74+
- `"zh"` - 中文
75+
- `"en"` - English (英文)
76+
77+
如果不设置,AICLI 会自动从 `LANG``LC_ALL` 环境变量检测系统语言。默认为中文。
78+
7179
也可以通过环境变量配置 API 密钥:
7280

7381
```bash
@@ -508,6 +516,7 @@ go tool cover -html=coverage.out
508516

509517
- [架构设计](docs/architecture.md)
510518
- [配置说明](docs/configuration.md)
519+
- [国际化指南](docs/i18n-guide.md)
511520
- [开发指南](docs/development.md)
512521

513522
## 🔐 安全与隐私

cmd/aicli/init.go

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/spf13/cobra"
1111
"github.com/studyzy/aicli/pkg/config"
12+
"github.com/studyzy/aicli/pkg/i18n"
1213
)
1314

1415
const (
@@ -20,8 +21,8 @@ const (
2021

2122
var initCmd = &cobra.Command{
2223
Use: "init",
23-
Short: "初始化配置",
24-
Long: `引导用户设置 LLM 配置并生成配置文件 ~/.aicli.json`,
24+
Short: "初始化配置 / Initialize configuration",
25+
Long: "引导用户设置 LLM 配置并生成配置文件 ~/.aicli.json / Guide user to set up LLM configuration",
2526
RunE: runInit,
2627
}
2728

@@ -30,6 +31,10 @@ func init() {
3031
}
3132

3233
func runInit(cmd *cobra.Command, args []string) error {
34+
// 初始化 i18n (在配置文件加载前使用默认配置)
35+
cfg := config.Default()
36+
i18n.Init(cfg)
37+
3338
reader := bufio.NewReader(os.Stdin)
3439

3540
configPath, err := getConfigPath()
@@ -41,12 +46,10 @@ func runInit(cmd *cobra.Command, args []string) error {
4146
return err
4247
}
4348

44-
fmt.Println("欢迎使用 aicli 配置向导!")
45-
fmt.Println("我们将引导您完成基本配置。")
49+
fmt.Println(i18n.T(i18n.MsgWelcomeInit))
50+
fmt.Println(i18n.T(i18n.MsgInitGuide))
4651
fmt.Println()
4752

48-
cfg := config.Default()
49-
5053
if err := configureProvider(reader, cfg); err != nil {
5154
return err
5255
}
@@ -69,31 +72,32 @@ func getConfigPath() (string, error) {
6972

7073
homeDir, err := os.UserHomeDir()
7174
if err != nil {
72-
return "", fmt.Errorf("获取用户主目录失败: %w", err)
75+
return "", fmt.Errorf("%s: %w", i18n.T(i18n.ErrGetUserHome), err)
7376
}
7477
return homeDir + "/.aicli.json", nil
7578
}
7679

7780
func checkExistingConfig(reader *bufio.Reader, configPath string) error {
7881
if _, err := os.Stat(configPath); err == nil {
79-
fmt.Printf("配置文件 %s 已存在。\n", configPath)
80-
if !promptBool(reader, "是否覆盖?", false) {
81-
fmt.Println("操作已取消。")
82-
return fmt.Errorf("用户取消操作")
82+
msg := i18n.T(i18n.MsgConfigExists, configPath)
83+
fmt.Printf("%s\n", msg)
84+
if !promptBool(reader, i18n.T(i18n.PromptOverwriteConfig), false) {
85+
fmt.Println(i18n.T(i18n.MsgOperationCancelled))
86+
return fmt.Errorf("%s", i18n.T(i18n.ErrUserCancelled))
8387
}
8488
}
8589
return nil
8690
}
8791

8892
func configureProvider(reader *bufio.Reader, cfg *config.Config) error {
89-
fmt.Println("请选择 LLM 提供商:")
90-
fmt.Println("1. OpenAI (GPT-4, GPT-3.5)")
91-
fmt.Println("2. Anthropic (Claude)")
92-
fmt.Println("3. Local (Ollama, LocalAI)")
93-
fmt.Println("4. DeepSeek (深度求索)")
94-
fmt.Println("5. Other (兼容 OpenAI 协议)")
93+
fmt.Println(i18n.T(i18n.PromptSelectProvider) + ":")
94+
fmt.Println(i18n.T(i18n.InitProviderOpenAI))
95+
fmt.Println(i18n.T(i18n.InitProviderAnthropic))
96+
fmt.Println(i18n.T(i18n.InitProviderLocal))
97+
fmt.Println(i18n.T(i18n.InitProviderDeepSeek))
98+
fmt.Println(i18n.T(i18n.InitProviderOther))
9599

96-
providerChoice := prompt(reader, "请输入序号 [1-5]", "1")
100+
providerChoice := prompt(reader, i18n.T(i18n.PromptInputChoice)+" [1-5]", "1")
97101

98102
switch providerChoice {
99103
case "1":
@@ -117,7 +121,7 @@ func configureProvider(reader *bufio.Reader, cfg *config.Config) error {
117121
cfg.LLM.Model = "gpt-3.5-turbo"
118122
cfg.LLM.APIBase = apiBaseOpenAI
119123
default:
120-
fmt.Println("无效的选择,默认使用 OpenAI")
124+
fmt.Println(i18n.T(i18n.MsgDefaultUseOpenAI))
121125
cfg.LLM.Provider = providerOpenAI
122126
}
123127

@@ -126,34 +130,37 @@ func configureProvider(reader *bufio.Reader, cfg *config.Config) error {
126130

127131
func configureAPI(reader *bufio.Reader, cfg *config.Config) error {
128132
if cfg.LLM.Provider != providerLocal {
129-
cfg.LLM.APIKey = prompt(reader, "请输入 API Key", "")
133+
cfg.LLM.APIKey = prompt(reader, i18n.T(i18n.PromptEnterAPIKey), "")
130134
if cfg.LLM.APIKey == "" {
131-
fmt.Println("警告: API Key 为空,您可能需要在环境变量 AICLI_API_KEY 中设置。")
135+
fmt.Println(i18n.T(i18n.WarnAPIKeyEmpty))
132136
}
133137
}
134138

135-
cfg.LLM.Model = prompt(reader, fmt.Sprintf("请输入模型名称 (默认: %s)", cfg.LLM.Model), cfg.LLM.Model)
136-
cfg.LLM.APIBase = prompt(reader, fmt.Sprintf("请输入 API Base URL (默认: %s)", cfg.LLM.APIBase), cfg.LLM.APIBase)
139+
modelPrompt := fmt.Sprintf("%s (%s: %s)", i18n.T(i18n.PromptEnterModel), i18n.T(i18n.MsgDefault), cfg.LLM.Model)
140+
cfg.LLM.Model = prompt(reader, modelPrompt, cfg.LLM.Model)
141+
142+
apiBasePrompt := fmt.Sprintf("%s (%s: %s)", i18n.T(i18n.PromptEnterAPIBase), i18n.T(i18n.MsgDefault), cfg.LLM.APIBase)
143+
cfg.LLM.APIBase = prompt(reader, apiBasePrompt, cfg.LLM.APIBase)
137144

138145
return nil
139146
}
140147

141148
func configureOtherSettings(reader *bufio.Reader, cfg *config.Config) error {
142-
fmt.Println("\n--- 其他设置 ---")
143-
cfg.Safety.EnableChecks = promptBool(reader, "是否启用危险命令安全检查?", true)
144-
cfg.History.Enabled = promptBool(reader, "是否启用历史记录?", true)
149+
fmt.Println("\n" + i18n.T(i18n.MsgOtherSettings))
150+
cfg.Safety.EnableChecks = promptBool(reader, i18n.T(i18n.PromptEnableCheck), true)
151+
cfg.History.Enabled = promptBool(reader, i18n.T(i18n.PromptEnableHistory), true)
145152
return nil
146153
}
147154

148155
func saveConfig(cfg *config.Config, configPath string) error {
149-
fmt.Println("\n正在保存配置...")
156+
fmt.Println("\n" + i18n.T(i18n.MsgSavingConfig))
150157
if err := cfg.Save(configPath); err != nil {
151-
return fmt.Errorf("保存配置失败: %w", err)
158+
return fmt.Errorf("%s: %w", i18n.T(i18n.ErrSaveConfig), err)
152159
}
153160

154-
fmt.Printf("配置已成功保存到 %s\n", configPath)
155-
fmt.Println("现在您可以开始使用 aicli 了!")
156-
fmt.Println("示例: aicli \"查询我的公网IP\"")
161+
fmt.Println(i18n.T(i18n.MsgConfigSaved, configPath))
162+
fmt.Println(i18n.T(i18n.MsgNowCanUse))
163+
fmt.Println(i18n.T(i18n.MsgExampleUsage))
157164

158165
return nil
159166
}

0 commit comments

Comments
 (0)