diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index a1545db26..d1e438ea5 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -9,6 +9,7 @@ import threading import time import timeago from blinker import signal +from pathlib import Path from changedetectionio.strtobool import strtobool from threading import Event @@ -84,6 +85,10 @@ app.config['NEW_VERSION_AVAILABLE'] = False if os.getenv('FLASK_SERVER_NAME'): app.config['SERVER_NAME'] = os.getenv('FLASK_SERVER_NAME') +# Babel/i18n configuration +app.config['BABEL_TRANSLATION_DIRECTORIES'] = str(Path(__file__).parent / 'translations') +app.config['BABEL_DEFAULT_LOCALE'] = 'en_GB' + #app.config["EXPLAIN_TEMPLATE_LOADING"] = True @@ -395,13 +400,9 @@ def changedetection_app(config=None, datastore_o=None): def get_locale(): # 1. Try to get locale from session (user explicitly selected) if 'locale' in session: - locale = session['locale'] - logger.trace(f"DEBUG: get_locale() returning from session: {locale}") - return locale + return session['locale'] # 2. Fall back to Accept-Language header - locale = request.accept_languages.best_match(language_codes) - logger.trace(f"DEBUG: get_locale() returning from Accept-Language: {locale}") - return locale + return request.accept_languages.best_match(language_codes) # Initialize Babel with locale selector babel = Babel(app, locale_selector=get_locale) @@ -518,9 +519,20 @@ def changedetection_app(config=None, datastore_o=None): @app.route('/set-language/') def set_language(locale): """Set the user's preferred language in the session""" + if not request.cookies: + logger.error("Cannot set language without session cookie") + flash("Cannot set language without session cookie", 'error') + return redirect(url_for('watchlist.index')) + # Validate the locale against available languages if locale in language_codes: session['locale'] = locale + + # CRITICAL: Flask-Babel caches the locale in the request context (ctx.babel_locale) + # We must refresh to clear this cache so the new locale takes effect immediately + # This is especially important for tests where multiple requests happen rapidly + from flask_babel import refresh + refresh() else: logger.error(f"Invalid locale {locale}, available: {language_codes}") diff --git a/changedetectionio/tests/test_i18n.py b/changedetectionio/tests/test_i18n.py index 43ca2ff2c..8f1b738ff 100644 --- a/changedetectionio/tests/test_i18n.py +++ b/changedetectionio/tests/test_i18n.py @@ -5,12 +5,8 @@ from .util import live_server_setup def test_zh_TW(client, live_server, measure_memory_usage, datastore_path): - res = client.get( - url_for("set_language", locale="zh"), # Simplified chinese - follow_redirects=True - ) - assert "选择语言".encode() in res.data - + # Be sure we got a session cookie + res = client.get(url_for("watchlist.index"), follow_redirects=True) res = client.get( url_for("set_language", locale="zh_Hant_TW"), # Traditional @@ -18,9 +14,28 @@ def test_zh_TW(client, live_server, measure_memory_usage, datastore_path): ) # HTML follows BCP 47 language tag rules, not underscore-based locale formats. assert b'zh + res = client.get( + url_for("set_language", locale="zh"), # Simplified chinese + follow_redirects=True + ) + res = client.get(url_for("watchlist.index"), follow_redirects=True) + assert "选择语言".encode() in res.data, "Simplified chinese worked and it means the flask-babel cache worked" + + + def test_language_switching(client, live_server, measure_memory_usage, datastore_path): """ @@ -31,6 +46,9 @@ def test_language_switching(client, live_server, measure_memory_usage, datastore 3. Switch back to English and verify English text appears """ + # Establish session cookie + client.get(url_for("watchlist.index"), follow_redirects=True) + # Step 1: Set the language to Italian using the /set-language endpoint res = client.get( url_for("set_language", locale="it"), @@ -79,6 +97,9 @@ def test_invalid_locale(client, live_server, measure_memory_usage, datastore_pat The app should ignore invalid locales and continue working. """ + # Establish session cookie + client.get(url_for("watchlist.index"), follow_redirects=True) + # First set to English res = client.get( url_for("set_language", locale="en"), @@ -111,6 +132,9 @@ def test_language_persistence_in_session(client, live_server, measure_memory_usa within the same session. """ + # Establish session cookie + client.get(url_for("watchlist.index"), follow_redirects=True) + # Set language to Italian res = client.get( url_for("set_language", locale="it"), @@ -137,6 +161,9 @@ def test_set_language_with_redirect(client, live_server, measure_memory_usage, d """ from flask import url_for + # Establish session cookie + client.get(url_for("watchlist.index"), follow_redirects=True) + # Set language with a redirect parameter (simulating language change from /settings) res = client.get( url_for("set_language", locale="de", redirect="/settings"),