From 7c07237544e41288326d5e9d013507d82996eca3 Mon Sep 17 00:00:00 2001 From: ghostubborn Date: Wed, 1 Apr 2026 16:55:51 +0800 Subject: [PATCH] fix(i18n): pass locale to background threads via thread-local storage Background threads (graph building, simulation prep, report generation, profile generation) now inherit the requesting user's locale preference. Previously these fell back to 'zh' because Flask request context was unavailable in spawned threads. --- backend/app/api/graph.py | 6 +++++- backend/app/api/report.py | 6 +++++- backend/app/api/simulation.py | 6 +++++- backend/app/services/graph_builder.py | 11 ++++++++--- backend/app/services/oasis_profile_generator.py | 6 +++++- backend/app/services/simulation_runner.py | 9 +++++++-- backend/app/services/zep_graph_memory_updater.py | 10 ++++++++-- backend/app/utils/__init__.py | 4 ++-- backend/app/utils/locale.py | 10 +++++++++- 9 files changed, 54 insertions(+), 14 deletions(-) diff --git a/backend/app/api/graph.py b/backend/app/api/graph.py index bcd6c5f..759ff48 100644 --- a/backend/app/api/graph.py +++ b/backend/app/api/graph.py @@ -15,7 +15,7 @@ from ..services.graph_builder import GraphBuilderService from ..services.text_processor import TextProcessor from ..utils.file_parser import FileParser from ..utils.logger import get_logger -from ..utils.locale import t +from ..utils.locale import t, get_locale, set_locale from ..models.task import TaskManager, TaskStatus from ..models.project import ProjectManager, ProjectStatus @@ -371,8 +371,12 @@ def build_graph(): project.graph_build_task_id = task_id ProjectManager.save_project(project) + # Capture locale before spawning background thread + current_locale = get_locale() + # 启动后台任务 def build_task(): + set_locale(current_locale) build_logger = get_logger('mirofish.build') try: build_logger.info(f"[{task_id}] 开始构建图谱...") diff --git a/backend/app/api/report.py b/backend/app/api/report.py index f803f1f..d7f2a4d 100644 --- a/backend/app/api/report.py +++ b/backend/app/api/report.py @@ -15,7 +15,7 @@ from ..services.simulation_manager import SimulationManager from ..models.project import ProjectManager from ..models.task import TaskManager, TaskStatus from ..utils.logger import get_logger -from ..utils.locale import t +from ..utils.locale import t, get_locale, set_locale logger = get_logger('mirofish.api.report') @@ -121,8 +121,12 @@ def generate_report(): } ) + # Capture locale before spawning background thread + current_locale = get_locale() + # 定义后台任务 def run_generate(): + set_locale(current_locale) try: task_manager.update_task( task_id, diff --git a/backend/app/api/simulation.py b/backend/app/api/simulation.py index 2535f13..2c1b6b1 100644 --- a/backend/app/api/simulation.py +++ b/backend/app/api/simulation.py @@ -14,7 +14,7 @@ from ..services.oasis_profile_generator import OasisProfileGenerator from ..services.simulation_manager import SimulationManager, SimulationStatus from ..services.simulation_runner import SimulationRunner, RunnerStatus from ..utils.logger import get_logger -from ..utils.locale import t +from ..utils.locale import t, get_locale, set_locale from ..models.project import ProjectManager logger = get_logger('mirofish.api.simulation') @@ -501,8 +501,12 @@ def prepare_simulation(): state.status = SimulationStatus.PREPARING manager._save_simulation_state(state) + # Capture locale before spawning background thread + current_locale = get_locale() + # 定义后台任务 def run_prepare(): + set_locale(current_locale) try: task_manager.update_task( task_id, diff --git a/backend/app/services/graph_builder.py b/backend/app/services/graph_builder.py index d642451..37c9969 100644 --- a/backend/app/services/graph_builder.py +++ b/backend/app/services/graph_builder.py @@ -17,7 +17,7 @@ from ..config import Config from ..models.task import TaskManager, TaskStatus from ..utils.zep_paging import fetch_all_nodes, fetch_all_edges from .text_processor import TextProcessor -from ..utils.locale import t +from ..utils.locale import t, get_locale, set_locale @dataclass @@ -84,10 +84,13 @@ class GraphBuilderService: } ) + # Capture locale before spawning background thread + current_locale = get_locale() + # 在后台线程中执行构建 thread = threading.Thread( target=self._build_graph_worker, - args=(task_id, text, ontology, graph_name, chunk_size, chunk_overlap, batch_size) + args=(task_id, text, ontology, graph_name, chunk_size, chunk_overlap, batch_size, current_locale) ) thread.daemon = True thread.start() @@ -102,9 +105,11 @@ class GraphBuilderService: graph_name: str, chunk_size: int, chunk_overlap: int, - batch_size: int + batch_size: int, + locale: str = 'zh' ): """图谱构建工作线程""" + set_locale(locale) try: self.task_manager.update_task( task_id, diff --git a/backend/app/services/oasis_profile_generator.py b/backend/app/services/oasis_profile_generator.py index 83f49cb..0c1d6ec 100644 --- a/backend/app/services/oasis_profile_generator.py +++ b/backend/app/services/oasis_profile_generator.py @@ -20,7 +20,7 @@ from zep_cloud.client import Zep from ..config import Config from ..utils.logger import get_logger -from ..utils.locale import get_language_instruction, t +from ..utils.locale import get_language_instruction, get_locale, set_locale, t from .zep_entity_reader import EntityNode, ZepEntityReader logger = get_logger('mirofish.oasis_profile') @@ -916,8 +916,12 @@ class OasisProfileGenerator: except Exception as e: logger.warning(f"实时保存 profiles 失败: {e}") + # Capture locale before spawning thread pool workers + current_locale = get_locale() + def generate_single_profile(idx: int, entity: EntityNode) -> tuple: """生成单个profile的工作函数""" + set_locale(current_locale) entity_type = entity.get_entity_type() or "Entity" try: diff --git a/backend/app/services/simulation_runner.py b/backend/app/services/simulation_runner.py index 8c35380..e86021f 100644 --- a/backend/app/services/simulation_runner.py +++ b/backend/app/services/simulation_runner.py @@ -20,6 +20,7 @@ from queue import Queue from ..config import Config from ..utils.logger import get_logger +from ..utils.locale import get_locale, set_locale from .zep_graph_memory_updater import ZepGraphMemoryManager from .simulation_ipc import SimulationIPCClient, CommandType, IPCResponse @@ -455,10 +456,13 @@ class SimulationRunner: cls._processes[simulation_id] = process cls._save_run_state(state) + # Capture locale before spawning monitor thread + current_locale = get_locale() + # 启动监控线程 monitor_thread = threading.Thread( target=cls._monitor_simulation, - args=(simulation_id,), + args=(simulation_id, current_locale), daemon=True ) monitor_thread.start() @@ -475,8 +479,9 @@ class SimulationRunner: return state @classmethod - def _monitor_simulation(cls, simulation_id: str): + def _monitor_simulation(cls, simulation_id: str, locale: str = 'zh'): """监控模拟进程,解析动作日志""" + set_locale(locale) sim_dir = os.path.join(cls.RUN_STATE_DIR, simulation_id) # 新的日志结构:分平台的动作日志 diff --git a/backend/app/services/zep_graph_memory_updater.py b/backend/app/services/zep_graph_memory_updater.py index a8f3cec..e034fee 100644 --- a/backend/app/services/zep_graph_memory_updater.py +++ b/backend/app/services/zep_graph_memory_updater.py @@ -16,6 +16,7 @@ from zep_cloud.client import Zep from ..config import Config from ..utils.logger import get_logger +from ..utils.locale import get_locale, set_locale logger = get_logger('mirofish.zep_graph_memory_updater') @@ -275,10 +276,14 @@ class ZepGraphMemoryUpdater: """启动后台工作线程""" if self._running: return - + + # Capture locale before spawning background thread + current_locale = get_locale() + self._running = True self._worker_thread = threading.Thread( target=self._worker_loop, + args=(current_locale,), daemon=True, name=f"ZepMemoryUpdater-{self.graph_id[:8]}" ) @@ -356,8 +361,9 @@ class ZepGraphMemoryUpdater: self.add_activity(activity) - def _worker_loop(self): + def _worker_loop(self, locale: str = 'zh'): """后台工作循环 - 按平台批量发送活动到Zep""" + set_locale(locale) while self._running or not self._activity_queue.empty(): try: # 尝试从队列获取活动(超时1秒) diff --git a/backend/app/utils/__init__.py b/backend/app/utils/__init__.py index e022abb..e70161a 100644 --- a/backend/app/utils/__init__.py +++ b/backend/app/utils/__init__.py @@ -4,7 +4,7 @@ from .file_parser import FileParser from .llm_client import LLMClient -from .locale import t, get_locale, get_language_instruction +from .locale import t, get_locale, set_locale, get_language_instruction -__all__ = ['FileParser', 'LLMClient', 't', 'get_locale', 'get_language_instruction'] +__all__ = ['FileParser', 'LLMClient', 't', 'get_locale', 'set_locale', 'get_language_instruction'] diff --git a/backend/app/utils/locale.py b/backend/app/utils/locale.py index 8da76c7..868c4f0 100644 --- a/backend/app/utils/locale.py +++ b/backend/app/utils/locale.py @@ -1,7 +1,10 @@ import json import os +import threading from flask import request, has_request_context +_thread_local = threading.local() + _locales_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'locales') # Load language registry @@ -17,10 +20,15 @@ for filename in os.listdir(_locales_dir): _translations[locale_name] = json.load(f) +def set_locale(locale: str): + """Set locale for current thread. Call at the start of background threads.""" + _thread_local.locale = locale + + def get_locale() -> str: if has_request_context(): return request.headers.get('Accept-Language', 'zh') - return 'zh' + return getattr(_thread_local, 'locale', 'zh') def t(key: str, **kwargs) -> str: