i18n: add LANGUAGES, ACTIVE_LANGUAGES, respond_in_clause helper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2ecf250d53
commit
5730aad73c
2 changed files with 92 additions and 0 deletions
48
app/services/i18n.py
Normal file
48
app/services/i18n.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
"""Language registry + prompt helpers for localized AI output.
|
||||||
|
|
||||||
|
Two surfaces consume this module:
|
||||||
|
- Per-user LLM call sites (portfolio analysis only at this stage) call
|
||||||
|
``respond_in_clause(user.lang)`` and append the result to their
|
||||||
|
system prompt.
|
||||||
|
- The settings dropdown + its PATCH endpoint consult ``ACTIVE_LANGUAGES``
|
||||||
|
to decide which options are selectable. The strategic-log and digest
|
||||||
|
translation fan-outs also consult it to decide which languages to
|
||||||
|
spend tokens on.
|
||||||
|
|
||||||
|
Adding Spanish/French/German support later is a one-line constant
|
||||||
|
change: extend ``ACTIVE_LANGUAGES`` to include the new code. No other
|
||||||
|
code change is required — the rest of the system already treats them
|
||||||
|
as first-class via ``LANGUAGES``.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
# Display labels for every language the system knows about. ES/FR/DE
|
||||||
|
# are kept here so labels still render in the dropdown (as disabled
|
||||||
|
# options) without requiring code changes to enable them later.
|
||||||
|
LANGUAGES: dict[str, str] = {
|
||||||
|
"en": "English",
|
||||||
|
"it": "Italian",
|
||||||
|
"es": "Spanish",
|
||||||
|
"fr": "French",
|
||||||
|
"de": "German",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Languages users can actually select. Settings POST validates against
|
||||||
|
# this; the strategic-log + digest translation fan-outs only consider
|
||||||
|
# these.
|
||||||
|
ACTIVE_LANGUAGES: set[str] = {"en", "it"}
|
||||||
|
|
||||||
|
|
||||||
|
def respond_in_clause(lang: str | None) -> str:
|
||||||
|
"""Suffix appended to per-user LLM system prompts.
|
||||||
|
|
||||||
|
Returns an empty string for ``en`` (no nudge needed), an unknown
|
||||||
|
code, or ``None``/empty input — those callers want the default
|
||||||
|
English path. Otherwise returns ``"\\n\\nRespond in <Language>."``
|
||||||
|
keyed off ``LANGUAGES``.
|
||||||
|
"""
|
||||||
|
if not lang or lang == "en" or lang not in LANGUAGES:
|
||||||
|
return ""
|
||||||
|
return f"\n\nRespond in {LANGUAGES[lang]}."
|
||||||
44
tests/test_i18n.py
Normal file
44
tests/test_i18n.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
"""Unit tests for app.services.i18n."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_languages_contains_all_four_plus_english():
|
||||||
|
from app.services.i18n import LANGUAGES
|
||||||
|
assert set(LANGUAGES.keys()) == {"en", "it", "es", "fr", "de"}
|
||||||
|
assert LANGUAGES["en"] == "English"
|
||||||
|
assert LANGUAGES["it"] == "Italian"
|
||||||
|
assert LANGUAGES["es"] == "Spanish"
|
||||||
|
assert LANGUAGES["fr"] == "French"
|
||||||
|
assert LANGUAGES["de"] == "German"
|
||||||
|
|
||||||
|
|
||||||
|
def test_active_languages_is_en_and_it_only():
|
||||||
|
from app.services.i18n import ACTIVE_LANGUAGES
|
||||||
|
assert ACTIVE_LANGUAGES == {"en", "it"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_respond_in_clause_empty_for_english():
|
||||||
|
from app.services.i18n import respond_in_clause
|
||||||
|
assert respond_in_clause("en") == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_respond_in_clause_empty_for_none_or_empty():
|
||||||
|
from app.services.i18n import respond_in_clause
|
||||||
|
assert respond_in_clause("") == ""
|
||||||
|
assert respond_in_clause(None) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_respond_in_clause_italian():
|
||||||
|
from app.services.i18n import respond_in_clause
|
||||||
|
result = respond_in_clause("it")
|
||||||
|
assert "Italian" in result
|
||||||
|
assert result.startswith("\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_respond_in_clause_unknown_lang_falls_back_to_english():
|
||||||
|
"""Defensive: a raw POST or stale lang code should not crash the
|
||||||
|
prompt assembly. Unknown codes map to no-suffix (English default)."""
|
||||||
|
from app.services.i18n import respond_in_clause
|
||||||
|
assert respond_in_clause("xx") == ""
|
||||||
Loading…
Add table
Add a link
Reference in a new issue