Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/chainlit/translations/ar-SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"favorites": {
"use": "استخدام رسالة مفضلة",
"headline": "الرسائل المفضلة",
"remove": "إزالة المفضلة",
"empty": {
"title": "لا توجد رسائل محفوظة بعد",
"description": "ابدأ بإرسال رسالة وقم بتمييزها بنجمة أو ميّز رسالة من محادثاتك السابقة"
Expand Down
1 change: 1 addition & 0 deletions backend/chainlit/translations/da-DK.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"favorites": {
"use": "Brug en favorit besked",
"headline": "Favorit beskeder",
"remove": "Fjern favorit",
"empty": {
"title": "Ingen gemte prompts endnu",
"description": "Start med at sende en prompt og markere den med en stjerne, eller vælg en prompt fra tidligere samtaler"
Expand Down
6 changes: 6 additions & 0 deletions backend/chainlit/translations/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@
"components": {
"MultiSelectInput": {
"placeholder": "Wähle aus..."
},
"DatePickerInput": {
"placeholder": {
"single": "Datum auswählen",
"range": "Datumsbereich auswählen"
}
}
}
}
6 changes: 6 additions & 0 deletions backend/chainlit/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@
"components": {
"MultiSelectInput": {
"placeholder": "Seleziona..."
},
"DatePickerInput": {
"placeholder": {
"single": "Scegli una data",
"range": "Scegli un intervallo di date"
}
}
}
}
1 change: 1 addition & 0 deletions backend/chainlit/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
},
"fileUpload": {
"dragDrop": "ここにファイルをドラッグ&ドロップ",
"browse": "ファイルを参照",
"sizeLimit": "制限:",
"errors": {
"failed": "アップロードに失敗しました",
Expand Down
6 changes: 6 additions & 0 deletions backend/chainlit/translations/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@
"components": {
"MultiSelectInput": {
"placeholder": "선택..."
},
"DatePickerInput": {
"placeholder": {
"single": "날짜 선택",
"range": "날짜 범위 선택"
}
}
}
}
68 changes: 68 additions & 0 deletions backend/tests/test_translations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
from io import StringIO
from pathlib import Path
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -384,3 +386,69 @@ def test_compare_preserves_error_order(self):
missing_errors = [e for e in errors if "Missing" in e]
assert len(extra_errors) == 2
assert len(missing_errors) == 3


# The shipped locale files, discovered at collection time so each locale gets
# its own test id and a failure names the offending file.
TRANSLATIONS_DIR = Path(__file__).parent.parent / "chainlit" / "translations"
GROUND_TRUTH_LOCALE = "en-US.json"
LOCALE_FILENAMES = sorted(path.name for path in TRANSLATIONS_DIR.glob("*.json"))


def _load_locale(filename: str) -> dict:
"""Load one locale file from the shipped translations directory."""
with open(TRANSLATIONS_DIR / filename, encoding="utf-8") as file:
return json.load(file)


class TestShippedTranslationFileParity:
"""Structural parity of the locale files actually shipped in this repo.

The suites above exercise ``compare_json_structures`` against synthetic
dictionaries, and ``chainlit lint-translations`` inspects a *consuming
app's* ``.chainlit/translations/`` rather than this directory. So nothing
checked the real locale files, and keys added to ``en-US.json`` could land
without the other locales - which is how ``components.DatePickerInput``,
``chat.favorites.remove`` and ``chat.fileUpload.browse`` went missing.

``en-US.json`` is both a locale file and the ground truth; comparing it to
itself is trivially clean, so it needs no special-casing here.
"""

def test_translations_directory_is_discoverable(self):
"""Guard against the parametrized tests passing vacuously.

If this file moves, or the translations directory is renamed,
``LOCALE_FILENAMES`` would be empty and every parametrized test below
would silently disappear rather than fail.
"""
assert TRANSLATIONS_DIR.is_dir(), (
f"translations directory not found at {TRANSLATIONS_DIR}"
)
assert GROUND_TRUTH_LOCALE in LOCALE_FILENAMES, (
f"{GROUND_TRUTH_LOCALE} missing from {TRANSLATIONS_DIR}"
)
# Two is the floor that makes a parity check meaningful at all: the
# ground truth plus something to compare against.
assert len(LOCALE_FILENAMES) > 1, (
f"expected multiple locale files, found {LOCALE_FILENAMES}"
)

@pytest.mark.parametrize("filename", LOCALE_FILENAMES)
def test_locale_matches_ground_truth_structure(self, filename):
"""Every locale must carry exactly the keys in en-US.json."""
truth = _load_locale(GROUND_TRUTH_LOCALE)
errors = compare_json_structures(truth, _load_locale(filename))

assert not errors, "{} differs from {}:\n{}".format(
filename,
GROUND_TRUTH_LOCALE,
"\n".join(f" {error}" for error in errors),
)

@pytest.mark.parametrize("filename", LOCALE_FILENAMES)
def test_locale_is_a_json_object(self, filename):
"""compare_json_structures rejects non-dict input, so fail clearly."""
assert isinstance(_load_locale(filename), dict), (
f"{filename} must contain a JSON object at the top level"
)