From 39401d9b42aa32d0cc54d7f17b91e790b3556ff6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:50:27 +0000 Subject: [PATCH 1/8] Initial plan From d7695a039a8f5e2e5076e105d728c4c33597ea7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:56:03 +0000 Subject: [PATCH 2/8] Implement URL-based language routing to enable caching Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/__init__.py | 14 +++ opac/webapp/main/__init__.py | 2 +- opac/webapp/main/views.py | 114 +++++++++--------- opac/webapp/templates/admin/opac_base.html | 4 +- .../article/includes/alternative_header.html | 4 +- .../article/includes/recent_articles_row.html | 4 +- opac/webapp/templates/base.html | 2 +- .../collection/includes/levelMenu_search.html | 2 +- .../templates/collection/includes/nav.html | 6 +- opac/webapp/templates/includes/language.html | 10 +- .../issue/includes/alternative_header.html | 2 +- .../webapp/templates/issue/includes/meta.html | 2 +- opac/webapp/templates/issue/toc.html | 10 +- opac/webapp/templates/journal/detail.html | 16 +-- .../journal/includes/alternative_header.html | 2 +- .../templates/journal/includes/levelMenu.html | 4 +- .../templates/journal/includes/meta.html | 2 +- opac/webapp/templates/macros/collection.html | 6 +- opac/webapp/utils/caching.py | 10 +- 19 files changed, 114 insertions(+), 102 deletions(-) diff --git a/opac/webapp/__init__.py b/opac/webapp/__init__.py index e6141aa4e..d146b507e 100644 --- a/opac/webapp/__init__.py +++ b/opac/webapp/__init__.py @@ -251,6 +251,20 @@ def create_app(): app.register_blueprint(main_bp) app.register_blueprint(rest_bp) + # Root route: redirect to default language + @app.route("/") + def root_redirect(): + """ + Redirect root URL to default language. + This handles requests to / and redirects to // + """ + default_lang = app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") + # Detect language from Accept-Language header if available + langs = app.config.get("LANGUAGES", {}) + lang_from_headers = request.accept_languages.best_match(list(langs.keys())) + target_lang = lang_from_headers if lang_from_headers else default_lang + return redirect(url_for("main.index", ilng=target_lang)) + # Setup RQ Dashboard e Scheduler: - mover para um modulo proprio @app.before_request def check_user_logged_in_or_redirect(): diff --git a/opac/webapp/main/__init__.py b/opac/webapp/main/__init__.py index d3b262699..470d11326 100644 --- a/opac/webapp/main/__init__.py +++ b/opac/webapp/main/__init__.py @@ -1,7 +1,7 @@ # coding: utf-8 from flask import Blueprint -main = Blueprint("main", __name__) +main = Blueprint("main", __name__, url_prefix="/") restapi = Blueprint("restapi", __name__, url_prefix="/api/v1") from . import errors, views # NOQA diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index a6012dc85..4a812f5a6 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -22,7 +22,6 @@ render_template, request, send_from_directory, - session, url_for, ) from flask_babelex import gettext as _ @@ -60,6 +59,36 @@ def url_external(endpoint, **kwargs): return urljoin(request.url_root, url) +@main.url_value_preprocessor +def pull_lang(endpoint, values): + """ + Extract language from URL and inject into request object. + This function runs before each request to extract the language + parameter from the URL. + """ + if values is None: + return + lang = values.pop('ilng', None) + supported_langs = current_app.config.get("LANGUAGES", {}).keys() + default_lang = current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") + + # Validate and set language + if lang in supported_langs: + g.lang = lang + else: + g.lang = default_lang + + +@main.url_defaults +def add_lang(endpoint, values): + """ + Inject language into all URLs generated by url_for. + This ensures all URLs have the language prefix. + """ + if 'ilng' not in values and hasattr(g, 'lang'): + values['ilng'] = g.lang + + @main.before_app_request def add_collection_to_g(): if not hasattr(g, "collection"): @@ -73,7 +102,10 @@ def add_collection_to_g(): @main.before_app_request def add_langs(): - session["langs"] = current_app.config.get("LANGUAGES") + """ + Add available languages to g object for use in templates. + """ + g.langs = current_app.config.get("LANGUAGES") @main.after_request @@ -85,13 +117,6 @@ def add_header(response): return response -@main.after_request -def add_language_code(response): - language = session.get("lang", get_locale()) - response.set_cookie("language", language) - return response - - @main.before_app_request def add_forms_to_g(): setattr(g, "email_share", forms.EmailShareForm()) @@ -101,7 +126,10 @@ def add_forms_to_g(): @main.before_app_request def add_scielo_org_config_to_g(): - language = session.get("lang", get_locale()) + """ + Set SciELO org links based on current language from URL. + """ + language = get_locale() scielo_org_links = { # if language doesnt exists set the 'en' to SciELO ORG links. key: url.get(language, "en") @@ -112,55 +140,25 @@ def add_scielo_org_config_to_g(): @babel.localeselector def get_locale(): - langs = current_app.config.get("LANGUAGES") - lang_from_headers = request.accept_languages.best_match(list(langs.keys())) - - if "lang" not in list(session.keys()): - session["lang"] = lang_from_headers - - if not lang_from_headers and not session["lang"]: - # Caso não seja possível detectar o idioma e não tenhamos a chave lang - # no seção, fixamos o idioma padrão. - session["lang"] = current_app.config.get("BABEL_DEFAULT_LOCALE") - - return session["lang"] - - -@main.route("/set_locale//") -def set_locale(lang_code): - langs = current_app.config.get("LANGUAGES") - - if lang_code not in list(langs.keys()): - abort(400, _("Código de idioma inválido")) - - referrer = request.referrer - hash = request.args.get("hash") - if hash: - referrer += "#" + hash - - # salvar o lang code na sessão - session["lang"] = lang_code - if referrer: - return redirect(referrer) - else: - return redirect("/") + """ + Define the active language based on URL parameter. + This is used by Flask-Babel for translations. + """ + return getattr(g, 'lang', current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR")) -def get_lang_from_session(): +def get_lang(): """ - Tenta retornar o idioma da seção, caso não consiga retorna - BABEL_DEFAULT_LOCALE. + Get the current language from the g object. + Returns BABEL_DEFAULT_LOCALE if not set. """ - try: - return session["lang"] - except KeyError: - return current_app.config.get("BABEL_DEFAULT_LOCALE") + return getattr(g, 'lang', current_app.config.get("BABEL_DEFAULT_LOCALE")) @main.route("/") @cache.cached(key_prefix=cache_key_with_lang) def index(): - language = session.get("lang", get_locale()) + language = get_locale() news = controllers.get_latest_news_by_lang(language) tweets = controllers.get_collection_tweets() @@ -261,7 +259,7 @@ def collection_list_thematic(): @main.route("/journals/feed/") @cache.cached(key_prefix=cache_key_with_lang) def collection_list_feed(): - language = session.get("lang", get_locale()) + language = get_locale() collection = controllers.get_current_collection() title = "SciELO - %s - %s" % ( @@ -327,7 +325,7 @@ def collection_list_feed(): @main.route("/about/", methods=["GET"]) @cache.cached(key_prefix=cache_key_with_lang_with_qs) def about_collection(slug_name=None): - language = session.get("lang", get_locale()) + language = get_locale() context = {} page = None @@ -486,7 +484,7 @@ def journal_detail(url_seg): abort(404, JOURNAL_UNPUBLISH + _(journal.unpublish_reason)) # todo: ajustar para que seja só noticias relacionadas ao periódico - language = session.get("lang", get_locale()) + language = get_locale() news = controllers.get_latest_news_by_lang(language) # Press releases @@ -576,7 +574,7 @@ def journal_feed(url_seg): subtitle=utils.get_label_issue(last_issue), ) - feed_language = session.get("lang", get_locale()) + feed_language = get_locale() feed_language = feed_language[:2].lower() for article in articles: @@ -607,7 +605,7 @@ def journal_feed(url_seg): @main.route("/journal//about/", methods=["GET"]) @cache.cached(key_prefix=cache_key_with_lang) def about_journal(url_seg): - language = session.get("lang", get_locale()) + language = get_locale() journal = controllers.get_journal_by_url_seg(url_seg) content = None @@ -824,7 +822,7 @@ def issue_grid(url_seg): abort(404, JOURNAL_UNPUBLISH + _(journal.unpublish_reason)) # idioma da sessão - language = session.get("lang", get_locale()) + language = get_locale() # A ordenação padrão da função ``get_issues_by_jid``: "-year", "-volume", "-order" issues_data = controllers.get_issues_for_grid_by_jid(journal.id, is_public=True) @@ -871,7 +869,7 @@ def issue_toc(url_seg, url_seg_issue): filter_section_enable = bool(current_app.config["FILTER_SECTION_ENABLE"]) # idioma da sessão - language = session.get("lang", get_locale()) + language = get_locale() # obtém o issue issue = controllers.get_issue_by_url_seg(url_seg, url_seg_issue) @@ -1035,7 +1033,7 @@ def issue_feed(url_seg, url_seg_issue): subtitle=utils.get_label_issue(issue), ) - feed_language = session.get("lang", get_locale()) + feed_language = get_locale() for article in articles: # ######### TODO: Revisar ######### diff --git a/opac/webapp/templates/admin/opac_base.html b/opac/webapp/templates/admin/opac_base.html index a71426188..630ef62b3 100644 --- a/opac/webapp/templates/admin/opac_base.html +++ b/opac/webapp/templates/admin/opac_base.html @@ -29,8 +29,8 @@

