diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..adee0ed --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ef979a3..272eaea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,11 +13,11 @@ COPY requirements.txt . # 安装依赖 RUN pip install --no-cache-dir -r requirements.txt -RUN mkdir -p log data conf +RUN mkdir -p log data COPY biz ./biz +COPY locales ./locales COPY api.py ./api.py COPY ui.py ./ui.py -COPY conf/prompt_templates.yml ./conf/prompt_templates.yml # 使用 supervisord 作为启动命令 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/README.md b/README.md index 1f1d09a..5fc7d8f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ GITLAB_ACCESS_TOKEN={YOUR_GITLAB_ACCESS_TOKEN} **2. 启动服务** ```bash +git clone https://github.com/sunmh207/ai-codereview-gitlab.git +cd ai-codereview-gitlab docker-compose up -d ``` @@ -93,8 +95,8 @@ docker-compose up -d **1. 获取源码** ```bash -git clone https://github.com/sunmh207/AI-Codereview-Gitlab.git -cd AI-Codereview-Gitlab +git clone https://github.com/sunmh207/ai-codereview-gitlab.git +cd ai-codereview-gitlab ``` **2. 安装依赖** diff --git a/api.py b/api.py index ef40870..d09bb48 100644 --- a/api.py +++ b/api.py @@ -13,16 +13,18 @@ from biz.gitlab.webhook_handler import slugify_url from biz.queue.worker import handle_merge_request_event, handle_push_event, handle_github_pull_request_event, handle_github_push_event from biz.service.review_service import ReviewService +from biz.utils.config_checker import check_config from biz.utils.im import notifier from biz.utils.log import logger from biz.utils.queue import handle_queue from biz.utils.reporter import Reporter -from biz.utils.config_checker import check_config load_dotenv("conf/.env") api_app = Flask(__name__) +from biz.utils.i18n import get_translator +_ = get_translator() PUSH_REVIEW_ENABLED = os.environ.get('PUSH_REVIEW_ENABLED', '0') == '1' @@ -30,9 +32,8 @@ @api_app.route('/') def home(): return """

The code review api server is running.

-

GitHub project address: - https://github.com/sunmh207/AI-Codereview-Gitlab

-

Gitee project address: https://gitee.com/sunminghui/ai-codereview-gitlab

+

GitHub project address: + https://github.com/sunmh207/ai-codereview-gitlab

