@webywz/vibe-commit 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,7 @@ vibe-commit install # optional prepare-commit-msg hook
57
57
  | `--type <type>` | Force `feat` / `fix` / `docs` / `style` / `refactor` / `perf` / `test` / `build` / `ci` / `chore` |
58
58
  | `--scope <scope>` | Override inferred scope · 覆盖推断出的 scope |
59
59
  | `--vibe <style>` | `playful` \| `pro` \| `terse` (default: `pro`) |
60
+ | `--locale <locale>` | `en` \| `zh` (default: `en`) · 提交信息主题语言 |
60
61
  | `--no-ai` | Skip the API even if `OPENAI_API_KEY` is set · 即使有 Key 也不走 AI |
61
62
  | `--breaking` | Force breaking (`!` + `BREAKING CHANGE:` footer = **subject**) · 强制破坏性变更(`!` + footer 文本即 **subject**) |
62
63
  | `--json` | Machine-readable proposal · 机器可读提案 |
@@ -125,6 +126,45 @@ Hook stays **offline by default** (`VIBE_COMMIT_HOOK_AI=1` to allow AI). Unique
125
126
 
126
127
  钩子默认离线;设 `VIBE_COMMIT_HOOK_AI=1` 才允许 AI。用标记块保证干净卸载。已有外来钩子会备份并链式调用。
127
128
 
129
+ ### Locale support · 语言支持
130
+
131
+ vibe-commit supports English (`en`, default) and Chinese (`zh`) commit subjects:
132
+
133
+ vibe-commit 支持英文(`en`,默认)和中文(`zh`)提交信息主题:
134
+
135
+ ```bash
136
+ vibe-commit --locale zh --dry-run # Chinese subject: 添加/修复/更新...
137
+ vibe-commit --locale en --dry-run # English subject: add/fix/update...
138
+ ```
139
+
140
+ **Configuration precedence** · 配置优先级:
141
+ 1. CLI flag `--locale`
142
+ 2. Environment variable `VIBE_COMMIT_LOCALE`
143
+ 3. Project config (`.vibe-commit.json` or `package.json#vibeCommit`)
144
+ 4. Default `en`
145
+
146
+ ```json
147
+ // .vibe-commit.json
148
+ {
149
+ "locale": "zh",
150
+ "vibe": "pro"
151
+ }
152
+ ```
153
+
154
+ ```json
155
+ // package.json
156
+ {
157
+ "vibeCommit": {
158
+ "locale": "zh",
159
+ "vibe": "terse"
160
+ }
161
+ }
162
+ ```
163
+
164
+ Both heuristic and AI engines respect the locale setting. The `--json` output includes a `locale` field.
165
+
166
+ 启发式引擎和 AI 引擎都会遵循 locale 设置。`--json` 输出包含 `locale` 字段。
167
+
128
168
  ### AI (optional) · AI(可选)
129
169
 
130
170
  ```bash
@@ -132,6 +172,7 @@ export OPENAI_API_KEY=sk-...
132
172
  export OPENAI_MODEL=gpt-4o-mini # optional
133
173
  export OPENAI_BASE_URL=https://api.openai.com/v1
134
174
  export VIBE_COMMIT_VIBE=pro # or VIBE
175
+ export VIBE_COMMIT_LOCALE=zh # or en (default)
135
176
  ```
136
177
 
137
178
  OpenAI-compatible Chat Completions. Missing key, timeout, or 4xx/5xx → silent fallback to the offline heuristic. See [`.env.example`](.env.example). Unstaged files are never sent; huge diffs are truncated.
package/dist/ai.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import type { ProposedMessage, StagedDiff, Vibe } from "./types.ts";
1
+ import type { Locale, ProposedMessage, StagedDiff, Vibe } from "./types.ts";
2
2
  import type { CommitType } from "./types.ts";
3
3
  export interface AiContext {
4
4
  diff: StagedDiff;
5
5
  vibe: Vibe;
6
+ locale: Locale;
6
7
  type?: CommitType;
7
8
  scope?: string;
8
9
  }
package/dist/ai.js CHANGED
@@ -17,7 +17,7 @@ export async function proposeWithAi(context, env = process.env) {
17
17
  model,
18
18
  temperature: context.vibe === "playful" ? 0.7 : 0.2,
19
19
  messages: [
20
- { role: "system", content: systemPrompt(context.vibe) },
20
+ { role: "system", content: systemPrompt(context.vibe, context.locale) },
21
21
  { role: "user", content: userPrompt(context) },
22
22
  ],
23
23
  }),
