Files
orion/app/api/v1/shared/language.py
Samir Boulahtit 9920430b9e fix: correct tojson|safe usage in templates and update validator
- 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>
2025-12-13 22:59:51 +01:00

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.",
)