fix:更新协议隐私问题

This commit is contained in:
吕新雨
2026-02-25 17:42:34 +08:00
parent 7fcc64f6e3
commit 43e33eb991
3 changed files with 61 additions and 18 deletions

View File

@@ -7,7 +7,13 @@ from fastapi.responses import HTMLResponse
from pydantic import BaseModel, HttpUrl from pydantic import BaseModel, HttpUrl
from app.core.config import get_settings from app.core.config import get_settings
from app.legal_docs import PRIVACY_POLICY_MD, TERMS_OF_USE_MD, choose_content_by_lang, render_as_simple_html from app.legal_docs import (
PRIVACY_POLICY_MD,
TERMS_OF_USE_MD,
choose_content_by_lang,
render_as_simple_html,
split_bilingual_markdown,
)
router = APIRouter(prefix="/v1/legal", tags=["legal"]) router = APIRouter(prefix="/v1/legal", tags=["legal"])
@@ -64,6 +70,7 @@ Dear Mama技術支援
# 当前多语言仅支持 EN / TC繁体 # 当前多语言仅支持 EN / TC繁体
ResolvedLang = Literal["en", "tc"] ResolvedLang = Literal["en", "tc"]
BILINGUAL_CONTENT_LANGUAGE = "en, zh-Hant"
class LegalLinksResponse(BaseModel): class LegalLinksResponse(BaseModel):
@@ -132,6 +139,18 @@ def _normalize_internal_url(request: Request, url: str, internal_path: str) -> s
return _join_base_url(str(request.base_url), internal_path) return _join_base_url(str(request.base_url), internal_path)
def _build_bilingual_content(text: str) -> str:
"""
固定输出双语协议内容EN 在前TC 在后。
若缺少 TC则仅返回 EN。
"""
en, tc = split_bilingual_markdown(text)
if not tc:
return en
return f"{en}\n\n---\n{tc}"
@router.get("/links", response_model=LegalLinksResponse) @router.get("/links", response_model=LegalLinksResponse)
async def get_legal_links(request: Request) -> LegalLinksResponse: async def get_legal_links(request: Request) -> LegalLinksResponse:
""" """
@@ -156,11 +175,18 @@ async def get_privacy_policy(request: Request) -> HTMLResponse:
""" """
accept_language = request.headers.get("accept-language") accept_language = request.headers.get("accept-language")
lang = _resolve_lang(accept_language) if accept_language:
content, resolved = choose_content_by_lang(PRIVACY_POLICY_MD, lang) lang = _resolve_lang(accept_language)
title = "Dear Mama | Privacy Policy" if resolved == "en" else "Dear Mama隱私權政策" content, resolved = choose_content_by_lang(PRIVACY_POLICY_MD, lang)
page = render_as_simple_html(title=title, content=content) title = "Dear Mama | Privacy Policy" if resolved == "en" else "Dear Mama隱私權政策"
return HTMLResponse(content=page, headers={"Content-Language": "en" if resolved == "en" else "zh-Hant"}) html_lang = "en" if resolved == "en" else "zh-Hant"
page = render_as_simple_html(title=title, content=content, html_lang=html_lang)
return HTMLResponse(content=page, headers={"Content-Language": html_lang})
content = _build_bilingual_content(PRIVACY_POLICY_MD)
title = "Dear Mama | Privacy Policy / 隱私權政策"
page = render_as_simple_html(title=title, content=content, html_lang="en")
return HTMLResponse(content=page, headers={"Content-Language": BILINGUAL_CONTENT_LANGUAGE})
@router.get("/terms", response_class=HTMLResponse) @router.get("/terms", response_class=HTMLResponse)
@@ -170,11 +196,18 @@ async def get_terms_of_use(request: Request) -> HTMLResponse:
""" """
accept_language = request.headers.get("accept-language") accept_language = request.headers.get("accept-language")
lang = _resolve_lang(accept_language) if accept_language:
content, resolved = choose_content_by_lang(TERMS_OF_USE_MD, lang) lang = _resolve_lang(accept_language)
title = "Dear Mama Terms of Use" if resolved == "en" else "Dear Mama 使用條款" content, resolved = choose_content_by_lang(TERMS_OF_USE_MD, lang)
page = render_as_simple_html(title=title, content=content) title = "Dear Mama Terms of Use" if resolved == "en" else "Dear Mama 使用條款"
return HTMLResponse(content=page, headers={"Content-Language": "en" if resolved == "en" else "zh-Hant"}) html_lang = "en" if resolved == "en" else "zh-Hant"
page = render_as_simple_html(title=title, content=content, html_lang=html_lang)
return HTMLResponse(content=page, headers={"Content-Language": html_lang})
content = _build_bilingual_content(TERMS_OF_USE_MD)
title = "Dear Mama Terms of Use / 使用條款"
page = render_as_simple_html(title=title, content=content, html_lang="en")
return HTMLResponse(content=page, headers={"Content-Language": BILINGUAL_CONTENT_LANGUAGE})
@router.get("/support", response_class=HTMLResponse) @router.get("/support", response_class=HTMLResponse)
@@ -184,9 +217,16 @@ async def get_support_page(request: Request) -> HTMLResponse:
""" """
accept_language = request.headers.get("accept-language") accept_language = request.headers.get("accept-language")
lang = _resolve_lang(accept_language) if accept_language:
content, resolved = choose_content_by_lang(SUPPORT_MD, lang) lang = _resolve_lang(accept_language)
title = "Dear Mama | Technical Support" if resolved == "en" else "Dear Mama技術支援" content, resolved = choose_content_by_lang(SUPPORT_MD, lang)
page = render_as_simple_html(title=title, content=content) title = "Dear Mama | Technical Support" if resolved == "en" else "Dear Mama技術支援"
return HTMLResponse(content=page, headers={"Content-Language": "en" if resolved == "en" else "zh-Hant"}) html_lang = "en" if resolved == "en" else "zh-Hant"
page = render_as_simple_html(title=title, content=content, html_lang=html_lang)
return HTMLResponse(content=page, headers={"Content-Language": html_lang})
content = _build_bilingual_content(SUPPORT_MD)
title = "Dear Mama | Technical Support / 技術支援"
page = render_as_simple_html(title=title, content=content, html_lang="en")
return HTMLResponse(content=page, headers={"Content-Language": BILINGUAL_CONTENT_LANGUAGE})

