@daylenjeez/ccm-switch 1.2.0
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 +195 -0
- package/README.zh-CN.md +195 -0
- package/dist/claude.d.ts +3 -0
- package/dist/claude.js +25 -0
- package/dist/i18n/en.d.ts +3 -0
- package/dist/i18n/en.js +124 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.js +35 -0
- package/dist/i18n/zh.d.ts +107 -0
- package/dist/i18n/zh.js +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +872 -0
- package/dist/store/cc-switch.d.ts +13 -0
- package/dist/store/cc-switch.js +91 -0
- package/dist/store/interface.d.ts +1 -0
- package/dist/store/interface.js +1 -0
- package/dist/store/standalone.d.ts +9 -0
- package/dist/store/standalone.js +65 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +34 -0
- package/package.json +32 -0
- package/src/claude.ts +30 -0
- package/src/i18n/en.ts +144 -0
- package/src/i18n/index.ts +44 -0
- package/src/i18n/zh.ts +143 -0
- package/src/index.ts +949 -0
- package/src/store/cc-switch.ts +114 -0
- package/src/store/interface.ts +1 -0
- package/src/store/standalone.ts +79 -0
- package/src/types.ts +20 -0
- package/src/utils.ts +37 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DataStore, Profile } from "./interface.js";
|
|
2
|
+
export declare function ccSwitchExists(): boolean;
|
|
3
|
+
export declare class CcSwitchStore implements DataStore {
|
|
4
|
+
private db;
|
|
5
|
+
constructor();
|
|
6
|
+
list(): Profile[];
|
|
7
|
+
get(name: string): Profile | undefined;
|
|
8
|
+
save(name: string, settingsConfig: Record<string, unknown>): void;
|
|
9
|
+
remove(name: string): boolean;
|
|
10
|
+
getCurrent(): string | undefined;
|
|
11
|
+
setCurrent(name: string): void;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { t } from "../i18n/index.js";
|
|
6
|
+
const DB_PATH = join(homedir(), ".cc-switch", "cc-switch.db");
|
|
7
|
+
const SETTINGS_PATH = join(homedir(), ".cc-switch", "settings.json");
|
|
8
|
+
export function ccSwitchExists() {
|
|
9
|
+
return existsSync(DB_PATH);
|
|
10
|
+
}
|
|
11
|
+
export class CcSwitchStore {
|
|
12
|
+
db;
|
|
13
|
+
constructor() {
|
|
14
|
+
if (!existsSync(DB_PATH)) {
|
|
15
|
+
throw new Error(t("store.db_not_found", { path: DB_PATH }));
|
|
16
|
+
}
|
|
17
|
+
this.db = new Database(DB_PATH);
|
|
18
|
+
}
|
|
19
|
+
list() {
|
|
20
|
+
const rows = this.db
|
|
21
|
+
.prepare(`SELECT id, name, settings_config FROM providers WHERE app_type = 'claude' ORDER BY sort_index`)
|
|
22
|
+
.all();
|
|
23
|
+
return rows.map((row) => ({
|
|
24
|
+
id: row.id,
|
|
25
|
+
name: row.name,
|
|
26
|
+
settingsConfig: JSON.parse(row.settings_config),
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
get(name) {
|
|
30
|
+
const row = this.db
|
|
31
|
+
.prepare(`SELECT id, name, settings_config FROM providers WHERE app_type = 'claude' AND name = ?`)
|
|
32
|
+
.get(name);
|
|
33
|
+
if (!row)
|
|
34
|
+
return undefined;
|
|
35
|
+
return {
|
|
36
|
+
id: row.id,
|
|
37
|
+
name: row.name,
|
|
38
|
+
settingsConfig: JSON.parse(row.settings_config),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
save(name, settingsConfig) {
|
|
42
|
+
const existing = this.get(name);
|
|
43
|
+
if (existing) {
|
|
44
|
+
this.db
|
|
45
|
+
.prepare(`UPDATE providers SET settings_config = ? WHERE app_type = 'claude' AND name = ?`)
|
|
46
|
+
.run(JSON.stringify(settingsConfig), name);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const id = crypto.randomUUID();
|
|
50
|
+
this.db
|
|
51
|
+
.prepare(`INSERT INTO providers (id, app_type, name, settings_config, meta, created_at) VALUES (?, 'claude', ?, ?, '{}', ?)`)
|
|
52
|
+
.run(id, name, JSON.stringify(settingsConfig), Date.now());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
remove(name) {
|
|
56
|
+
const result = this.db
|
|
57
|
+
.prepare(`DELETE FROM providers WHERE app_type = 'claude' AND name = ?`)
|
|
58
|
+
.run(name);
|
|
59
|
+
return result.changes > 0;
|
|
60
|
+
}
|
|
61
|
+
getCurrent() {
|
|
62
|
+
if (!existsSync(SETTINGS_PATH))
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
const settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
66
|
+
const currentId = settings.currentProviderClaude;
|
|
67
|
+
if (!currentId)
|
|
68
|
+
return undefined;
|
|
69
|
+
const row = this.db
|
|
70
|
+
.prepare(`SELECT name FROM providers WHERE app_type = 'claude' AND id = ?`)
|
|
71
|
+
.get(currentId);
|
|
72
|
+
return row?.name;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
setCurrent(name) {
|
|
79
|
+
const profile = this.get(name);
|
|
80
|
+
if (!profile)
|
|
81
|
+
throw new Error(t("error.not_found", { name }));
|
|
82
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
83
|
+
const settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
84
|
+
settings.currentProviderClaude = profile.id;
|
|
85
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
close() {
|
|
89
|
+
this.db.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { DataStore, Profile } from "../types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DataStore, Profile } from "./interface.js";
|
|
2
|
+
export declare class StandaloneStore implements DataStore {
|
|
3
|
+
list(): Profile[];
|
|
4
|
+
get(name: string): Profile | undefined;
|
|
5
|
+
save(name: string, settingsConfig: Record<string, unknown>): void;
|
|
6
|
+
remove(name: string): boolean;
|
|
7
|
+
getCurrent(): string | undefined;
|
|
8
|
+
setCurrent(name: string): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { t } from "../i18n/index.js";
|
|
5
|
+
const CCM_DIR = join(homedir(), ".ccm");
|
|
6
|
+
const CONFIG_PATH = join(CCM_DIR, "config.json");
|
|
7
|
+
function ensureDir() {
|
|
8
|
+
if (!existsSync(CCM_DIR)) {
|
|
9
|
+
mkdirSync(CCM_DIR, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function readConfig() {
|
|
13
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
14
|
+
return { profiles: {} };
|
|
15
|
+
}
|
|
16
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
17
|
+
}
|
|
18
|
+
function writeConfig(config) {
|
|
19
|
+
ensureDir();
|
|
20
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
21
|
+
}
|
|
22
|
+
export class StandaloneStore {
|
|
23
|
+
list() {
|
|
24
|
+
const config = readConfig();
|
|
25
|
+
return Object.entries(config.profiles).map(([name, settingsConfig]) => ({
|
|
26
|
+
id: name,
|
|
27
|
+
name,
|
|
28
|
+
settingsConfig,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
get(name) {
|
|
32
|
+
const config = readConfig();
|
|
33
|
+
const settingsConfig = config.profiles[name];
|
|
34
|
+
if (!settingsConfig)
|
|
35
|
+
return undefined;
|
|
36
|
+
return { id: name, name, settingsConfig };
|
|
37
|
+
}
|
|
38
|
+
save(name, settingsConfig) {
|
|
39
|
+
const config = readConfig();
|
|
40
|
+
config.profiles[name] = settingsConfig;
|
|
41
|
+
writeConfig(config);
|
|
42
|
+
}
|
|
43
|
+
remove(name) {
|
|
44
|
+
const config = readConfig();
|
|
45
|
+
if (!(name in config.profiles))
|
|
46
|
+
return false;
|
|
47
|
+
delete config.profiles[name];
|
|
48
|
+
if (config.current === name) {
|
|
49
|
+
config.current = undefined;
|
|
50
|
+
}
|
|
51
|
+
writeConfig(config);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
getCurrent() {
|
|
55
|
+
return readConfig().current;
|
|
56
|
+
}
|
|
57
|
+
setCurrent(name) {
|
|
58
|
+
const config = readConfig();
|
|
59
|
+
if (!(name in config.profiles)) {
|
|
60
|
+
throw new Error(t("error.not_found", { name }));
|
|
61
|
+
}
|
|
62
|
+
config.current = name;
|
|
63
|
+
writeConfig(config);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Profile {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
settingsConfig: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface DataStore {
|
|
7
|
+
list(): Profile[];
|
|
8
|
+
get(name: string): Profile | undefined;
|
|
9
|
+
save(name: string, settingsConfig: Record<string, unknown>): void;
|
|
10
|
+
remove(name: string): boolean;
|
|
11
|
+
getCurrent(): string | undefined;
|
|
12
|
+
setCurrent(name: string): void;
|
|
13
|
+
}
|
|
14
|
+
export interface RcConfig {
|
|
15
|
+
mode: "cc-switch" | "standalone";
|
|
16
|
+
aliases?: Record<string, string>;
|
|
17
|
+
locale?: "zh" | "en";
|
|
18
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { CcSwitchStore } from "./store/cc-switch.js";
|
|
5
|
+
import { StandaloneStore } from "./store/standalone.js";
|
|
6
|
+
const CCM_DIR = join(homedir(), ".ccm");
|
|
7
|
+
const RC_PATH = join(CCM_DIR, "rc.json");
|
|
8
|
+
export function readRc() {
|
|
9
|
+
if (!existsSync(RC_PATH))
|
|
10
|
+
return undefined;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(RC_PATH, "utf-8"));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function writeRc(rc) {
|
|
19
|
+
if (!existsSync(CCM_DIR)) {
|
|
20
|
+
mkdirSync(CCM_DIR, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
writeFileSync(RC_PATH, JSON.stringify(rc, null, 2));
|
|
23
|
+
}
|
|
24
|
+
export function getStore() {
|
|
25
|
+
const rc = readRc();
|
|
26
|
+
if (!rc)
|
|
27
|
+
return null;
|
|
28
|
+
if (rc.mode === "cc-switch") {
|
|
29
|
+
return new CcSwitchStore();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return new StandaloneStore();
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@daylenjeez/ccm-switch",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Claude Code Model Switcher - 快速切换 Claude Code 自定义模型配置",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ccm": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude",
|
|
16
|
+
"claude-code",
|
|
17
|
+
"model",
|
|
18
|
+
"switcher"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@clack/prompts": "^1.2.0",
|
|
23
|
+
"better-sqlite3": "^12.8.0",
|
|
24
|
+
"chalk": "^5.6.2",
|
|
25
|
+
"commander": "^14.0.3"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
29
|
+
"@types/node": "^25.5.2",
|
|
30
|
+
"typescript": "^6.0.2"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/claude.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
|
|
5
|
+
const SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
6
|
+
|
|
7
|
+
export function readClaudeSettings(): Record<string, unknown> {
|
|
8
|
+
if (!existsSync(SETTINGS_PATH)) return {};
|
|
9
|
+
return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function applyProfile(settingsConfig: Record<string, unknown>): void {
|
|
13
|
+
const current = readClaudeSettings();
|
|
14
|
+
|
|
15
|
+
// 保留用户级字段,用 profile 的配置覆盖
|
|
16
|
+
const preserved: Record<string, unknown> = {};
|
|
17
|
+
const USER_FIELDS = ["language", "permissions"];
|
|
18
|
+
for (const key of USER_FIELDS) {
|
|
19
|
+
if (key in current) {
|
|
20
|
+
preserved[key] = current[key];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const merged = { ...preserved, ...settingsConfig };
|
|
25
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getSettingsPath(): string {
|
|
29
|
+
return SETTINGS_PATH;
|
|
30
|
+
}
|
package/src/i18n/en.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { TranslationKey } from "./zh.js";
|
|
2
|
+
|
|
3
|
+
const en: Record<TranslationKey, string> = {
|
|
4
|
+
// program
|
|
5
|
+
"program.description": "Claude Code Model Switcher - Quickly switch Claude Code custom model configurations",
|
|
6
|
+
|
|
7
|
+
// common
|
|
8
|
+
"common.not_init": "Not initialized yet. Run: ccm init",
|
|
9
|
+
"common.model": "Model",
|
|
10
|
+
"common.model_default": "default",
|
|
11
|
+
"common.source": "Source",
|
|
12
|
+
|
|
13
|
+
// error
|
|
14
|
+
"error.not_found": 'Configuration "{name}" not found',
|
|
15
|
+
"error.alias_target_missing": 'Alias "{alias}" points to "{target}", but it does not exist',
|
|
16
|
+
"error.invalid_choice": "Invalid choice",
|
|
17
|
+
|
|
18
|
+
// suggest
|
|
19
|
+
"suggest.did_you_mean": "Did you mean: {name}?",
|
|
20
|
+
"suggest.did_you_mean_header": "Did you mean:",
|
|
21
|
+
"suggest.use_list": "Use ccm list to see all available configurations",
|
|
22
|
+
|
|
23
|
+
// init
|
|
24
|
+
"init.description": "Initialize ccm",
|
|
25
|
+
"init.cc_switch_found":"cc-switch detected. Import configurations from it? (Y/n) ",
|
|
26
|
+
"init.imported": "✓ Imported {count} configurations",
|
|
27
|
+
"init.current": "Active: {name}",
|
|
28
|
+
"init.no_current": "No active configuration",
|
|
29
|
+
"init.done_cc_switch": "✓ Initialized in cc-switch mode",
|
|
30
|
+
"init.done_standalone": "✓ Initialized in standalone mode",
|
|
31
|
+
|
|
32
|
+
// config
|
|
33
|
+
"config.description": "View or switch data source mode",
|
|
34
|
+
"config.current_mode": "Current mode: {mode}",
|
|
35
|
+
"config.switch_confirm": "Switch mode? (y/N) ",
|
|
36
|
+
"config.cc_switch_not_installed": "cc-switch is not installed",
|
|
37
|
+
"config.switched": "✓ Switched to {mode} mode",
|
|
38
|
+
|
|
39
|
+
// list
|
|
40
|
+
"list.description": "List and select configurations",
|
|
41
|
+
"list.empty": "No configurations yet. Use ccm save <name> to save current config",
|
|
42
|
+
"list.header": "Available configurations:",
|
|
43
|
+
"list.select": "Select configuration:",
|
|
44
|
+
"list.current_marker": "(current)",
|
|
45
|
+
"list.cancelled": "Cancelled",
|
|
46
|
+
"list.choose_number": "Enter number to switch (Enter to skip): ",
|
|
47
|
+
|
|
48
|
+
// current
|
|
49
|
+
"current.description": "Show the currently active configuration",
|
|
50
|
+
"current.none": "No active configuration",
|
|
51
|
+
"current.settings_header": "Current settings.json:",
|
|
52
|
+
"current.not_exist": 'Current configuration "{name}" no longer exists',
|
|
53
|
+
"current.header": "Current configuration: {name}",
|
|
54
|
+
|
|
55
|
+
// use
|
|
56
|
+
"use.description": "Switch to a specified configuration",
|
|
57
|
+
"use.done": "✓ Switched to {name}",
|
|
58
|
+
"use.restart": "Restart Claude Code to apply",
|
|
59
|
+
|
|
60
|
+
// save
|
|
61
|
+
"save.description": "Save current settings.json as a new configuration",
|
|
62
|
+
"save.overwrite": 'Configuration "{name}" already exists, will overwrite',
|
|
63
|
+
"save.done": '✓ Saved current configuration as "{name}"',
|
|
64
|
+
|
|
65
|
+
// show
|
|
66
|
+
"show.description": "View configuration details (defaults to current)",
|
|
67
|
+
"show.no_current": "No active configuration. Specify a name: ccm show <name>",
|
|
68
|
+
|
|
69
|
+
// remove
|
|
70
|
+
"remove.description": "Delete a configuration",
|
|
71
|
+
"remove.select": "Select configuration to delete:",
|
|
72
|
+
"remove.confirm": 'Delete "{name}"? (y/N) ',
|
|
73
|
+
"remove.done": '✓ Deleted "{name}"',
|
|
74
|
+
|
|
75
|
+
// alias
|
|
76
|
+
"alias.description": "Manage aliases",
|
|
77
|
+
"alias.set_description": "Set alias, e.g.: ccm alias set or openrouter-opus4.6",
|
|
78
|
+
"alias.set_done": "✓ Alias set: {short} → {name}",
|
|
79
|
+
"alias.rm_description": "Remove an alias",
|
|
80
|
+
"alias.rm_not_found": 'Alias "{short}" not found',
|
|
81
|
+
"alias.rm_done": '✓ Removed alias "{short}"',
|
|
82
|
+
"alias.list_description": "List all aliases",
|
|
83
|
+
"alias.list_empty": "No aliases yet. Use ccm alias set <short> <name> to add one",
|
|
84
|
+
"alias.list_header": "Aliases:",
|
|
85
|
+
|
|
86
|
+
// locale
|
|
87
|
+
"locale.description": "Manage interface language",
|
|
88
|
+
"locale.current": "Current language: {locale}",
|
|
89
|
+
"locale.set_description": "Set language (zh/en)",
|
|
90
|
+
"locale.set_done": "✓ Language set to {locale}",
|
|
91
|
+
"locale.set_invalid": "Unsupported language: {locale}. Available: zh, en",
|
|
92
|
+
"locale.list_description": "List and select language",
|
|
93
|
+
"locale.list_header": "Supported languages:",
|
|
94
|
+
"locale.list_current_marker": "(current)",
|
|
95
|
+
"locale.select": "Select language:",
|
|
96
|
+
"locale.choose_number": "Enter number to switch (Enter to skip): ",
|
|
97
|
+
|
|
98
|
+
// add
|
|
99
|
+
"add.description": "Interactively add a new configuration",
|
|
100
|
+
"add.prompt_name": "Provider name (e.g. OpenRouter): ",
|
|
101
|
+
"add.prompt_base_url": "ANTHROPIC_BASE_URL: ",
|
|
102
|
+
"add.prompt_auth_token": "ANTHROPIC_AUTH_TOKEN: ",
|
|
103
|
+
"add.prompt_model": "ANTHROPIC_MODEL: ",
|
|
104
|
+
"add.prompt_default_opus": "ANTHROPIC_DEFAULT_OPUS_MODEL (press Enter to skip): ",
|
|
105
|
+
"add.prompt_default_sonnet": "ANTHROPIC_DEFAULT_SONNET_MODEL (press Enter to skip): ",
|
|
106
|
+
"add.prompt_default_haiku": "ANTHROPIC_DEFAULT_HAIKU_MODEL (press Enter to skip): ",
|
|
107
|
+
"add.mode_select": "Choose how to add:",
|
|
108
|
+
"add.mode_interactive": "Step by step",
|
|
109
|
+
"add.mode_json": "Write JSON directly",
|
|
110
|
+
"add.mode_choose": "Choose (1/2): ",
|
|
111
|
+
"add.json_template_hint": "Fill in the configuration in editor, save and exit",
|
|
112
|
+
"add.json_parse_error": "JSON parse error, please check format",
|
|
113
|
+
"add.back_hint": "Type < to go back",
|
|
114
|
+
"add.name_required": "Provider name cannot be empty",
|
|
115
|
+
"add.field_required": "{field} cannot be empty",
|
|
116
|
+
"add.already_exists": 'Configuration "{name}" already exists. Overwrite? (y/N) ',
|
|
117
|
+
"add.edit_confirm": "Edit configuration in editor? (y/N) ",
|
|
118
|
+
"add.preview_header": "Configuration preview:",
|
|
119
|
+
"add.done": '✓ Saved configuration "{name}"',
|
|
120
|
+
"add.switch_confirm": "Switch to this configuration now? (Y/n) ",
|
|
121
|
+
"add.cancelled": "Cancelled",
|
|
122
|
+
|
|
123
|
+
// modify
|
|
124
|
+
"modify.description": "Modify an existing configuration",
|
|
125
|
+
"modify.select": "Select configuration to modify:",
|
|
126
|
+
"modify.done": '✓ Updated configuration "{name}"',
|
|
127
|
+
"modify.no_change": "No changes made",
|
|
128
|
+
|
|
129
|
+
// alias conflict
|
|
130
|
+
"alias.is_alias": '"{name}" is an alias for "{target}"',
|
|
131
|
+
"alias.conflict": '"{name}" is both an alias (→ {target}) and a config name. Which one?',
|
|
132
|
+
"alias.conflict_alias": "Alias (→ {target})",
|
|
133
|
+
"alias.conflict_config": "Config {name}",
|
|
134
|
+
"alias.choose_conflict": "Choose (1/2): ",
|
|
135
|
+
"alias.rm_which": "Which one to delete?",
|
|
136
|
+
"alias.rm_alias": "Alias {name}",
|
|
137
|
+
"alias.rm_config": "Config {target}",
|
|
138
|
+
"alias.rm_choose": "Choose (1/2): ",
|
|
139
|
+
|
|
140
|
+
// store errors
|
|
141
|
+
"store.db_not_found": "cc-switch database not found: {path}",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default en;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import zh, { type TranslationKey } from "./zh.js";
|
|
2
|
+
import en from "./en.js";
|
|
3
|
+
import { readRc } from "../utils.js";
|
|
4
|
+
|
|
5
|
+
export type Locale = "zh" | "en";
|
|
6
|
+
|
|
7
|
+
const locales: Record<Locale, Record<string, string>> = { zh, en };
|
|
8
|
+
|
|
9
|
+
function detectLocale(): Locale {
|
|
10
|
+
// 1. rc.json locale setting takes priority
|
|
11
|
+
const rc = readRc();
|
|
12
|
+
if (rc?.locale && rc.locale in locales) return rc.locale;
|
|
13
|
+
|
|
14
|
+
// 2. Fallback to system LANG/LC_ALL
|
|
15
|
+
const lang = process.env.LC_ALL || process.env.LANG || "";
|
|
16
|
+
if (lang.startsWith("en")) return "en";
|
|
17
|
+
|
|
18
|
+
// 3. Default to Chinese
|
|
19
|
+
return "zh";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let currentLocale: Locale | undefined;
|
|
23
|
+
|
|
24
|
+
function getLocale(): Locale {
|
|
25
|
+
if (!currentLocale) currentLocale = detectLocale();
|
|
26
|
+
return currentLocale;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function setLocale(locale: Locale): void {
|
|
30
|
+
currentLocale = locale;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function t(key: TranslationKey, vars?: Record<string, string>): string {
|
|
34
|
+
const locale = getLocale();
|
|
35
|
+
let text = locales[locale][key] ?? locales.zh[key] ?? key;
|
|
36
|
+
if (vars) {
|
|
37
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
38
|
+
text = text.replace(new RegExp(`\\{${k}\\}`, "g"), v);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { type TranslationKey };
|
package/src/i18n/zh.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const zh = {
|
|
2
|
+
// program
|
|
3
|
+
"program.description": "Claude Code Model Switcher - 快速切换 Claude Code 自定义模型配置",
|
|
4
|
+
|
|
5
|
+
// common
|
|
6
|
+
"common.not_init": "尚未初始化,请先运行: ccm init",
|
|
7
|
+
"common.model": "模型",
|
|
8
|
+
"common.model_default": "默认",
|
|
9
|
+
"common.source": "来源",
|
|
10
|
+
|
|
11
|
+
// error
|
|
12
|
+
"error.not_found": '配置 "{name}" 不存在',
|
|
13
|
+
"error.alias_target_missing": '别名 "{alias}" 指向 "{target}",但该配置不存在',
|
|
14
|
+
"error.invalid_choice": "无效选择",
|
|
15
|
+
|
|
16
|
+
// suggest
|
|
17
|
+
"suggest.did_you_mean": "你是不是想说: {name}?",
|
|
18
|
+
"suggest.did_you_mean_header": "你是不是想说:",
|
|
19
|
+
"suggest.use_list": "使用 ccm list 查看所有可用配置",
|
|
20
|
+
|
|
21
|
+
// init
|
|
22
|
+
"init.description": "初始化 ccm",
|
|
23
|
+
"init.cc_switch_found":"检测到 cc-switch 已安装,是否从中导入配置?(Y/n) ",
|
|
24
|
+
"init.imported": "✓ 已导入 {count} 个配置",
|
|
25
|
+
"init.current": "当前激活: {name}",
|
|
26
|
+
"init.no_current": "当前无激活配置",
|
|
27
|
+
"init.done_cc_switch": "✓ 已初始化为 cc-switch 模式",
|
|
28
|
+
"init.done_standalone": "✓ 已初始化为独立模式",
|
|
29
|
+
|
|
30
|
+
// config
|
|
31
|
+
"config.description": "查看或切换数据源模式",
|
|
32
|
+
"config.current_mode": "当前模式: {mode}",
|
|
33
|
+
"config.switch_confirm": "是否切换模式?(y/N) ",
|
|
34
|
+
"config.cc_switch_not_installed": "cc-switch 未安装",
|
|
35
|
+
"config.switched": "✓ 已切换为 {mode} 模式",
|
|
36
|
+
|
|
37
|
+
// list
|
|
38
|
+
"list.description": "列出并选择配置方案",
|
|
39
|
+
"list.empty": "暂无配置方案。使用 ccm save <name> 保存当前配置",
|
|
40
|
+
"list.header": "可用配置:",
|
|
41
|
+
"list.select": "选择配置:",
|
|
42
|
+
"list.current_marker": "(当前)",
|
|
43
|
+
"list.cancelled": "已取消",
|
|
44
|
+
"list.choose_number": "输入序号切换 (回车跳过): ",
|
|
45
|
+
|
|
46
|
+
// current
|
|
47
|
+
"current.description": "显示当前生效的配置",
|
|
48
|
+
"current.none": "当前无激活配置",
|
|
49
|
+
"current.settings_header": "当前 settings.json:",
|
|
50
|
+
"current.not_exist": '当前配置 "{name}" 已不存在',
|
|
51
|
+
"current.header": "当前配置: {name}",
|
|
52
|
+
|
|
53
|
+
// use
|
|
54
|
+
"use.description": "切换到指定配置方案",
|
|
55
|
+
"use.done": "✓ 已切换到 {name}",
|
|
56
|
+
"use.restart": "重启 Claude Code 生效",
|
|
57
|
+
|
|
58
|
+
// save
|
|
59
|
+
"save.description": "从当前 settings.json 保存为新配置",
|
|
60
|
+
"save.overwrite": '配置 "{name}" 已存在,将覆盖',
|
|
61
|
+
"save.done": '✓ 已保存当前配置为 "{name}"',
|
|
62
|
+
|
|
63
|
+
// show
|
|
64
|
+
"show.description": "查看配置详情(不指定则显示当前)",
|
|
65
|
+
"show.no_current": "当前无激活配置,请指定名称: ccm show <name>",
|
|
66
|
+
|
|
67
|
+
// remove
|
|
68
|
+
"remove.description": "删除配置方案",
|
|
69
|
+
"remove.select": "选择要删除的配置:",
|
|
70
|
+
"remove.confirm": '确认删除 "{name}"?(y/N) ',
|
|
71
|
+
"remove.done": '✓ 已删除 "{name}"',
|
|
72
|
+
|
|
73
|
+
// alias
|
|
74
|
+
"alias.description": "管理别名",
|
|
75
|
+
"alias.set_description": "设置别名,如: ccm alias set or openrouter-opus4.6",
|
|
76
|
+
"alias.set_done": "✓ 别名已设置: {short} → {name}",
|
|
77
|
+
"alias.rm_description": "删除别名",
|
|
78
|
+
"alias.rm_not_found": '别名 "{short}" 不存在',
|
|
79
|
+
"alias.rm_done": '✓ 已删除别名 "{short}"',
|
|
80
|
+
"alias.list_description": "列出所有别名",
|
|
81
|
+
"alias.list_empty": "暂无别名。使用 ccm alias set <short> <name> 添加",
|
|
82
|
+
"alias.list_header": "别名列表:",
|
|
83
|
+
|
|
84
|
+
// locale
|
|
85
|
+
"locale.description": "管理界面语言",
|
|
86
|
+
"locale.current": "当前语言: {locale}",
|
|
87
|
+
"locale.set_description": "设置语言 (zh/en)",
|
|
88
|
+
"locale.set_done": "✓ 语言已设置为 {locale}",
|
|
89
|
+
"locale.set_invalid": "不支持的语言: {locale},可选: zh, en",
|
|
90
|
+
"locale.list_description": "列出并选择语言",
|
|
91
|
+
"locale.list_header": "支持的语言:",
|
|
92
|
+
"locale.list_current_marker": "(当前)",
|
|
93
|
+
"locale.select": "选择语言:",
|
|
94
|
+
"locale.choose_number": "输入序号切换 (回车跳过): ",
|
|
95
|
+
|
|
96
|
+
// add
|
|
97
|
+
"add.description": "交互式添加新配置",
|
|
98
|
+
"add.prompt_name": "供应商名称 (如 OpenRouter): ",
|
|
99
|
+
"add.prompt_base_url": "ANTHROPIC_BASE_URL: ",
|
|
100
|
+
"add.prompt_auth_token": "ANTHROPIC_AUTH_TOKEN: ",
|
|
101
|
+
"add.prompt_model": "ANTHROPIC_MODEL: ",
|
|
102
|
+
"add.prompt_default_opus": "ANTHROPIC_DEFAULT_OPUS_MODEL (回车跳过): ",
|
|
103
|
+
"add.prompt_default_sonnet": "ANTHROPIC_DEFAULT_SONNET_MODEL (回车跳过): ",
|
|
104
|
+
"add.prompt_default_haiku": "ANTHROPIC_DEFAULT_HAIKU_MODEL (回车跳过): ",
|
|
105
|
+
"add.mode_select": "选择添加方式:",
|
|
106
|
+
"add.mode_interactive": "逐步填写",
|
|
107
|
+
"add.mode_json": "直接编写 JSON",
|
|
108
|
+
"add.mode_choose": "请选择 (1/2): ",
|
|
109
|
+
"add.json_template_hint": "请在编辑器中填写配置,保存并退出",
|
|
110
|
+
"add.json_parse_error": "JSON 解析失败,请检查格式",
|
|
111
|
+
"add.back_hint": "输入 < 返回上一步",
|
|
112
|
+
"add.name_required": "供应商名称不能为空",
|
|
113
|
+
"add.field_required": "{field} 不能为空",
|
|
114
|
+
"add.already_exists": '配置 "{name}" 已存在,是否覆盖?(y/N) ',
|
|
115
|
+
"add.edit_confirm": "是否在编辑器中编辑配置?(y/N) ",
|
|
116
|
+
"add.preview_header": "配置预览:",
|
|
117
|
+
"add.done": '✓ 已保存配置 "{name}"',
|
|
118
|
+
"add.switch_confirm": "是否立即切换到此配置?(Y/n) ",
|
|
119
|
+
"add.cancelled": "已取消",
|
|
120
|
+
|
|
121
|
+
// modify
|
|
122
|
+
"modify.description": "修改已有配置",
|
|
123
|
+
"modify.select": "选择要修改的配置:",
|
|
124
|
+
"modify.done": '✓ 已更新配置 "{name}"',
|
|
125
|
+
"modify.no_change": "未做任何修改",
|
|
126
|
+
|
|
127
|
+
// alias conflict
|
|
128
|
+
"alias.is_alias": '"{name}" 是别名,指向 "{target}"',
|
|
129
|
+
"alias.conflict": '"{name}" 同时是别名(→ {target})和配置名,使用哪个?',
|
|
130
|
+
"alias.conflict_alias": "别名(→ {target})",
|
|
131
|
+
"alias.conflict_config": "配置 {name}",
|
|
132
|
+
"alias.choose_conflict": "请选择 (1/2): ",
|
|
133
|
+
"alias.rm_which": "要删除哪个?",
|
|
134
|
+
"alias.rm_alias": "别名 {name}",
|
|
135
|
+
"alias.rm_config": "配置 {target}",
|
|
136
|
+
"alias.rm_choose": "请选择 (1/2): ",
|
|
137
|
+
|
|
138
|
+
// store errors
|
|
139
|
+
"store.db_not_found": "cc-switch 数据库不存在: {path}",
|
|
140
|
+
} as const;
|
|
141
|
+
|
|
142
|
+
export type TranslationKey = keyof typeof zh;
|
|
143
|
+
export default zh;
|