""" @@ -42,6 +43,10 @@ def daily_report(): start_time = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() end_time = datetime.now().replace(hour=23, minute=59, second=59, microsecond=0).timestamp() + # import translator once more due to separate cronjob process + from biz.utils.i18n import get_translator + _ = get_translator() + try: if PUSH_REVIEW_ENABLED: df = ReviewService().get_push_review_logs(updated_at_gte=start_time, updated_at_lte=end_time) @@ -49,8 +54,8 @@ def daily_report(): df = ReviewService().get_mr_review_logs(updated_at_gte=start_time, updated_at_lte=end_time) if df.empty: - logger.info("No data to process.") - return jsonify({'message': 'No data to process.'}), 200 + logger.info(_("No data to process.")) + return jsonify({'message': _('No data to process.')}), 200 # 去重:基于 (author, message) 组合 df_unique = df.drop_duplicates(subset=["author", "commit_messages"]) # 按照 author 排序 @@ -60,12 +65,12 @@ def daily_report(): # 生成日报内容 report_txt = Reporter().generate_report(json.dumps(commits)) # 发送钉钉通知 - notifier.send_notification(content=report_txt, msg_type="markdown", title="代码提交日报") + notifier.send_notification(content=report_txt, msg_type="markdown", title=_("代码提交日报")) # 返回生成的日报内容 return json.dumps(report_txt, ensure_ascii=False, indent=4) except Exception as e: - logger.error(f"Failed to generate daily report: {e}") + logger.error(_("Failed to generate daily report: {}").format(e)) return jsonify({'message': f"Failed to generate daily report: {e}"}), 500 @@ -93,12 +98,12 @@ def setup_scheduler(): # Start the scheduler scheduler.start() - logger.info("Scheduler started successfully.") + logger.info(_("Scheduler started successfully.")) # Shut down the scheduler when exiting the app atexit.register(lambda: scheduler.shutdown()) except Exception as e: - logger.error(f"Error setting up scheduler: {e}") + logger.error(_("Error setting up scheduler: {}").format(e)) logger.error(traceback.format_exc()) @@ -109,46 +114,52 @@ def handle_webhook(): if request.is_json: data = request.get_json() if not data: - return jsonify({"error": "Invalid JSON"}), 400 + return jsonify({"error": _("Invalid JSON")}), 400 # 判断是GitLab还是GitHub的webhook webhook_source = request.headers.get('X-GitHub-Event') - + if webhook_source: # GitHub webhook return handle_github_webhook(webhook_source, data) else: # GitLab webhook return handle_gitlab_webhook(data) else: - return jsonify({'message': 'Invalid data format'}), 400 + return jsonify({'message': _('Invalid data format')}), 400 + def handle_github_webhook(event_type, data): # 获取GitHub配置 github_token = os.getenv('GITHUB_ACCESS_TOKEN') or request.headers.get('X-GitHub-Token') if not github_token: - return jsonify({'message': 'Missing GitHub access token'}), 400 - + return jsonify({'message': _('Missing GitLab URL')}), 400 + github_url = os.getenv('GITHUB_URL') or 'https://github.com' github_url_slug = slugify_url(github_url) - + # 打印整个payload数据 - logger.info(f'Received GitHub event: {event_type}') - logger.info(f'Payload: {json.dumps(data)}') - + logger.info(_('Received event: {}').format(event_type)) + logger.info(_('Payload: {}').format(json.dumps(data))) + if event_type == "pull_request": # 使用handle_queue进行异步处理 handle_queue(handle_github_pull_request_event, data, github_token, github_url, github_url_slug) # 立马返回响应 - return jsonify({'message': f'GitHub request received(event_type={event_type}), will process asynchronously.'}), 200 + return jsonify({'message': _('GitHub request received(event_type={}), will process asynchronously.').format( + event_type)}), 200 elif event_type == "push": # 使用handle_queue进行异步处理 handle_queue(handle_github_push_event, data, github_token, github_url, github_url_slug) # 立马返回响应 - return jsonify({'message': f'GitHub request received(event_type={event_type}), will process asynchronously.'}), 200 + return jsonify({'message': _('GitHub request received(event_type={}), will process asynchronously.').format( + event_type)}), 200 else: - error_message = f'Only pull_request and push events are supported for GitHub webhook, but received: {event_type}.' + error_message = _( + 'Only pull_request and push events are supported for GitHub webhook, but received:: {}.').format( + event_type) logger.error(error_message) return jsonify(error_message), 400 + def handle_gitlab_webhook(data): object_kind = data.get("object_kind") @@ -157,47 +168,50 @@ def handle_gitlab_webhook(data): if not gitlab_url: repository = data.get('repository') if not repository: - return jsonify({'message': 'Missing GitLab URL'}), 400 + return jsonify({'message': _('Missing GitLab URL')}), 400 homepage = repository.get("homepage") if not homepage: - return jsonify({'message': 'Missing GitLab URL'}), 400 + return jsonify({'message': _('Missing GitLab URL')}), 400 try: parsed_url = urlparse(homepage) gitlab_url = f"{parsed_url.scheme}://{parsed_url.netloc}/" except Exception as e: - return jsonify({"error": f"Failed to parse homepage URL: {str(e)}"}), 400 + return jsonify({"error": _("Failed to parse homepage URL: {}").format(str(e))}), 400 # 优先从环境变量获取,如果没有,则从请求头获取 gitlab_token = os.getenv('GITLAB_ACCESS_TOKEN') or request.headers.get('X-Gitlab-Token') # 如果gitlab_token为空,返回错误 if not gitlab_token: - return jsonify({'message': 'Missing GitLab access token'}), 400 + return jsonify({'message': _('Missing GitLab access token')}), 400 gitlab_url_slug = slugify_url(gitlab_url) # 打印整个payload数据,或根据需求进行处理 - logger.info(f'Received event: {object_kind}') - logger.info(f'Payload: {json.dumps(data)}') + logger.info(_('Received event: {}').format(object_kind)) + logger.info(_('Payload: {}').format(json.dumps(data))) # 处理Merge Request Hook if object_kind == "merge_request": # 创建一个新进程进行异步处理 handle_queue(handle_merge_request_event, data, gitlab_token, gitlab_url, gitlab_url_slug) # 立马返回响应 - return jsonify( - {'message': f'Request received(object_kind={object_kind}), will process asynchronously.'}), 200 + return jsonify({'message': _('Request received(object_kind={}), will process asynchronously.').format( + object_kind)}), 200 elif object_kind == "push": # 创建一个新进程进行异步处理 # TODO check if PUSH_REVIEW_ENABLED is needed here handle_queue(handle_push_event, data, gitlab_token, gitlab_url, gitlab_url_slug) # 立马返回响应 - return jsonify( - {'message': f'Request received(object_kind={object_kind}), will process asynchronously.'}), 200 + return jsonify({'message': _('Request received(object_kind={}), will process asynchronously.').format( + object_kind)}), 200 else: - error_message = f'Only merge_request and push events are supported (both Webhook and System Hook), but received: {object_kind}.' + error_message = _( + 'Only merge_request and push events are supported (both Webhook and System Hook), but received: {}.').format( + object_kind) logger.error(error_message) return jsonify(error_message), 400 + if __name__ == '__main__': check_config() # 启动定时任务调度器 diff --git a/biz/cmd/review.py b/biz/cmd/review.py index 0abf8aa..5104ffd 100644 --- a/biz/cmd/review.py +++ b/biz/cmd/review.py @@ -3,28 +3,31 @@ from biz.cmd.func.branch import BranchReviewFunc from biz.cmd.func.complexity import ComplexityReviewFunc from biz.cmd.func.directory import DirectoryReviewFunc +from biz.utils.i18n import get_translator +_ = get_translator() def welcome_message(): - print("\n欢迎使用 Codebase Review 工具!\n") + print(_("\n欢迎使用 Codebase Review 工具!\n")) def get_func_choice(): + _ = get_translator() options = { - "1": ("Review 目录结构规范", DirectoryReviewFunc), - "2": ("Review 代码分支命名规范", BranchReviewFunc), - "3": ("Review 代码复杂度", ComplexityReviewFunc), + "1": (_("Review 目录结构规范"), DirectoryReviewFunc), + "2": (_("Review 代码分支命名规范"), BranchReviewFunc), + "3": (_("Review 代码复杂度"), ComplexityReviewFunc), } - print("📌 请选择功能:") + print(_("📌 请选择功能:")) for key, (desc, _) in options.items(): print(f"{key}. {desc}") while True: - choice = input("请输入数字 (1-3): ").strip() + choice = input(_("请输入数字 (1-3): ")).strip() if choice in options: return options[choice][1] # 返回对应的类 - print("❌ 无效的选择,请输入 1-3") + print(_("❌ 无效的选择,请输入 1-3")) if __name__ == "__main__": diff --git a/biz/event/event_manager.py b/biz/event/event_manager.py index 26e316c..fef1934 100644 --- a/biz/event/event_manager.py +++ b/biz/event/event_manager.py @@ -4,6 +4,9 @@ from biz.service.review_service import ReviewService from biz.utils.im import notifier +from biz.utils.i18n import get_translator +_ = get_translator() + # 定义全局事件管理器(事件信号) event_manager = { "merge_request_reviewed": Signal(), @@ -14,24 +17,33 @@ # 定义事件处理函数 def on_merge_request_reviewed(mr_review_entity: MergeRequestReviewEntity): # 发送IM消息通知 - im_msg = f""" -### 🔀 {mr_review_entity.project_name}: Merge Request + im_msg = _(""" +### 🔀 {project_name}: Merge Request #### 合并请求信息: -- **提交者:** {mr_review_entity.author} +- **提交者:** {author} -- **源分支**: {mr_review_entity.source_branch} -- **目标分支**: {mr_review_entity.target_branch} -- **更新时间**: {mr_review_entity.updated_at} -- **提交信息:** {mr_review_entity.commit_messages} +- **源分支**: {source_branch} +- **目标分支**: {target_branch} +- **更新时间**: {updated_at} +- **提交信息:** {commit_messages} -- [查看合并详情]({mr_review_entity.url}) +- [查看合并详情]({url}) - **AI Review 结果:** -{mr_review_entity.review_result} - """ - notifier.send_notification(content=im_msg, msg_type='markdown', title='Merge Request Review', +{review_result} + """).format( + project_name=mr_review_entity.project_name, + author=mr_review_entity.author, + source_branch=mr_review_entity.source_branch, + target_branch=mr_review_entity.target_branch, + updated_at=mr_review_entity.updated_at, + commit_messages=mr_review_entity.commit_messages, + url=mr_review_entity.url, + review_result=mr_review_entity.review_result + ) + notifier.send_notification(content=im_msg, msg_type='markdown', title=_('Merge Request Review'), project_name=mr_review_entity.project_name, url_slug=mr_review_entity.url_slug) @@ -41,26 +53,32 @@ def on_merge_request_reviewed(mr_review_entity: MergeRequestReviewEntity): def on_push_reviewed(entity: PushReviewEntity): # 发送IM消息通知 - im_msg = f"### 🚀 {entity.project_name}: Push\n\n" - im_msg += "#### 提交记录:\n" + im_msg = _("### 🚀 {project_name}: Push\n\n").format(project_name=entity.project_name) + im_msg += _("#### 提交记录:\n") for commit in entity.commits: message = commit.get('message', '').strip() - author = commit.get('author', 'Unknown Author') + author = commit.get('author', _('Unknown Author')) timestamp = commit.get('timestamp', '') url = commit.get('url', '#') im_msg += ( - f"- **提交信息**: {message}\n" - f"- **提交者**: {author}\n" - f"- **时间**: {timestamp}\n" - f"- [查看提交详情]({url})\n\n" + _("- **提交信息**: {message}\n" + "- **提交者**: {author}\n" + "- **时间**: {timestamp}\n" + "- [查看提交详情]({url})\n\n").format( + message=message, + author=author, + timestamp=timestamp, + url=url + ) ) if entity.review_result: - im_msg += f"#### AI Review 结果: \n {entity.review_result}\n\n" + im_msg += _("#### AI Review 结果: \n {review_result}\n\n").format(review_result=entity.review_result) notifier.send_notification(content=im_msg, msg_type='markdown', - title=f"{entity.project_name} Push Event", project_name=entity.project_name, - url_slug=entity.url_slug) + title=_("{project_name} Push Event").format(project_name=entity.project_name), + project_name=entity.project_name, + url_slug=entity.url_slug) # 记录到数据库 ReviewService().insert_push_review_log(entity) @@ -68,4 +86,4 @@ def on_push_reviewed(entity: PushReviewEntity): # 连接事件处理函数到事件信号 event_manager["merge_request_reviewed"].connect(on_merge_request_reviewed) -event_manager["push_reviewed"].connect(on_push_reviewed) +event_manager["push_reviewed"].connect(on_push_reviewed) \ No newline at end of file diff --git a/biz/github/webhook_handler.py b/biz/github/webhook_handler.py index f2948e7..eea3d1c 100644 --- a/biz/github/webhook_handler.py +++ b/biz/github/webhook_handler.py @@ -1,12 +1,14 @@ -import json import os import re import time import requests +from biz.utils.i18n import get_translator from biz.utils.log import logger +_ = get_translator() + # 从环境变量中获取支持的文件扩展名 SUPPORTED_EXTENSIONS = os.getenv('SUPPORTED_EXTENSIONS', '.java,.py,.php').split(',') @@ -21,9 +23,9 @@ def filter_changes(changes: list): for change in changes: # 优先检查status字段是否为"removed" if change.get('status') == 'removed': - logger.info(f"Detected file deletion via status field: {change.get('new_path')}") + logger.info(_("Detected file deletion via status field: {}").format(change.get('new_path'))) continue - + # 如果没有status字段或status不为"removed",继续检查diff模式 diff = change.get('diff', '') if diff: @@ -34,12 +36,12 @@ def filter_changes(changes: list): if all(line.startswith('-') or not line for line in diff_lines): logger.info(f"Detected file deletion via diff pattern: {change.get('new_path')}") continue - + not_deleted_changes.append(change) - - logger.info(f"SUPPORTED_EXTENSIONS: {SUPPORTED_EXTENSIONS}") - logger.info(f"After filtering deleted files: {not_deleted_changes}") - + + logger.info(_("SUPPORTED_EXTENSIONS: {}").format(SUPPORTED_EXTENSIONS)) + logger.info(_("After filtering deleted files: {}").format(not_deleted_changes)) + # 过滤 `new_path` 以支持的扩展名结尾的元素, 仅保留diff和new_path字段 filtered_changes = [ { @@ -49,7 +51,7 @@ def filter_changes(changes: list): for item in not_deleted_changes if any(item.get('new_path', '').endswith(ext) for ext in SUPPORTED_EXTENSIONS) ] - logger.info(f"After filtering by extension: {filtered_changes}") + logger.info(_("After filtering by extension: {}").format(filtered_changes)) return filtered_changes @@ -78,7 +80,8 @@ def parse_pull_request_event(self): def get_pull_request_changes(self) -> list: # 检查是否为 Pull Request Hook 事件 if self.event_type != 'pull_request': - logger.warn(f"Invalid event type: {self.event_type}. Only 'pull_request' event is supported now.") + logger.warn( + _("Invalid event type: {}. Only 'pull_request' event is supported now.").format(self.event_type)) return [] # GitHub pull request changes API可能存在延迟,多次尝试 @@ -93,7 +96,9 @@ def get_pull_request_changes(self) -> list: } response = requests.get(url, headers=headers) logger.debug( - f"Get changes response from GitHub (attempt {attempt + 1}): {response.status_code}, {response.text}, URL: {url}") + _("Get changes response from GitHub (attempt {attempt}): {response_status_code}, {response_text}, URL: {url}").format( + attempt={attempt + 1}, response_status_code=response.status_code, response_text=response.text, + url=url)) # 检查请求是否成功 if response.status_code == 200: @@ -111,13 +116,14 @@ def get_pull_request_changes(self) -> list: return changes else: logger.info( - f"Changes is empty, retrying in {retry_delay} seconds... (attempt {attempt + 1}/{max_retries}), URL: {url}") + _("Changes is empty, retrying in {retry_delay} seconds... (attempt {attempt}/{max_retries}), URL: {url}").format(retry_delay=retry_delay, + attempt={attempt + 1}, max_retries=max_retries, url=url)) time.sleep(retry_delay) else: - logger.warn(f"Failed to get changes from GitHub (URL: {url}): {response.status_code}, {response.text}") + logger.warn(_("Failed to get changes from GitHub (URL: {url}): {response.status_code}, {response.text}").format(url, response.status_code, response.text)) return [] - logger.warning(f"Max retries ({max_retries}) reached. Changes is still empty.") + logger.warning(_("Max retries ({}) reached. Changes is still empty.").format(max_retries)) return [] # 达到最大重试次数后返回空列表 def get_pull_request_commits(self) -> list: @@ -132,8 +138,8 @@ def get_pull_request_commits(self) -> list: 'Accept': 'application/vnd.github.v3+json' } response = requests.get(url, headers=headers) - logger.debug(f"Get commits response from GitHub: {response.status_code}, {response.text}") - + logger.debug(_("Get commits response from GitHub: {}, {}").format(response.status_code, response.text)) + # 检查请求是否成功 if response.status_code == 200: # 将GitHub的commits转换为GitLab格式的commits @@ -152,7 +158,7 @@ def get_pull_request_commits(self) -> list: gitlab_format_commits.append(gitlab_commit) return gitlab_format_commits else: - logger.warn(f"Failed to get commits: {response.status_code}, {response.text}") + logger.warn(_("Failed to get commits: {}, {}").format(response.status_code, response.text)) return [] def add_pull_request_notes(self, review_result): @@ -165,11 +171,11 @@ def add_pull_request_notes(self, review_result): 'body': review_result } response = requests.post(url, headers=headers, json=data) - logger.debug(f"Add comment to GitHub PR {url}: {response.status_code}, {response.text}") + logger.debug(_("Add comment to GitHub PR {url}: {response_status_code}, {response_text}").format(url=url, response_status_code=response.status_code, response_text=response.text)) if response.status_code == 201: - logger.info("Comment successfully added to pull request.") + logger.info(_("Comment successfully added to pull request.")) else: - logger.error(f"Failed to add comment: {response.status_code}") + logger.error(_("Failed to add comment: {}").format(response.status_code)) logger.error(response.text) @@ -198,7 +204,7 @@ def parse_push_event(self): def get_push_commits(self) -> list: # 检查是否为 Push 事件 if self.event_type != 'push': - logger.warn(f"Invalid event type: {self.event_type}. Only 'push' event is supported now.") + logger.warn(_("Invalid event type: {}. Only 'push' event is supported now.").format(self.event_type)) return [] # 提取提交信息 @@ -212,19 +218,19 @@ def get_push_commits(self) -> list: } commit_details.append(commit_info) - logger.info(f"Collected {len(commit_details)} commits from push event.") + logger.info(_("Collected {} commits from push event.").format(len(commit_details))) return commit_details def add_push_notes(self, message: str): # 添加评论到 GitHub Push 请求的提交中(此处假设是在最后一次提交上添加注释) if not self.commit_list: - logger.warn("No commits found to add notes to.") + logger.warn(_("No commits found to add notes to.")) return # 获取最后一个提交的ID last_commit_id = self.commit_list[-1].get('id') if not last_commit_id: - logger.error("Last commit ID not found.") + logger.error(_("Last commit ID not found.")) return url = f"https://api.github.com/repos/{self.repo_full_name}/commits/{last_commit_id}/comments" @@ -236,11 +242,11 @@ def add_push_notes(self, message: str): 'body': message } response = requests.post(url, headers=headers, json=data) - logger.debug(f"Add comment to commit {last_commit_id}: {response.status_code}, {response.text}") + logger.debug(_("Add comment to commit {last_commit_id}: {response_status_code}, {response_text}").format(last_commit_id, response.status_code, response.text)) if response.status_code == 201: - logger.info("Comment successfully added to push commit.") + logger.info(_("Comment successfully added to push commit.")) else: - logger.error(f"Failed to add comment: {response.status_code}") + logger.error(_("Failed to add comment: {}").format(response.status_code)) logger.error(response.text) def __repository_commits(self, sha: str = "", per_page: int = 100, page: int = 1): @@ -252,13 +258,14 @@ def __repository_commits(self, sha: str = "", per_page: int = 100, page: int = 1 } response = requests.get(url, headers=headers) logger.debug( - f"Get commits response from GitHub for repository_commits: {response.status_code}, {response.text}, URL: {url}") + _("Get commits response from GitHub for repository_commits: {response_status_code}, {response_text}, URL: {url}").format( + response_status_code=response.status_code, response_text=response.text, url=url)) if response.status_code == 200: return response.json() else: logger.warn( - f"Failed to get commits for sha {sha}: {response.status_code}, {response.text}") + _("Failed to get commits for sha {sha}: {response_status_code}, {response_text}").format(sha=sha, response_status_code=response.status_code, response_text=response.text)) return [] def get_parent_commit_id(self, commit_id: str) -> str: @@ -269,7 +276,8 @@ def get_parent_commit_id(self, commit_id: str) -> str: } response = requests.get(url, headers=headers) logger.debug( - f"Get commit response from GitHub: {response.status_code}, {response.text}, URL: {url}") + _("Get commit response from GitHub: {response_status_code}, {response_text}, URL: {url}").format( + response_status_code=response.status_code, response_text=response.text, url=url)) if response.status_code == 200 and response.json().get('parents'): return response.json().get('parents')[0].get('sha', '') @@ -284,7 +292,8 @@ def repository_compare(self, base: str, head: str): } response = requests.get(url, headers=headers) logger.debug( - f"Get changes response from GitHub for repository_compare: {response.status_code}, {response.text}, URL: {url}") + _("Get changes response from GitHub for repository_compare: {response_status_code}, {response_text}, URL: {url}").format( + response_status_code=response.status_code, response_text=response.text, url=url)) if response.status_code == 200: # 转换为GitLab格式的diffs @@ -301,18 +310,18 @@ def repository_compare(self, base: str, head: str): return diffs else: logger.warn( - f"Failed to get changes for repository_compare: {response.status_code}, {response.text}") + _("Failed to get changes for repository_compare: {response.status_code}, {response.text}").format(response_status_code=response.status_code, response_text=response.text)) return [] def get_push_changes(self) -> list: # 检查是否为 Push 事件 if self.event_type != 'push': - logger.warn(f"Invalid event type: {self.event_type}. Only 'push' event is supported now.") + logger.warn(_("Invalid event type: {}. Only 'push' event is supported now.").format(self.event_type)) return [] # 如果没有提交,返回空列表 if not self.commit_list: - logger.info("No commits found in push event.") + logger.info(_("No commits found in push event.")) return [] # 优先尝试compare API获取变更 @@ -330,12 +339,12 @@ def get_push_changes(self) -> list: elif self.webhook_data.get('deleted', False): # 删除分支处理 return [] - + return self.repository_compare(before, after) else: # 如果before和after不存在,尝试通过commits获取 - logger.info("before or after not found in webhook data, trying to get changes from commits.") - + logger.info(_("before or after not found in webhook data, trying to get changes from commits.")) + changes = [] for commit in self.commit_list: commit_id = commit.get('id') @@ -344,5 +353,5 @@ def get_push_changes(self) -> list: if parent_id: commit_changes = self.repository_compare(parent_id, commit_id) changes.extend(commit_changes) - - return changes \ No newline at end of file + + return changes diff --git a/biz/gitlab/webhook_handler.py b/biz/gitlab/webhook_handler.py index ade6e98..b79c639 100644 --- a/biz/gitlab/webhook_handler.py +++ b/biz/gitlab/webhook_handler.py @@ -6,6 +6,9 @@ import requests from biz.utils.log import logger +from biz.utils.i18n import get_translator +_ = get_translator() + # 从环境变量中获取支持的文件扩展名 SUPPORTED_EXTENSIONS = os.getenv('SUPPORTED_EXTENSIONS', '.java,.py,.php').split(',') @@ -76,7 +79,7 @@ def parse_merge_request_event(self): def get_merge_request_changes(self) -> list: # 检查是否为 Merge Request Hook 事件 if self.event_type != 'merge_request': - logger.warn(f"Invalid event type: {self.event_type}. Only 'merge_request' event is supported now.") + logger.warn(_("Invalid event type: {}. Only 'merge_request' event is supported now.").format(self.event_type)) return [] # Gitlab merge request changes API可能存在延迟,多次尝试 @@ -91,8 +94,7 @@ def get_merge_request_changes(self) -> list: } response = requests.get(url, headers=headers, verify=False) logger.debug( - f"Get changes response from GitLab (attempt {attempt + 1}): {response.status_code}, {response.text}, URL: {url}") - + _("Get changes response from GitLab (attempt {}): {}, {}, URL: {}").format(attempt + 1, response.status_code, response.text, url)) # 检查请求是否成功 if response.status_code == 200: changes = response.json().get('changes', []) @@ -100,13 +102,13 @@ def get_merge_request_changes(self) -> list: return changes else: logger.info( - f"Changes is empty, retrying in {retry_delay} seconds... (attempt {attempt + 1}/{max_retries}), URL: {url}") + _("Changes is empty, retrying in {} seconds... (attempt {}/{}), URL: {}").format(retry_delay, attempt + 1, max_retries, url)) time.sleep(retry_delay) else: - logger.warn(f"Failed to get changes from GitLab (URL: {url}): {response.status_code}, {response.text}") + logger.warn(_("Failed to get changes from GitLab (URL: {}): {}, {}").format(url, response.status_code, response.text)) return [] - logger.warning(f"Max retries ({max_retries}) reached. Changes is still empty.") + logger.warning(_("Max retries ({}) reached. Changes is still empty.").format(max_retries)) return [] # 达到最大重试次数后返回空列表 def get_merge_request_commits(self) -> list: @@ -121,12 +123,11 @@ def get_merge_request_commits(self) -> list: 'Private-Token': self.gitlab_token } response = requests.get(url, headers=headers, verify=False) - logger.debug(f"Get commits response from gitlab: {response.status_code}, {response.text}") - # 检查请求是否成功 + logger.debug(_("Get commits response from gitlab: {}, {}").format(response.status_code, response.text)) # 检查请求是否成功 if response.status_code == 200: return response.json() else: - logger.warn(f"Failed to get commits: {response.status_code}, {response.text}") + logger.warn(_("Failed to get commits: {}, {}").format(response.status_code, response.text)) return [] def add_merge_request_notes(self, review_result): @@ -140,11 +141,11 @@ def add_merge_request_notes(self, review_result): 'body': review_result } response = requests.post(url, headers=headers, json=data, verify=False) - logger.debug(f"Add notes to gitlab {url}: {response.status_code}, {response.text}") + logger.debug(_("Add notes to gitlab {}: {}, {}").format(url, response.status_code, response.text)) if response.status_code == 201: logger.info("Note successfully added to merge request.") else: - logger.error(f"Failed to add note: {response.status_code}") + logger.error(_("Failed to add note: {}").format(response.status_code)) logger.error(response.text) @@ -174,7 +175,7 @@ def parse_push_event(self): def get_push_commits(self) -> list: # 检查是否为 Push 事件 if self.event_type != 'push': - logger.warn(f"Invalid event type: {self.event_type}. Only 'push' event is supported now.") + logger.warn(_("Invalid event type: {}. Only 'push' event is supported now.").format(self.event_type)) return [] # 提取提交信息 @@ -188,19 +189,19 @@ def get_push_commits(self) -> list: } commit_details.append(commit_info) - logger.info(f"Collected {len(commit_details)} commits from push event.") + logger.info(_("Collected {} commits from push event.").format(len(commit_details))) return commit_details def add_push_notes(self, message: str): # 添加评论到 GitLab Push 请求的提交中(此处假设是在最后一次提交上添加注释) if not self.commit_list: - logger.warn("No commits found to add notes to.") + logger.warn(_("No commits found to add notes to.")) return # 获取最后一个提交的ID last_commit_id = self.commit_list[-1].get('id') if not last_commit_id: - logger.error("Last commit ID not found.") + logger.error(_("Last commit ID not found.")) return url = urljoin(f"{self.gitlab_url}/", @@ -220,8 +221,7 @@ def add_push_notes(self, message: str): logger.error(f"Failed to add comment: {response.status_code}") logger.error(response.text) - def __repository_commits(self, ref_name: str = "", since: str = "", until: str = "", pre_page: int = 100, - page: int = 1): + def __repository_commits(self, ref_name: str = "", since: str = "", until: str = "", pre_page: int = 100, page: int = 1): # 获取仓库提交信息 url = f"{urljoin(f'{self.gitlab_url}/', f'api/v4/projects/{self.project_id}/repository/commits')}?ref_name={ref_name}&since={since}&until={until}&per_page={pre_page}&page={page}" headers = { @@ -229,7 +229,7 @@ def __repository_commits(self, ref_name: str = "", since: str = "", until: str = } response = requests.get(url, headers=headers, verify=False) logger.debug( - f"Get commits response from GitLab for repository_commits: {response.status_code}, {response.text}, URL: {url}") + _("Get commits response from GitLab for repository_commits: {response.status_code}, {}, URL: {url}").format(response.status_code, response.text, url)) if response.status_code == 200: return response.json() @@ -252,24 +252,24 @@ def repository_compare(self, before: str, after: str): } response = requests.get(url, headers=headers, verify=False) logger.debug( - f"Get changes response from GitLab for repository_compare: {response.status_code}, {response.text}, URL: {url}") + _("Get changes response from GitLab for repository_compare: {}, {response.text}, URL: {}").format(response.status_code, url)) if response.status_code == 200: return response.json().get('diffs', []) else: logger.warn( - f"Failed to get changes for repository_compare: {response.status_code}, {response.text}") + _("Failed to get changes for repository_compare: {}, {}").format(response.status_code, response.text)) return [] def get_push_changes(self) -> list: # 检查是否为 Push 事件 if self.event_type != 'push': - logger.warn(f"Invalid event type: {self.event_type}. Only 'push' event is supported now.") + logger.warn(_("Invalid event type: {}. Only 'push' event is supported now.").format(self.event_type)) return [] # 如果没有提交,返回空列表 if not self.commit_list: - logger.info("No commits found in push event.") + logger.info(_("No commits found in push event.")) return [] headers = { 'Private-Token': self.gitlab_token @@ -290,4 +290,4 @@ def get_push_changes(self) -> list: before = parent_commit_id return self.repository_compare(before, after) else: - return [] + return [] \ No newline at end of file diff --git a/biz/llm/client/deepseek.py b/biz/llm/client/deepseek.py index 9cd63b5..c711db1 100644 --- a/biz/llm/client/deepseek.py +++ b/biz/llm/client/deepseek.py @@ -5,17 +5,20 @@ from biz.llm.client.base import BaseClient from biz.llm.types import NotGiven, NOT_GIVEN +from biz.utils.i18n import get_translator from biz.utils.log import logger +_ = get_translator() + class DeepSeekClient(BaseClient): def __init__(self, api_key: str = None): self.api_key = api_key or os.getenv("DEEPSEEK_API_KEY") self.base_url = os.getenv("DEEPSEEK_API_BASE_URL", "https://api.deepseek.com") if not self.api_key: - raise ValueError("API key is required. Please provide it or set it in the environment variables.") + raise ValueError(_("API key is required. Please provide it or set it in the environment variables.")) - self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) # DeepSeek supports OpenAI API SDK + self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) # DeepSeek supports OpenAI API SDK self.default_model = os.getenv("DEEPSEEK_API_MODEL", "deepseek-chat") def completions(self, @@ -24,25 +27,26 @@ def completions(self, ) -> str: try: model = model or self.default_model - logger.debug(f"Sending request to DeepSeek API. Model: {model}, Messages: {messages}") - + logger.debug(_("Sending request to DeepSeek API. Model: {model}, Messages: {messages}").format(model=model, + messages=messages)) + completion = self.client.chat.completions.create( model=model, messages=messages ) - + if not completion or not completion.choices: - logger.error("Empty response from DeepSeek API") - return "AI服务返回为空,请稍后重试" - + logger.error(_("Empty response from DeepSeek API")) + return _("AI服务返回为空,请稍后重试") + return completion.choices[0].message.content - + except Exception as e: - logger.error(f"DeepSeek API error: {str(e)}") + logger.error(_("DeepSeek API error: {e}").format(e=str(e))) # 检查是否是认证错误 if "401" in str(e): - return "DeepSeek API认证失败,请检查API密钥是否正确" + return _("DeepSeek API认证失败,请检查API密钥是否正确") elif "404" in str(e): - return "DeepSeek API接口未找到,请检查API地址是否正确" + return _("DeepSeek API接口未找到,请检查API地址是否正确") else: - return f"调用DeepSeek API时出错: {str(e)}" + return _("调用DeepSeek API时出错: {e}").format(e=str(e)) diff --git a/biz/llm/client/openai.py b/biz/llm/client/openai.py index 69d35f1..3a8fc60 100644 --- a/biz/llm/client/openai.py +++ b/biz/llm/client/openai.py @@ -5,6 +5,9 @@ from biz.llm.client.base import BaseClient from biz.llm.types import NotGiven, NOT_GIVEN +from biz.utils.i18n import get_translator + +_ = get_translator() class OpenAIClient(BaseClient): @@ -12,7 +15,7 @@ def __init__(self, api_key: str = None): self.api_key = api_key or os.getenv("OPENAI_API_KEY") self.base_url = os.getenv("OPENAI_API_BASE_URL", "https://api.openai.com") if not self.api_key: - raise ValueError("API key is required. Please provide it or set it in the environment variables.") + raise ValueError(_("API key is required. Please provide it or set it in the environment variables.")) self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) self.default_model = os.getenv("OPENAI_API_MODEL", "gpt-4o-mini") diff --git a/biz/llm/client/zhipuai.py b/biz/llm/client/zhipuai.py index 0790cd9..d5075c2 100644 --- a/biz/llm/client/zhipuai.py +++ b/biz/llm/client/zhipuai.py @@ -5,13 +5,16 @@ from biz.llm.client.base import BaseClient from biz.llm.types import NotGiven, NOT_GIVEN +from biz.utils.i18n import get_translator + +_ = get_translator() class ZhipuAIClient(BaseClient): def __init__(self, api_key: str = None): self.api_key = api_key or os.getenv("ZHIPUAI_API_KEY") if not self.api_key: - raise ValueError("API key is required. Please provide it or set it in the environment variables.") + raise ValueError(_("API key is required. Please provide it or set it in the environment variables.")) self.client = ZhipuAI(api_key=api_key) self.default_model = os.getenv("ZHIPUAI_API_MODEL", "GLM-4-Flash") diff --git a/biz/llm/factory.py b/biz/llm/factory.py index c532759..1a4a732 100644 --- a/biz/llm/factory.py +++ b/biz/llm/factory.py @@ -5,8 +5,11 @@ from biz.llm.client.ollama_client import OllamaClient from biz.llm.client.openai import OpenAIClient from biz.llm.client.zhipuai import ZhipuAIClient +from biz.utils.i18n import get_translator from biz.utils.log import logger +_ = get_translator() + class Factory: @staticmethod @@ -16,11 +19,11 @@ def getClient(provider: str = None) -> BaseClient: 'zhipuai': lambda: ZhipuAIClient(), 'openai': lambda: OpenAIClient(), 'deepseek': lambda: DeepSeekClient(), - 'ollama': lambda : OllamaClient() + 'ollama': lambda: OllamaClient() } provider_func = chat_model_providers.get(provider) if provider_func: return provider_func() else: - raise Exception(f'Unknown chat model provider: {provider}') + raise Exception(_('Unknown chat model provider: {provider}'.format(provider=provider))) diff --git a/biz/queue/worker.py b/biz/queue/worker.py index 1fa4774..7fa6f60 100644 --- a/biz/queue/worker.py +++ b/biz/queue/worker.py @@ -2,15 +2,23 @@ import traceback from datetime import datetime +from dotenv import load_dotenv + from biz.entity.review_entity import MergeRequestReviewEntity, PushReviewEntity from biz.event.event_manager import event_manager +from biz.github.webhook_handler import filter_changes as filter_github_changes, \ + PullRequestHandler as GithubPullRequestHandler, PushHandler as GithubPushHandler from biz.gitlab.webhook_handler import filter_changes, MergeRequestHandler, PushHandler -from biz.github.webhook_handler import filter_changes as filter_github_changes, PullRequestHandler as GithubPullRequestHandler, PushHandler as GithubPushHandler from biz.utils.code_reviewer import CodeReviewer from biz.utils.im import notifier from biz.utils.log import logger PUSH_REVIEW_ENABLED = os.environ.get('PUSH_REVIEW_ENABLED', '0') == '1' +load_dotenv() + +from biz.utils.i18n import get_translator + +_ = get_translator() def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gitlab_url_slug: str): @@ -19,7 +27,7 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi logger.info('Push Hook event received') commits = handler.get_push_commits() if not commits: - logger.error('Failed to get commits') + logger.error(_('Failed to get commits')) return review_result = None @@ -30,15 +38,15 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi logger.info('changes: %s', changes) changes = filter_changes(changes) if not changes: - logger.info('未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。') - review_result = "关注的文件没有修改" + logger.info(_('未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。')) + review_result = _("关注的文件没有修改") if len(changes) > 0: commits_text = ';'.join(commit.get('message', '').strip() for commit in commits) review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) score = CodeReviewer.parse_review_score(review_text=review_result) # 将review结果提交到Gitlab的 notes - handler.add_push_notes(f'Auto Review Result: \n{review_result}') + handler.add_push_notes(_('Auto Review Result: \n{}').format(review_result)) # TODO check if not also queueing makes sense here event_manager['push_reviewed'].send(PushReviewEntity( @@ -53,9 +61,9 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi )) except Exception as e: - error_message = f'服务出现未知错误: {str(e)}\n{traceback.format_exc()}' + error_message = _('服务出现未知错误: {}').format(f'{str(e)}\n{traceback.format_exc()}') notifier.send_notification(content=error_message) - logger.error('出现未知错误: %s', error_message) + logger.error(_('出现未知错误: {}').format(error_message)) def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gitlab_url_slug: str): @@ -70,21 +78,21 @@ def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url try: # 解析Webhook数据 handler = MergeRequestHandler(webhook_data, gitlab_token, gitlab_url) - logger.info('Merge Request Hook event received') + logger.info(_('Merge Request Hook event received')) - if (handler.action in ['open', 'update']): # 仅仅在MR创建或更新时进行Code Review + if handler.action in ['open', 'update']: # 仅仅在MR创建或更新时进行Code Review # 获取Merge Request的changes changes = handler.get_merge_request_changes() - logger.info('changes: %s', changes) + logger.info(_('changes: {}').format(changes)) changes = filter_changes(changes) if not changes: - logger.info('未检测到有关代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。') + logger.info(_('未检测到有关代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。')) return # 获取Merge Request的commits commits = handler.get_merge_request_commits() if not commits: - logger.error('Failed to get commits') + logger.error(_('Failed to get commits')) return # review 代码 @@ -92,7 +100,7 @@ def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) # 将review结果提交到Gitlab的 notes - handler.add_merge_request_notes(f'Auto Review Result: \n{review_result}') + handler.add_merge_request_notes(_('Auto Review Result: \n{}').format(review_result)) # dispatch merge_request_reviewed event # TODO check if not also queueing makes sense here @@ -113,20 +121,21 @@ def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url ) else: - logger.info(f"Merge Request Hook event, action={handler.action}, ignored.") + logger.info(_("Merge Request Hook event, action={}, ignored.").format(handler.action)) except Exception as e: - error_message = f'AI Code Review 服务出现未知错误: {str(e)}\n{traceback.format_exc()}' + error_message = _('AI Code Review 服务出现未知错误: {}').format(f'{str(e)}\n{traceback.format_exc()}') notifier.send_notification(content=error_message) - logger.error('出现未知错误: %s', error_message) + logger.error(_('出现未知错误: {}').format(error_message)) + def handle_github_push_event(webhook_data: dict, github_token: str, github_url: str, github_url_slug: str): try: handler = GithubPushHandler(webhook_data, github_token, github_url) - logger.info('GitHub Push event received') + logger.info(_('GitHub Push event received')) commits = handler.get_push_commits() if not commits: - logger.error('Failed to get commits') + logger.error(_('Failed to get commits')) return review_result = None @@ -134,11 +143,11 @@ def handle_github_push_event(webhook_data: dict, github_token: str, github_url: if PUSH_REVIEW_ENABLED: # 获取PUSH的changes changes = handler.get_push_changes() - logger.info('changes: %s', changes) + logger.info(_('changes: {}').format(changes)) changes = filter_github_changes(changes) if not changes: - logger.info('未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。') - review_result = "关注的文件没有修改" + logger.info(_('未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。')) + review_result = _("关注的文件没有修改") if len(changes) > 0: commits_text = ';'.join(commit.get('message', '').strip() for commit in commits) @@ -159,9 +168,9 @@ def handle_github_push_event(webhook_data: dict, github_token: str, github_url: )) except Exception as e: - error_message = f'服务出现未知错误: {str(e)}\n{traceback.format_exc()}' + error_message = _('服务出现未知错误: {}').format(f'{str(e)}\n{traceback.format_exc()}') notifier.send_notification(content=error_message) - logger.error('出现未知错误: %s', error_message) + logger.error(_('出现未知错误: {}').format(error_message)) def handle_github_pull_request_event(webhook_data: dict, github_token: str, github_url: str, github_url_slug: str): @@ -176,21 +185,21 @@ def handle_github_pull_request_event(webhook_data: dict, github_token: str, gith try: # 解析Webhook数据 handler = GithubPullRequestHandler(webhook_data, github_token, github_url) - logger.info('GitHub Pull Request event received') + logger.info(_('GitHub Pull Request event received')) if (handler.action in ['opened', 'synchronize']): # 仅仅在PR创建或更新时进行Code Review # 获取Pull Request的changes changes = handler.get_pull_request_changes() - logger.info('changes: %s', changes) + logger.info(_('changes: {}').format(changes)) changes = filter_github_changes(changes) if not changes: - logger.info('未检测到有关代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。') + logger.info(_('未检测到有关代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。')) return # 获取Pull Request的commits commits = handler.get_pull_request_commits() if not commits: - logger.error('Failed to get commits') + logger.error(_('Failed to get commits')) return # review 代码 @@ -198,7 +207,7 @@ def handle_github_pull_request_event(webhook_data: dict, github_token: str, gith review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) # 将review结果提交到GitHub的 notes - handler.add_pull_request_notes(f'Auto Review Result: \n{review_result}') + handler.add_pull_request_notes(_('Auto Review Result: \n{}').format(review_result)) # dispatch pull_request_reviewed event event_manager['merge_request_reviewed'].send( @@ -216,6 +225,6 @@ def handle_github_pull_request_event(webhook_data: dict, github_token: str, gith )) except Exception as e: - error_message = f'服务出现未知错误: {str(e)}\n{traceback.format_exc()}' + error_message = _('服务出现未知错误: {}').format(f'{str(e)}\n{traceback.format_exc()}') notifier.send_notification(content=error_message) - logger.error('出现未知错误: %s', error_message) + logger.error(_('出现未知错误: {}').format(error_message)) diff --git a/biz/utils/code_reviewer.py b/biz/utils/code_reviewer.py index e31942f..a361bbe 100644 --- a/biz/utils/code_reviewer.py +++ b/biz/utils/code_reviewer.py @@ -7,9 +7,12 @@ from jinja2 import Template from biz.llm.factory import Factory +from biz.utils.i18n import get_translator from biz.utils.log import logger from biz.utils.token_util import count_tokens, truncate_text_by_tokens +_ = get_translator() + class BaseReviewer(abc.ABC): """代码审查基类""" @@ -20,7 +23,8 @@ def __init__(self, prompt_key: str): def _load_prompts(self, prompt_key: str, style="professional") -> Dict[str, Any]: """加载提示词配置""" - prompt_templates_file = "conf/prompt_templates.yml" + lang = os.environ.get('LANGUAGE', 'zh_CN') + prompt_templates_file = os.path.join("locales", lang, "prompt_templates.yml") try: # 在打开 YAML 文件时显式指定编码为 UTF-8,避免使用系统默认的 GBK 编码。 with open(prompt_templates_file, "r", encoding="utf-8") as file: @@ -38,14 +42,14 @@ def render_template(template_str: str) -> str: "user_message": {"role": "user", "content": user_prompt}, } except (FileNotFoundError, KeyError, yaml.YAMLError) as e: - logger.error(f"加载提示词配置失败: {e}") - raise Exception(f"提示词配置加载失败: {e}") + logger.error(_("加载提示词配置失败: {e}").format(e=e)) + raise Exception(_("提示词配置加载失败: {e}").format(e=e)) def call_llm(self, messages: List[Dict[str, Any]]) -> str: """调用 LLM 进行代码审核""" - logger.info(f"向 AI 发送代码 Review 请求, messages: {messages}") + logger.info(_("向 AI 发送代码 Review 请求, messages: {messages}").format(messages=messages)) review_result = self.client.completions(messages=messages) - logger.info(f"收到 AI 返回结果: {review_result}") + logger.info(_("收到 AI 返回结果: {review_result}").format(review_result=review_result)) return review_result @abc.abstractmethod @@ -60,7 +64,7 @@ class CodeReviewer(BaseReviewer): def __init__(self): super().__init__("code_review_prompt") - def review_and_strip_code(self, changes_text: str, commits_text: str = "") -> str: + def review_and_strip_code(self, changes_text: str, commits_text: str = '') -> str: """ Review判断changes_text超出取前REVIEW_MAX_TOKENS个token,超出则截断changes_text, 调用review_code方法,返回review_result,如果review_result是markdown格式,则去掉头尾的``` @@ -69,11 +73,11 @@ def review_and_strip_code(self, changes_text: str, commits_text: str = "") -> st :return: """ # 如果超长,取前REVIEW_MAX_TOKENS个token - review_max_tokens = int(os.getenv("REVIEW_MAX_TOKENS", 10000)) + review_max_tokens = int(os.getenv('REVIEW_MAX_TOKENS', 10000)) # 如果changes为空,打印日志 if not changes_text: - logger.info("代码为空, diffs_text = %", str(changes_text)) - return "代码为空" + logger.info(_('代码为空, diffs_text = {}').format(str(changes_text))) + return _('代码为空') # 计算tokens数量,如果超过REVIEW_MAX_TOKENS,截断changes_text tokens_count = count_tokens(changes_text) @@ -81,6 +85,7 @@ def review_and_strip_code(self, changes_text: str, commits_text: str = "") -> st changes_text = truncate_text_by_tokens(changes_text, review_max_tokens) review_result = self.review_code(changes_text, commits_text).strip() + if review_result.startswith("```markdown") and review_result.endswith("```"): return review_result[11:-3].strip() return review_result @@ -103,6 +108,6 @@ def parse_review_score(review_text: str) -> int: """解析 AI 返回的 Review 结果,返回评分""" if not review_text: return 0 - match = re.search(r"总分[::]\s*(\d+)分?", review_text) + match = re.search(_("总分[::]\\s*\\**(\\d+)分?"), review_text) return int(match.group(1)) if match else 0 diff --git a/biz/utils/config_checker.py b/biz/utils/config_checker.py index a09c662..53e8794 100644 --- a/biz/utils/config_checker.py +++ b/biz/utils/config_checker.py @@ -9,6 +9,9 @@ ENV_FILE_PATH = "conf/.env" load_dotenv(ENV_FILE_PATH) +from i18n import get_translator + +_ = get_translator() REQUIRED_ENV_VARS = [ "LLM_PROVIDER", @@ -30,9 +33,9 @@ def check_env_vars(): """检查环境变量""" missing_vars = [var for var in REQUIRED_ENV_VARS if var not in os.environ] if missing_vars: - logger.warning(f"缺少环境变量: {', '.join(missing_vars)}") + logger.warning(_("缺少环境变量: {}").format(", ".join(missing_vars))) else: - logger.info("所有必要的环境变量均已设置。") + logger.info(_("所有必要的环境变量均已设置。")) def check_llm_provider(): @@ -40,33 +43,35 @@ def check_llm_provider(): llm_provider = os.getenv("LLM_PROVIDER") if not llm_provider: - logger.error("LLM_PROVIDER 未设置!") + logger.error(_("LLM_PROVIDER 未设置!")) return if llm_provider not in LLM_PROVIDERS: - logger.error(f"LLM_PROVIDER 值错误,应为 {LLM_PROVIDERS} 之一。") + logger.error(_("LLM_PROVIDER 值错误,应为 {} 之一。").format(LLM_PROVIDERS)) return required_keys = LLM_REQUIRED_KEYS.get(llm_provider, []) missing_keys = [key for key in required_keys if not os.getenv(key)] if missing_keys: - logger.error(f"当前 LLM 供应商为 {llm_provider},但缺少必要的环境变量: {', '.join(missing_keys)}") + logger.error(_("当前 LLM 供应商为 {},但缺少必要的环境变量: {}").format(llm_provider, ', '.join(missing_keys))) else: - logger.info(f"LLM 供应商 {llm_provider} 的配置项已设置。") + logger.info(_("LLM 供应商 {} 的配置项已设置。").format(llm_provider)) + def check_llm_connectivity(): client = Factory().getClient() - logger.info(f"正在检查 LLM 供应商的连接...") + logger.info(_("正在检查 LLM 供应商的连接...")) if client.ping(): - logger.info("LLM 可以连接成功。") + logger.info(_("LLM 可以连接成功。")) else: - logger.error("LLM连接可能有问题,请检查配置项。") + logger.error(_("LLM连接可能有问题,请检查配置项。")) + def check_config(): """主检查入口""" - logger.info("开始检查配置项...") + logger.info(_("开始检查配置项...")) check_env_vars() check_llm_provider() check_llm_connectivity() - logger.info("配置项检查完成。") \ No newline at end of file + logger.info(_("配置项检查完成。")) diff --git a/biz/utils/i18n.py b/biz/utils/i18n.py new file mode 100644 index 0000000..555247b --- /dev/null +++ b/biz/utils/i18n.py @@ -0,0 +1,25 @@ +import gettext +import os +from typing import Callable + +from dotenv import load_dotenv + +load_dotenv("conf/.env") + + +def init_language(lang_code=None) -> Callable[[str], str]: + if lang_code is None: + lang_code = os.environ.get('LANGUAGE', 'zh_CN') + print(f"Using language: {lang_code}") + lang = gettext.translation("messages", localedir="locales", languages=[lang_code], fallback=True) + lang.install() + global _ + _ = lang.gettext + return _ + + +def get_translator() -> Callable[[str], str]: + return _ + + +init_language() diff --git a/biz/utils/im/dingtalk.py b/biz/utils/im/dingtalk.py index 17c99e2..4c5b3fa 100644 --- a/biz/utils/im/dingtalk.py +++ b/biz/utils/im/dingtalk.py @@ -1,15 +1,13 @@ -import base64 -import hashlib -import hmac import json import os -import time -import urllib.parse import requests +from biz.utils.i18n import get_translator from biz.utils.log import logger +_ = get_translator() + class DingTalkNotifier: def __init__(self, webhook_url=None): @@ -29,7 +27,7 @@ def _get_webhook_url(self, project_name=None, url_slug=None): if self.default_webhook_url: return self.default_webhook_url else: - raise ValueError("未提供项目名称,且未设置默认的钉钉 Webhook URL。") + raise ValueError(_("未提供项目名称,且未设置默认的钉钉 Webhook URL。")) # 构造目标键 target_key_project = f"DINGTALK_WEBHOOK_URL_{project_name.upper()}" @@ -48,11 +46,11 @@ def _get_webhook_url(self, project_name=None, url_slug=None): return self.default_webhook_url # 如果既未找到匹配项,也没有默认值,抛出异常 - raise ValueError(f"未找到项目 '{project_name}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。") + raise ValueError(_("未找到项目 '{}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。").format(project_name)) def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None, url_slug = None): if not self.enabled: - logger.info("钉钉推送未启用") + logger.info(_("钉钉推送未启用")) return try: @@ -85,8 +83,8 @@ def send_message(self, content: str, msg_type='text', title='通知', is_at_all= response = requests.post(url=post_url, data=json.dumps(message), headers=headers) response_data = response.json() if response_data.get('errmsg') == 'ok': - logger.info(f"钉钉消息发送成功! webhook_url:{post_url}") + logger.info(_("钉钉消息发送成功! webhook_url: {}").format(post_url)) else: - logger.error(f"钉钉消息发送失败! webhook_url:{post_url},errmsg:{response_data.get('errmsg')}") + logger.error(_("钉钉消息发送失败! webhook_url: {}, errmsg: {}").format(post_url, response_data.get('errmsg'))) except Exception as e: - logger.error(f"钉钉消息发送失败! ", e) + logger.error(_("钉钉消息发送失败!"), e) diff --git a/biz/utils/im/feishu.py b/biz/utils/im/feishu.py index 071225d..f828530 100644 --- a/biz/utils/im/feishu.py +++ b/biz/utils/im/feishu.py @@ -1,9 +1,8 @@ -import json import requests import os -import re from biz.utils.log import logger - +from biz.utils.i18n import get_translator +_ = get_translator() class FeishuNotifier: def __init__(self, webhook_url=None): @@ -26,7 +25,7 @@ def _get_webhook_url(self, project_name=None, url_slug=None): if self.default_webhook_url: return self.default_webhook_url else: - raise ValueError("未提供项目名称,且未设置默认的 飞书 Webhook URL。") + raise ValueError(_("未提供项目名称,且未设置默认的 飞书 Webhook URL。")) # 构造目标键 target_key_project = f"FEISHU_WEBHOOK_URL_{project_name.upper()}" @@ -45,7 +44,7 @@ def _get_webhook_url(self, project_name=None, url_slug=None): return self.default_webhook_url # 如果既未找到匹配项,也没有默认值,抛出异常 - raise ValueError(f"未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 Webhook URL。") + raise ValueError(_("未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 Webhook URL。")) def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, url_slug=None): """ @@ -57,7 +56,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr :param project_name: 项目名称 """ if not self.enabled: - logger.info("飞书推送未启用") + logger.info(_("飞书推送未启用")) return try: @@ -117,14 +116,14 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr ) if response.status_code != 200: - logger.error(f"飞书消息发送失败! webhook_url:{post_url}, error_msg:{response.text}") + logger.error(_("飞书消息发送失败! webhook_url: {post_url}, error_msg: {post_url}").format(post_url=post_url, error_msg=response.text)) return result = response.json() if result.get('msg') != "success": - logger.error(f"发送飞书消息失败! webhook_url:{post_url},errmsg:{result}") + logger.error(_("发送飞书消息失败! webhook_url: {post_url}, errmsg: {result}").format(post_url=post_url, result=result)) else: - logger.info(f"飞书消息发送成功! webhook_url:{post_url}") + logger.info(_("飞书消息发送成功! webhook_url: {post_url}").format(post_url=post_url)) except Exception as e: - logger.error(f"飞书消息发送失败! ", e) + logger.error(_("飞书消息发送失败!"), e) diff --git a/biz/utils/im/notifier.py b/biz/utils/im/notifier.py index 3d6b84f..4b39114 100644 --- a/biz/utils/im/notifier.py +++ b/biz/utils/im/notifier.py @@ -1,9 +1,12 @@ +from biz.utils.i18n import get_translator from biz.utils.im.dingtalk import DingTalkNotifier from biz.utils.im.feishu import FeishuNotifier from biz.utils.im.wecom import WeComNotifier +_ = get_translator() -def send_notification(content, msg_type='text', title="通知", is_at_all=False, project_name=None, url_slug=None): + +def send_notification(content, msg_type='text', title=_("通知"), is_at_all=False, project_name=None, url_slug=None): """ 发送通知消息到配置的平台(钉钉和企业微信) :param content: 消息内容 @@ -15,7 +18,7 @@ def send_notification(content, msg_type='text', title="通知", is_at_all=False, # 钉钉推送 dingtalk_notifier = DingTalkNotifier() dingtalk_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all, - project_name=project_name, url_slug=url_slug) + project_name=project_name, url_slug=url_slug) # 企业微信推送 wecom_notifier = WeComNotifier() diff --git a/biz/utils/im/wecom.py b/biz/utils/im/wecom.py index e4327f1..6dee73b 100644 --- a/biz/utils/im/wecom.py +++ b/biz/utils/im/wecom.py @@ -3,7 +3,8 @@ import os import re from biz.utils.log import logger - +from biz.utils.i18n import get_translator +_ = get_translator() class WeComNotifier: def __init__(self, webhook_url=None): @@ -26,7 +27,7 @@ def _get_webhook_url(self, project_name=None, url_slug=None): if self.default_webhook_url: return self.default_webhook_url else: - raise ValueError("未提供项目名称,且未设置默认的企业微信 Webhook URL。") + raise ValueError(_("未提供项目名称,且未设置默认的企业微信 Webhook URL。")) # 构造目标键 target_key_project = f"WECOM_WEBHOOK_URL_{project_name.upper()}" @@ -45,7 +46,7 @@ def _get_webhook_url(self, project_name=None, url_slug=None): return self.default_webhook_url # 如果既未找到匹配项,也没有默认值,抛出异常 - raise ValueError(f"未找到项目 '{project_name}' 对应的企业微信 Webhook URL,且未设置默认的 Webhook URL。") + raise ValueError(_("未找到项目 '{}' 对应的企业微信 Webhook URL,且未设置默认的 Webhook URL。").format(project_name)) def format_markdown_content(self, content, title=None): """ @@ -78,7 +79,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr :param url_slug: GitLab URL Slug """ if not self.enabled: - logger.info("企业微信推送未启用") + logger.info(_("企业微信推送未启用")) return try: @@ -86,20 +87,20 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr data = self._build_markdown_message(content, title) if msg_type == 'markdown' else self._build_text_message( content, is_at_all) - logger.debug(f"发送企业微信消息: url={post_url}, data={data}") + logger.debug(_("发送企业微信消息: url={post_url}, data={data}").format(post_url=post_url, data=data)) response = self._send_request(post_url, data) if response and response.get('errcode') != 0: - logger.error(f"企业微信消息发送失败! webhook_url:{post_url}, errmsg:{response}") + logger.error(_("企业微信消息发送失败! webhook_url:{}, error_msg:{}").format(post_url, response.text)) if response.get("errmsg") and "markdown.content exceed max length" in response["errmsg"]: - logger.warning("Markdown 消息过长,尝试发送纯文本") + logger.warning(_("Markdown 消息过长,尝试发送纯文本")) data = self._build_text_message(content, is_at_all) self._send_request(post_url, data) else: - logger.info(f"企业微信消息发送成功! webhook_url:{post_url}") + logger.info(_("企业微信消息发送成功! webhook_url: {}").format(post_url)) except Exception as e: - logger.error(f"企业微信消息发送失败! {e}") + logger.error(_("企业微信消息发送失败!"), e) def _send_request(self, url, data): """ 发送请求并返回 JSON 响应 """ @@ -108,9 +109,9 @@ def _send_request(self, url, data): response.raise_for_status() # 触发 HTTP 错误 return response.json() except requests.RequestException as e: - logger.error(f"企业微信消息发送请求失败! url:{url}, error: {e}") + logger.error(_("企业微信消息发送请求失败! url:{url}, error: {e}").format(url=url, e=e)) except json.JSONDecodeError as e: - logger.error(f"企业微信返回的 JSON 解析失败! url:{url}, error: {e}") + logger.error(_("企业微信返回的 JSON 解析失败! url:{url}, error: {e}").format(url=url, e=e)) return None def _build_text_message(self, content, is_at_all): diff --git a/biz/utils/reporter.py b/biz/utils/reporter.py index ab9a56e..ba4e851 100644 --- a/biz/utils/reporter.py +++ b/biz/utils/reporter.py @@ -1,5 +1,6 @@ from biz.llm.factory import Factory - +from biz.utils.i18n import get_translator +_ = get_translator() class Reporter: def __init__(self): @@ -9,6 +10,6 @@ def generate_report(self, data: str) -> str: # 根据data生成报告 return self.client.completions( messages=[ - {"role": "user", "content": f"下面是以json格式记录员工代码提交信息。请总结这些信息,生成每个员工的工作日报摘要。员工姓名直接用json内容中的author属性值,不要进行转换。特别要求:以Markdown格式返回。\n{data}"}, + {"role": "user", "content": _("下面是以json格式记录员工代码提交信息。请总结这些信息,生成每个员工的工作日报摘要。员工姓名直接用json内容中的author属性值,不要进行转换。特别要求:以Markdown格式返回。\n{}").format(data)}, ], ) diff --git a/conf/.env.dist b/conf/.env.dist index 5857290..cb64990 100644 --- a/conf/.env.dist +++ b/conf/.env.dist @@ -53,8 +53,8 @@ LOG_LEVEL=DEBUG REPORT_CRONTAB_EXPRESSION=0 18 * * 1-5 #Gitlab配置 -#GITLAB_URL={YOUR_GITLAB_URL} #部分老版本Gitlab webhook不传递URL,需要开启此配置,示例:https://gitlab.example.com -#GITLAB_ACCESS_TOKEN={YOUR_GITLAB_ACCESS_TOKEN} #系统会优先使用此GITLAB_ACCESS_TOKEN,如果未配置,则使用Webhook 传递的Secret Token +GITLAB_URL={YOUR_GITLAB_URL} #部分老版本Gitlab webhook不传递URL,需要开启此配置,示例:https://gitlab.example.com +GITLAB_ACCESS_TOKEN={YOUR_GITLAB_ACCESS_TOKEN} #系统会优先使用此GITLAB_ACCESS_TOKEN,如果未配置,则使用Webhook 传递的Secret Token #Github配置(如果使用 Github 作为代码托管平台,需要配置此项) #GITHUB_ACCESS_TOKEN={YOUR_GITHUB_ACCESS_TOKEN} @@ -62,6 +62,9 @@ REPORT_CRONTAB_EXPRESSION=0 18 * * 1-5 # 开启Push Review功能(如果不需要push事件触发Code Review,设置为0) PUSH_REVIEW_ENABLED=1 +# language (supported: zh_CN/en_US) +LANGUAGE=zh_CN + # Dashboard登录用户名和密码 DASHBOARD_USER=admin DASHBOARD_PASSWORD=admin diff --git a/doc/faq.md b/doc/faq.md index 8826b18..b4fa75b 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -75,6 +75,7 @@ DINGTALK_WEBHOOK_example_gitlab_com=https://oapi.dingtalk.com/robot/send?access_ **可能原因** 配置127.0.0.1:11434连接Ollama。由于docker容器的网络模式为bridge,容器内的127.0.0.1并不是宿主机的127.0.0.1,所以连接失败。 +对于Docker Desktop,请使用此地址连接Ollama: http://host.docker.internal:11434。 **解决方案** @@ -109,6 +110,7 @@ docker compose -f docker-compose.prod.yml up -d WORKER_QUEUE=gitlab_test_cn ``` +<<<<<<< HEAD ### 如何配置企业微信和飞书消息推送? **1.配置企业微信推送** @@ -160,3 +162,13 @@ WORKER_QUEUE=gitlab_test_cn GITHUB_ACCESS_TOKEN=your-access-token #替换为你的Access Token ``` +### 翻译 + +更改翻译后无法正确应用,我该怎么办? + +该应用程序使用 python 原生 gettext 实现来加载各种语言的翻译。如果您更改了翻译文件,但是应用程序没有正确应用新的翻译,您可以尝试以下步骤: + +1. 更新翻译: `bash translations_update.sh` +2. 通过向具有空 msgstr 的新行添加值来手动调整 `locales//LC_Messages/messages.po` 中缺失的翻译 +3. 编译翻译:`bash translations_compile.sh` +4. 重新启动您的应用程序 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..37c461d --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,66 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + target: prod + image: ghcr.io/sunmh207/ai-codereview-gitlab:1 + ports: + - "5001:5001" + - "5002:5002" + volumes: + - data:/app/data + - log:/app/log + env_file: + - ./conf/.env + depends_on: + redis: + condition: service_started + restart: unless-stopped + + worker: + build: + context: . + dockerfile: Dockerfile + target: worker + image: ghcr.io/sunmh207/ai-codereview-gitlab:1-worker + volumes_from: + - app + env_file: + - ./conf/.env +# environment: +# - WORKER_QUEUE=git_test1_com +# - OLLAMA_API_BASE_URL=https://ollama.test1.com:11434 + depends_on: + redis: + condition: service_started + restart: unless-stopped + +# worker2: +# build: +# context: . +# dockerfile: Dockerfile +# target: worker +# image: ghcr.io/sunmh207/ai-codereview-gitlab:1-worker +# volumes_from: +# - app +# env_file: +# - ./conf/.env +# environment: +# - WORKER_QUEUE=git_test2_com +# - OLLAMA_API_BASE_URL=https://ollama.test2.com:11434 +# depends_on: +# redis: +# condition: service_started +# restart: unless-stopped + + redis: + image: redis:alpine + volumes: + - redis_data:/data + restart: unless-stopped + +volumes: + data: + log: + redis_data: \ No newline at end of file diff --git a/docker-compose.rq.yml b/docker-compose.rq.yml index 84f4858..a1ac163 100644 --- a/docker-compose.rq.yml +++ b/docker-compose.rq.yml @@ -4,8 +4,8 @@ services: build: context: . dockerfile: Dockerfile - target: app - image: ghcr.io/sunmh207/ai-codereview-gitlab:1.3.6 + target: dev + image: ghcr.io/sunmh207/ai-codereview-gitlab:1 ports: - "5001:5001" - "5002:5002" @@ -24,7 +24,7 @@ services: context: . dockerfile: Dockerfile target: worker - image: ghcr.io/sunmh207/ai-codereview-gitlab:1.3.6-worker + image: ghcr.io/sunmh207/ai-codereview-gitlab:1-worker volumes_from: - app env_file: @@ -42,11 +42,11 @@ services: # context: . # dockerfile: Dockerfile # target: worker -# image: ghcr.io/sunmh207/ai-codereview-gitlab:1.3.6-worker +# image: ghcr.io/sunmh207/ai-codereview-gitlab:1-worker # volumes_from: # - app # env_file: -# - .env +# - ./conf/.env # environment: # - WORKER_QUEUE=git_test2_com # - OLLAMA_API_BASE_URL=https://ollama.test2.com:11434 diff --git a/docker-compose.yml b/docker-compose.yml index 379c1f7..2b20372 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,13 +3,14 @@ services: build: context: . dockerfile: Dockerfile - target: app - image: ghcr.io/sunmh207/ai-codereview-gitlab:1.3.6 + target: dev + image: ghcr.io/sunmh207/ai-codereview-gitlab:1 ports: - "5001:5001" - "5002:5002" volumes: - ./data:/app/data + - ./locales:/app/locales - ./log:/app/log env_file: - ./conf/.env diff --git a/locales/en_US/LC_MESSAGES/messages.mo b/locales/en_US/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..3376a82 Binary files /dev/null and b/locales/en_US/LC_MESSAGES/messages.mo differ diff --git a/locales/en_US/LC_MESSAGES/messages.po b/locales/en_US/LC_MESSAGES/messages.po new file mode 100644 index 0000000..f8dd56e --- /dev/null +++ b/locales/en_US/LC_MESSAGES/messages.po @@ -0,0 +1,824 @@ +# Language locales/en translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-29 16:32+0100\n" +"PO-Revision-Date: 2025-03-18 15:05+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: locales/en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.py:57 api.py:58 +msgid "No data to process." +msgstr "No data to process." + +#: api.py:68 +msgid "代码提交日报" +msgstr "Code Submission Daily Report" + +#: api.py:73 +msgid "Failed to generate daily report: {}" +msgstr "Failed to generate daily report: {}" + +#: api.py:101 +msgid "Scheduler started successfully." +msgstr "Scheduler started successfully." + +#: api.py:106 +msgid "Error setting up scheduler: {}" +msgstr "Error setting up scheduler: {}" + +#: api.py:117 +msgid "Invalid JSON" +msgstr "Invalid JSON" + +#: api.py:127 +msgid "Invalid data format" +msgstr "Invalid data format" + +#: api.py:134 api.py:171 api.py:174 +msgid "Missing GitLab URL" +msgstr "Missing GitLab URL" + +#: api.py:140 api.py:190 +msgid "Received event: {}" +msgstr "Received event: {}" + +#: api.py:141 api.py:191 +msgid "Payload: {}" +msgstr "Payload: {}" + +#: api.py:147 api.py:153 +msgid "GitHub request received(event_type={}), will process asynchronously." +msgstr "GitHub request received(event_type={}), will process asynchronously." + +#: api.py:157 +msgid "" +"Only pull_request and push events are supported for GitHub webhook, but " +"received:: {}." +msgstr "" +"Only pull_request and push events are supported for GitHub webhook, but " +"received:: {}." + +#: api.py:179 +msgid "Failed to parse homepage URL: {}" +msgstr "Failed to parse homepage URL: {}" + +#: api.py:185 +msgid "Missing GitLab access token" +msgstr "Missing GitLab access token" + +#: api.py:198 api.py:205 +msgid "Request received(object_kind={}), will process asynchronously." +msgstr "Request received(object_kind={}), will process asynchronously." + +#: api.py:209 +msgid "" +"Only merge_request and push events are supported (both Webhook and System " +"Hook), but received: {}." +msgstr "" +"Only merge_request and push events are supported (both Webhook and System " +"Hook), but received: {}." + +#: biz/cmd/review.py:11 +msgid "" +"\n" +"欢迎使用 Codebase Review 工具!\n" +msgstr "" +"\n" +"欢迎使用 Codebase Review 工具!\n" + +#: biz/cmd/review.py:17 +msgid "Review 目录结构规范" +msgstr "Review Directory Structure Specification" + +#: biz/cmd/review.py:18 +msgid "Review 代码分支命名规范" +msgstr "Review code branch naming conventions" + +#: biz/cmd/review.py:19 +msgid "Review 代码复杂度" +msgstr "Review code complexity" + +#: biz/cmd/review.py:22 +msgid "📌 请选择功能:" +msgstr "📌 Please select a function:" + +#: biz/cmd/review.py:27 +msgid "请输入数字 (1-3): " +msgstr "Please enter a number (1-3):" + +#: biz/cmd/review.py:30 +msgid "❌ 无效的选择,请输入 1-3" +msgstr "❌ Invalid selection, please enter 1-3" + +#: biz/event/event_manager.py:20 +#, python-brace-format +msgid "" +"\n" +"### 🔀 {project_name}: Merge Request\n" +"\n" +"#### 合并请求信息:\n" +"- **提交者:** {author}\n" +"\n" +"- **源分支**: {source_branch}\n" +"- **目标分支**: {target_branch}\n" +"- **更新时间**: {updated_at}\n" +"- **提交信息:** {commit_messages}\n" +"\n" +"- [查看合并详情]({url})\n" +"\n" +"- **AI Review 结果:** \n" +"\n" +"{review_result}\n" +" " +msgstr "" +"\n" +"### 🔀 {project_name}: Merge Request\n" +"\n" +"#### Merge Request Information:\n" +"- **Author:** {author}\n" +"\n" +"- **Source Branch**: {source_branch}\n" +"- **Target Branch**: {target_branch}\n" +"- **Updated At**: {updated_at}\n" +"- **Commit Messages:** {commit_messages}\n" +"\n" +"- [View Merge Details]({url})\n" +"\n" +"- **AI Review Results:** \n" +"\n" +"{review_result}\n" +" " + +#: biz/event/event_manager.py:46 +msgid "Merge Request Review" +msgstr "Merge Request Review" + +#: biz/event/event_manager.py:56 +#, python-brace-format +msgid "" +"### 🚀 {project_name}: Push\n" +"\n" +msgstr "" +"### 🚀 {project_name}: Push\n" +"\n" + +#: biz/event/event_manager.py:57 +msgid "#### 提交记录:\n" +msgstr "#### Commit Records:\n" + +#: biz/event/event_manager.py:61 +msgid "Unknown Author" +msgstr "Unknown Author" + +#: biz/event/event_manager.py:65 +#, python-brace-format +msgid "" +"- **提交信息**: {message}\n" +"- **提交者**: {author}\n" +"- **时间**: {timestamp}\n" +"- [查看提交详情]({url})\n" +"\n" +msgstr "" +"- **Commit Message**: {message}\n" +"- **Author**: {author}\n" +"- **Timestamp**: {timestamp}\n" +"- [View Commit Details]({url})\n" +"\n" + +#: biz/event/event_manager.py:77 +#, python-brace-format +msgid "" +"#### AI Review 结果: \n" +" {review_result}\n" +"\n" +msgstr "" +"#### AI Review 结果: \n" +" {review_result}\n" +"\n" + +#: biz/event/event_manager.py:79 +#, python-brace-format +msgid "{project_name} Push Event" +msgstr "{project_name} Push Event" + +#: biz/github/webhook_handler.py:26 +msgid "Detected file deletion via status field: {}" +msgstr "Detected file deletion via status field: {}" + +#: biz/github/webhook_handler.py:42 +msgid "SUPPORTED_EXTENSIONS: {}" +msgstr "SUPPORTED_EXTENSIONS: {}" + +#: biz/github/webhook_handler.py:43 +msgid "After filtering deleted files: {}" +msgstr "After filtering deleted files: {}" + +#: biz/github/webhook_handler.py:54 +msgid "After filtering by extension: {}" +msgstr "After filtering by extension: {}" + +#: biz/github/webhook_handler.py:84 +msgid "Invalid event type: {}. Only 'pull_request' event is supported now." +msgstr "Invalid event type: {}. Only 'pull_request' event is supported now." + +#: biz/github/webhook_handler.py:99 +#, python-brace-format +msgid "" +"Get changes response from GitHub (attempt {attempt}): " +"{response_status_code}, {response_text}, URL: {url}" +msgstr "" +"Get changes response from GitHub (attempt {attempt}): " +"{response_status_code}, {response_text}, URL: {url}" + +#: biz/github/webhook_handler.py:119 +#, python-brace-format +msgid "" +"Changes is empty, retrying in {retry_delay} seconds... (attempt {attempt}/" +"{max_retries}), URL: {url}" +msgstr "" +"Changes is empty, retrying in {retry_delay} seconds... (attempt {attempt}/" +"{max_retries}), URL: {url}" + +#: biz/github/webhook_handler.py:123 +#, python-brace-format +msgid "" +"Failed to get changes from GitHub (URL: {url}): {response.status_code}, " +"{response.text}" +msgstr "" +"Failed to get changes from GitHub (URL: {url}): {response.status_code}, " +"{response.text}" + +#: biz/github/webhook_handler.py:126 biz/gitlab/webhook_handler.py:111 +msgid "Max retries ({}) reached. Changes is still empty." +msgstr "Max retries ({}) reached. Changes are still empty." + +#: biz/github/webhook_handler.py:141 +msgid "Get commits response from GitHub: {}, {}" +msgstr "Get commits response from GitHub: {}, {}" + +#: biz/github/webhook_handler.py:161 biz/gitlab/webhook_handler.py:130 +msgid "Failed to get commits: {}, {}" +msgstr "Failed to get commits: {}, {}" + +#: biz/github/webhook_handler.py:174 +#, python-brace-format +msgid "Add comment to GitHub PR {url}: {response_status_code}, {response_text}" +msgstr "Add comment to GitHub PR {url}: {response_status_code}, {response_text}" + +#: biz/github/webhook_handler.py:176 +msgid "Comment successfully added to pull request." +msgstr "Comment successfully added to pull request." + +#: biz/github/webhook_handler.py:178 biz/github/webhook_handler.py:249 +msgid "Failed to add comment: {}" +msgstr "Failed to add comment: {}" + +#: biz/github/webhook_handler.py:207 biz/github/webhook_handler.py:319 +#: biz/gitlab/webhook_handler.py:178 biz/gitlab/webhook_handler.py:267 +msgid "Invalid event type: {}. Only 'push' event is supported now." +msgstr "Invalid event type: {}. Only 'push' event is supported now." + +#: biz/github/webhook_handler.py:221 biz/gitlab/webhook_handler.py:192 +msgid "Collected {} commits from push event." +msgstr "Collected {} commits from push event." + +#: biz/github/webhook_handler.py:227 biz/gitlab/webhook_handler.py:198 +msgid "No commits found to add notes to." +msgstr "No commits found to add notes to." + +#: biz/github/webhook_handler.py:233 biz/gitlab/webhook_handler.py:204 +msgid "Last commit ID not found." +msgstr "Last commit ID not found." + +#: biz/github/webhook_handler.py:245 +#, python-brace-format +msgid "" +"Add comment to commit {last_commit_id}: {response_status_code}, " +"{response_text}" +msgstr "" +"Add comment to commit {last_commit_id}: {response_status_code}, " +"{response_text}" + +#: biz/github/webhook_handler.py:247 +msgid "Comment successfully added to push commit." +msgstr "Comment successfully added to push commit." + +#: biz/github/webhook_handler.py:261 +#, python-brace-format +msgid "" +"Get commits response from GitHub for repository_commits: " +"{response_status_code}, {response_text}, URL: {url}" +msgstr "" +"Get commits response from GitHub for repository_commits: " +"{response_status_code}, {response_text}, URL: {url}" + +#: biz/github/webhook_handler.py:268 +#, python-brace-format +msgid "" +"Failed to get commits for sha {sha}: {response_status_code}, {response_text}" +msgstr "" +"Failed to get commits for sha {sha}: {response_status_code}, {response_text}" + +#: biz/github/webhook_handler.py:279 +#, python-brace-format +msgid "" +"Get commit response from GitHub: {response_status_code}, {response_text}, " +"URL: {url}" +msgstr "" +"Get commit response from GitHub: {response_status_code}, {response_text}, " +"URL: {url}" + +#: biz/github/webhook_handler.py:295 +#, python-brace-format +msgid "" +"Get changes response from GitHub for repository_compare: " +"{response_status_code}, {response_text}, URL: {url}" +msgstr "" +"Get changes response from GitHub for repository_compare: " +"{response_status_code}, {response_text}, URL: {url}" + +#: biz/github/webhook_handler.py:313 +#, python-brace-format +msgid "" +"Failed to get changes for repository_compare: {response.status_code}, " +"{response.text}" +msgstr "" +"Failed to get changes for repository_compare: {response.status_code}, " +"{response.text}" + +#: biz/github/webhook_handler.py:324 biz/gitlab/webhook_handler.py:272 +msgid "No commits found in push event." +msgstr "No commits found in push event." + +#: biz/github/webhook_handler.py:346 +msgid "" +"before or after not found in webhook data, trying to get changes from " +"commits." +msgstr "" +"before or after not found in webhook data, trying to get changes from " +"commits." + +#: biz/gitlab/webhook_handler.py:82 +msgid "Invalid event type: {}. Only 'merge_request' event is supported now." +msgstr "Invalid event type: {}. Only 'merge_request' event is supported now." + +#: biz/gitlab/webhook_handler.py:97 +msgid "Get changes response from GitLab (attempt {}): {}, {}, URL: {}" +msgstr "Get changes response from GitLab (attempt {}): {}, {}, URL: {}" + +#: biz/gitlab/webhook_handler.py:105 +msgid "Changes is empty, retrying in {} seconds... (attempt {}/{}), URL: {}" +msgstr "Changes are empty, retrying in {} seconds... (attempt {}/{}), URL: {}" + +#: biz/gitlab/webhook_handler.py:108 +msgid "Failed to get changes from GitLab (URL: {}): {}, {}" +msgstr "Failed to retrieve changes from GitLab (URL: {}): {}, {}" + +#: biz/gitlab/webhook_handler.py:126 +msgid "Get commits response from gitlab: {}, {}" +msgstr "Get commits response from gitlab: {}, {}" + +#: biz/gitlab/webhook_handler.py:144 +msgid "Add notes to gitlab {}: {}, {}" +msgstr "Add notes to gitlab {}: {}, {}" + +#: biz/gitlab/webhook_handler.py:148 +msgid "Failed to add note: {}" +msgstr "Failed to add note: {}" + +#: biz/gitlab/webhook_handler.py:232 +msgid "" +"Get commits response from GitLab for repository_commits: {response." +"status_code}, {}, URL: {url}" +msgstr "" +"Get commits response from GitLab for repository_commits: {response." +"status_code}, {}, URL: {url}" + +#: biz/gitlab/webhook_handler.py:255 +msgid "" +"Get changes response from GitLab for repository_compare: {}, {response." +"text}, URL: {}" +msgstr "" +"Get changes response from GitLab for repository_compare: {}, {response." +"text}, URL: {}" + +#: biz/gitlab/webhook_handler.py:261 +msgid "Failed to get changes for repository_compare: {}, {}" +msgstr "Failed to get changes for repository_compare: {}, {}" + +#: biz/llm/client/deepseek.py:19 biz/llm/client/openai.py:18 +#: biz/llm/client/zhipuai.py:17 +msgid "" +"API key is required. Please provide it or set it in the environment " +"variables." +msgstr "" +"API key is required. Please provide it or set it in the environment " +"variables." + +#: biz/llm/client/deepseek.py:30 +#, python-brace-format +msgid "Sending request to DeepSeek API. Model: {model}, Messages: {messages}" +msgstr "Sending request to DeepSeek API. Model: {model}, Messages: {messages}" + +#: biz/llm/client/deepseek.py:39 +msgid "Empty response from DeepSeek API" +msgstr "Empty response from DeepSeek API" + +#: biz/llm/client/deepseek.py:40 +msgid "AI服务返回为空,请稍后重试" +msgstr "AI service returned empty, please try again later" + +#: biz/llm/client/deepseek.py:45 +#, python-brace-format +msgid "DeepSeek API error: {e}" +msgstr "DeepSeek API error: {e}" + +#: biz/llm/client/deepseek.py:48 +msgid "DeepSeek API认证失败,请检查API密钥是否正确" +msgstr "" +"DeepSeek API authentication failed, please check if the API key is correct" + +#: biz/llm/client/deepseek.py:50 +msgid "DeepSeek API接口未找到,请检查API地址是否正确" +msgstr "" +"DeepSeek API endpoint not found, please check if the API address is correct" + +#: biz/llm/client/deepseek.py:52 +#, python-brace-format +msgid "调用DeepSeek API时出错: {e}" +msgstr "Error occurred while calling DeepSeek API: {e}" + +#: biz/llm/factory.py:29 +#, python-brace-format +msgid "Unknown chat model provider: {provider}" +msgstr "Unknown chat model provider: {provider}" + +#: biz/queue/worker.py:30 biz/queue/worker.py:95 biz/queue/worker.py:138 +#: biz/queue/worker.py:202 +msgid "Failed to get commits" +msgstr "Failed to retrieve commits" + +#: biz/queue/worker.py:41 biz/queue/worker.py:149 +msgid "未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。" +msgstr "" +"No changes detected in the pushed code. The modified files may not meet " +"SUPPORTED_EXTENSIONS." + +#: biz/queue/worker.py:42 biz/queue/worker.py:150 +msgid "关注的文件没有修改" +msgstr "No changes in the monitored files" + +#: biz/queue/worker.py:49 biz/queue/worker.py:103 biz/queue/worker.py:210 +msgid "" +"Auto Review Result: \n" +"{}" +msgstr "" +"Auto Review Result: \n" +"{}" + +#: biz/queue/worker.py:64 biz/queue/worker.py:171 biz/queue/worker.py:228 +msgid "服务出现未知错误: {}" +msgstr "An unknown error occurred in the service: {}" + +#: biz/queue/worker.py:66 biz/queue/worker.py:129 biz/queue/worker.py:173 +#: biz/queue/worker.py:230 +msgid "出现未知错误: {}" +msgstr "An unknown error occurred: {}" + +#: biz/queue/worker.py:81 +msgid "Merge Request Hook event received" +msgstr "Merge Request Hook event received" + +#: biz/queue/worker.py:86 biz/queue/worker.py:146 biz/queue/worker.py:193 +msgid "changes: {}" +msgstr "Changes: {}" + +#: biz/queue/worker.py:89 biz/queue/worker.py:196 +msgid "未检测到有关代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。" +msgstr "" +"No relevant code changes detected. The modified files may not meet " +"SUPPORTED_EXTENSIONS." + +#: biz/queue/worker.py:124 +msgid "Merge Request Hook event, action={}, ignored." +msgstr "Merge Request Hook event, action={}, ignored." + +#: biz/queue/worker.py:127 +msgid "AI Code Review 服务出现未知错误: {}" +msgstr "AI Code Review service encountered an unknown error: {}" + +#: biz/queue/worker.py:135 +msgid "GitHub Push event received" +msgstr "GitHub Push event received" + +#: biz/queue/worker.py:188 +msgid "GitHub Pull Request event received" +msgstr "GitHub Pull Request event received" + +#: biz/utils/code_reviewer.py:45 +#, python-brace-format +msgid "加载提示词配置失败: {e}" +msgstr "Failed to load prompt configuration: {e}" + +#: biz/utils/code_reviewer.py:46 +#, python-brace-format +msgid "提示词配置加载失败: {e}" +msgstr "Failed to load prompt configuration: {e}" + +#: biz/utils/code_reviewer.py:50 +#, python-brace-format +msgid "向 AI 发送代码 Review 请求, messages: {messages}" +msgstr "Send code review request to AI, messages: {messages}" + +#: biz/utils/code_reviewer.py:52 +#, python-brace-format +msgid "收到 AI 返回结果: {review_result}" +msgstr "Received AI return result: {review_result}" + +#: biz/utils/code_reviewer.py:79 +msgid "代码为空, diffs_text = {}" +msgstr "Code is empty, diffs_text = {}" + +#: biz/utils/code_reviewer.py:80 +msgid "代码为空" +msgstr "Code is empty" + +#: biz/utils/code_reviewer.py:111 +msgid "总分[::]\\s*\\**(\\d+)分?" +msgstr "Total [sS]core[::]\\s*\\**(\\d+) points" + +#: biz/utils/config_checker.py:36 +msgid "缺少环境变量: {}" +msgstr "Missing environment variables: {}" + +#: biz/utils/config_checker.py:38 +msgid "所有必要的环境变量均已设置。" +msgstr "All necessary environment variables are set." + +#: biz/utils/config_checker.py:46 +msgid "LLM_PROVIDER 未设置!" +msgstr "LLM_PROVIDER not set!" + +#: biz/utils/config_checker.py:50 +msgid "LLM_PROVIDER 值错误,应为 {} 之一。" +msgstr "Wrong LLM_PROVIDER value, should be one of {}." + +#: biz/utils/config_checker.py:57 +msgid "当前 LLM 供应商为 {},但缺少必要的环境变量: {}" +msgstr "" +"The current LLM provider is {}, but the required environment variables are " +"missing: {}" + +#: biz/utils/config_checker.py:59 +msgid "LLM 供应商 {} 的配置项已设置。" +msgstr "Configuration item for LLM vendor {} has been set." + +#: biz/utils/config_checker.py:64 +msgid "正在检查 LLM 供应商的连接..." +msgstr "Checking LLM supplier connectivity..." + +#: biz/utils/config_checker.py:66 +msgid "LLM 可以连接成功。" +msgstr "LLM can connect successfully." + +#: biz/utils/config_checker.py:68 +msgid "LLM连接可能有问题,请检查配置项。" +msgstr "" +"There may be a problem with the LLM connection. Please check the " +"configuration items." + +#: biz/utils/config_checker.py:73 +msgid "开始检查配置项..." +msgstr "Starting to check configuration items..." + +#: biz/utils/config_checker.py:77 +msgid "配置项检查完成。" +msgstr "The configuration item check is complete." + +#: biz/utils/im/dingtalk.py:30 +msgid "未提供项目名称,且未设置默认的钉钉 Webhook URL。" +msgstr "Project name not provided, and no default DingTalk Webhook URL set." + +#: biz/utils/im/dingtalk.py:49 +msgid "未找到项目 '{}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。" +msgstr "" +"No DingTalk Webhook URL found for project '{}', and no default Webhook URL " +"set." + +#: biz/utils/im/dingtalk.py:53 +msgid "钉钉推送未启用" +msgstr "DingTalk notifications are not enabled." + +#: biz/utils/im/dingtalk.py:86 +msgid "钉钉消息发送成功! webhook_url: {}" +msgstr "DingTalk message sent successfully! Webhook URL: {}" + +#: biz/utils/im/dingtalk.py:88 +msgid "钉钉消息发送失败! webhook_url: {}, errmsg: {}" +msgstr "DingTalk message failed to send! Webhook URL: {}, error message: {}" + +#: biz/utils/im/dingtalk.py:90 +msgid "钉钉消息发送失败!" +msgstr "DingTalk message failed to send!" + +#: biz/utils/im/feishu.py:28 +msgid "未提供项目名称,且未设置默认的 飞书 Webhook URL。" +msgstr "Project name not provided, and no default Feishu Webhook URL set." + +#: biz/utils/im/feishu.py:47 +#, python-brace-format +msgid "" +"未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 " +"Webhook URL。" +msgstr "" +"No Feishu Webhook URL found for project '{project_name}', and no default " +"Webhook URL set." + +#: biz/utils/im/feishu.py:59 +msgid "飞书推送未启用" +msgstr "Feishu push is not enabled" + +#: biz/utils/im/feishu.py:119 +#, python-brace-format +msgid "飞书消息发送失败! webhook_url: {post_url}, error_msg: {post_url}" +msgstr "" +"Feishu message failed to send! Webhook URL: {post_url}, error message: " +"{post_url}" + +#: biz/utils/im/feishu.py:124 +#, python-brace-format +msgid "发送飞书消息失败! webhook_url: {post_url}, errmsg: {result}" +msgstr "" +"Feishu message failed to send! webhook_url: {post_url}, errmsg: {post_url}" + +#: biz/utils/im/feishu.py:126 +#, python-brace-format +msgid "飞书消息发送成功! webhook_url: {post_url}" +msgstr "Feishu message sent successfully! Webhook URL: {post_url}" + +#: biz/utils/im/feishu.py:129 +msgid "飞书消息发送失败!" +msgstr "Feishu message failed to send!" + +#: biz/utils/im/notifier.py:9 +msgid "通知" +msgstr "Notification" + +#: biz/utils/im/wecom.py:30 +msgid "未提供项目名称,且未设置默认的企业微信 Webhook URL。" +msgstr "Project name not provided, and no default WeCom Webhook URL set." + +#: biz/utils/im/wecom.py:49 +msgid "" +"未找到项目 '{}' 对应的企业微信 Webhook URL,且未设置默认的 Webhook URL。" +msgstr "" +"No WeCom Webhook URL found for project '{}', and no default Webhook URL set." + +#: biz/utils/im/wecom.py:82 +msgid "企业微信推送未启用" +msgstr "WeCom notifications are not enabled." + +#: biz/utils/im/wecom.py:90 +#, python-brace-format +msgid "发送企业微信消息: url={post_url}, data={data}" +msgstr "Send enterprise WeChat message: url={post_url}, data={data}" + +#: biz/utils/im/wecom.py:94 +msgid "企业微信消息发送失败! webhook_url:{}, error_msg:{}" +msgstr "WeCom message failed to send! Webhook URL: {}, error message: {}" + +#: biz/utils/im/wecom.py:96 +msgid "Markdown 消息过长,尝试发送纯文本" +msgstr "Markdown message is too long, trying sending plain text" + +#: biz/utils/im/wecom.py:100 +msgid "企业微信消息发送成功! webhook_url: {}" +msgstr "WeCom message sent successfully! Webhook URL: {}" + +#: biz/utils/im/wecom.py:103 +msgid "企业微信消息发送失败!" +msgstr "WeCom message failed to send!" + +#: biz/utils/im/wecom.py:112 +#, python-brace-format +msgid "企业微信消息发送请求失败! url:{url}, error: {e}" +msgstr "" +"Enterprise WeChat message sending request failed! url:{url}, error: {e}" + +#: biz/utils/im/wecom.py:114 +#, python-brace-format +msgid "企业微信返回的 JSON 解析失败! url:{url}, error: {e}" +msgstr "" +"Failed to parse the JSON returned by WeChat Enterprise! url:{url}, error: {e}" + +#: biz/utils/reporter.py:13 +msgid "" +"下面是以json格式记录员工代码提交信息。请总结这些信息,生成每个员工的工作日报" +"摘要。员工姓名直接用json内容中的author属性值,不要进行转换。特别要求:以" +"Markdown格式返回。\n" +"{}" +msgstr "" +"The following is a JSON record of employee code submissions. Please " +"summarize this information to generate each employee's daily work report " +"summary. Use the `author` attribute value from the JSON content directly " +"without conversion. Special requirement: Return in Markdown format.\n" +"{}" + +#: ui.py:56 ui.py:71 +msgid "登录" +msgstr "Login" + +#: ui.py:60 +msgid "" +"安全提示:检测到默认用户名和密码为 'admin',存在安全风险!\n" +"\n" +"请立即修改:\n" +"1. 打开 `.env` 文件\n" +"2. 修改 `DASHBOARD_USER` 和 `DASHBOARD_PASSWORD` 变量\n" +"3. 保存并重启应用" +msgstr "" +"Security Alert: The default username and password are detected as 'admin', " +"which poses a security risk!\n" +"\n" +"Please update them immediately:\n" +"1. Open the `.env` file\n" +"2. Change the `DASHBOARD_USER` and `DASHBOARD_PASSWORD` variables\n" +"3. Save and restart the application" + +#: ui.py:66 +msgid "当前用户名: `{}`, 当前密码: `{}`" +msgstr "Current Username: `{}`, Current Password: `{}`" + +#: ui.py:68 ui.py:211 +msgid "用户名" +msgstr "Username" + +#: ui.py:69 +msgid "密码" +msgstr "Password" + +#: ui.py:77 +msgid "用户名或密码错误" +msgstr "Incorrect username or password" + +#: ui.py:83 ui.py:108 ui.py:133 ui.py:157 +msgid "没有数据可供展示" +msgstr "No data to display" + +#: ui.py:180 +msgid "#### 审查日志" +msgstr "#### Review Log" + +#: ui.py:189 +msgid "Merge Request" +msgstr "Merge Request" + +#: ui.py:189 +msgid "Push" +msgstr "Push" + +#: ui.py:197 +msgid "开始日期" +msgstr "Start Date" + +#: ui.py:199 +msgid "结束日期" +msgstr "End Date" + +#: ui.py:213 +msgid "项目名" +msgstr "Project name" + +#: ui.py:228 +msgid "**总记录数:** {},**平均分:** {}" +msgstr "**Total Records:** {},**Average Score:** {}" + +#: ui.py:233 +msgid "项目提交次数" +msgstr "Number of project submissions" + +#: ui.py:236 +msgid "项目平均分数" +msgstr "Average project score" + +#: ui.py:239 +msgid "人员提交次数" +msgstr "Number of personnel submissions" + +#: ui.py:242 +msgid "人员平均分数" +msgstr "Personnel average score" + +#: ui.py:257 +msgid "查看" +msgstr "Open" diff --git a/locales/en_US/prompt_templates.yml b/locales/en_US/prompt_templates.yml new file mode 100644 index 0000000..1b22dac --- /dev/null +++ b/locales/en_US/prompt_templates.yml @@ -0,0 +1,43 @@ +code_review_prompt: + system_prompt: |- + You are a senior software developer, focusing on code standardization, functionality, security, and stability. The task is to review an employee's code, with the following specific requirements: + + ### Code Review Objectives: + 1. Correctness and robustness of functional implementation (40 points): Ensure that the code logic is correct and can handle various edge cases and unexpected inputs. + 2. Security and potential risks (30 points): Check whether the code contains security vulnerabilities (such as SQL injection, XSS attacks, etc.) and assess their potential risks. + 3. Adherence to best practices (20 points): Evaluate whether the code follows industry best practices, including code structure, naming conventions, and clarity of comments. + 4. Performance and resource utilization efficiency (5 points): Analyze the code's performance and evaluate whether there is any resource wastage or performance bottlenecks. + 5. Clarity and accuracy of commit information (5 points): Check whether the commit messages are clear and accurate, facilitating subsequent maintenance and collaboration. + + ### Output Format: + Please output the code review report in Markdown format, including the following: + 1. Problem descriptions and optimization suggestions (if any): List the issues present in the code, briefly explain their impact, and provide optimization suggestions. + 2. Detailed scoring: Provide specific scores for each scoring criterion. + 3. Total Score: Format as "Total score: XX points" (for example: Total score: 80 points), ensuring that the total score can be parsed using the regex r"Total score[::]\s*(\d+) points". + + ### Special Instructions: + The entire review should maintain a {{ style }} style. + {% if style == 'professional' %} + When reviewing, please use standard engineering terminology and maintain a professional and rigorous tone. + {% elif style == 'sarcastic' %} + When reviewing, feel free to use sarcastic language, but ensure that the technical critique remains accurate. + {% elif style == 'gentle' %} + When reviewing, please use gentle wording such as "suggest" or "could consider". + {% elif style == 'humorous' %} + When reviewing, please: + 1. Incorporate appropriate humorous elements in the technical critique. + 2. Use related emojis appropriately (but not excessively): + - 🐛 indicates bugs + - 💥 indicates serious issues + - 🎯 indicates suggestions for improvement + - 🔍 indicates areas that require closer inspection + {% endif %} + + user_prompt: |- + Below is the code submitted by an employee to the GitLab repository. Please review the following code in a {{ style }} style. + + Code changes: + {diffs_text} + + Commit history: + {{commits_text}} \ No newline at end of file diff --git a/locales/zh_CN/LC_MESSAGES/messages.mo b/locales/zh_CN/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..09fc6b4 Binary files /dev/null and b/locales/zh_CN/LC_MESSAGES/messages.mo differ diff --git a/locales/zh_CN/LC_MESSAGES/messages.po b/locales/zh_CN/LC_MESSAGES/messages.po new file mode 100644 index 0000000..a545e51 --- /dev/null +++ b/locales/zh_CN/LC_MESSAGES/messages.po @@ -0,0 +1,732 @@ +# Language locales/zh translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-29 16:32+0100\n" +"PO-Revision-Date: 2025-03-18 15:05+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: locales/zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: api.py:57 api.py:58 +msgid "No data to process." +msgstr "" + +#: api.py:68 +msgid "代码提交日报" +msgstr "" + +#: api.py:73 +msgid "Failed to generate daily report: {}" +msgstr "" + +#: api.py:101 +msgid "Scheduler started successfully." +msgstr "" + +#: api.py:106 +msgid "Error setting up scheduler: {}" +msgstr "" + +#: api.py:117 +msgid "Invalid JSON" +msgstr "" + +#: api.py:127 +msgid "Invalid data format" +msgstr "" + +#: api.py:134 api.py:171 api.py:174 +msgid "Missing GitLab URL" +msgstr "" + +#: api.py:140 api.py:190 +msgid "Received event: {}" +msgstr "" + +#: api.py:141 api.py:191 +msgid "Payload: {}" +msgstr "" + +#: api.py:147 api.py:153 +msgid "GitHub request received(event_type={}), will process asynchronously." +msgstr "" + +#: api.py:157 +msgid "" +"Only pull_request and push events are supported for GitHub webhook, but " +"received:: {}." +msgstr "" + +#: api.py:179 +msgid "Failed to parse homepage URL: {}" +msgstr "" + +#: api.py:185 +msgid "Missing GitLab access token" +msgstr "" + +#: api.py:198 api.py:205 +msgid "Request received(object_kind={}), will process asynchronously." +msgstr "" + +#: api.py:209 +msgid "" +"Only merge_request and push events are supported (both Webhook and System " +"Hook), but received: {}." +msgstr "" + +#: biz/cmd/review.py:11 +msgid "" +"\n" +"欢迎使用 Codebase Review 工具!\n" +msgstr "" + +#: biz/cmd/review.py:17 +msgid "Review 目录结构规范" +msgstr "" + +#: biz/cmd/review.py:18 +msgid "Review 代码分支命名规范" +msgstr "" + +#: biz/cmd/review.py:19 +msgid "Review 代码复杂度" +msgstr "" + +#: biz/cmd/review.py:22 +msgid "📌 请选择功能:" +msgstr "" + +#: biz/cmd/review.py:27 +msgid "请输入数字 (1-3): " +msgstr "" + +#: biz/cmd/review.py:30 +msgid "❌ 无效的选择,请输入 1-3" +msgstr "" + +#: biz/event/event_manager.py:20 +#, python-brace-format +msgid "" +"\n" +"### 🔀 {project_name}: Merge Request\n" +"\n" +"#### 合并请求信息:\n" +"- **提交者:** {author}\n" +"\n" +"- **源分支**: {source_branch}\n" +"- **目标分支**: {target_branch}\n" +"- **更新时间**: {updated_at}\n" +"- **提交信息:** {commit_messages}\n" +"\n" +"- [查看合并详情]({url})\n" +"\n" +"- **AI Review 结果:** \n" +"\n" +"{review_result}\n" +" " +msgstr "" + +#: biz/event/event_manager.py:46 +msgid "Merge Request Review" +msgstr "" + +#: biz/event/event_manager.py:56 +#, python-brace-format +msgid "" +"### 🚀 {project_name}: Push\n" +"\n" +msgstr "" + +#: biz/event/event_manager.py:57 +msgid "#### 提交记录:\n" +msgstr "" + +#: biz/event/event_manager.py:61 +msgid "Unknown Author" +msgstr "" + +#: biz/event/event_manager.py:65 +#, python-brace-format +msgid "" +"- **提交信息**: {message}\n" +"- **提交者**: {author}\n" +"- **时间**: {timestamp}\n" +"- [查看提交详情]({url})\n" +"\n" +msgstr "" + +#: biz/event/event_manager.py:77 +#, python-brace-format +msgid "" +"#### AI Review 结果: \n" +" {review_result}\n" +"\n" +msgstr "" + +#: biz/event/event_manager.py:79 +#, python-brace-format +msgid "{project_name} Push Event" +msgstr "" + +#: biz/github/webhook_handler.py:26 +msgid "Detected file deletion via status field: {}" +msgstr "" + +#: biz/github/webhook_handler.py:42 +msgid "SUPPORTED_EXTENSIONS: {}" +msgstr "" + +#: biz/github/webhook_handler.py:43 +msgid "After filtering deleted files: {}" +msgstr "" + +#: biz/github/webhook_handler.py:54 +msgid "After filtering by extension: {}" +msgstr "" + +#: biz/github/webhook_handler.py:84 +msgid "Invalid event type: {}. Only 'pull_request' event is supported now." +msgstr "" + +#: biz/github/webhook_handler.py:99 +#, python-brace-format +msgid "" +"Get changes response from GitHub (attempt {attempt}): " +"{response_status_code}, {response_text}, URL: {url}" +msgstr "" + +#: biz/github/webhook_handler.py:119 +#, python-brace-format +msgid "" +"Changes is empty, retrying in {retry_delay} seconds... (attempt {attempt}/" +"{max_retries}), URL: {url}" +msgstr "" + +#: biz/github/webhook_handler.py:123 +#, python-brace-format +msgid "" +"Failed to get changes from GitHub (URL: {url}): {response.status_code}, " +"{response.text}" +msgstr "" + +#: biz/github/webhook_handler.py:126 biz/gitlab/webhook_handler.py:111 +msgid "Max retries ({}) reached. Changes is still empty." +msgstr "" + +#: biz/github/webhook_handler.py:141 +msgid "Get commits response from GitHub: {}, {}" +msgstr "" + +#: biz/github/webhook_handler.py:161 biz/gitlab/webhook_handler.py:130 +msgid "Failed to get commits: {}, {}" +msgstr "" + +#: biz/github/webhook_handler.py:174 +#, python-brace-format +msgid "Add comment to GitHub PR {url}: {response_status_code}, {response_text}" +msgstr "" + +#: biz/github/webhook_handler.py:176 +msgid "Comment successfully added to pull request." +msgstr "" + +#: biz/github/webhook_handler.py:178 biz/github/webhook_handler.py:249 +msgid "Failed to add comment: {}" +msgstr "" + +#: biz/github/webhook_handler.py:207 biz/github/webhook_handler.py:319 +#: biz/gitlab/webhook_handler.py:178 biz/gitlab/webhook_handler.py:267 +msgid "Invalid event type: {}. Only 'push' event is supported now." +msgstr "" + +#: biz/github/webhook_handler.py:221 biz/gitlab/webhook_handler.py:192 +msgid "Collected {} commits from push event." +msgstr "" + +#: biz/github/webhook_handler.py:227 biz/gitlab/webhook_handler.py:198 +msgid "No commits found to add notes to." +msgstr "" + +#: biz/github/webhook_handler.py:233 biz/gitlab/webhook_handler.py:204 +msgid "Last commit ID not found." +msgstr "" + +#: biz/github/webhook_handler.py:245 +#, python-brace-format +msgid "" +"Add comment to commit {last_commit_id}: {response_status_code}, " +"{response_text}" +msgstr "" + +#: biz/github/webhook_handler.py:247 +msgid "Comment successfully added to push commit." +msgstr "" + +#: biz/github/webhook_handler.py:261 +#, python-brace-format +msgid "" +"Get commits response from GitHub for repository_commits: " +"{response_status_code}, {response_text}, URL: {url}" +msgstr "" + +#: biz/github/webhook_handler.py:268 +#, python-brace-format +msgid "" +"Failed to get commits for sha {sha}: {response_status_code}, {response_text}" +msgstr "" + +#: biz/github/webhook_handler.py:279 +#, python-brace-format +msgid "" +"Get commit response from GitHub: {response_status_code}, {response_text}, " +"URL: {url}" +msgstr "" + +#: biz/github/webhook_handler.py:295 +#, python-brace-format +msgid "" +"Get changes response from GitHub for repository_compare: " +"{response_status_code}, {response_text}, URL: {url}" +msgstr "" + +#: biz/github/webhook_handler.py:313 +#, python-brace-format +msgid "" +"Failed to get changes for repository_compare: {response.status_code}, " +"{response.text}" +msgstr "" + +#: biz/github/webhook_handler.py:324 biz/gitlab/webhook_handler.py:272 +msgid "No commits found in push event." +msgstr "" + +#: biz/github/webhook_handler.py:346 +msgid "" +"before or after not found in webhook data, trying to get changes from " +"commits." +msgstr "" + +#: biz/gitlab/webhook_handler.py:82 +msgid "Invalid event type: {}. Only 'merge_request' event is supported now." +msgstr "" + +#: biz/gitlab/webhook_handler.py:97 +msgid "Get changes response from GitLab (attempt {}): {}, {}, URL: {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:105 +msgid "Changes is empty, retrying in {} seconds... (attempt {}/{}), URL: {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:108 +msgid "Failed to get changes from GitLab (URL: {}): {}, {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:126 +msgid "Get commits response from gitlab: {}, {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:144 +msgid "Add notes to gitlab {}: {}, {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:148 +msgid "Failed to add note: {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:232 +msgid "" +"Get commits response from GitLab for repository_commits: {response." +"status_code}, {}, URL: {url}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:255 +msgid "" +"Get changes response from GitLab for repository_compare: {}, {response." +"text}, URL: {}" +msgstr "" + +#: biz/gitlab/webhook_handler.py:261 +msgid "Failed to get changes for repository_compare: {}, {}" +msgstr "" + +#: biz/llm/client/deepseek.py:19 biz/llm/client/openai.py:18 +#: biz/llm/client/zhipuai.py:17 +msgid "" +"API key is required. Please provide it or set it in the environment " +"variables." +msgstr "" + +#: biz/llm/client/deepseek.py:30 +#, python-brace-format +msgid "Sending request to DeepSeek API. Model: {model}, Messages: {messages}" +msgstr "" + +#: biz/llm/client/deepseek.py:39 +msgid "Empty response from DeepSeek API" +msgstr "" + +#: biz/llm/client/deepseek.py:40 +msgid "AI服务返回为空,请稍后重试" +msgstr "" + +#: biz/llm/client/deepseek.py:45 +#, python-brace-format +msgid "DeepSeek API error: {e}" +msgstr "" + +#: biz/llm/client/deepseek.py:48 +msgid "DeepSeek API认证失败,请检查API密钥是否正确" +msgstr "" + +#: biz/llm/client/deepseek.py:50 +msgid "DeepSeek API接口未找到,请检查API地址是否正确" +msgstr "" + +#: biz/llm/client/deepseek.py:52 +#, python-brace-format +msgid "调用DeepSeek API时出错: {e}" +msgstr "" + +#: biz/llm/factory.py:29 +#, python-brace-format +msgid "Unknown chat model provider: {provider}" +msgstr "" + +#: biz/queue/worker.py:30 biz/queue/worker.py:95 biz/queue/worker.py:138 +#: biz/queue/worker.py:202 +msgid "Failed to get commits" +msgstr "" + +#: biz/queue/worker.py:41 biz/queue/worker.py:149 +msgid "未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。" +msgstr "" + +#: biz/queue/worker.py:42 biz/queue/worker.py:150 +msgid "关注的文件没有修改" +msgstr "" + +#: biz/queue/worker.py:49 biz/queue/worker.py:103 biz/queue/worker.py:210 +msgid "" +"Auto Review Result: \n" +"{}" +msgstr "" + +#: biz/queue/worker.py:64 biz/queue/worker.py:171 biz/queue/worker.py:228 +msgid "服务出现未知错误: {}" +msgstr "" + +#: biz/queue/worker.py:66 biz/queue/worker.py:129 biz/queue/worker.py:173 +#: biz/queue/worker.py:230 +msgid "出现未知错误: {}" +msgstr "" + +#: biz/queue/worker.py:81 +msgid "Merge Request Hook event received" +msgstr "" + +#: biz/queue/worker.py:86 biz/queue/worker.py:146 biz/queue/worker.py:193 +msgid "changes: {}" +msgstr "" + +#: biz/queue/worker.py:89 biz/queue/worker.py:196 +msgid "未检测到有关代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。" +msgstr "" + +#: biz/queue/worker.py:124 +msgid "Merge Request Hook event, action={}, ignored." +msgstr "" + +#: biz/queue/worker.py:127 +msgid "AI Code Review 服务出现未知错误: {}" +msgstr "" + +#: biz/queue/worker.py:135 +msgid "GitHub Push event received" +msgstr "" + +#: biz/queue/worker.py:188 +msgid "GitHub Pull Request event received" +msgstr "" + +#: biz/utils/code_reviewer.py:45 +#, python-brace-format +msgid "加载提示词配置失败: {e}" +msgstr "" + +#: biz/utils/code_reviewer.py:46 +#, python-brace-format +msgid "提示词配置加载失败: {e}" +msgstr "" + +#: biz/utils/code_reviewer.py:50 +#, python-brace-format +msgid "向 AI 发送代码 Review 请求, messages: {messages}" +msgstr "" + +#: biz/utils/code_reviewer.py:52 +#, python-brace-format +msgid "收到 AI 返回结果: {review_result}" +msgstr "" + +#: biz/utils/code_reviewer.py:79 +msgid "代码为空, diffs_text = {}" +msgstr "" + +#: biz/utils/code_reviewer.py:80 +msgid "代码为空" +msgstr "" + +#: biz/utils/code_reviewer.py:111 +msgid "总分[::]\\s*\\**(\\d+)分?" +msgstr "" + +#: biz/utils/config_checker.py:36 +msgid "缺少环境变量: {}" +msgstr "" + +#: biz/utils/config_checker.py:38 +msgid "所有必要的环境变量均已设置。" +msgstr "" + +#: biz/utils/config_checker.py:46 +msgid "LLM_PROVIDER 未设置!" +msgstr "" + +#: biz/utils/config_checker.py:50 +msgid "LLM_PROVIDER 值错误,应为 {} 之一。" +msgstr "" + +#: biz/utils/config_checker.py:57 +msgid "当前 LLM 供应商为 {},但缺少必要的环境变量: {}" +msgstr "" + +#: biz/utils/config_checker.py:59 +msgid "LLM 供应商 {} 的配置项已设置。" +msgstr "" + +#: biz/utils/config_checker.py:64 +msgid "正在检查 LLM 供应商的连接..." +msgstr "" + +#: biz/utils/config_checker.py:66 +msgid "LLM 可以连接成功。" +msgstr "" + +#: biz/utils/config_checker.py:68 +msgid "LLM连接可能有问题,请检查配置项。" +msgstr "" + +#: biz/utils/config_checker.py:73 +msgid "开始检查配置项..." +msgstr "" + +#: biz/utils/config_checker.py:77 +msgid "配置项检查完成。" +msgstr "" + +#: biz/utils/im/dingtalk.py:30 +msgid "未提供项目名称,且未设置默认的钉钉 Webhook URL。" +msgstr "" + +#: biz/utils/im/dingtalk.py:49 +msgid "未找到项目 '{}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。" +msgstr "" + +#: biz/utils/im/dingtalk.py:53 +msgid "钉钉推送未启用" +msgstr "" + +#: biz/utils/im/dingtalk.py:86 +msgid "钉钉消息发送成功! webhook_url: {}" +msgstr "" + +#: biz/utils/im/dingtalk.py:88 +msgid "钉钉消息发送失败! webhook_url: {}, errmsg: {}" +msgstr "" + +#: biz/utils/im/dingtalk.py:90 +msgid "钉钉消息发送失败!" +msgstr "" + +#: biz/utils/im/feishu.py:28 +msgid "未提供项目名称,且未设置默认的 飞书 Webhook URL。" +msgstr "" + +#: biz/utils/im/feishu.py:47 +#, python-brace-format +msgid "" +"未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 " +"Webhook URL。" +msgstr "" + +#: biz/utils/im/feishu.py:59 +msgid "飞书推送未启用" +msgstr "" + +#: biz/utils/im/feishu.py:119 +#, python-brace-format +msgid "飞书消息发送失败! webhook_url: {post_url}, error_msg: {post_url}" +msgstr "" + +#: biz/utils/im/feishu.py:124 +#, python-brace-format +msgid "发送飞书消息失败! webhook_url: {post_url}, errmsg: {result}" +msgstr "" + +#: biz/utils/im/feishu.py:126 +#, python-brace-format +msgid "飞书消息发送成功! webhook_url: {post_url}" +msgstr "" + +#: biz/utils/im/feishu.py:129 +msgid "飞书消息发送失败!" +msgstr "" + +#: biz/utils/im/notifier.py:9 +msgid "通知" +msgstr "" + +#: biz/utils/im/wecom.py:30 +msgid "未提供项目名称,且未设置默认的企业微信 Webhook URL。" +msgstr "" + +#: biz/utils/im/wecom.py:49 +msgid "" +"未找到项目 '{}' 对应的企业微信 Webhook URL,且未设置默认的 Webhook URL。" +msgstr "" + +#: biz/utils/im/wecom.py:82 +msgid "企业微信推送未启用" +msgstr "" + +#: biz/utils/im/wecom.py:90 +#, python-brace-format +msgid "发送企业微信消息: url={post_url}, data={data}" +msgstr "" + +#: biz/utils/im/wecom.py:94 +msgid "企业微信消息发送失败! webhook_url:{}, error_msg:{}" +msgstr "" + +#: biz/utils/im/wecom.py:96 +msgid "Markdown 消息过长,尝试发送纯文本" +msgstr "" + +#: biz/utils/im/wecom.py:100 +msgid "企业微信消息发送成功! webhook_url: {}" +msgstr "" + +#: biz/utils/im/wecom.py:103 +msgid "企业微信消息发送失败!" +msgstr "" + +#: biz/utils/im/wecom.py:112 +#, python-brace-format +msgid "企业微信消息发送请求失败! url:{url}, error: {e}" +msgstr "" + +#: biz/utils/im/wecom.py:114 +#, python-brace-format +msgid "企业微信返回的 JSON 解析失败! url:{url}, error: {e}" +msgstr "" + +#: biz/utils/reporter.py:13 +msgid "" +"下面是以json格式记录员工代码提交信息。请总结这些信息,生成每个员工的工作日报" +"摘要。员工姓名直接用json内容中的author属性值,不要进行转换。特别要求:以" +"Markdown格式返回。\n" +"{}" +msgstr "" + +#: ui.py:56 ui.py:71 +msgid "登录" +msgstr "登录" + +#: ui.py:60 +msgid "" +"安全提示:检测到默认用户名和密码为 'admin',存在安全风险!\n" +"\n" +"请立即修改:\n" +"1. 打开 `.env` 文件\n" +"2. 修改 `DASHBOARD_USER` 和 `DASHBOARD_PASSWORD` 变量\n" +"3. 保存并重启应用" +msgstr "" + +#: ui.py:66 +msgid "当前用户名: `{}`, 当前密码: `{}`" +msgstr "" + +#: ui.py:68 ui.py:211 +msgid "用户名" +msgstr "" + +#: ui.py:69 +msgid "密码" +msgstr "" + +#: ui.py:77 +msgid "用户名或密码错误" +msgstr "" + +#: ui.py:83 ui.py:108 ui.py:133 ui.py:157 +msgid "没有数据可供展示" +msgstr "" + +#: ui.py:180 +msgid "#### 审查日志" +msgstr "" + +#: ui.py:189 +msgid "Merge Request" +msgstr "" + +#: ui.py:189 +msgid "Push" +msgstr "" + +#: ui.py:197 +msgid "开始日期" +msgstr "" + +#: ui.py:199 +msgid "结束日期" +msgstr "" + +#: ui.py:213 +msgid "项目名" +msgstr "" + +#: ui.py:228 +msgid "**总记录数:** {},**平均分:** {}" +msgstr "" + +#: ui.py:233 +msgid "项目提交次数" +msgstr "" + +#: ui.py:236 +msgid "项目平均分数" +msgstr "" + +#: ui.py:239 +msgid "人员提交次数" +msgstr "" + +#: ui.py:242 +msgid "人员平均分数" +msgstr "" + +#: ui.py:257 +msgid "查看" +msgstr "" diff --git a/conf/prompt_templates.yml b/locales/zh_CN/prompt_templates.yml similarity index 100% rename from conf/prompt_templates.yml rename to locales/zh_CN/prompt_templates.yml diff --git a/translations_compile.sh b/translations_compile.sh new file mode 100644 index 0000000..3738e3c --- /dev/null +++ b/translations_compile.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# This script compiles messages.po translation files to messages.mo. + +# Compile the .po files into .mo files. +for lang in locales/*; do + if [ -f "$lang/LC_MESSAGES/messages.po" ]; then + msgfmt $lang/LC_MESSAGES/messages.po -o $lang/LC_MESSAGES/messages.mo + fi +done \ No newline at end of file diff --git a/translations_update.sh b/translations_update.sh new file mode 100644 index 0000000..4433bc4 --- /dev/null +++ b/translations_update.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# This script updates .po translation files. + +# Generate the .pot file. +shopt -s globstar +xgettext -d messages -o messages.pot **/*.py + +# Merge the .pot file into existing .po file for each language in locales directory (if exists): +for lang in locales/*; do + if [ -f "$lang/LC_MESSAGES/messages.po" ]; then + msgmerge --no-fuzzy-matching -U $lang/LC_MESSAGES/messages.po messages.pot + else + msginit --no-translator -i messages.pot -o $lang/LC_MESSAGES/messages.po -l $lang + fi +done + +# Cleanup +rm messages.pot \ No newline at end of file diff --git a/ui.py b/ui.py index d2fd8e7..d0dcf09 100644 --- a/ui.py +++ b/ui.py @@ -9,6 +9,10 @@ load_dotenv("conf/.env") +from biz.utils.i18n import get_translator + +_ = get_translator() + # 从环境变量中读取用户名和密码 DASHBOARD_USER = os.getenv("DASHBOARD_USER", "admin") DASHBOARD_PASSWORD = os.getenv("DASHBOARD_PASSWORD", "admin") @@ -49,34 +53,34 @@ def login_page(): # 使用 st.columns 创建居中布局 col1, col2, col3 = st.columns([1, 2, 1]) with col2: - st.title("登录") + st.title(_("登录")) # 如果用户名和密码都为 'admin',提示用户修改密码 if DASHBOARD_USER == "admin" and DASHBOARD_PASSWORD == "admin": - st.warning( + st.warning(_( "安全提示:检测到默认用户名和密码为 'admin',存在安全风险!\n\n" "请立即修改:\n" "1. 打开 `.env` 文件\n" "2. 修改 `DASHBOARD_USER` 和 `DASHBOARD_PASSWORD` 变量\n" "3. 保存并重启应用" - ) - st.write(f"当前用户名: `{DASHBOARD_USER}`, 当前密码: `{DASHBOARD_PASSWORD}`") + )) + st.write(_("当前用户名: `{}`, 当前密码: `{}`").format(DASHBOARD_USER, DASHBOARD_PASSWORD)) - username = st.text_input("用户名") - password = st.text_input("密码", type="password") + username = st.text_input(_("用户名")) + password = st.text_input(_("密码"), type="password") - if st.button("登录"): + if st.button(_("登录")): if authenticate(username, password): st.session_state["authenticated"] = True st.session_state["username"] = username st.rerun() # 重新运行应用以显示主要内容 else: - st.error("用户名或密码错误") + st.error(_("用户名或密码错误")) # 生成项目提交数量图表 def generate_project_count_chart(df): if df.empty: - st.info("没有数据可供展示") + st.info(_("没有数据可供展示")) return # 计算每个项目的提交数量 @@ -101,7 +105,7 @@ def generate_project_count_chart(df): # 生成项目平均分数图表 def generate_project_score_chart(df): if df.empty: - st.info("没有数据可供展示") + st.info(_("没有数据可供展示")) return # 计算每个项目的平均分数 @@ -126,7 +130,7 @@ def generate_project_score_chart(df): # 生成人员提交数量图表 def generate_author_count_chart(df): if df.empty: - st.info("没有数据可供展示") + st.info(_("没有数据可供展示")) return # 计算每个人员的提交数量 @@ -150,7 +154,7 @@ def generate_author_count_chart(df): # 生成人员平均分数图表 def generate_author_score_chart(df): if df.empty: - st.info("没有数据可供展示") + st.info(_("没有数据可供展示")) return # 计算每个人员的平均分数 @@ -173,7 +177,7 @@ def generate_author_score_chart(df): # 主要内容 def main_page(): - st.markdown("#### 审查日志") + st.markdown(_("#### 审查日志")) current_date = datetime.date.today() start_date_default = current_date - datetime.timedelta(days=7) @@ -182,7 +186,7 @@ def main_page(): show_push_tab = os.environ.get('PUSH_REVIEW_ENABLED', '0') == '1' if show_push_tab: - mr_tab, push_tab = st.tabs(["Merge Request", "Push"]) + mr_tab, push_tab = st.tabs([_("Merge Request"), _("Push")]) else: mr_tab = st.container() @@ -190,9 +194,9 @@ def display_data(tab, service_func, columns, column_config): with tab: col1, col2, col3, col4 = st.columns(4) with col1: - start_date = st.date_input("开始日期", start_date_default, key=f"{tab}_start_date") + start_date = st.date_input(_("开始日期"), start_date_default, key=f"{tab}_start_date") with col2: - end_date = st.date_input("结束日期", current_date, key=f"{tab}_end_date") + end_date = st.date_input(_("结束日期"), current_date, key=f"{tab}_end_date") start_datetime = datetime.datetime.combine(start_date, datetime.time.min) end_datetime = datetime.datetime.combine(end_date, datetime.time.max) @@ -204,9 +208,9 @@ def display_data(tab, service_func, columns, column_config): unique_authors = sorted(df["author"].dropna().unique().tolist()) if not df.empty else [] unique_projects = sorted(df["project_name"].dropna().unique().tolist()) if not df.empty else [] with col3: - authors = st.multiselect("用户名", unique_authors, default=[], key=f"{tab}_authors") + authors = st.multiselect(_("用户名"), unique_authors, default=[], key=f"{tab}_authors") with col4: - project_names = st.multiselect("项目名", unique_projects, default=[], key=f"{tab}_projects") + project_names = st.multiselect(_("项目名"), unique_projects, default=[], key=f"{tab}_projects") data = get_data(service_func, authors=authors, project_names=project_names, updated_at_gte=int(start_datetime.timestamp()), @@ -221,21 +225,21 @@ def display_data(tab, service_func, columns, column_config): total_records = len(df) average_score = df["score"].mean() if not df.empty else 0 - st.markdown(f"**总记录数:** {total_records},**平均分:** {average_score:.2f}") + st.markdown(_("**总记录数:** {},**平均分:** {}").format(total_records, f'{average_score:.2f}')) # 创建2x2网格布局展示四个图表 row1, row2, row3, row4 = st.columns(4) with row1: - st.markdown("
项目提交次数
", unsafe_allow_html=True) + st.markdown("
" + _("项目提交次数") + "
", unsafe_allow_html=True) generate_project_count_chart(df) with row2: - st.markdown("
项目平均分数
", unsafe_allow_html=True) + st.markdown("
" + _("项目平均分数") + "
", unsafe_allow_html=True) generate_project_score_chart(df) with row3: - st.markdown("
人员提交次数
", unsafe_allow_html=True) + st.markdown("
" + _("人员提交次数") + "
", unsafe_allow_html=True) generate_author_count_chart(df) with row4: - st.markdown("
人员平均分数
", unsafe_allow_html=True) + st.markdown("
" + _("人员平均分数") + "
", unsafe_allow_html=True) generate_author_score_chart(df) # Merge Request 数据展示 @@ -250,7 +254,7 @@ def display_data(tab, service_func, columns, column_config): ), "url": st.column_config.LinkColumn( max_chars=100, - display_text=r"查看" + display_text=_(r"查看") ), }