"""미래 준비 — 다국어(i18n) 번역 데이터 관리.""" from __future__ import annotations import logging from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy import select, update, delete from sqlalchemy.ext.asyncio import AsyncSession from core.auth import get_current_user, require_admin_role from database import get_db from models import User, I18nEntry logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/i18n", tags=["미래준비-다국어"]) SUPPORTED_LOCALES = ["ko", "en", "ja", "zh"] class TranslationIn(BaseModel): key: str locale: str value: str namespace: str = "common" class BulkTranslationIn(BaseModel): locale: str namespace: str = "common" entries: dict[str, str] @router.get("/locales") async def list_locales(user: User = Depends(get_current_user)): """지원 로케일 목록.""" return {"locales": SUPPORTED_LOCALES} @router.get("/translations/{locale}") async def get_translations( locale: str, namespace: str = Query("common"), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """특정 로케일 + 네임스페이스 번역 데이터 반환 (JSON 맵).""" if locale not in SUPPORTED_LOCALES: raise HTTPException(400, f"지원 로케일: {SUPPORTED_LOCALES}") rows = await db.execute( select(I18nEntry).where(I18nEntry.locale == locale, I18nEntry.namespace == namespace) ) return {e.key: e.value for e in rows.scalars().all()} @router.put("/translation") async def upsert_translation( t: TranslationIn, db: AsyncSession = Depends(get_db), user: User = Depends(require_admin_role), ): """번역 항목 추가/수정.""" if t.locale not in SUPPORTED_LOCALES: raise HTTPException(400, f"지원 로케일: {SUPPORTED_LOCALES}") existing = await db.execute( select(I18nEntry).where( I18nEntry.key == t.key, I18nEntry.locale == t.locale, I18nEntry.namespace == t.namespace ).limit(1) ) entry = existing.scalar_one_or_none() if entry: entry.value = t.value entry.updated_at = datetime.utcnow() else: entry = I18nEntry( key=t.key, locale=t.locale, value=t.value, namespace=t.namespace, created_at=datetime.utcnow(), ) db.add(entry) await db.commit() return {"ok": True} @router.post("/bulk") async def bulk_import( req: BulkTranslationIn, db: AsyncSession = Depends(get_db), user: User = Depends(require_admin_role), ): """번역 데이터 일괄 임포트 (JSON 맵).""" if req.locale not in SUPPORTED_LOCALES: raise HTTPException(400, f"지원 로케일: {SUPPORTED_LOCALES}") count = 0 for key, value in req.entries.items(): existing = await db.execute( select(I18nEntry).where( I18nEntry.key == key, I18nEntry.locale == req.locale, I18nEntry.namespace == req.namespace ).limit(1) ) entry = existing.scalar_one_or_none() if entry: entry.value = value entry.updated_at = datetime.utcnow() else: db.add(I18nEntry( key=key, locale=req.locale, value=value, namespace=req.namespace, created_at=datetime.utcnow(), )) count += 1 await db.commit() return {"ok": True, "imported": count} @router.get("/coverage") async def translation_coverage( db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """로케일별 번역 커버리지.""" from sqlalchemy import func as sqlfunc rows = await db.execute( select(I18nEntry.locale, sqlfunc.count().label("cnt")) .group_by(I18nEntry.locale) ) result = {r.locale: r.cnt for r in rows.all()} base = result.get("ko", 1) return { "coverage": { locale: {"count": result.get(locale, 0), "pct": round(result.get(locale, 0) / base * 100)} for locale in SUPPORTED_LOCALES } }