- Remove |safe from |tojson in HTML attributes (x-data) - quotes must become " for browsers to parse correctly - Update LANG-002 and LANG-003 architecture rules to document correct |tojson usage patterns: - HTML attributes: |tojson (no |safe) - Script blocks: |tojson|safe - Fix validator to warn when |tojson|safe is used in x-data (breaks HTML attribute parsing) - Improve code quality across services, APIs, and tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
181 lines
4.6 KiB
Python
181 lines
4.6 KiB
Python
# app/api/v1/shared/language.py
|
|
"""
|
|
Language API endpoints for setting user/customer language preferences.
|
|
|
|
These endpoints handle:
|
|
- Setting language preference via cookie
|
|
- Getting current language info
|
|
- Listing available languages
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Request, Response
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.utils.i18n import (
|
|
DEFAULT_LANGUAGE,
|
|
LANGUAGE_FLAGS,
|
|
LANGUAGE_NAMES,
|
|
LANGUAGE_NAMES_EN,
|
|
SUPPORTED_LANGUAGES,
|
|
get_language_info,
|
|
)
|
|
from middleware.language import LANGUAGE_COOKIE_NAME, set_language_cookie
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/language", tags=["language"])
|
|
|
|
|
|
class SetLanguageRequest(BaseModel):
|
|
"""Request body for setting language preference."""
|
|
|
|
language: str = Field(
|
|
...,
|
|
description="Language code (en, fr, de, lb)",
|
|
min_length=2,
|
|
max_length=5,
|
|
)
|
|
|
|
|
|
class SetLanguageResponse(BaseModel):
|
|
"""Response after setting language preference."""
|
|
|
|
success: bool
|
|
language: str
|
|
message: str
|
|
|
|
|
|
class LanguageInfo(BaseModel):
|
|
"""Information about a single language."""
|
|
|
|
code: str
|
|
name: str
|
|
name_en: str
|
|
flag: str
|
|
|
|
|
|
class LanguageListResponse(BaseModel):
|
|
"""Response listing all available languages."""
|
|
|
|
languages: list[LanguageInfo]
|
|
current: str
|
|
default: str
|
|
|
|
|
|
class CurrentLanguageResponse(BaseModel):
|
|
"""Response with current language information."""
|
|
|
|
code: str
|
|
name: str
|
|
name_en: str
|
|
flag: str
|
|
source: str # Where the language was determined from (cookie, browser, default)
|
|
|
|
|
|
# public - Language preference can be set without authentication
|
|
@router.post("/set", response_model=SetLanguageResponse)
|
|
async def set_language(
|
|
request: Request,
|
|
response: Response,
|
|
body: SetLanguageRequest,
|
|
) -> SetLanguageResponse:
|
|
"""
|
|
Set the user's language preference.
|
|
|
|
This sets a cookie that will be used for subsequent requests.
|
|
The page should be reloaded after calling this endpoint.
|
|
"""
|
|
language = body.language.lower()
|
|
|
|
if language not in SUPPORTED_LANGUAGES:
|
|
return SetLanguageResponse(
|
|
success=False,
|
|
language=language,
|
|
message=f"Unsupported language: {language}. Supported: {', '.join(SUPPORTED_LANGUAGES)}",
|
|
)
|
|
|
|
# Set language cookie
|
|
set_language_cookie(response, language)
|
|
|
|
logger.info(f"Language preference set to: {language}")
|
|
|
|
return SetLanguageResponse(
|
|
success=True,
|
|
language=language,
|
|
message=f"Language set to {LANGUAGE_NAMES.get(language, language)}",
|
|
)
|
|
|
|
|
|
@router.get("/current", response_model=CurrentLanguageResponse)
|
|
async def get_current_language(request: Request) -> CurrentLanguageResponse:
|
|
"""
|
|
Get the current language for this request.
|
|
|
|
Returns information about the detected language and where it came from.
|
|
"""
|
|
# Get language from request state (set by middleware)
|
|
language = getattr(request.state, "language", DEFAULT_LANGUAGE)
|
|
language_info = getattr(request.state, "language_info", {})
|
|
|
|
# Determine source
|
|
source = "default"
|
|
if language_info.get("cookie"):
|
|
source = "cookie"
|
|
elif language_info.get("browser"):
|
|
source = "browser"
|
|
|
|
info = get_language_info(language)
|
|
|
|
return CurrentLanguageResponse(
|
|
code=info["code"],
|
|
name=info["name"],
|
|
name_en=info["name_en"],
|
|
flag=info["flag"],
|
|
source=source,
|
|
)
|
|
|
|
|
|
@router.get("/list", response_model=LanguageListResponse)
|
|
async def list_languages(request: Request) -> LanguageListResponse:
|
|
"""
|
|
List all available languages.
|
|
|
|
Returns all supported languages with their display names.
|
|
"""
|
|
current = getattr(request.state, "language", DEFAULT_LANGUAGE)
|
|
|
|
languages = [
|
|
LanguageInfo(
|
|
code=code,
|
|
name=LANGUAGE_NAMES.get(code, code),
|
|
name_en=LANGUAGE_NAMES_EN.get(code, code),
|
|
flag=LANGUAGE_FLAGS.get(code, ""),
|
|
)
|
|
for code in SUPPORTED_LANGUAGES
|
|
]
|
|
|
|
return LanguageListResponse(
|
|
languages=languages,
|
|
current=current,
|
|
default=DEFAULT_LANGUAGE,
|
|
)
|
|
|
|
|
|
# public - Language preference clearing doesn't require authentication
|
|
@router.delete("/clear")
|
|
async def clear_language(response: Response) -> SetLanguageResponse:
|
|
"""
|
|
Clear the language preference cookie.
|
|
|
|
After clearing, the language will be determined from browser settings or defaults.
|
|
"""
|
|
response.delete_cookie(key=LANGUAGE_COOKIE_NAME)
|
|
|
|
return SetLanguageResponse(
|
|
success=True,
|
|
language=DEFAULT_LANGUAGE,
|
|
message="Language preference cleared. Using browser/default language.",
|
|
)
|