diff --git a/backend/chainlit/translations/ar-SA.json b/backend/chainlit/translations/ar-SA.json index b98b427d23..cbea0e3fb7 100644 --- a/backend/chainlit/translations/ar-SA.json +++ b/backend/chainlit/translations/ar-SA.json @@ -68,6 +68,7 @@ "favorites": { "use": "استخدام رسالة مفضلة", "headline": "الرسائل المفضلة", + "remove": "إزالة المفضلة", "empty": { "title": "لا توجد رسائل محفوظة بعد", "description": "ابدأ بإرسال رسالة وقم بتمييزها بنجمة أو ميّز رسالة من محادثاتك السابقة" diff --git a/backend/chainlit/translations/da-DK.json b/backend/chainlit/translations/da-DK.json index aba214e617..8c9e8607b0 100644 --- a/backend/chainlit/translations/da-DK.json +++ b/backend/chainlit/translations/da-DK.json @@ -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" diff --git a/backend/chainlit/translations/de-DE.json b/backend/chainlit/translations/de-DE.json index db5934b6f1..1469a3f569 100644 --- a/backend/chainlit/translations/de-DE.json +++ b/backend/chainlit/translations/de-DE.json @@ -249,6 +249,12 @@ "components": { "MultiSelectInput": { "placeholder": "Wähle aus..." + }, + "DatePickerInput": { + "placeholder": { + "single": "Datum auswählen", + "range": "Datumsbereich auswählen" + } } } } diff --git a/backend/chainlit/translations/it.json b/backend/chainlit/translations/it.json index 69021fe630..367e1bb0c9 100644 --- a/backend/chainlit/translations/it.json +++ b/backend/chainlit/translations/it.json @@ -249,6 +249,12 @@ "components": { "MultiSelectInput": { "placeholder": "Seleziona..." + }, + "DatePickerInput": { + "placeholder": { + "single": "Scegli una data", + "range": "Scegli un intervallo di date" + } } } } diff --git a/backend/chainlit/translations/ja.json b/backend/chainlit/translations/ja.json index b1ea49659b..aa6e6a8322 100644 --- a/backend/chainlit/translations/ja.json +++ b/backend/chainlit/translations/ja.json @@ -86,6 +86,7 @@ }, "fileUpload": { "dragDrop": "ここにファイルをドラッグ&ドロップ", + "browse": "ファイルを参照", "sizeLimit": "制限:", "errors": { "failed": "アップロードに失敗しました", diff --git a/backend/chainlit/translations/ko.json b/backend/chainlit/translations/ko.json index f05622faf1..3a0cca3bf2 100644 --- a/backend/chainlit/translations/ko.json +++ b/backend/chainlit/translations/ko.json @@ -249,6 +249,12 @@ "components": { "MultiSelectInput": { "placeholder": "선택..." + }, + "DatePickerInput": { + "placeholder": { + "single": "날짜 선택", + "range": "날짜 범위 선택" + } } } } diff --git a/backend/tests/test_translations.py b/backend/tests/test_translations.py index 6e517c4748..0fb9583566 100644 --- a/backend/tests/test_translations.py +++ b/backend/tests/test_translations.py @@ -1,4 +1,6 @@ +import json from io import StringIO +from pathlib import Path from unittest.mock import patch import pytest @@ -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" + )