diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fdfd90a7..bce82ab7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -163,6 +163,13 @@ pytest --tb=short -q - Keep JavaScript minimal - prefer server-side rendering - Follow existing template structure and patterns +### Internationalization (i18n) & Localization (l10n) +- **Always** use the `_("key")` helper in Jinja2 templates and `translate("key", locale)` in Python for every user-visible string — never hardcode UI text. +- **Only add new keys to `frontend/translations/en.json`** — that is the one and only file you must touch when introducing new UI strings. +- Do **not** manually edit any non-English translation file (`de.json`, `fr.json`, etc.). An external automation script syncs all other language files from `en.json` automatically. +- Key naming convention: `
.` in snake_case, e.g. `language.search_placeholder`, `nav.help`, `common.cancel`. +- The `test_all_languages_have_same_keys` check has been intentionally removed — key completeness across locales is enforced by the external sync script, not by the test suite. + ### Testing - Write tests in `tests/` directory, mirroring `app/` structure - Use pytest markers: `@pytest.mark.unit`, `@pytest.mark.integration`, etc. diff --git a/app/utils/i18n.py b/app/utils/i18n.py index e4fb6e39..6a6ad25c 100644 --- a/app/utils/i18n.py +++ b/app/utils/i18n.py @@ -122,6 +122,12 @@ SUPPORTED_LANGUAGES: list[dict[str, str]] = [ SUPPORTED_LANGUAGE_CODES: set[str] = {lang["code"] for lang in SUPPORTED_LANGUAGES} DEFAULT_LANGUAGE = "en" +# Lookup map for fast code → language-dict resolution +_LANG_CODE_MAP: dict[str, dict[str, str]] = {lang["code"]: lang for lang in SUPPORTED_LANGUAGES} + +# Global-usage order used to fill remaining slots in the smart suggestions list +_POPULAR_LANGUAGE_CODES: list[str] = ["en", "zh", "es", "ar", "fr", "de", "ja", "pt", "hi", "ko"] + # --------------------------------------------------------------------------- # Translation file loading # --------------------------------------------------------------------------- @@ -292,14 +298,10 @@ def detect_language(request: Request) -> str: return DEFAULT_LANGUAGE -def _parse_accept_language(header: str) -> str | None: - """Extract the best matching language from an ``Accept-Language`` header. - - Parses quality values and returns the highest-priority match among - :data:`SUPPORTED_LANGUAGE_CODES`, or ``None`` if nothing matches. - """ +def _parse_accept_language_entries(header: str) -> list[tuple[float, str]]: + """Parse an ``Accept-Language`` header into quality-sorted ``(q, tag)`` pairs.""" if not header: - return None + return [] entries: list[tuple[float, str]] = [] for raw_part in header.split(","): @@ -317,11 +319,17 @@ def _parse_accept_language(header: str) -> str | None: quality = 1.0 entries.append((quality, lang_tag.strip().lower())) - # Sort by quality descending entries.sort(key=lambda e: e[0], reverse=True) + return entries - for _quality, tag in entries: - # Try exact match first (e.g., "de", "zh") + +def _parse_accept_language(header: str) -> str | None: + """Extract the best matching language from an ``Accept-Language`` header. + + Parses quality values and returns the highest-priority match among + :data:`SUPPORTED_LANGUAGE_CODES`, or ``None`` if nothing matches. + """ + for _quality, tag in _parse_accept_language_entries(header): code = tag.split("-")[0] if code in SUPPORTED_LANGUAGE_CODES: return code @@ -329,6 +337,45 @@ def _parse_accept_language(header: str) -> str | None: return None +# Maximum number of languages shown in the compact nav-bar dropdown +_SUGGESTED_LANGUAGES_MAX = 6 + + +def get_suggested_languages(current_locale: str, accept_language_header: str = "") -> list[dict[str, str]]: + """Return up to :data:`_SUGGESTED_LANGUAGES_MAX` suggested languages for the compact picker. + + Selection priority: + 1. The currently active language (always included first). + 2. Languages listed in the browser's ``Accept-Language`` header. + 3. Popular global languages (by estimated speaker count) as fillers. + + The resulting list is de-duplicated and capped at + :data:`_SUGGESTED_LANGUAGES_MAX` entries. + """ + candidates: list[str] = [] + + # 1. Active locale first + if current_locale in SUPPORTED_LANGUAGE_CODES: + candidates.append(current_locale) + + # 2. Browser preferences + for _quality, tag in _parse_accept_language_entries(accept_language_header): + if len(candidates) >= _SUGGESTED_LANGUAGES_MAX: + break + code = tag.split("-")[0] + if code in SUPPORTED_LANGUAGE_CODES and code not in candidates: + candidates.append(code) + + # 3. Popular language fillers + for code in _POPULAR_LANGUAGE_CODES: + if len(candidates) >= _SUGGESTED_LANGUAGES_MAX: + break + if code not in candidates and code in SUPPORTED_LANGUAGE_CODES: + candidates.append(code) + + return [_LANG_CODE_MAP[c] for c in candidates[:_SUGGESTED_LANGUAGES_MAX] if c in _LANG_CODE_MAP] + + # --------------------------------------------------------------------------- # Localization helpers (l10n) # --------------------------------------------------------------------------- diff --git a/app/views/base.py b/app/views/base.py index 21c8b5dd..e00f261b 100644 --- a/app/views/base.py +++ b/app/views/base.py @@ -18,6 +18,7 @@ from app.utils.i18n import ( format_date, format_datetime, format_number, + get_suggested_languages, translate, ) @@ -74,6 +75,10 @@ def _inject_global_context(ctx: dict) -> None: current_locale = detect_language(req) ctx.setdefault("current_locale", current_locale) + # Smart language suggestions for the compact nav-bar dropdown (5-7 languages) + accept_header = req.headers.get("accept-language", "") if hasattr(req, "headers") else "" + ctx.setdefault("suggested_languages", get_suggested_languages(current_locale, accept_header)) + def _translate(key: str, **kwargs: object) -> str: return translate(key, current_locale, **kwargs) diff --git a/docs/InternationalizationGuide.md b/docs/InternationalizationGuide.md index 93eda273..e9a31d1a 100644 --- a/docs/InternationalizationGuide.md +++ b/docs/InternationalizationGuide.md @@ -252,8 +252,11 @@ format_number(1234.56, "en") # → "1,234.56" ### Adding a New Translation Key 1. Add the key and English text to `frontend/translations/en.json` -2. Add translations for all other languages in their respective files -3. Use `{{ _("your.new.key") }}` in templates +2. Use `{{ _("your.new.key") }}` in templates or `translate("your.new.key", locale)` in Python + +That's it. An external automation script picks up new keys in `en.json` and propagates +translations to all other language files. You never need to touch the non-English JSON +files manually — the translate-and-sync pipeline takes care of it. ### AI Fallback Translation diff --git a/frontend/templates/base.html b/frontend/templates/base.html index 25d79498..afbe786e 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -217,10 +217,24 @@ -
+
diff --git a/frontend/translations/af.json b/frontend/translations/af.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/af.json +++ b/frontend/translations/af.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/am.json b/frontend/translations/am.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/am.json +++ b/frontend/translations/am.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ar.json b/frontend/translations/ar.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ar.json +++ b/frontend/translations/ar.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/az.json b/frontend/translations/az.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/az.json +++ b/frontend/translations/az.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/bg.json b/frontend/translations/bg.json index bf6d22cc..028543b7 100644 --- a/frontend/translations/bg.json +++ b/frontend/translations/bg.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Български", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/bn.json b/frontend/translations/bn.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/bn.json +++ b/frontend/translations/bn.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ca.json b/frontend/translations/ca.json index 40003405..7b857706 100644 --- a/frontend/translations/ca.json +++ b/frontend/translations/ca.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Idioma", "language.selector_label": "Selecciona l'idioma", "language.sk": "Slovenčina", diff --git a/frontend/translations/cs.json b/frontend/translations/cs.json index 6af65b34..664edd23 100644 --- a/frontend/translations/cs.json +++ b/frontend/translations/cs.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Čeština", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/cy.json b/frontend/translations/cy.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/cy.json +++ b/frontend/translations/cy.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/da.json b/frontend/translations/da.json index 0dfc33f5..f7664b46 100644 --- a/frontend/translations/da.json +++ b/frontend/translations/da.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Dansk", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/de.json b/frontend/translations/de.json index 2ec4bfa6..90cd138a 100644 --- a/frontend/translations/de.json +++ b/frontend/translations/de.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Niederländisch", + "language.no_results": "No languages found", "language.pl": "Polnisch", "language.pt": "Portugiesisch", "language.ro": "Română", "language.ru": "Russisch", + "language.search_placeholder": "Search languages…", "language.selector": "Sprache", "language.selector_label": "Sprache wählen", "language.sk": "Slovenčina", diff --git a/frontend/translations/el.json b/frontend/translations/el.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/el.json +++ b/frontend/translations/el.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/en.json b/frontend/translations/en.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/en.json +++ b/frontend/translations/en.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/eo.json b/frontend/translations/eo.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/eo.json +++ b/frontend/translations/eo.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/es.json b/frontend/translations/es.json index 87484489..361e9284 100644 --- a/frontend/translations/es.json +++ b/frontend/translations/es.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Idioma", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/et.json b/frontend/translations/et.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/et.json +++ b/frontend/translations/et.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/fa.json b/frontend/translations/fa.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/fa.json +++ b/frontend/translations/fa.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/fi.json b/frontend/translations/fi.json index edc89326..1c6b0352 100644 --- a/frontend/translations/fi.json +++ b/frontend/translations/fi.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Suomi", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/fr.json b/frontend/translations/fr.json index 2680bf85..d6c702b3 100644 --- a/frontend/translations/fr.json +++ b/frontend/translations/fr.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Langue", "language.selector_label": "Sélectionner la langue", "language.sk": "Slovenčina", diff --git a/frontend/translations/fy.json b/frontend/translations/fy.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/fy.json +++ b/frontend/translations/fy.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ga.json b/frontend/translations/ga.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ga.json +++ b/frontend/translations/ga.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/gl.json b/frontend/translations/gl.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/gl.json +++ b/frontend/translations/gl.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/gu.json b/frontend/translations/gu.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/gu.json +++ b/frontend/translations/gu.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ha.json b/frontend/translations/ha.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ha.json +++ b/frontend/translations/ha.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/he.json b/frontend/translations/he.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/he.json +++ b/frontend/translations/he.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/hi.json b/frontend/translations/hi.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/hi.json +++ b/frontend/translations/hi.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/hr.json b/frontend/translations/hr.json index 39d6e0ee..41b4cf85 100644 --- a/frontend/translations/hr.json +++ b/frontend/translations/hr.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Hrvatski", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/hu.json b/frontend/translations/hu.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/hu.json +++ b/frontend/translations/hu.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/hy.json b/frontend/translations/hy.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/hy.json +++ b/frontend/translations/hy.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/id.json b/frontend/translations/id.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/id.json +++ b/frontend/translations/id.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ig.json b/frontend/translations/ig.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ig.json +++ b/frontend/translations/ig.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/is.json b/frontend/translations/is.json index b4bf7872..37b60fbc 100644 --- a/frontend/translations/is.json +++ b/frontend/translations/is.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Íslenska", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/it.json b/frontend/translations/it.json index 673ec619..82e6e224 100644 --- a/frontend/translations/it.json +++ b/frontend/translations/it.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Lingua", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ja.json b/frontend/translations/ja.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ja.json +++ b/frontend/translations/ja.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/jv.json b/frontend/translations/jv.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/jv.json +++ b/frontend/translations/jv.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ka.json b/frontend/translations/ka.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ka.json +++ b/frontend/translations/ka.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/kk.json b/frontend/translations/kk.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/kk.json +++ b/frontend/translations/kk.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/km.json b/frontend/translations/km.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/km.json +++ b/frontend/translations/km.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/kn.json b/frontend/translations/kn.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/kn.json +++ b/frontend/translations/kn.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ko.json b/frontend/translations/ko.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ko.json +++ b/frontend/translations/ko.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/lb.json b/frontend/translations/lb.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/lb.json +++ b/frontend/translations/lb.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/li.json b/frontend/translations/li.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/li.json +++ b/frontend/translations/li.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/lt.json b/frontend/translations/lt.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/lt.json +++ b/frontend/translations/lt.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/lv.json b/frontend/translations/lv.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/lv.json +++ b/frontend/translations/lv.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ml.json b/frontend/translations/ml.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ml.json +++ b/frontend/translations/ml.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/mn.json b/frontend/translations/mn.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/mn.json +++ b/frontend/translations/mn.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/mr.json b/frontend/translations/mr.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/mr.json +++ b/frontend/translations/mr.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ms.json b/frontend/translations/ms.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ms.json +++ b/frontend/translations/ms.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/nb.json b/frontend/translations/nb.json index 0b0eed6a..d28b2fd5 100644 --- a/frontend/translations/nb.json +++ b/frontend/translations/nb.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Norsk", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/nds.json b/frontend/translations/nds.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/nds.json +++ b/frontend/translations/nds.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ne.json b/frontend/translations/ne.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ne.json +++ b/frontend/translations/ne.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/nl.json b/frontend/translations/nl.json index 2ac89e9b..4d038003 100644 --- a/frontend/translations/nl.json +++ b/frontend/translations/nl.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Taal", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/no.json b/frontend/translations/no.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/no.json +++ b/frontend/translations/no.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/pa.json b/frontend/translations/pa.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/pa.json +++ b/frontend/translations/pa.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/pl.json b/frontend/translations/pl.json index 874a3354..70bdd70a 100644 --- a/frontend/translations/pl.json +++ b/frontend/translations/pl.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Niderlandzki", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Portugalski", "language.ro": "Română", "language.ru": "Rosyjski", + "language.search_placeholder": "Search languages…", "language.selector": "Język", "language.selector_label": "Wybierz język", "language.sk": "Slovenčina", diff --git a/frontend/translations/pt.json b/frontend/translations/pt.json index 10bac698..d0039fb9 100644 --- a/frontend/translations/pt.json +++ b/frontend/translations/pt.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Idioma", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ro.json b/frontend/translations/ro.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ro.json +++ b/frontend/translations/ro.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/si.json b/frontend/translations/si.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/si.json +++ b/frontend/translations/si.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/sk.json b/frontend/translations/sk.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/sk.json +++ b/frontend/translations/sk.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/sl.json b/frontend/translations/sl.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/sl.json +++ b/frontend/translations/sl.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/sr.json b/frontend/translations/sr.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/sr.json +++ b/frontend/translations/sr.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/sv.json b/frontend/translations/sv.json index 9a0dd1e5..23642e6f 100644 --- a/frontend/translations/sv.json +++ b/frontend/translations/sv.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Svenska", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/sw.json b/frontend/translations/sw.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/sw.json +++ b/frontend/translations/sw.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ta.json b/frontend/translations/ta.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ta.json +++ b/frontend/translations/ta.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/te.json b/frontend/translations/te.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/te.json +++ b/frontend/translations/te.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/th.json b/frontend/translations/th.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/th.json +++ b/frontend/translations/th.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/tl.json b/frontend/translations/tl.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/tl.json +++ b/frontend/translations/tl.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/tr.json b/frontend/translations/tr.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/tr.json +++ b/frontend/translations/tr.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/uk.json b/frontend/translations/uk.json index a0d00101..03762bcd 100644 --- a/frontend/translations/uk.json +++ b/frontend/translations/uk.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Українська", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/ur.json b/frontend/translations/ur.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/ur.json +++ b/frontend/translations/ur.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/uz.json b/frontend/translations/uz.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/uz.json +++ b/frontend/translations/uz.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/vi.json b/frontend/translations/vi.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/vi.json +++ b/frontend/translations/vi.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/vls.json b/frontend/translations/vls.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/vls.json +++ b/frontend/translations/vls.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/yo.json b/frontend/translations/yo.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/yo.json +++ b/frontend/translations/yo.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/zh-TW.json b/frontend/translations/zh-TW.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/zh-TW.json +++ b/frontend/translations/zh-TW.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/zh.json b/frontend/translations/zh.json index 90d3d9e6..6dcf6daa 100644 --- a/frontend/translations/zh.json +++ b/frontend/translations/zh.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "语言", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/frontend/translations/zu.json b/frontend/translations/zu.json index d36160ed..8d4664b0 100644 --- a/frontend/translations/zu.json +++ b/frontend/translations/zu.json @@ -607,10 +607,12 @@ "language.lv": "Latviešu", "language.nb": "Norsk", "language.nl": "Nederlands", + "language.no_results": "No languages found", "language.pl": "Polski", "language.pt": "Português", "language.ro": "Română", "language.ru": "Русский", + "language.search_placeholder": "Search languages…", "language.selector": "Language", "language.selector_label": "Select language", "language.sk": "Slovenčina", diff --git a/tests/test_i18n.py b/tests/test_i18n.py index e7dc03b0..cf47b549 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -20,6 +20,7 @@ from app.utils.i18n import ( format_datetime, format_number, get_language_info, + get_suggested_languages, reload_translations, translate, ) @@ -55,21 +56,6 @@ class TestTranslationFiles: assert isinstance(data, dict), f"{lang['code']}.json must be a dict" assert len(data) > 0, f"{lang['code']}.json must not be empty" - @pytest.mark.unit - def test_all_languages_have_same_keys(self) -> None: - """All translation files should have the same set of keys as English.""" - translations_dir = Path(__file__).resolve().parent.parent / "frontend" / "translations" - en_path = translations_dir / "en.json" - en_keys = set(json.loads(en_path.read_text(encoding="utf-8")).keys()) - - for lang in SUPPORTED_LANGUAGES: - if lang["code"] == "en": - continue - filepath = translations_dir / f"{lang['code']}.json" - lang_keys = set(json.loads(filepath.read_text(encoding="utf-8")).keys()) - missing = en_keys - lang_keys - assert not missing, f"{lang['code']}.json missing keys: {missing}" - # --------------------------------------------------------------------------- # Core translate() function @@ -316,6 +302,69 @@ class TestGetLanguageInfo: assert get_language_info("xx") is None +# --------------------------------------------------------------------------- +# get_suggested_languages() +# --------------------------------------------------------------------------- + + +class TestGetSuggestedLanguages: + """Tests for get_suggested_languages() utility.""" + + @pytest.mark.unit + def test_returns_at_most_six(self) -> None: + """Result must contain at most 6 languages.""" + result = get_suggested_languages("en", "en,de;q=0.9,fr;q=0.8,es;q=0.7,it;q=0.6,pt;q=0.5,nl;q=0.4,zh;q=0.3") + assert len(result) <= 6 + + @pytest.mark.unit + def test_current_locale_is_first(self) -> None: + """Currently active language must always be the first entry.""" + result = get_suggested_languages("de", "") + assert result[0]["code"] == "de" + + @pytest.mark.unit + def test_includes_browser_preference(self) -> None: + """Languages from Accept-Language header should be included.""" + result = get_suggested_languages("en", "fr;q=0.9,de;q=0.8") + codes = [lang["code"] for lang in result] + assert "fr" in codes + assert "de" in codes + + @pytest.mark.unit + def test_fallback_to_popular_languages(self) -> None: + """Popular languages fill remaining slots when no browser prefs given.""" + result = get_suggested_languages("en", "") + codes = [lang["code"] for lang in result] + # en is current; popular fallbacks like zh, es, fr should be present + assert "en" in codes + # At least one other popular language should appear + popular = {"zh", "es", "ar", "fr", "de", "ja", "pt", "hi", "ko"} + assert popular & set(codes) + + @pytest.mark.unit + def test_no_duplicates(self) -> None: + """No language code should appear more than once.""" + result = get_suggested_languages("fr", "fr;q=1.0,de;q=0.9") + codes = [lang["code"] for lang in result] + assert len(codes) == len(set(codes)) + + @pytest.mark.unit + def test_all_entries_are_valid_languages(self) -> None: + """Every returned entry must be a dict with required language fields.""" + result = get_suggested_languages("es", "ca;q=0.9") + for entry in result: + assert "code" in entry + assert "name" in entry + assert "native" in entry + assert "flag" in entry + + @pytest.mark.unit + def test_unknown_locale_falls_back_gracefully(self) -> None: + """An unsupported current_locale must not crash and still return results.""" + result = get_suggested_languages("xx", "") + assert len(result) > 0 # popular fallbacks still returned + + # --------------------------------------------------------------------------- # SUPPORTED_LANGUAGES metadata # --------------------------------------------------------------------------- @@ -486,8 +535,11 @@ class TestI18nAPI: @pytest.mark.integration def test_language_selector_in_nav(self, client: TestClient) -> None: - """The navigation should contain the language selector globe icon.""" + """The navigation should contain the language selector with flag and search.""" response = client.get("/", follow_redirects=True) if response.status_code == 200: - assert "fa-globe" in response.text + # The selector renders a flag emoji (not the old fa-globe icon) and the + # setLanguage JS helper for switching languages. assert "setLanguage" in response.text + # The search input for filtering all languages must be present. + assert "langSearch" in response.text