From 87e686ff27e564079028a3c5607eebbbed54bc42 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Mon, 19 Jan 2026 15:48:59 +0100 Subject: [PATCH] Fixes for #3779 --- changedetectionio/flask_app.py | 18 ++- changedetectionio/tests/test_i18n.py | 164 +++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index b5dcffa48..6e85b061f 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -400,11 +400,27 @@ def changedetection_app(config=None, datastore_o=None): language_codes = get_language_codes() def get_locale(): + # Locale aliases: map browser language codes to translation directory names + # This handles cases where browsers send standard codes (e.g., zh-TW) + # but our translations use more specific codes (e.g., zh_Hant_TW) + locale_aliases = { + 'zh-TW': 'zh_Hant_TW', # Traditional Chinese: browser sends zh-TW, we use zh_Hant_TW + 'zh_TW': 'zh_Hant_TW', # Also handle underscore variant + } + # 1. Try to get locale from session (user explicitly selected) if 'locale' in session: return session['locale'] + # 2. Fall back to Accept-Language header - return request.accept_languages.best_match(language_codes) + # Get the best match from browser's Accept-Language header + browser_locale = request.accept_languages.best_match(language_codes + list(locale_aliases.keys())) + + # 3. Check if we need to map the browser locale to our internal locale + if browser_locale in locale_aliases: + return locale_aliases[browser_locale] + + return browser_locale # Initialize Babel with locale selector babel = Babel(app, locale_selector=get_locale) diff --git a/changedetectionio/tests/test_i18n.py b/changedetectionio/tests/test_i18n.py index 0083c7365..5775bddec 100644 --- a/changedetectionio/tests/test_i18n.py +++ b/changedetectionio/tests/test_i18n.py @@ -325,3 +325,167 @@ def test_time_unit_translations(client, live_server, measure_memory_usage, datas assert b"Time Between Check" not in res.data, "Should not have English 'Time Between Check'" assert "Chrome 請求".encode() not in res.data, "Should not have incorrect 'Chrome 請求' (Chrome requests)" assert "使用預設通知".encode() not in res.data, "Should not have incorrect '使用預設通知' (Use default notification)" + + +def test_accept_language_header_zh_tw(client, live_server, measure_memory_usage, datastore_path): + """ + Test that browsers sending zh-TW in Accept-Language header get Traditional Chinese. + This tests the locale alias mapping for issue #3779. + """ + from flask import url_for + + # Clear any session data to simulate a fresh visitor + with client.session_transaction() as sess: + sess.clear() + + # Request the index page with zh-TW in Accept-Language header (what browsers send) + res = client.get( + url_for("watchlist.index"), + headers={'Accept-Language': 'zh-TW,zh;q=0.9,en;q=0.8'}, + follow_redirects=True + ) + + assert res.status_code == 200 + + # Should get Traditional Chinese content, not Simplified Chinese + # Traditional: 選擇語言, Simplified: 选择语言 + assert '選擇語言'.encode() in res.data, "Expected Traditional Chinese '選擇語言' (Select Language)" + assert '选择语言'.encode() not in res.data, "Should not get Simplified Chinese '选择语言'" + + # Check HTML lang attribute uses BCP 47 format + assert b'