feat(i18n): persist language preference and thread through chat, memoir, TTS

- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
  only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
  names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
  for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-11 16:16:49 +08:00
parent 5ce29aad64
commit ccdc4e4277
64 changed files with 3233 additions and 208 deletions

View File

@@ -666,13 +666,21 @@ def process_memoir_phase2(
user_birth_year = None
background_voice = "default"
user_occupation = ""
user_language = "zh"
if user_obj:
user_birth_year = user_obj.birth_year
user_language = (
"en"
if str(getattr(user_obj, "language_preference", "zh") or "zh").lower()
== "en"
else "zh"
)
user_profile = format_user_profile_context(
birth_year=user_obj.birth_year,
birth_place=user_obj.birth_place,
grew_up_place=user_obj.grew_up_place,
occupation=user_obj.occupation,
language=user_language,
)
background_voice = infer_background_voice(user_obj.occupation)
user_occupation = user_obj.occupation or ""
@@ -752,6 +760,7 @@ def process_memoir_phase2(
memoir_correlation_id=cid,
llm_fast=llm_fast,
memory_evidence=memory_evidence,
language=user_language,
)
pipeline_elapsed = time.perf_counter() - pipeline_t0
@@ -931,6 +940,14 @@ def process_memoir_phase1(self, user_id: str, segment_ids: List[str]):
try:
with get_sync_db() as db:
user_obj_for_lang = db.get(User, user_id)
user_language = (
"en"
if user_obj_for_lang is not None
and str(getattr(user_obj_for_lang, "language_preference", "zh") or "zh").lower()
== "en"
else "zh"
)
stmt = (
select(Segment)
.where(Segment.id.in_(segment_ids))
@@ -1056,6 +1073,7 @@ def process_memoir_phase1(self, user_id: str, segment_ids: List[str]):
memoir_batch=True,
),
on_phase1_chunk=_phase1_chunk_cb,
language=user_language,
)
prep_elapsed = time.perf_counter() - prep_t0
merge_pipeline_run(
@@ -1273,13 +1291,21 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
user_birth_year = None
background_voice = "default"
user_occupation = ""
user_language = "zh"
if user_obj:
user_birth_year = user_obj.birth_year
user_language = (
"en"
if str(getattr(user_obj, "language_preference", "zh") or "zh").lower()
== "en"
else "zh"
)
user_profile = format_user_profile_context(
birth_year=user_obj.birth_year,
birth_place=user_obj.birth_place,
grew_up_place=user_obj.grew_up_place,
occupation=user_obj.occupation,
language=user_language,
)
background_voice = infer_background_voice(user_obj.occupation)
user_occupation = user_obj.occupation or ""
@@ -1303,6 +1329,7 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
occupation=user_occupation,
memoir_correlation_id=cid,
llm_fast=llm_fast,
language=user_language,
)
db.flush()
if chapter is None: