fix(ui): replace emoji flags with cross-platform flag-icons CSS library

Windows does not render regional indicator emoji as graphical flags,
showing raw text (DE, GB) instead. Replace all emoji flag values in
SUPPORTED_LANGUAGES with lowercase ISO 3166-1 alpha-2 country codes,
add the flag-icons@7.3.2 CSS library via CDN, and update templates to
render <span class="fi fi-{code}"> instead of emoji text.

Special cases:
- Welsh (cy): uses flag-icons region code "gb-wls"
- Catalan (ca): falls back to "es" (no dedicated ISO flag)
- Esperanto (eo): uses "un" (UN flag for international language)

<option> elements in profile.html no longer display flags since CSS
icon classes cannot be applied inside native <option> tags.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-16 14:25:28 +00:00
parent 62f246df9c
commit 4619e6b8a6
3 changed files with 86 additions and 81 deletions
+79 -77
View File
@@ -34,89 +34,91 @@ logger = logging.getLogger(__name__)
SUPPORTED_LANGUAGES: list[dict[str, str]] = [
# --- Tier 1: Primary European languages ---
{"code": "en", "name": "English", "native": "English", "flag": "🇬🇧"},
{"code": "de", "name": "German", "native": "Deutsch", "flag": "🇩🇪"},
{"code": "fr", "name": "French", "native": "Français", "flag": "🇫🇷"},
{"code": "es", "name": "Spanish", "native": "Español", "flag": "🇪🇸"},
{"code": "it", "name": "Italian", "native": "Italiano", "flag": "🇮🇹"},
{"code": "pt", "name": "Portuguese", "native": "Português", "flag": "🇵🇹"},
# flag: lowercase ISO 3166-1 alpha-2 country code used with the flag-icons CSS library
# (e.g. "gb" → <span class="fi fi-gb">). Regional codes like "gb-wls" are also supported.
{"code": "en", "name": "English", "native": "English", "flag": "gb"},
{"code": "de", "name": "German", "native": "Deutsch", "flag": "de"},
{"code": "fr", "name": "French", "native": "Français", "flag": "fr"},
{"code": "es", "name": "Spanish", "native": "Español", "flag": "es"},
{"code": "it", "name": "Italian", "native": "Italiano", "flag": "it"},
{"code": "pt", "name": "Portuguese", "native": "Português", "flag": "pt"},
# --- Tier 2: Western & Northern European ---
{"code": "nl", "name": "Dutch", "native": "Nederlands", "flag": "🇳🇱"},
{"code": "nb", "name": "Norwegian Bokmål", "native": "Norsk bokmål", "flag": "🇳🇴"},
{"code": "no", "name": "Norwegian", "native": "Norsk", "flag": "🇳🇴"},
{"code": "da", "name": "Danish", "native": "Dansk", "flag": "🇩🇰"},
{"code": "sv", "name": "Swedish", "native": "Svenska", "flag": "🇸🇪"},
{"code": "fi", "name": "Finnish", "native": "Suomi", "flag": "🇫🇮"},
{"code": "is", "name": "Icelandic", "native": "Íslenska", "flag": "🇮🇸"},
{"code": "ga", "name": "Irish", "native": "Gaeilge", "flag": "🇮🇪"},
{"code": "lb", "name": "Luxembourgish", "native": "Lëtzebuergesch", "flag": "🇱🇺"},
{"code": "ca", "name": "Catalan", "native": "Català", "flag": "🏴"},
{"code": "cy", "name": "Welsh", "native": "Cymraeg", "flag": "🏴󠁧󠁢󠁷󠁬󠁳󠁿"}, # Wales subdivision flag (U+1F3F4 + tag chars)
{"code": "fy", "name": "Western Frisian", "native": "Frysk", "flag": "🇳🇱"},
{"code": "gl", "name": "Galician", "native": "Galego", "flag": "🇪🇸"},
{"code": "li", "name": "Limburgish", "native": "Limburgs", "flag": "🇳🇱"},
{"code": "vls", "name": "Flemish", "native": "West-Vlams", "flag": "🇧🇪"},
{"code": "nds", "name": "Low German", "native": "Plattdüütsch", "flag": "🇩🇪"},
{"code": "nl", "name": "Dutch", "native": "Nederlands", "flag": "nl"},
{"code": "nb", "name": "Norwegian Bokmål", "native": "Norsk bokmål", "flag": "no"},
{"code": "no", "name": "Norwegian", "native": "Norsk", "flag": "no"},
{"code": "da", "name": "Danish", "native": "Dansk", "flag": "dk"},
{"code": "sv", "name": "Swedish", "native": "Svenska", "flag": "se"},
{"code": "fi", "name": "Finnish", "native": "Suomi", "flag": "fi"},
{"code": "is", "name": "Icelandic", "native": "Íslenska", "flag": "is"},
{"code": "ga", "name": "Irish", "native": "Gaeilge", "flag": "ie"},
{"code": "lb", "name": "Luxembourgish", "native": "Lëtzebuergesch", "flag": "lu"},
{"code": "ca", "name": "Catalan", "native": "Català", "flag": "es"}, # no dedicated ISO flag; use Spain
{"code": "cy", "name": "Welsh", "native": "Cymraeg", "flag": "gb-wls"}, # flag-icons GB region code
{"code": "fy", "name": "Western Frisian", "native": "Frysk", "flag": "nl"},
{"code": "gl", "name": "Galician", "native": "Galego", "flag": "es"},
{"code": "li", "name": "Limburgish", "native": "Limburgs", "flag": "nl"},
{"code": "vls", "name": "Flemish", "native": "West-Vlams", "flag": "be"},
{"code": "nds", "name": "Low German", "native": "Plattdüütsch", "flag": "de"},
# --- Tier 3: Central & Eastern European ---
{"code": "pl", "name": "Polish", "native": "Polski", "flag": "🇵🇱"},
{"code": "cs", "name": "Czech", "native": "Čeština", "flag": "🇨🇿"},
{"code": "sk", "name": "Slovak", "native": "Slovenčina", "flag": "🇸🇰"},
{"code": "hu", "name": "Hungarian", "native": "Magyar", "flag": "🇭🇺"},
{"code": "sl", "name": "Slovenian", "native": "Slovenščina", "flag": "🇸🇮"},
{"code": "hr", "name": "Croatian", "native": "Hrvatski", "flag": "🇭🇷"},
{"code": "ro", "name": "Romanian", "native": "Română", "flag": "🇷🇴"},
{"code": "bg", "name": "Bulgarian", "native": "Български", "flag": "🇧🇬"},
{"code": "el", "name": "Greek", "native": "Ελληνικά", "flag": "🇬🇷"},
{"code": "et", "name": "Estonian", "native": "Eesti", "flag": "🇪🇪"},
{"code": "lv", "name": "Latvian", "native": "Latviešu", "flag": "🇱🇻"},
{"code": "lt", "name": "Lithuanian", "native": "Lietuvių", "flag": "🇱🇹"},
{"code": "sr", "name": "Serbian", "native": "Српски", "flag": "🇷🇸"},
{"code": "pl", "name": "Polish", "native": "Polski", "flag": "pl"},
{"code": "cs", "name": "Czech", "native": "Čeština", "flag": "cz"},
{"code": "sk", "name": "Slovak", "native": "Slovenčina", "flag": "sk"},
{"code": "hu", "name": "Hungarian", "native": "Magyar", "flag": "hu"},
{"code": "sl", "name": "Slovenian", "native": "Slovenščina", "flag": "si"},
{"code": "hr", "name": "Croatian", "native": "Hrvatski", "flag": "hr"},
{"code": "ro", "name": "Romanian", "native": "Română", "flag": "ro"},
{"code": "bg", "name": "Bulgarian", "native": "Български", "flag": "bg"},
{"code": "el", "name": "Greek", "native": "Ελληνικά", "flag": "gr"},
{"code": "et", "name": "Estonian", "native": "Eesti", "flag": "ee"},
{"code": "lv", "name": "Latvian", "native": "Latviešu", "flag": "lv"},
{"code": "lt", "name": "Lithuanian", "native": "Lietuvių", "flag": "lt"},
{"code": "sr", "name": "Serbian", "native": "Српски", "flag": "rs"},
# --- Tier 4: Non-EU European, Middle Eastern & African ---
{"code": "tr", "name": "Turkish", "native": "Türkçe", "flag": "🇹🇷"},
{"code": "uk", "name": "Ukrainian", "native": "Українська", "flag": "🇺🇦"},
{"code": "he", "name": "Hebrew", "native": "עברית", "flag": "🇮🇱"},
{"code": "ar", "name": "Arabic", "native": "العربية", "flag": "🇸🇦"},
{"code": "fa", "name": "Persian", "native": "فارسی", "flag": "🇮🇷"},
{"code": "af", "name": "Afrikaans", "native": "Afrikaans", "flag": "🇿🇦"},
{"code": "tr", "name": "Turkish", "native": "Türkçe", "flag": "tr"},
{"code": "uk", "name": "Ukrainian", "native": "Українська", "flag": "ua"},
{"code": "he", "name": "Hebrew", "native": "עברית", "flag": "il"},
{"code": "ar", "name": "Arabic", "native": "العربية", "flag": "sa"},
{"code": "fa", "name": "Persian", "native": "فارسی", "flag": "ir"},
{"code": "af", "name": "Afrikaans", "native": "Afrikaans", "flag": "za"},
# --- Tier 5: Asian languages ---
{"code": "zh", "name": "Chinese", "native": "中文", "flag": "🇨🇳"},
{"code": "zh-TW", "name": "Traditional Chinese", "native": "繁體中文", "flag": "🇹🇼"},
{"code": "ja", "name": "Japanese", "native": "日本語", "flag": "🇯🇵"},
{"code": "ko", "name": "Korean", "native": "한국어", "flag": "🇰🇷"},
{"code": "vi", "name": "Vietnamese", "native": "Tiếng Việt", "flag": "🇻🇳"},
{"code": "pa", "name": "Punjabi", "native": "ਪੰਜਾਬੀ", "flag": "🇮🇳"},
{"code": "kn", "name": "Kannada", "native": "ಕನ್ನಡ", "flag": "🇮🇳"},
{"code": "hi", "name": "Hindi", "native": "हिन्दी", "flag": "🇮🇳"},
{"code": "bn", "name": "Bengali", "native": "বাংলা", "flag": "🇧🇩"},
{"code": "gu", "name": "Gujarati", "native": "ગુજરાતી", "flag": "🇮🇳"},
{"code": "ml", "name": "Malayalam", "native": "മലയാളം", "flag": "🇮🇳"},
{"code": "mr", "name": "Marathi", "native": "मराठी", "flag": "🇮🇳"},
{"code": "ta", "name": "Tamil", "native": "தமிழ்", "flag": "🇮🇳"},
{"code": "te", "name": "Telugu", "native": "తెలుగు", "flag": "🇮🇳"},
{"code": "ur", "name": "Urdu", "native": "اردو", "flag": "🇵🇰"},
{"code": "si", "name": "Sinhala", "native": "සිංහල", "flag": "🇱🇰"},
{"code": "ne", "name": "Nepali", "native": "नेपाली", "flag": "🇳🇵"},
{"code": "th", "name": "Thai", "native": "ไทย", "flag": "🇹🇭"},
{"code": "km", "name": "Khmer", "native": "ខ្មែរ", "flag": "🇰🇭"},
{"code": "id", "name": "Indonesian", "native": "Bahasa Indonesia", "flag": "🇮🇩"},
{"code": "ms", "name": "Malay", "native": "Bahasa Melayu", "flag": "🇲🇾"},
{"code": "jv", "name": "Javanese", "native": "Basa Jawa", "flag": "🇮🇩"},
{"code": "tl", "name": "Tagalog", "native": "Filipino", "flag": "🇵🇭"},
{"code": "mn", "name": "Mongolian", "native": "Монгол", "flag": "🇲🇳"},
{"code": "kk", "name": "Kazakh", "native": "Қазақ тілі", "flag": "🇰🇿"},
{"code": "uz", "name": "Uzbek", "native": "Oʻzbekcha", "flag": "🇺🇿"},
{"code": "az", "name": "Azerbaijani", "native": "Azərbaycan dili", "flag": "🇦🇿"},
{"code": "hy", "name": "Armenian", "native": "Հայերեն", "flag": "🇦🇲"},
{"code": "ka", "name": "Georgian", "native": "ქართული", "flag": "🇬🇪"},
{"code": "zh", "name": "Chinese", "native": "中文", "flag": "cn"},
{"code": "zh-TW", "name": "Traditional Chinese", "native": "繁體中文", "flag": "tw"},
{"code": "ja", "name": "Japanese", "native": "日本語", "flag": "jp"},
{"code": "ko", "name": "Korean", "native": "한국어", "flag": "kr"},
{"code": "vi", "name": "Vietnamese", "native": "Tiếng Việt", "flag": "vn"},
{"code": "pa", "name": "Punjabi", "native": "ਪੰਜਾਬੀ", "flag": "in"},
{"code": "kn", "name": "Kannada", "native": "ಕನ್ನಡ", "flag": "in"},
{"code": "hi", "name": "Hindi", "native": "हिन्दी", "flag": "in"},
{"code": "bn", "name": "Bengali", "native": "বাংলা", "flag": "bd"},
{"code": "gu", "name": "Gujarati", "native": "ગુજરાતી", "flag": "in"},
{"code": "ml", "name": "Malayalam", "native": "മലയാളം", "flag": "in"},
{"code": "mr", "name": "Marathi", "native": "मराठी", "flag": "in"},
{"code": "ta", "name": "Tamil", "native": "தமிழ்", "flag": "in"},
{"code": "te", "name": "Telugu", "native": "తెలుగు", "flag": "in"},
{"code": "ur", "name": "Urdu", "native": "اردو", "flag": "pk"},
{"code": "si", "name": "Sinhala", "native": "සිංහල", "flag": "lk"},
{"code": "ne", "name": "Nepali", "native": "नेपाली", "flag": "np"},
{"code": "th", "name": "Thai", "native": "ไทย", "flag": "th"},
{"code": "km", "name": "Khmer", "native": "ខ្មែរ", "flag": "kh"},
{"code": "id", "name": "Indonesian", "native": "Bahasa Indonesia", "flag": "id"},
{"code": "ms", "name": "Malay", "native": "Bahasa Melayu", "flag": "my"},
{"code": "jv", "name": "Javanese", "native": "Basa Jawa", "flag": "id"},
{"code": "tl", "name": "Tagalog", "native": "Filipino", "flag": "ph"},
{"code": "mn", "name": "Mongolian", "native": "Монгол", "flag": "mn"},
{"code": "kk", "name": "Kazakh", "native": "Қазақ тілі", "flag": "kz"},
{"code": "uz", "name": "Uzbek", "native": "Oʻzbekcha", "flag": "uz"},
{"code": "az", "name": "Azerbaijani", "native": "Azərbaycan dili", "flag": "az"},
{"code": "hy", "name": "Armenian", "native": "Հայերեն", "flag": "am"},
{"code": "ka", "name": "Georgian", "native": "ქართული", "flag": "ge"},
# --- Tier 6: African languages ---
{"code": "sw", "name": "Swahili", "native": "Kiswahili", "flag": "🇰🇪"},
{"code": "am", "name": "Amharic", "native": "አማርኛ", "flag": "🇪🇹"},
{"code": "ha", "name": "Hausa", "native": "Hausa", "flag": "🇳🇬"},
{"code": "yo", "name": "Yoruba", "native": "Yorùbá", "flag": "🇳🇬"},
{"code": "ig", "name": "Igbo", "native": "Igbo", "flag": "🇳🇬"},
{"code": "zu", "name": "Zulu", "native": "isiZulu", "flag": "🇿🇦"},
{"code": "sw", "name": "Swahili", "native": "Kiswahili", "flag": "ke"},
{"code": "am", "name": "Amharic", "native": "አማርኛ", "flag": "et"},
{"code": "ha", "name": "Hausa", "native": "Hausa", "flag": "ng"},
{"code": "yo", "name": "Yoruba", "native": "Yorùbá", "flag": "ng"},
{"code": "ig", "name": "Igbo", "native": "Igbo", "flag": "ng"},
{"code": "zu", "name": "Zulu", "native": "isiZulu", "flag": "za"},
# --- Tier 7: Constructed & other languages ---
{"code": "eo", "name": "Esperanto", "native": "Esperanto", "flag": "🌍"},
{"code": "eo", "name": "Esperanto", "native": "Esperanto", "flag": "un"}, # UN flag for international language
]
SUPPORTED_LANGUAGE_CODES: set[str] = {lang["code"] for lang in SUPPORTED_LANGUAGES}
+5 -2
View File
@@ -27,6 +27,9 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- flag-icons: SVG/CSS flag sprites that render correctly on all platforms including Windows -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flag-icons@7.3.2/css/flag-icons.min.css"
crossorigin="anonymous" referrerpolicy="no-referrer" />
{% endblock %}
{% block head_extra %}{% endblock %}
<!-- CSRF token for AJAX/fetch requests -->
@@ -250,7 +253,7 @@
>
<span class="text-base leading-none" aria-hidden="true">
{% set cur_lang = suggested_languages | selectattr("code", "equalto", current_locale) | list %}
{% if cur_lang %}{{ cur_lang[0].flag }}{% else %}🌐{% endif %}
{% if cur_lang %}<span class="fi fi-{{ cur_lang[0].flag }}"></span>{% else %}<i class="fas fa-globe"></i>{% endif %}
</span>
</button>
<div
@@ -290,7 +293,7 @@
class="flex items-center w-full px-4 py-2 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700"
:onclick="`setLanguage('${lang.code}')`"
>
<span class="mr-2 text-base" x-text="lang.flag"></span>
<span class="fi mr-2" :class="`fi-${lang.flag}`" aria-hidden="true"></span>
<span x-text="lang.native"></span>
<i x-show="lang.code === '{{ current_locale }}'" class="fas fa-check ml-auto text-blue-500" aria-hidden="true"></i>
</button>
+2 -2
View File
@@ -179,7 +179,7 @@
>
<option value="">{{ _("profile.language_auto_detect") }}</option>
{% for lang in supported_languages %}
<option value="{{ lang.code }}">{{ lang.flag }} {{ lang.native }} ({{ lang.name }})</option>
<option value="{{ lang.code }}">{{ lang.native }} ({{ lang.name }})</option>
{% endfor %}
</select>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
@@ -246,7 +246,7 @@
>
<option value="">{{ _("profile.default_document_language_auto") }}</option>
{% for lang in supported_languages %}
<option value="{{ lang.code }}">{{ lang.flag }} {{ lang.native }} ({{ lang.name }})</option>
<option value="{{ lang.code }}">{{ lang.native }} ({{ lang.name }})</option>
{% endfor %}
</select>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">