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

@@ -1,7 +1,9 @@
from typing import Optional
from typing import Literal, Optional
from pydantic import BaseModel, Field
LanguagePreference = Literal["zh", "en"]
class RegisterRequest(BaseModel):
phone: str = Field(..., min_length=11, max_length=11, description="手机号11位")
@@ -9,6 +11,10 @@ class RegisterRequest(BaseModel):
nickname: str = Field(..., min_length=1, max_length=50, description="昵称")
email: Optional[str] = Field(None, description="邮箱(可选)")
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
language: Optional[LanguagePreference] = Field(
None,
description="device language at signup; only used when creating a new user",
)
class LoginRequest(BaseModel):
@@ -35,6 +41,7 @@ class UserResponse(BaseModel):
avatar_url: Optional[str] = None
subscription_type: str
created_at: str
language_preference: LanguagePreference = "zh"
class SendSmsRequest(BaseModel):
@@ -57,6 +64,10 @@ class SmsLoginRequest(BaseModel):
nickname: Optional[str] = Field(
None, max_length=50, description="昵称(注册时必填,登录时可选)"
)
language: Optional[LanguagePreference] = Field(
None,
description="device language at signup; only used when creating a new user",
)
class MockSmsLoginRequest(BaseModel):
@@ -67,6 +78,10 @@ class MockSmsLoginRequest(BaseModel):
nickname: Optional[str] = Field(
None, max_length=50, description="新用户昵称(可选)"
)
language: Optional[LanguagePreference] = Field(
None,
description="device language at signup; only used when creating a new user",
)
class SmsRegisterRequest(BaseModel):
@@ -76,6 +91,10 @@ class SmsRegisterRequest(BaseModel):
nickname: str = Field(..., min_length=1, max_length=50, description="昵称")
email: Optional[str] = Field(None, description="邮箱(可选)")
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
language: Optional[LanguagePreference] = Field(
None,
description="device language at signup; only used when creating a new user",
)
class ResetPasswordRequest(BaseModel):