Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,374 changes: 1,015 additions & 359 deletions locales/en_US.json

Large diffs are not rendered by default.

1,408 changes: 1,020 additions & 388 deletions locales/zh_CN.json

Large diffs are not rendered by default.

46 changes: 35 additions & 11 deletions occm_core/agent_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from .i18n import tr


class AgentGroupManager:
Expand Down Expand Up @@ -184,7 +185,7 @@ def load_groups(self) -> None:
"default_group_id": None,
}
except Exception as e:
print(f"加载分组配置失败: {e}")
print(tr("agent_groups.load_failed", error=str(e)))
self.groups_data = {
"version": "1.0.0",
"groups": [],
Expand All @@ -210,7 +211,7 @@ def save_groups(self) -> None:
with open(self.groups_file, "w", encoding="utf-8") as f:
json.dump(self.groups_data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"保存分组配置失败: {e}")
print(tr("agent_groups.save_failed", error=str(e)))
raise

def backup_groups(self) -> Optional[Path]:
Expand Down Expand Up @@ -238,7 +239,7 @@ def backup_groups(self) -> Optional[Path]:

return backup_file
except Exception as e:
print(f"备份分组配置失败: {e}")
print(tr("agent_groups.backup_failed", error=str(e)))
return None

def _cleanup_old_backups(self, keep_count: int = 10) -> None:
Expand All @@ -259,7 +260,7 @@ def _cleanup_old_backups(self, keep_count: int = 10) -> None:
for backup_file in backup_files[keep_count:]:
backup_file.unlink()
except Exception as e:
print(f"清理旧备份失败: {e}")
print(tr("agent_groups.cleanup_failed", error=str(e)))

# ========== 分组CRUD操作 ==========

Expand Down Expand Up @@ -372,10 +373,9 @@ def list_groups(self, include_presets: bool = False) -> List[Dict]:
if include_presets:
# 添加预设模板(标记为preset类型)
for preset in self.PRESETS:
preset_copy = preset.copy()
preset_copy = self._localize_preset(preset)
preset_copy["type"] = "preset"
groups.append(preset_copy)

return groups

# ========== 分组应用 ==========
Expand Down Expand Up @@ -509,13 +509,37 @@ def get_current_group_match(

# ========== 预设模板 ==========

# Mapping from preset id to locale key prefix
_PRESET_LOCALE_MAP = {
"preset-minimal": "preset_minimal",
"preset-standard": "preset_standard",
"preset-full": "preset_common",
"preset-complete": "preset_complete",
"preset-frontend": "preset_frontend",
"preset-backend": "preset_backend",
}

@staticmethod
def _localize_preset(preset: Dict) -> Dict:
"""Return a copy of the preset with localized name/description."""
p = preset.copy()
key = AgentGroupManager._PRESET_LOCALE_MAP.get(preset["id"])
if key:
localized_name = tr(f"agent_groups.{key}")
if localized_name != f"agent_groups.{key}":
p["name"] = localized_name
localized_desc = tr(f"agent_groups.{key}_desc")
if localized_desc != f"agent_groups.{key}_desc":
p["description"] = localized_desc
return p

def get_presets(self) -> List[Dict]:
"""获取所有预设模板

Returns:
List[Dict]: 预设模板列表
"""
return self.PRESETS.copy()
return [self._localize_preset(p) for p in self.PRESETS]

def create_from_preset(
self, preset_id: str, name: str, description: Optional[str] = None
Expand Down Expand Up @@ -582,7 +606,7 @@ def export_group(self, group_id: str, file_path: Path) -> bool:

return True
except Exception as e:
print(f"导出分组失败: {e}")
print(tr("agent_groups.export_failed", error=str(e)))
return False

def import_group(self, file_path: Path, overwrite: bool = False) -> Optional[str]:
Expand All @@ -602,7 +626,7 @@ def import_group(self, file_path: Path, overwrite: bool = False) -> Optional[str

# 验证格式
if "group" not in import_data:
print("导入文件格式错误:缺少group字段")
print(tr("agent_groups.import_format_error"))
return None

group = import_data["group"]
Expand All @@ -615,7 +639,7 @@ def import_group(self, file_path: Path, overwrite: bool = False) -> Optional[str
break

if existing_group and not overwrite:
print(f"分组 '{group['name']}' 已存在")
print(tr("agent_groups.import_exists", name=group['name']))
return None

if existing_group and overwrite:
Expand All @@ -637,7 +661,7 @@ def import_group(self, file_path: Path, overwrite: bool = False) -> Optional[str
icon=group.get("icon", "📁"),
)
except Exception as e:
print(f"导入分组失败: {e}")
print(tr("agent_groups.import_failed", error=str(e)))
return None

# ========== 统计信息 ==========
Expand Down
81 changes: 41 additions & 40 deletions occm_core/cli_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ExportResult,
ValidationResult,
)
from .i18n import tr


# ==================== CLI 导出模块异常类 ====================
Expand All @@ -28,7 +29,7 @@ class ProviderValidationError(CLIExportError):

def __init__(self, missing_fields: List[str]):
self.missing_fields = missing_fields
super().__init__(f"Provider 配置不完整: 缺少 {', '.join(missing_fields)}")
super().__init__(tr("cli_export_msg.provider_config_incomplete", fields=", ".join(missing_fields)))


class ConfigWriteError(CLIExportError):
Expand All @@ -37,7 +38,7 @@ class ConfigWriteError(CLIExportError):
def __init__(self, path: Path, reason: str):
self.path = path
self.reason = reason
super().__init__(f"写入配置失败 ({path}): {reason}")
super().__init__(tr("cli_export_msg.write_config_failed", path=path, reason=reason))


class ConfigParseError(CLIExportError):
Expand All @@ -47,7 +48,7 @@ def __init__(self, path: Path, format_type: str, reason: str):
self.path = path
self.format_type = format_type
self.reason = reason
super().__init__(f"解析 {format_type} 配置失败 ({path}): {reason}")
super().__init__(tr("cli_export_msg.parse_config_failed", format=format_type, path=path, reason=reason))


class BackupError(CLIExportError):
Expand All @@ -56,7 +57,7 @@ class BackupError(CLIExportError):
def __init__(self, cli_type: str, reason: str):
self.cli_type = cli_type
self.reason = reason
super().__init__(f"备份 {cli_type} 配置失败: {reason}")
super().__init__(tr("cli_export_msg.backup_failed", cli_type=cli_type, reason=reason))


class RestoreError(CLIExportError):
Expand All @@ -65,7 +66,7 @@ class RestoreError(CLIExportError):
def __init__(self, backup_path: Path, reason: str):
self.backup_path = backup_path
self.reason = reason
super().__init__(f"恢复备份失败 ({backup_path}): {reason}")
super().__init__(tr("cli_export_msg.restore_failed", path=backup_path, reason=reason))


class CLIConfigWriter:
Expand Down Expand Up @@ -132,7 +133,7 @@ def atomic_write_json(self, path: Path, data: Dict) -> None:
except json.JSONDecodeError as e:
if temp_path.exists():
temp_path.unlink()
raise ConfigWriteError(path, f"JSON 格式验证失败: {e}")
raise ConfigWriteError(path, tr("cli_export_msg.json_validation_failed", error=e))
except Exception as e:
if temp_path.exists():
temp_path.unlink()
Expand Down Expand Up @@ -177,7 +178,7 @@ def set_file_permissions(self, path: Path, mode: int = 0o600) -> None:
try:
path.chmod(mode)
except Exception as e:
print(f"设置文件权限失败 ({path}): {e}")
print(tr("cli_export_msg.set_permissions_failed", path=path, error=e))

def write_claude_settings(self, config: Dict, merge: bool = True) -> None:
"""写入 Claude settings.json
Expand Down Expand Up @@ -339,7 +340,7 @@ def restore_backup(self, backup_path: Path, cli_type: str) -> bool:
"""从备份恢复配置"""
try:
if not backup_path.exists():
raise RestoreError(backup_path, "备份目录不存在")
raise RestoreError(backup_path, tr("cli_export_msg.backup_dir_not_found"))

cli_dir = CLIConfigWriter.get_cli_dir(cli_type)
cli_dir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -386,7 +387,7 @@ def list_backups(self, cli_type: str) -> List[BackupInfo]:
backups.sort(key=lambda x: x.created_at, reverse=True)

except Exception as e:
print(f"列出备份失败: {e}")
print(tr("cli_export_msg.list_backups_failed", error=e))

return backups

Expand All @@ -397,7 +398,7 @@ def cleanup_old_backups(self, cli_type: str) -> None:
try:
shutil.rmtree(backup.path)
except Exception as e:
print(f"删除旧备份失败 ({backup.path}): {e}")
print(tr("cli_export_msg.delete_old_backup_failed", path=backup.path, error=e))


class CLIConfigGenerator:
Expand Down Expand Up @@ -531,17 +532,17 @@ def validate_provider(self, provider: Dict) -> ValidationResult:
"baseURL", ""
)
if not base_url or not base_url.strip():
errors.append("缺少 API 地址 (baseURL)")
errors.append(tr("cli_export_msg.missing_base_url"))

api_key = provider.get("apiKey", "") or provider.get("options", {}).get(
"apiKey", ""
)
if not api_key or not api_key.strip():
errors.append("缺少 API 密钥 (apiKey)")
errors.append(tr("cli_export_msg.missing_api_key"))

models = provider.get("models", {})
if not models:
warnings.append("未配置任何模型")
warnings.append(tr("cli_export_msg.no_models_configured"))

if errors:
return ValidationResult.failure(errors, warnings)
Expand All @@ -566,7 +567,7 @@ def export_to_claude(self, provider: Dict, model: str) -> ExportResult:
except CLIExportError as e:
return ExportResult.fail(cli_type, str(e), backup_path)
except Exception as e:
return ExportResult.fail(cli_type, f"导出失败: {e}", backup_path)
return ExportResult.fail(cli_type, tr("cli_export_msg.export_failed", error=e), backup_path)

def export_to_codex(self, provider: Dict, model: str) -> ExportResult:
cli_type = "codex"
Expand Down Expand Up @@ -595,7 +596,7 @@ def export_to_codex(self, provider: Dict, model: str) -> ExportResult:
except CLIExportError as e:
return ExportResult.fail(cli_type, str(e), backup_path)
except Exception as e:
return ExportResult.fail(cli_type, f"导出失败: {e}", backup_path)
return ExportResult.fail(cli_type, tr("cli_export_msg.export_failed", error=e), backup_path)

def export_to_gemini(self, provider: Dict, model: str) -> ExportResult:
cli_type = "gemini"
Expand Down Expand Up @@ -623,7 +624,7 @@ def export_to_gemini(self, provider: Dict, model: str) -> ExportResult:
except CLIExportError as e:
return ExportResult.fail(cli_type, str(e), backup_path)
except Exception as e:
return ExportResult.fail(cli_type, f"导出失败: {e}", backup_path)
return ExportResult.fail(cli_type, tr("cli_export_msg.export_failed", error=e), backup_path)

def batch_export(
self, provider: Dict, models: Dict[str, str], targets: List[str]
Expand All @@ -642,9 +643,9 @@ def batch_export(
elif cli_type == "gemini":
result = self.export_to_gemini(provider, model)
else:
result = ExportResult.fail(cli_type, f"未知的 CLI 类型: {cli_type}")
result = ExportResult.fail(cli_type, tr("cli_export_msg.unknown_cli_type", cli_type=cli_type))
except Exception as e:
result = ExportResult.fail(cli_type, f"导出异常: {e}")
result = ExportResult.fail(cli_type, tr("cli_export_msg.export_exception", error=e))

results.append(result)

Expand All @@ -665,83 +666,83 @@ def validate_exported_config(self, cli_type: str) -> ValidationResult:
if cli_type == "claude":
settings_path = cli_dir / "settings.json"
if not settings_path.exists():
errors.append("settings.json 文件不存在")
errors.append(tr("cli_export_msg.settings_json_not_found"))
else:
try:
with open(settings_path, "r", encoding="utf-8") as f:
config = json.load(f)
if "env" not in config:
errors.append("settings.json 缺少 env 字段")
errors.append(tr("cli_export_msg.settings_json_missing_env"))
else:
env = config["env"]
if "ANTHROPIC_BASE_URL" not in env:
errors.append("缺少 ANTHROPIC_BASE_URL")
errors.append(tr("cli_export_msg.missing_anthropic_base_url"))
if "ANTHROPIC_AUTH_TOKEN" not in env:
errors.append("缺少 ANTHROPIC_AUTH_TOKEN")
errors.append(tr("cli_export_msg.missing_anthropic_auth_token"))
except json.JSONDecodeError as e:
errors.append(f"settings.json 格式错误: {e}")
errors.append(tr("cli_export_msg.settings_json_format_error", error=e))
except Exception as e:
errors.append(f"读取 settings.json 失败: {e}")
errors.append(tr("cli_export_msg.read_settings_json_failed", error=e))

elif cli_type == "codex":
auth_path = cli_dir / "auth.json"
config_path = cli_dir / "config.toml"

if not auth_path.exists():
errors.append("auth.json 文件不存在")
errors.append(tr("cli_export_msg.auth_json_not_found"))
else:
try:
with open(auth_path, "r", encoding="utf-8") as f:
auth = json.load(f)
if "OPENAI_API_KEY" not in auth:
errors.append("auth.json 缺少 OPENAI_API_KEY")
errors.append(tr("cli_export_msg.auth_json_missing_key"))
except json.JSONDecodeError as e:
errors.append(f"auth.json 格式错误: {e}")
errors.append(tr("cli_export_msg.auth_json_format_error", error=e))
except Exception as e:
errors.append(f"读取 auth.json 失败: {e}")
errors.append(tr("cli_export_msg.read_auth_json_failed", error=e))

if not config_path.exists():
errors.append("config.toml 文件不存在")
errors.append(tr("cli_export_msg.config_toml_not_found"))
else:
try:
with open(config_path, "r", encoding="utf-8") as f:
content = f.read()
if "model_provider" not in content:
errors.append("config.toml 缺少 model_provider")
errors.append(tr("cli_export_msg.config_toml_missing_provider"))
if "model =" not in content:
errors.append("config.toml 缺少 model")
errors.append(tr("cli_export_msg.config_toml_missing_model"))
except Exception as e:
errors.append(f"读取 config.toml 失败: {e}")
errors.append(tr("cli_export_msg.read_config_toml_failed", error=e))

elif cli_type == "gemini":
env_path = cli_dir / ".env"
settings_path = cli_dir / "settings.json"

if not env_path.exists():
errors.append(".env 文件不存在")
errors.append(tr("cli_export_msg.env_file_not_found"))
else:
try:
with open(env_path, "r", encoding="utf-8") as f:
content = f.read()
if "GEMINI_API_KEY" not in content:
errors.append(".env 缺少 GEMINI_API_KEY")
errors.append(tr("cli_export_msg.env_missing_api_key"))
if "GOOGLE_GEMINI_BASE_URL" not in content:
errors.append(".env 缺少 GOOGLE_GEMINI_BASE_URL")
errors.append(tr("cli_export_msg.env_missing_base_url"))
except Exception as e:
errors.append(f"读取 .env 失败: {e}")
errors.append(tr("cli_export_msg.read_env_failed", error=e))

if not settings_path.exists():
warnings.append("settings.json 文件不存在")
warnings.append(tr("cli_export_msg.settings_json_not_found_warn"))
else:
try:
with open(settings_path, "r", encoding="utf-8") as f:
config = json.load(f)
if "security" not in config:
warnings.append("settings.json 缺少 security 字段")
warnings.append(tr("cli_export_msg.settings_json_missing_security"))
except json.JSONDecodeError as e:
errors.append(f"settings.json 格式错误: {e}")
errors.append(tr("cli_export_msg.settings_json_format_error_gemini", error=e))
except Exception as e:
errors.append(f"读取 settings.json 失败: {e}")
errors.append(tr("cli_export_msg.read_settings_json_failed_gemini", error=e))

if errors:
return ValidationResult.failure(errors, warnings)
Expand Down
Loading
Loading