View File

@@ -265,7 +265,7 @@ def choose_content_by_lang(text: str, lang: ResolvedLang) -> tuple[str, Resolved
return en, "en" return en, "en"
def render_as_simple_html(title: str, content: str) -> str: def render_as_simple_html(title: str, content: str, html_lang: str = "en") -> str:
""" """
将文本以简单 HTML 的方式展示(使用 pre 保留换行并自动换行)。 将文本以简单 HTML 的方式展示(使用 pre 保留换行并自动换行)。
不做 Markdown 渲染,避免引入额外依赖,确保“最小可用、必有内容”。 不做 Markdown 渲染,避免引入额外依赖,确保“最小可用、必有内容”。
@@ -273,8 +273,9 @@ def render_as_simple_html(title: str, content: str) -> str:
safe_title = html.escape(title) safe_title = html.escape(title)
safe_content = html.escape(content) safe_content = html.escape(content)
safe_lang = html.escape(html_lang or "en")
return f"""<!doctype html> return f"""<!doctype html>
<html lang="en"> <html lang="{safe_lang}">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />

View File

@@ -203,6 +203,8 @@
- `GET /v1/legal/privacy` - `GET /v1/legal/privacy`
- `GET /v1/legal/terms` - `GET /v1/legal/terms`
- `GET /v1/legal/support`(技术支持页面,提供审核可用的公开支持信息) - `GET /v1/legal/support`(技术支持页面,提供审核可用的公开支持信息)
- 协议展示策略调整:`/v1/legal/privacy``/v1/legal/terms``/v1/legal/support` 在携带 `Accept-Language` 时按语言单语展示EN/TC缺省时展示 EN + TCEN 在前、TC 在后)
- 协议页面语义修复HTML 根节点 `lang` 属性不再写死,改为随页面实际语言输出(单语 en/zh-Hant双语默认 en
- 客户端新增协议接口封装 `client/src/services/legalApi.ts` - 客户端新增协议接口封装 `client/src/services/legalApi.ts`
- 客户端工程化:新增统一 HTTP 封装 `client/src/utils/http.ts`baseURL/超时/JSON/统一错误),并将 `legalApi.ts` / `recoApi.ts` 接入 - 客户端工程化:新增统一 HTTP 封装 `client/src/utils/http.ts`baseURL/超时/JSON/统一错误),并将 `legalApi.ts` / `recoApi.ts` 接入
- 客户端接入两处入口:`app/(splash)/splash.tsx``components/home/ProfileModal.tsx` - 客户端接入两处入口:`app/(splash)/splash.tsx``components/home/ProfileModal.tsx`