{% for k,v in config.LANGUAGES.items() %} - - {% if k == session['lang'] %} + + {% if k == g.lang %} {{ v }} {% else %} {{ v }} diff --git a/opac/webapp/templates/article/includes/alternative_header.html b/opac/webapp/templates/article/includes/alternative_header.html index 6a8104659..a5d3f00a1 100644 --- a/opac/webapp/templates/article/includes/alternative_header.html +++ b/opac/webapp/templates/article/includes/alternative_header.html @@ -67,7 +67,7 @@

  • - + Busca
  • @@ -143,7 +143,7 @@

  • - + Blog SciELO em Perspectiva
  • diff --git a/opac/webapp/templates/article/includes/recent_articles_row.html b/opac/webapp/templates/article/includes/recent_articles_row.html index 2cf36060d..f8e34903e 100644 --- a/opac/webapp/templates/article/includes/recent_articles_row.html +++ b/opac/webapp/templates/article/includes/recent_articles_row.html @@ -5,10 +5,10 @@
    {% trans%}Volume{% endtrans %}: {{ article.issue.volume }} - {% trans%}Publicado em{% endtrans %}: {{ article.publication_date }}
    - {% if session.lang %} + {% if g.lang %} - {{ article.get_title_by_lang(session.lang[:2])|default(_('Documento sem título'), true)|striptags|capitalize }} + {{ article.get_title_by_lang(g.lang[:2])|default(_('Documento sem título'), true)|striptags|capitalize }} {% endif %} diff --git a/opac/webapp/templates/base.html b/opac/webapp/templates/base.html index 190ed4820..980c35a54 100644 --- a/opac/webapp/templates/base.html +++ b/opac/webapp/templates/base.html @@ -146,7 +146,7 @@ // Garante que o valor do campo share_url é a URL corrente. $('#share_url').val(window.location.href); - //moment.locale('{{ session.lang }}'); + //moment.locale('{{ g.lang }}'); //$("#date").text(moment().format("L HH:mm:ss ZZ")); diff --git a/opac/webapp/templates/collection/includes/levelMenu_search.html b/opac/webapp/templates/collection/includes/levelMenu_search.html index 3d890dcdf..87566252f 100644 --- a/opac/webapp/templates/collection/includes/levelMenu_search.html +++ b/opac/webapp/templates/collection/includes/levelMenu_search.html @@ -4,7 +4,7 @@
    - +
    diff --git a/opac/webapp/templates/collection/includes/nav.html b/opac/webapp/templates/collection/includes/nav.html index 3637a8e30..ebf8ab8a5 100644 --- a/opac/webapp/templates/collection/includes/nav.html +++ b/opac/webapp/templates/collection/includes/nav.html @@ -26,7 +26,7 @@
  • - + {% trans %}Busca{% endtrans %}
  • @@ -117,10 +117,10 @@
  • - {% if session.lang == 'pt_BR' %} + {% if g.lang == 'pt_BR' %} {% else %} - + {% endif %} {% trans %}Blog SciELO em Perspectiva{% endtrans %} diff --git a/opac/webapp/templates/includes/language.html b/opac/webapp/templates/includes/language.html index 37baecf16..7a9869ca6 100644 --- a/opac/webapp/templates/includes/language.html +++ b/opac/webapp/templates/includes/language.html @@ -3,9 +3,9 @@
  • - +
  • diff --git a/opac/webapp/templates/issue/includes/meta.html b/opac/webapp/templates/issue/includes/meta.html index e816f1be1..33d0532ce 100755 --- a/opac/webapp/templates/issue/includes/meta.html +++ b/opac/webapp/templates/issue/includes/meta.html @@ -3,7 +3,7 @@ - diff --git a/opac/webapp/templates/issue/toc.html b/opac/webapp/templates/issue/toc.html index dd5694d00..4252fbe8b 100644 --- a/opac/webapp/templates/issue/toc.html +++ b/opac/webapp/templates/issue/toc.html @@ -195,22 +195,22 @@

    - {% if session.lang %} - {% with nova_variavel=article.get_section_by_lang(session.lang[:2])%} + {% if g.lang %} + {% with nova_variavel=article.get_section_by_lang(g.lang[:2])%} {% if nova_variavel %} {{ nova_variavel | safe }}
    {% endif %} {% endwith %} - {{ article.get_title_by_lang(session.lang[:2])|default(_('Documento sem título'), true) | safe }} + {{ article.get_title_by_lang(g.lang[:2])|default(_('Documento sem título'), true) | safe }} {% endif %} {%- for author in article.authors %} - {% if session.lang %} - {{- author|striptags -}} + {% if g.lang %} + {{- author|striptags -}} {% else %} {{- author|striptags -}} {% if not loop.last %};{% endif %} diff --git a/opac/webapp/templates/journal/detail.html b/opac/webapp/templates/journal/detail.html index 64f792c60..ed537b60a 100644 --- a/opac/webapp/templates/journal/detail.html +++ b/opac/webapp/templates/journal/detail.html @@ -52,8 +52,8 @@ {# mission #} {% if journal.mission %}

    {% trans %}Nossa Missão{% endtrans %}

    - {% if session.lang %} -

    {{ journal.get_mission_by_lang(session.lang[:2])|safe|default(_("Periódico sem missão cadastrada"), true) }}

    + {% if g.lang %} +

    {{ journal.get_mission_by_lang(g.lang[:2])|safe|default(_("Periódico sem missão cadastrada"), true) }}

    {% endif %} {% endif %} {# mission #} @@ -107,8 +107,8 @@

    {% trans %}Sumário{% endtrans %}

    Press-releases - {% if session.lang != "pt_BR" %} - rss_feed + {% if g.lang != "pt_BR" %} + rss_feed {% else %} rss_feed {% endif %} @@ -229,8 +229,8 @@

    {% trans %}Notícias{% endtrans %}

    {% if journal.mission %}

    {% trans %}Nossa Missão{% endtrans %}

    - {% if session.lang %} -

    {{ journal.get_mission_by_lang(session.lang[:2])|safe|default(_("Periódico sem missão cadastrada"), true) }}

    + {% if g.lang %} +

    {{ journal.get_mission_by_lang(g.lang[:2])|safe|default(_("Periódico sem missão cadastrada"), true) }}

    {% endif %}
    {% endif %} @@ -290,8 +290,8 @@

    {% trans %}Sumário{% endtrans %}

    -->

    Press-releases - {% if session.lang != "pt_BR" %} - rss_feed + {% if g.lang != "pt_BR" %} + rss_feed {% else %} rss_feed {% endif %} diff --git a/opac/webapp/templates/journal/includes/alternative_header.html b/opac/webapp/templates/journal/includes/alternative_header.html index a1d844422..20d2e8fdd 100644 --- a/opac/webapp/templates/journal/includes/alternative_header.html +++ b/opac/webapp/templates/journal/includes/alternative_header.html @@ -82,7 +82,7 @@

  • - search + search
  • {% if journal.scielo_issn or journal.eletronic_issn or journal.print_issn %} diff --git a/opac/webapp/templates/journal/includes/levelMenu.html b/opac/webapp/templates/journal/includes/levelMenu.html index adeb3b8e5..36539f324 100644 --- a/opac/webapp/templates/journal/includes/levelMenu.html +++ b/opac/webapp/templates/journal/includes/levelMenu.html @@ -59,7 +59,7 @@
  • - {% trans %}Buscar{% endtrans %} + {% trans %}Buscar{% endtrans %} {% if journal.scielo_issn or journal.eletronic_issn or journal.print_issn %} show_chart {% trans %}Métricas{% endtrans %} @@ -92,7 +92,7 @@ {% trans %}Todos{% endtrans %} - {% trans %}Buscar{% endtrans %} + {% trans %}Buscar{% endtrans %} {% if journal.scielo_issn or journal.eletronic_issn or journal.print_issn %} {% trans %}Métricas{% endtrans %} {% else %} diff --git a/opac/webapp/templates/journal/includes/meta.html b/opac/webapp/templates/journal/includes/meta.html index b69c2c57d..8031947c2 100755 --- a/opac/webapp/templates/journal/includes/meta.html +++ b/opac/webapp/templates/journal/includes/meta.html @@ -3,6 +3,6 @@ - + diff --git a/opac/webapp/templates/macros/collection.html b/opac/webapp/templates/macros/collection.html index 5be43731a..27246a4f1 100644 --- a/opac/webapp/templates/macros/collection.html +++ b/opac/webapp/templates/macros/collection.html @@ -1,10 +1,10 @@ {% macro get_collection_name() -%} - {%- if session.lang == 'pt_BR' and g.collection.name_pt -%} + {%- if g.lang == 'pt_BR' and g.collection.name_pt -%} {{ g.collection.name_pt }} - {%- elif session.lang == 'en' and g.collection.name_en-%} + {%- elif g.lang == 'en' and g.collection.name_en-%} {{ g.collection.name_en }} - {%- elif session.lang == 'es' and g.collection.name_es-%} + {%- elif g.lang == 'es' and g.collection.name_es-%} {{ g.collection.name_es }} {%- else -%} {{ g.collection.name }} diff --git a/opac/webapp/utils/caching.py b/opac/webapp/utils/caching.py index 8625f8fa0..f5210ebfe 100644 --- a/opac/webapp/utils/caching.py +++ b/opac/webapp/utils/caching.py @@ -1,7 +1,7 @@ # Redis Cache Key Generation: import hashlib -from flask import current_app, request, session +from flask import current_app, request, g def _make_querystring_hash(): @@ -35,12 +35,12 @@ def cache_key_with_lang(): """ Função chamada no decorator @cache.cached Retorna a chave para usar no cache (redis) usando: - - o códiog do idioma armazenado na sessão (ou o default se não tiver) + - o código do idioma armazenado em g.lang (ou o default se não tiver) - o path do request """ default_lang = current_app.config.get("BABEL_DEFAULT_LOCALE") - language = session.get("lang", default_lang) + language = getattr(g, 'lang', default_lang) return _cache_key_format(language, request.path) @@ -48,12 +48,12 @@ def cache_key_with_lang_with_qs(): """ Função chamada no decorator @cache.cached Retorna a chave para usar no cache (redis) usando: - - o códiog do idioma armazenado na sessão (ou o default se não tiver) + - o código do idioma armazenado em g.lang (ou o default se não tiver) - o path do request - o hash gerado a partir dos parametros da querystring """ default_lang = current_app.config.get("BABEL_DEFAULT_LOCALE") - language = session.get("lang", default_lang) + language = getattr(g, 'lang', default_lang) qs_hash = _make_querystring_hash() return _cache_key_format(language, request.path, qs_hash) From ea1da7d00abb69b0476d360230ee0c2808ece324 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:58:48 +0000 Subject: [PATCH 3/8] Add fallback for url_defaults when g.lang is not set Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/main/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index 4a812f5a6..30001ef3d 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -85,8 +85,12 @@ def add_lang(endpoint, values): Inject language into all URLs generated by url_for. This ensures all URLs have the language prefix. """ - if 'ilng' not in values and hasattr(g, 'lang'): - values['ilng'] = g.lang + if 'ilng' not in values: + if hasattr(g, 'lang'): + values['ilng'] = g.lang + else: + # Fallback to default language if g.lang is not set + values['ilng'] = current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") @main.before_app_request From 7487d628ef53669066389ecd02a0f13fd7a8a410 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:01:18 +0000 Subject: [PATCH 4/8] Move static file routes to app level to avoid language prefix requirement Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/__init__.py | 13 ++++++++++++- opac/webapp/main/views.py | 11 ++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/opac/webapp/__init__.py b/opac/webapp/__init__.py index d146b507e..dfd0ca12b 100644 --- a/opac/webapp/__init__.py +++ b/opac/webapp/__init__.py @@ -6,7 +6,7 @@ import rq_dashboard import rq_scheduler_dashboard from elasticapm.contrib.flask import ElasticAPM -from flask import Flask, flash, redirect, request, url_for +from flask import Flask, flash, redirect, request, url_for, send_from_directory from flask_babelex import Babel, lazy_gettext from flask_caching import Cache from flask_htmlmin import HTMLMIN @@ -265,6 +265,17 @@ def root_redirect(): target_lang = lang_from_headers if lang_from_headers else default_lang return redirect(url_for("main.index", ilng=target_lang)) + # Static file routes that should not have language prefix + @app.route("/robots.txt", methods=["GET"]) + def get_robots_txt_file(): + """Serve robots.txt from static folder""" + return send_from_directory("static", "robots.txt") + + @app.route("/img/scielo.gif", methods=["GET"]) + def full_text_image(): + """Serve legacy full text image""" + return send_from_directory("static", "img/full_text_scielo_img.gif") + # Setup RQ Dashboard e Scheduler: - mover para um modulo proprio @app.before_request def check_user_logged_in_or_redirect(): diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index 30001ef3d..2fc05cc1c 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -1798,15 +1798,8 @@ def download_file_by_filename(filename): return send_from_directory(media_root, filename) -@main.route("/img/scielo.gif", methods=["GET"]) -def full_text_image(): - return send_from_directory("static", "img/full_text_scielo_img.gif") - - -@main.route("/robots.txt", methods=["GET"]) -def get_robots_txt_file(): - return send_from_directory("static", "robots.txt") - +# Note: robots.txt and img/scielo.gif routes moved to app level +# (see opac/webapp/__init__.py) to avoid language prefix requirement @main.route("/revistas//.htm", methods=["GET"]) def router_legacy_info_pages(journal_seg, page): From c8dc5b54f2c19f1cd04fc37b40dfc4fabe2c87bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:02:53 +0000 Subject: [PATCH 5/8] Add backward compatibility redirects for legacy URLs Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/__init__.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/opac/webapp/__init__.py b/opac/webapp/__init__.py index dfd0ca12b..556744d18 100644 --- a/opac/webapp/__init__.py +++ b/opac/webapp/__init__.py @@ -276,6 +276,40 @@ def full_text_image(): """Serve legacy full text image""" return send_from_directory("static", "img/full_text_scielo_img.gif") + # Legacy URL redirects - redirect to language-prefixed versions + @app.route("/scielo.php/") + def scielo_php_redirect(): + """ + Redirect old scielo.php URLs to language-prefixed version. + This maintains backward compatibility with old external links. + """ + default_lang = app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") + langs = app.config.get("LANGUAGES", {}) + lang_from_headers = request.accept_languages.best_match(list(langs.keys())) + target_lang = lang_from_headers if lang_from_headers else default_lang + + # Build the new URL with language prefix and preserve query string + from werkzeug.urls import url_encode + query_string = url_encode(request.args) + new_path = f"/{target_lang}/scielo.php/" + if query_string: + new_path += f"?{query_string}" + return redirect(new_path, code=301) + + @app.route("/revistas//.htm") + def revistas_redirect(journal_seg, page): + """ + Redirect old /revistas/ URLs to language-prefixed version. + This maintains backward compatibility with old external links. + """ + default_lang = app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") + langs = app.config.get("LANGUAGES", {}) + lang_from_headers = request.accept_languages.best_match(list(langs.keys())) + target_lang = lang_from_headers if lang_from_headers else default_lang + + new_path = f"/{target_lang}/revistas/{journal_seg}/{page}.htm" + return redirect(new_path, code=301) + # Setup RQ Dashboard e Scheduler: - mover para um modulo proprio @app.before_request def check_user_logged_in_or_redirect(): From 1e4d082c742f844908a1fb1b63c6da6228003d24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:05:58 +0000 Subject: [PATCH 6/8] Address code review comments: Add guards and improve code style Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/__init__.py | 2 +- opac/webapp/main/views.py | 3 +++ opac/webapp/templates/includes/language.html | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/opac/webapp/__init__.py b/opac/webapp/__init__.py index 556744d18..8ec11fa42 100644 --- a/opac/webapp/__init__.py +++ b/opac/webapp/__init__.py @@ -28,6 +28,7 @@ from raven.contrib.flask import Sentry from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.routing import BaseConverter +from werkzeug.urls import url_encode login_manager = LoginManager() @@ -289,7 +290,6 @@ def scielo_php_redirect(): target_lang = lang_from_headers if lang_from_headers else default_lang # Build the new URL with language prefix and preserve query string - from werkzeug.urls import url_encode query_string = url_encode(request.args) new_path = f"/{target_lang}/scielo.php/" if query_string: diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index 2fc05cc1c..6f79e242b 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -65,6 +65,9 @@ def pull_lang(endpoint, values): Extract language from URL and inject into request object. This function runs before each request to extract the language parameter from the URL. + + Note: values can be None for static file requests, error handlers, + or other special routes that don't have URL parameters. """ if values is None: return diff --git a/opac/webapp/templates/includes/language.html b/opac/webapp/templates/includes/language.html index 7a9869ca6..9f8dbcaae 100644 --- a/opac/webapp/templates/includes/language.html +++ b/opac/webapp/templates/includes/language.html @@ -18,7 +18,11 @@ {% for lang_code, lang_name in g.langs.items() %} {% if g.lang != lang_code %} + {% if request.endpoint %}
  • {{lang_name}}
  • + {% else %} +
  • {{lang_name}}
  • + {% endif %} {% endif %} {% endfor %} From fc1c86334558bca69a9fb53f10a7d0569c71b720 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:06:50 +0000 Subject: [PATCH 7/8] Remove redundant get_lang() function Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/main/views.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index 6f79e242b..06bde2531 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -154,14 +154,6 @@ def get_locale(): return getattr(g, 'lang', current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR")) -def get_lang(): - """ - Get the current language from the g object. - Returns BABEL_DEFAULT_LOCALE if not set. - """ - return getattr(g, 'lang', current_app.config.get("BABEL_DEFAULT_LOCALE")) - - @main.route("/") @cache.cached(key_prefix=cache_key_with_lang) def index(): From f7a7307bb8520350ee5b49d66bfed6d962050c19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:27:03 +0000 Subject: [PATCH 8/8] Use Accept-Language header and query params instead of URL-based routing - Remove language prefix from URLs (keep original URL structure) - Use Accept-Language header as primary language detection - Support ?lang= query parameter for explicit language selection - Remove Set-Cookie: language header to enable CDN caching - Store language in g.lang (per-request) instead of session - Update all templates to use g.lang instead of session.lang - CDN can now cache with Vary: Accept-Language instead of Vary: Cookie Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- opac/webapp/__init__.py | 61 +-------- opac/webapp/main/__init__.py | 2 +- opac/webapp/main/views.py | 136 ++++++++++++------- opac/webapp/templates/admin/opac_base.html | 2 +- opac/webapp/templates/includes/language.html | 6 +- 5 files changed, 92 insertions(+), 115 deletions(-) diff --git a/opac/webapp/__init__.py b/opac/webapp/__init__.py index 8ec11fa42..e6141aa4e 100644 --- a/opac/webapp/__init__.py +++ b/opac/webapp/__init__.py @@ -6,7 +6,7 @@ import rq_dashboard import rq_scheduler_dashboard from elasticapm.contrib.flask import ElasticAPM -from flask import Flask, flash, redirect, request, url_for, send_from_directory +from flask import Flask, flash, redirect, request, url_for from flask_babelex import Babel, lazy_gettext from flask_caching import Cache from flask_htmlmin import HTMLMIN @@ -28,7 +28,6 @@ from raven.contrib.flask import Sentry from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.routing import BaseConverter -from werkzeug.urls import url_encode login_manager = LoginManager() @@ -252,64 +251,6 @@ def create_app(): app.register_blueprint(main_bp) app.register_blueprint(rest_bp) - # Root route: redirect to default language - @app.route("/") - def root_redirect(): - """ - Redirect root URL to default language. - This handles requests to / and redirects to // - """ - default_lang = app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") - # Detect language from Accept-Language header if available - langs = app.config.get("LANGUAGES", {}) - lang_from_headers = request.accept_languages.best_match(list(langs.keys())) - target_lang = lang_from_headers if lang_from_headers else default_lang - return redirect(url_for("main.index", ilng=target_lang)) - - # Static file routes that should not have language prefix - @app.route("/robots.txt", methods=["GET"]) - def get_robots_txt_file(): - """Serve robots.txt from static folder""" - return send_from_directory("static", "robots.txt") - - @app.route("/img/scielo.gif", methods=["GET"]) - def full_text_image(): - """Serve legacy full text image""" - return send_from_directory("static", "img/full_text_scielo_img.gif") - - # Legacy URL redirects - redirect to language-prefixed versions - @app.route("/scielo.php/") - def scielo_php_redirect(): - """ - Redirect old scielo.php URLs to language-prefixed version. - This maintains backward compatibility with old external links. - """ - default_lang = app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") - langs = app.config.get("LANGUAGES", {}) - lang_from_headers = request.accept_languages.best_match(list(langs.keys())) - target_lang = lang_from_headers if lang_from_headers else default_lang - - # Build the new URL with language prefix and preserve query string - query_string = url_encode(request.args) - new_path = f"/{target_lang}/scielo.php/" - if query_string: - new_path += f"?{query_string}" - return redirect(new_path, code=301) - - @app.route("/revistas//.htm") - def revistas_redirect(journal_seg, page): - """ - Redirect old /revistas/ URLs to language-prefixed version. - This maintains backward compatibility with old external links. - """ - default_lang = app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") - langs = app.config.get("LANGUAGES", {}) - lang_from_headers = request.accept_languages.best_match(list(langs.keys())) - target_lang = lang_from_headers if lang_from_headers else default_lang - - new_path = f"/{target_lang}/revistas/{journal_seg}/{page}.htm" - return redirect(new_path, code=301) - # Setup RQ Dashboard e Scheduler: - mover para um modulo proprio @app.before_request def check_user_logged_in_or_redirect(): diff --git a/opac/webapp/main/__init__.py b/opac/webapp/main/__init__.py index 470d11326..d3b262699 100644 --- a/opac/webapp/main/__init__.py +++ b/opac/webapp/main/__init__.py @@ -1,7 +1,7 @@ # coding: utf-8 from flask import Blueprint -main = Blueprint("main", __name__, url_prefix="/") +main = Blueprint("main", __name__) restapi = Blueprint("restapi", __name__, url_prefix="/api/v1") from . import errors, views # NOQA diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index 06bde2531..81afc09cf 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -22,6 +22,7 @@ render_template, request, send_from_directory, + session, url_for, ) from flask_babelex import gettext as _ @@ -59,43 +60,6 @@ def url_external(endpoint, **kwargs): return urljoin(request.url_root, url) -@main.url_value_preprocessor -def pull_lang(endpoint, values): - """ - Extract language from URL and inject into request object. - This function runs before each request to extract the language - parameter from the URL. - - Note: values can be None for static file requests, error handlers, - or other special routes that don't have URL parameters. - """ - if values is None: - return - lang = values.pop('ilng', None) - supported_langs = current_app.config.get("LANGUAGES", {}).keys() - default_lang = current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") - - # Validate and set language - if lang in supported_langs: - g.lang = lang - else: - g.lang = default_lang - - -@main.url_defaults -def add_lang(endpoint, values): - """ - Inject language into all URLs generated by url_for. - This ensures all URLs have the language prefix. - """ - if 'ilng' not in values: - if hasattr(g, 'lang'): - values['ilng'] = g.lang - else: - # Fallback to default language if g.lang is not set - values['ilng'] = current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") - - @main.before_app_request def add_collection_to_g(): if not hasattr(g, "collection"): @@ -109,9 +73,7 @@ def add_collection_to_g(): @main.before_app_request def add_langs(): - """ - Add available languages to g object for use in templates. - """ + # Store available languages in g instead of session to avoid session cookie g.langs = current_app.config.get("LANGUAGES") @@ -124,6 +86,10 @@ def add_header(response): return response +# Removed add_language_code() to eliminate Set-Cookie: language header +# This allows CDN/Varnish to cache pages properly + + @main.before_app_request def add_forms_to_g(): setattr(g, "email_share", forms.EmailShareForm()) @@ -133,9 +99,6 @@ def add_forms_to_g(): @main.before_app_request def add_scielo_org_config_to_g(): - """ - Set SciELO org links based on current language from URL. - """ language = get_locale() scielo_org_links = { # if language doesnt exists set the 'en' to SciELO ORG links. @@ -148,10 +111,80 @@ def add_scielo_org_config_to_g(): @babel.localeselector def get_locale(): """ - Define the active language based on URL parameter. - This is used by Flask-Babel for translations. + Determine language based on query parameter or Accept-Language header. + Does not use session/cookies to allow CDN/Varnish caching. + CDN can use Vary: Accept-Language for proper caching by language. + """ + # Cache the result in g to avoid repeated computation + if hasattr(g, 'lang'): + return g.lang + + langs = current_app.config.get("LANGUAGES") + + # Check for explicit language in query parameter (for language switcher) + lang_from_query = request.args.get('lang') + if lang_from_query and lang_from_query in langs: + g.lang = lang_from_query + return g.lang + + # Fall back to Accept-Language header + lang_from_headers = request.accept_languages.best_match(list(langs.keys())) + + # Use detected language or fall back to default + if lang_from_headers: + g.lang = lang_from_headers + else: + g.lang = current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR") + + return g.lang + + +@main.route("/set_locale//") +def set_locale(lang_code): + """ + Language switcher endpoint. Instead of storing in session/cookie, + redirects to the same page with ?lang= query parameter. + This allows language selection without cookies, enabling CDN caching. """ - return getattr(g, 'lang', current_app.config.get("BABEL_DEFAULT_LOCALE", "pt_BR")) + langs = current_app.config.get("LANGUAGES") + + if lang_code not in list(langs.keys()): + abort(400, _("Código de idioma inválido")) + + referrer = request.referrer + if not referrer: + referrer = url_for('main.index') + + # Parse the referrer URL to add/update lang parameter + from urllib.parse import urlparse, parse_qs, urlencode, urlunparse + parsed = urlparse(referrer) + query_params = parse_qs(parsed.query) + query_params['lang'] = [lang_code] + + # Handle hash fragment + hash_fragment = request.args.get("hash", "") + + # Rebuild URL with updated query string + new_query = urlencode(query_params, doseq=True) + new_url = urlunparse(( + parsed.scheme, + parsed.netloc, + parsed.path, + parsed.params, + new_query, + hash_fragment + )) + + return redirect(new_url) + + +def get_lang_from_session(): + """ + Returns the current language. Updated to use get_locale() instead of session. + Kept for backward compatibility with existing code. + """ + return get_locale() + @main.route("/") @@ -1793,8 +1826,15 @@ def download_file_by_filename(filename): return send_from_directory(media_root, filename) -# Note: robots.txt and img/scielo.gif routes moved to app level -# (see opac/webapp/__init__.py) to avoid language prefix requirement +@main.route("/img/scielo.gif", methods=["GET"]) +def full_text_image(): + return send_from_directory("static", "img/full_text_scielo_img.gif") + + +@main.route("/robots.txt", methods=["GET"]) +def get_robots_txt_file(): + return send_from_directory("static", "robots.txt") + @main.route("/revistas//.htm", methods=["GET"]) def router_legacy_info_pages(journal_seg, page): diff --git a/opac/webapp/templates/admin/opac_base.html b/opac/webapp/templates/admin/opac_base.html index 630ef62b3..2bc211f32 100644 --- a/opac/webapp/templates/admin/opac_base.html +++ b/opac/webapp/templates/admin/opac_base.html @@ -29,7 +29,7 @@

    {% for k,v in config.LANGUAGES.items() %} - + {% if k == g.lang %} {{ v }} {% else %} diff --git a/opac/webapp/templates/includes/language.html b/opac/webapp/templates/includes/language.html index 9f8dbcaae..57b9c3703 100644 --- a/opac/webapp/templates/includes/language.html +++ b/opac/webapp/templates/includes/language.html @@ -18,11 +18,7 @@ {% for lang_code, lang_name in g.langs.items() %} {% if g.lang != lang_code %} - {% if request.endpoint %} -

  • {{lang_name}}
  • - {% else %} -
  • {{lang_name}}
  • - {% endif %} +
  • {{lang_name}}
  • {% endif %} {% endfor %}