@@ -35,24 +35,29 @@ export async function proposeWithAi(context, env = process.env) {
35
35
  parsed.type = context.type;
36
36
  if (context.scope)
37
37
  parsed.scope = context.scope;
38
+ parsed.locale = context.locale;
38
39
  return parsed;
39
40
  }
40
41
  catch {
41
42
  return null;
42
43
  }
43
44
  }
44
- function systemPrompt(vibe) {
45
+ function systemPrompt(vibe, locale) {
45
46
  const vibeLine = vibe === "playful"
46
47
  ? "Tone: witty, still professional; no memes, no emoji, no slang that would embarrass a code review."
47
48
  : vibe === "terse"
48
49
  ? "Tone: as short as possible. Headline only, no body."
49
50
  : "Tone: clear, professional, imperative mood.";
51
+ const langLine = locale === "zh"
52
+ ? "Subject language: Chinese (中文). Use Chinese verbs like 添加/修复/更新/重构/优化/删除/格式化 etc."
53
+ : "Subject language: English.";
50
54
  return `You write conventional commit messages.
51
55
  Return only the commit message, no quotes, no markdown fences.
52
56
  Format: type(scope): subject
53
57
  Optional body after a blank line.
54
58
  Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
55
59
  Subject: lowercase, imperative, no trailing period, <= 72 chars.
60
+ ${langLine}
56
61
  ${vibeLine}`;
57
62
  }
58
63
  function userPrompt(context) {
package/dist/cli.js CHANGED
@@ -46,6 +46,7 @@ export async function main(argv = process.argv.slice(2)) {
46
46
  type: options.type,
47
47
  scope: options.scope,
48
48
  vibe: options.vibe,
49
+ locale: options.locale,
49
50
  preferAi: !options.noAi,
50
51
  breaking: options.breaking,
51
52
  });
@@ -0,0 +1,6 @@
1
+ import type { Locale, Vibe } from "./types.ts";
2
+ export interface ProjectConfig {
3
+ locale?: Locale;
4
+ vibe?: Vibe;
5
+ }
6
+ export declare function loadProjectConfig(cwd?: string): ProjectConfig | undefined;
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { LOCALES, VIBES } from "./types.js";
4
+ export function loadProjectConfig(cwd = process.cwd()) {
5
+ const vibeCommitPath = join(cwd, ".vibe-commit.json");
6
+ if (existsSync(vibeCommitPath)) {
7
+ try {
8
+ const raw = readFileSync(vibeCommitPath, "utf8");
9
+ const parsed = JSON.parse(raw);
10
+ return normalizeConfig(parsed);
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ const pkgPath = join(cwd, "package.json");
17
+ if (existsSync(pkgPath)) {
18
+ try {
19
+ const raw = readFileSync(pkgPath, "utf8");
20
+ const parsed = JSON.parse(raw);
21
+ if (parsed.vibeCommit &&
22
+ typeof parsed.vibeCommit === "object" &&
23
+ !Array.isArray(parsed.vibeCommit)) {
24
+ return normalizeConfig(parsed.vibeCommit);
25
+ }
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ function normalizeConfig(raw) {
34
+ const config = {};
35
+ if (typeof raw.locale === "string") {
36
+ const locale = raw.locale.trim().toLowerCase();
37
+ if (LOCALES.includes(locale)) {
38
+ config.locale = locale;
39
+ }
40
+ }
41
+ if (typeof raw.vibe === "string") {
42
+ const vibe = raw.vibe.trim().toLowerCase();
43
+ if (VIBES.includes(vibe)) {
44
+ config.vibe = vibe;
45
+ }
46
+ }
47
+ return config;
48
+ }
49
+ //# sourceMappingURL=config.js.map
package/dist/format.js CHANGED
@@ -61,6 +61,7 @@ export function parseCommit(text, engine = "heuristic") {
61
61
  body: rest || undefined,
62
62
  breaking,
63
63
  engine,
64
+ locale: "en",
64
65
  };
65
66
  }
66
67
  export function stripFences(text) {
@@ -1,9 +1,10 @@
1
- import type { CommitType, DiffHunk, ProposedMessage, StagedDiff, StagedFile, Vibe } from "./types.ts";
1
+ import type { CommitType, DiffHunk, Locale, ProposedMessage, StagedDiff, StagedFile, Vibe } from "./types.ts";
2
2
  export interface HeuristicInput {
3
3
  diff: StagedDiff;
4
4
  type?: CommitType;
5
5
  scope?: string;
6
6
  vibe: Vibe;
7
+ locale: Locale;
7
8
  /** Force breaking: true (ORs with inferBreaking). */
8
9
  breaking?: boolean;
9
10
  }
@@ -12,7 +13,7 @@ export declare function inferType(diff: StagedDiff): CommitType;
12
13
  export declare function primaryRole(path: string): CommitType;
13
14
  export declare function classifyPath(path: string): CommitType[];
14
15
  export declare function inferScope(files: StagedFile[]): string | undefined;
15
- export declare function inferSubject(diff: StagedDiff, type: CommitType, scope?: string): string;
16
+ export declare function inferSubject(diff: StagedDiff, type: CommitType, scope: string | undefined, locale: Locale): string;
16
17
  export type BreakingKind = "export-removed" | "major-bump" | "footer" | "forced";
17
18
  export declare function inferBreaking(diff: StagedDiff): boolean;
18
19
  /** Why breaking was set — heuristic signal first, then --breaking. */
package/dist/heuristic.js CHANGED
@@ -23,10 +23,10 @@ const FEAT_WORD_RE = /\b(add|added|feat|feature|implement|introduce|support|enab
23
23
  const REFACTOR_WORD_RE = /\b(refactor|rename|move|extract|simplify|cleanup|clean up|restructure)\b/i;
24
24
  const PERF_WORD_RE = /\b(perf|performance|optimiz|cache|faster|speed)\b/i;
25
25
  export function proposeHeuristic(input) {
26
- const { diff, vibe } = input;
26
+ const { diff, vibe, locale } = input;
27
27
  const type = input.type ?? inferType(diff);
28
28
  const scope = input.scope ?? inferScope(diff.files);
29
- const subject = inferSubject(diff, type, scope);
29
+ const subject = inferSubject(diff, type, scope, locale);
30
30
  const body = inferBody(diff, type);
31
31
  const forced = Boolean(input.breaking);
32
32
  const kind = detectBreakingKind(diff, forced);
@@ -35,6 +35,7 @@ export function proposeHeuristic(input) {
35
35
  const message = {
36
36
  ...styled,
37
37
  engine: "heuristic",
38
+ locale,
38
39
  breakingReason: kind ? formatBreakingReason(kind) : undefined,
39
40
  };
40
41
  // Forced --breaking: prefer subject in the footer (not bilingual default).
@@ -144,10 +145,11 @@ export function inferScope(files) {
144
145
  return undefined;
145
146
  return name.slice(0, 32);
146
147
  }
147
- export function inferSubject(diff, type, scope) {
148
+ export function inferSubject(diff, type, scope, locale) {
148
149
  const files = diff.files;
149
- if (files.length === 0)
150
- return "update project files";
150
+ if (files.length === 0) {
151
+ return normalizeSubject(locale === "zh" ? "更新项目文件" : "update project files");
152
+ }
151
153
  const added = files.filter((file) => file.status === "A");
152
154
  const deleted = files.filter((file) => file.status === "D");
153
155
  const renamed = files.filter((file) => file.status === "R");
@@ -156,31 +158,67 @@ export function inferSubject(diff, type, scope) {
156
158
  added.length >= files.length - 1 &&
157
159
  !scope &&
158
160
  type === "feat") {
159
- return normalizeSubject("add initial implementation");
161
+ return normalizeSubject(locale === "zh" ? "添加初始实现" : "add initial implementation");
160
162
  }
161
163
  if (renamed.length === 1 && files.length === 1) {
162
164
  const file = renamed[0];
163
- return normalizeSubject(`rename ${stem(file.fromPath ?? file.path)} to ${stem(file.path)}`);
165
+ const fromName = stem(file.fromPath ?? file.path);
166
+ const toName = stem(file.path);
167
+ return normalizeSubject(locale === "zh"
168
+ ? `重命名 ${fromName} 为 ${toName}`
169
+ : `rename ${fromName} to ${toName}`);
164
170
  }
165
171
  if (deleted.length === files.length) {
166
- return normalizeSubject(`remove ${joinNames(deleted.map((file) => stem(file.path)))}`);
172
+ const names = joinNames(deleted.map((file) => stem(file.path)));
173
+ return normalizeSubject(locale === "zh" ? `删除 ${names}` : `remove ${names}`);
167
174
  }
168
175
  if (idents.length > 0 && (type === "feat" || type === "fix")) {
169
- const verb = type === "fix" ? "fix" : "add";
176
+ const verb = type === "fix" ? (locale === "zh" ? "修复" : "fix") : (locale === "zh" ? "添加" : "add");
170
177
  return normalizeSubject(`${verb} ${joinNames(idents.map(humanizeIdent))}`);
171
178
  }
172
179
  if (added.length === 1 && files.length === 1) {
173
- return normalizeSubject(`add ${stem(added[0].path)}`);
180
+ const name = stem(added[0].path);
181
+ return normalizeSubject(locale === "zh" ? `添加 ${name}` : `add ${name}`);
174
182
  }
175
183
  if (added.length > 1 && added.length === files.length) {
176
- return normalizeSubject(`add ${summarizeFiles(added, scope)}`);
184
+ const summary = summarizeFiles(added, scope);
185
+ return normalizeSubject(locale === "zh" ? `添加 ${summary}` : `add ${summary}`);
177
186
  }
178
187
  const focus = files.length === 1 ? stem(files[0].path) : summarizeFiles(files, scope);
179
- return normalizeSubject(verbFor(type, focus, files));
188
+ return normalizeSubject(verbFor(type, focus, files, locale));
180
189
  }
181
- function verbFor(type, focus, files) {
190
+ function verbFor(type, focus, files, locale) {
182
191
  const mostlyAdd = files.reduce((sum, file) => sum + file.additions, 0) >
183
192
  files.reduce((sum, file) => sum + file.deletions, 0) * 1.4;
193
+ if (locale === "zh") {
194
+ switch (type) {
195
+ case "feat":
196
+ return mostlyAdd ? `添加 ${focus}` : `更新 ${focus}`;
197
+ case "fix":
198
+ return `修复 ${focus}`;
199
+ case "docs":
200
+ return files.some((file) => /readme/i.test(file.path))
201
+ ? "更新 readme"
202
+ : `更新 ${focus}`;
203
+ case "test":
204
+ return mostlyAdd ? `添加 ${focus} 测试` : `更新 ${focus} 测试`;
205
+ case "refactor":
206
+ return `重构 ${focus}`;
207
+ case "perf":
208
+ return `优化 ${focus} 性能`;
209
+ case "style":
210
+ return `格式化 ${focus}`;
211
+ case "ci":
212
+ return `更新 ${focus} 工作流`;
213
+ case "build":
214
+ return `更新 ${focus} 构建`;
215
+ case "chore":
216
+ if (files.some((file) => /package(-lock)?\.json|pnpm-lock|yarn\.lock/.test(file.path))) {
217
+ return "更新依赖";
218
+ }
219
+ return `更新 ${focus}`;
220
+ }
221
+ }
184
222
  switch (type) {
185
223
  case "feat":
186
224
  return mostlyAdd ? `add ${focus}` : `update ${focus}`;
package/dist/hook.js CHANGED
@@ -4,8 +4,9 @@ import { dirname, isAbsolute, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { formatCommit } from "./format.js";
6
6
  import { assertRepo, GitError, inspectStaged } from "./git.js";
7
- import { defaultVibeFromEnv } from "./parse-args.js";
7
+ import { defaultLocaleFromEnv, defaultVibeFromEnv } from "./parse-args.js";
8
8
  import { proposeMessage } from "./propose.js";
9
+ import { loadProjectConfig } from "./config.js";
9
10
  export const HOOK_BEGIN = "# BEGIN VIBE-COMMIT";
10
11
  export const HOOK_END = "# END VIBE-COMMIT";
11
12
  export const HOOK_NAME = "prepare-commit-msg";
@@ -42,8 +43,10 @@ export async function runPrepareCommitMsg(messageFile, source, cwd = process.cwd
42
43
  const diff = inspectStaged(cwd);
43
44
  // Avoid hanging `git commit` on network — hooks stay offline unless opted in.
44
45
  const useAi = env.VIBE_COMMIT_HOOK_AI === "1";
46
+ const projectConfig = loadProjectConfig(cwd);
45
47
  const message = await proposeMessage(diff, {
46
- vibe: defaultVibeFromEnv(env),
48
+ vibe: projectConfig?.vibe ?? defaultVibeFromEnv(env),
49
+ locale: projectConfig?.locale ?? defaultLocaleFromEnv(env),
47
50
  preferAi: useAi,
48
51
  }, env);
49
52
  const formatted = formatCommit(message);
@@ -1,9 +1,11 @@
1
- import { type CliOptions, type CommitType, type Vibe } from "./types.ts";
1
+ import { type CliOptions, type CommitType, type Locale, type Vibe } from "./types.ts";
2
2
  export declare function defaultVibeFromEnv(env?: NodeJS.ProcessEnv): Vibe;
3
- export declare function parseArgs(argv: string[], env?: NodeJS.ProcessEnv): CliOptions;
3
+ export declare function defaultLocaleFromEnv(env?: NodeJS.ProcessEnv): Locale;
4
+ export declare function parseArgs(argv: string[], env?: NodeJS.ProcessEnv, cwd?: string): CliOptions;
4
5
  export declare function parseMaxAnnotations(value: string | undefined): number;
5
6
  export declare function parseType(value: string | undefined): CommitType;
6
7
  export declare function parseVibe(value: string | undefined): Vibe;
8
+ export declare function parseLocale(value: string | undefined): Locale;
7
9
  export declare function sanitizeScope(value: string): string;
8
10
  export declare class UsageError extends Error {
9
11
  constructor(message: string);
@@ -1,4 +1,5 @@
1
- import { COMMIT_TYPES, VIBES, } from "./types.js";
1
+ import { COMMIT_TYPES, LOCALES, VIBES, } from "./types.js";
2
+ import { loadProjectConfig } from "./config.js";
2
3
  const HELP = `vibe-commit — stylish conventional commits, offline-first
3
4
  有品味的 Conventional Commits(离线优先)
4
5
 
@@ -17,6 +18,7 @@ Options · 参数
17
18
  --type <type> feat | fix | docs | style | refactor | perf | test | build | ci | chore
18
19
  --scope <scope> Override inferred scope
19
20
  --vibe <style> playful | pro | terse (default: pro)
21
+ --locale <locale> en | zh (default: en)
20
22
  --no-ai Skip AI even if OPENAI_API_KEY is set
21
23
  --breaking Force ! + BREAKING CHANGE footer (text = subject)
22
24
  --json Print a machine-readable proposal
@@ -33,7 +35,8 @@ Env
33
35
  OPENAI_API_KEY Optional. When set, draft with an OpenAI-compatible API
34
36
  OPENAI_MODEL Default: gpt-4o-mini
35
37
  OPENAI_BASE_URL Default: https://api.openai.com/v1
36
- VIBE / VIBE_COMMIT_VIBE Default vibe when --vibe is omitted
38
+ VIBE / VIBE_COMMIT_VIBE Default vibe when --vibe is omitted
39
+ VIBE_COMMIT_LOCALE Default locale when --locale is omitted
37
40
  VIBE_COMMIT_HOOK_AI Set to 1 to allow AI inside the git hook (off by default)
38
41
 
39
42
  With --github, annotations go to stdout (or stderr when combined with --json) so
@@ -51,10 +54,16 @@ Git hook
51
54
  comment-only message file with a vibe-commit proposal. Skips merge, squash,
52
55
  amend (source=commit), and any message that already has non-comment text.
53
56
 
57
+ Project config
58
+ .vibe-commit.json or package.json#vibeCommit
59
+ { "locale": "zh", "vibe": "pro" }
60
+ Precedence: CLI > env > project config > default
61
+
54
62
  Examples · 示例
55
63
  git add -p
56
64
  vibe-commit
57
65
  vibe-commit --vibe playful --commit
66
+ vibe-commit --locale zh --dry-run
58
67
  vibe-commit --breaking --dry-run
59
68
  vibe-commit --github --quiet --no-ai
60
69
  vibe-commit --github --json --max-annotations 10
@@ -66,12 +75,18 @@ export function defaultVibeFromEnv(env = process.env) {
66
75
  const raw = (env.VIBE_COMMIT_VIBE ?? env.VIBE ?? "pro").trim().toLowerCase();
67
76
  return VIBES.includes(raw) ? raw : "pro";
68
77
  }
69
- export function parseArgs(argv, env = process.env) {
78
+ export function defaultLocaleFromEnv(env = process.env) {
79
+ const raw = (env.VIBE_COMMIT_LOCALE ?? "en").trim().toLowerCase();
80
+ return LOCALES.includes(raw) ? raw : "en";
81
+ }
82
+ export function parseArgs(argv, env = process.env, cwd = process.cwd()) {
83
+ const projectConfig = loadProjectConfig(cwd);
70
84
  const options = {
71
85
  command: "propose",
72
86
  commit: false,
73
87
  dryRun: false,
74
- vibe: defaultVibeFromEnv(env),
88
+ vibe: projectConfig?.vibe ?? defaultVibeFromEnv(env),
89
+ locale: projectConfig?.locale ?? defaultLocaleFromEnv(env),
75
90
  noAi: false,
76
91
  json: false,
77
92
  github: false,
@@ -153,6 +168,11 @@ export function parseArgs(argv, env = process.env) {
153
168
  options.vibe = parseVibe(value);
154
169
  continue;
155
170
  }
171
+ if (arg === "--locale" || arg.startsWith("--locale=")) {
172
+ const value = arg.includes("=") ? arg.slice("--locale=".length) : argv[++i];
173
+ options.locale = parseLocale(value);
174
+ continue;
175
+ }
156
176
  if (arg === "--path" || arg.startsWith("--path=")) {
157
177
  const value = arg.includes("=") ? arg.slice("--path=".length) : argv[++i];
158
178
  if (!value)
@@ -224,6 +244,15 @@ export function parseVibe(value) {
224
244
  }
225
245
  return raw;
226
246
  }
247
+ export function parseLocale(value) {
248
+ const raw = value?.trim().toLowerCase();
249
+ if (!raw)
250
+ throw new UsageError("Missing value for --locale");
251
+ if (!LOCALES.includes(raw)) {
252
+ throw new UsageError(`Invalid --locale "${value}". Use: ${LOCALES.join(", ")}`);
253
+ }
254
+ return raw;
255
+ }
227
256
  export function sanitizeScope(value) {
228
257
  const scope = value.trim().replace(/^[\s(/]+|[\s)/]+$/g, "");
229
258
  if (!scope)
package/dist/propose.js CHANGED
@@ -6,6 +6,7 @@ export async function proposeMessage(diff, options, env = process.env) {
6
6
  type: options.type,
7
7
  scope: options.scope,
8
8
  vibe: options.vibe,
9
+ locale: options.locale,
9
10
  breaking: options.breaking,
10
11
  });
11
12
  let message = heuristic;
@@ -15,6 +16,7 @@ export async function proposeMessage(diff, options, env = process.env) {
15
16
  const ai = await proposeWithAi({
16
17
  diff,
17
18
  vibe: options.vibe,
19
+ locale: options.locale,
18
20
  type: options.type,
19
21
  scope: options.scope,
20
22
  }, env);
package/dist/types.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export declare const VIBES: readonly ["playful", "pro", "terse"];
2
2
  export type Vibe = (typeof VIBES)[number];
3
+ export declare const LOCALES: readonly ["en", "zh"];
4
+ export type Locale = (typeof LOCALES)[number];
3
5
  export declare const COMMIT_TYPES: readonly ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore"];
4
6
  export type CommitType = (typeof COMMIT_TYPES)[number];
5
7
  export type FileStatus = "A" | "M" | "D" | "R" | "C" | "T" | "U";
@@ -30,11 +32,13 @@ export interface ProposedMessage {
30
32
  /** Short cause when breaking (export removed / major bump / --breaking / footer). */
31
33
  breakingReason?: string;
32
34
  engine: Engine;
35
+ locale: Locale;
33
36
  }
34
37
  export interface ProposeOptions {
35
38
  type?: CommitType;
36
39
  scope?: string;
37
40
  vibe: Vibe;
41
+ locale: Locale;
38
42
  preferAi?: boolean;
39
43
  /** Force a breaking change (! + BREAKING CHANGE footer). */
40
44
  breaking?: boolean;
@@ -47,6 +51,7 @@ export interface CliOptions {
47
51
  type?: CommitType;
48
52
  scope?: string;
49
53
  vibe: Vibe;
54
+ locale: Locale;
50
55
  noAi: boolean;
51
56
  json: boolean;
52
57
  /** Emit GitHub Actions annotations / job outputs / step summary. */
package/dist/types.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const VIBES = ["playful", "pro", "terse"];
2
+ export const LOCALES = ["en", "zh"];
2
3
  export const COMMIT_TYPES = [
3
4
  "feat",
4
5
  "fix",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webywz/vibe-commit",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Stylish conventional commit messages with personality. Offline-first, AI-optional.",
5
5
  "keywords": [
6
6
  "git",