Replace cookie-based language with Accept-Language header for CDN/Varnish caching#439
Open
Replace cookie-based language with Accept-Language header for CDN/Varnish caching#439
Conversation
…hing
- Remove language cookie (add_language_code after_request handler)
- Move session["langs"] to g.langs to avoid session modification on every request
- Modify get_locale() to not auto-write to session; only read session["lang"]
(set by explicit language switch), fall back to Accept-Language header
- Add Vary: Accept-Language header and remove Vary: Cookie
- Set g.lang in before_request for template usage
- Update caching.py to use get_locale() instead of session.get("lang")
- Update all templates: session.lang → g.lang, session.langs → g.langs
Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
…save_session) Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix caching issue caused by language cookie
Replace cookie-based language with Accept-Language header for CDN/Varnish caching
Mar 16, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Este PR muda a detecção de idioma para priorizar Accept-Language (com fallback), evitando escrita em sessão/cookie a cada request e permitindo cache em CDN/Varnish via Vary: Accept-Language.
Changes:
- Remove o
after_requestque setavaset-cookie: language=...e adicionaVary: Accept-Languagenas respostas. - Move
session["langs"]parag.langse passag.langpara templates/links. - Atualiza geração de chave de cache (Redis) para usar
flask_babelex.get_locale()e refatora diversas views/templates para dependerem deg.lang.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| opac/webapp/main/views.py | Refatora seleção de locale, remove cookie language, adiciona Vary: Accept-Language e troca leituras de idioma nas views. |
| opac/webapp/utils/caching.py | Atualiza as funções de cache key para obter o locale via Babel (em vez de sessão). |
| opac/webapp/templates/base.html | Ajusta referência comentada de locale (JS) para usar g.lang. |
| opac/webapp/templates/includes/language.html | Migra dropdown de idioma de session.lang[s] para g.lang[s]. |
| opac/webapp/templates/macros/collection.html | Usa g.lang para resolver nome da coleção por idioma. |
| opac/webapp/templates/collection/includes/nav.html | Migra decisões/URLs baseadas em idioma para g.lang. |
| opac/webapp/templates/collection/includes/header.html | Migra escolha de logo por idioma para g.lang. |
| opac/webapp/templates/collection/includes/levelMenu_search.html | Migra parâmetro lang (search) para g.lang. |
| opac/webapp/templates/journal/includes/meta.html | Migra missão/OG description para g.lang. |
| opac/webapp/templates/journal/includes/levelMenu.html | Migra parâmetro lang (search) para g.lang. |
| opac/webapp/templates/journal/includes/header.html | Migra escolha de logo por idioma para g.lang. |
| opac/webapp/templates/journal/includes/alternative_header.html | Migra parâmetro lang (search) para g.lang. |
| opac/webapp/templates/journal/detail.html | Migra mission/feeds/links condicionais para g.lang. |
| opac/webapp/templates/issue/toc.html | Migra título/seção/links de autor para g.lang. |
| opac/webapp/templates/issue/includes/alternative_header.html | Migra parâmetro lang (search) para g.lang. |
| opac/webapp/templates/article/includes/header.html | Migra escolha de logo por idioma para g.lang. |
| opac/webapp/templates/article/includes/recent_articles_row.html | Migra título por idioma para g.lang. |
| opac/webapp/templates/article/includes/alternative_header.html | Migra parte dos links/branding para g.lang. |
| opac/webapp/templates/admin/opac_base.html | Migra seleção de idioma no admin para g.lang. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
109
to
+118
| 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 user explicitly set language via set_locale, use that | ||
| if "lang" in session: | ||
| return session["lang"] | ||
|
|
||
| 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") | ||
| # Otherwise, use Accept-Language header | ||
| lang_from_headers = request.accept_languages.best_match(list(langs.keys())) | ||
| if lang_from_headers: |
Comment on lines
81
to
87
| def add_header(response): | ||
| response.cache_control.max_age = current_app.config.get( | ||
| "CACHE_CONTROL_MAX_AGE_HEADER" | ||
| ) | ||
| response.headers["x-content-type-options"] = "nosniff" | ||
| return response | ||
|
|
||
|
|
||
| @main.after_request | ||
| def add_language_code(response): | ||
| language = session.get("lang", get_locale()) | ||
| response.set_cookie("language", language) | ||
| response.vary.add("Accept-Language") | ||
| return response |
Comment on lines
108
to
+122
| @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 user explicitly set language via set_locale, use that | ||
| if "lang" in session: | ||
| return session["lang"] | ||
|
|
||
| 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") | ||
| # Otherwise, use Accept-Language header | ||
| lang_from_headers = request.accept_languages.best_match(list(langs.keys())) | ||
| if lang_from_headers: | ||
| return lang_from_headers | ||
|
|
||
| return session["lang"] | ||
| # Default locale | ||
| return current_app.config.get("BABEL_DEFAULT_LOCALE") |
| import hashlib | ||
|
|
||
| from flask import current_app, request, session | ||
| from flask import current_app, request |
Comment on lines
74
to
76
| <li> | ||
| <a href="{{ config.URL_SEARCH }}?q=*&lang={% if session.lang %}{{ session.lang[:2] }}{% endif %}&filter[in][]={{config.OPAC_COLLECTION}}"> | ||
| <a href="{{ config.URL_SEARCH }}?q=*&lang={% if g.lang %}{{ g.lang[:2] }}{% endif %}&filter[in][]={{config.OPAC_COLLECTION}}"> | ||
| Busca |
Comment on lines
146
to
150
| // 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 }}'); | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que esse PR faz?
O backend emite
set-cookie: language=pt_BReVary: Cookieem toda resposta, impedindo cache no BunnyCDN e Varnish — qualquer CDN interpreta isso como conteúdo personalizado por cookie.Este PR elimina o cookie
language, para de modificar a sessão em cada request (que disparaVary: Cookie), e passa a usar o header padrãoAccept-Languagepara detecção de idioma, emitindoVary: Accept-Language.Mudanças principais:
add_language_code()after_request — para de emitirset-cookie: language=pt_BRsession["langs"]→g.langs— evita marcar a sessão como modificada em cada request (que causaVary: Cookieviasave_session)get_locale()— lêsession["lang"]somente se o usuário trocou idioma explicitamente via UI; caso contrário usaAccept-Languageheader com fallback paraBABEL_DEFAULT_LOCALEVary: Accept-Language— permite cache por idioma no CDN/Varnishcaching.py— usaflask_babelex.get_locale()em vez desession.get("lang")session.lang→g.lang,session.langs→g.langsFluxo após a mudança:
Accept-Language→ sem cookie → cacheávelset_locale()grava na sessão → funciona como antesset_locale()é o único ponto que modifica a sessãoOnde a revisão poderia começar?
opac/webapp/main/views.py— as funçõesadd_langs(),add_header(),get_locale()e a remoção deadd_language_code().Como este poderia ser testado manualmente?
curl -I https://<host>/— verificar que não háset-cookie: language=...e que háVary: Accept-Language(semVary: Cookiepara requests sem sessão)Accept-Languagedo navegadorAccept-Language: es— site deve aparecer em espanholAlgum cenário de contexto que queira dar?
O
Vary: Cookieera emitido por dois motivos:add_language_code()setava explicitamente o cookielanguageadd_langs()faziasession["langs"] = ...em todo request, marcando a sessão como modificada → Flask chamavasave_session()→ adicionavaVary: CookieCom
SESSION_REFRESH_EACH_REQUEST=False(já configurado) e sem modificar a sessão em requests normais,save_session()não adicionaVary: Cookie.Screenshots
N/A — mudança de headers HTTP, sem impacto visual.
Quais são tickets relevantes?
Issue #355
Referências
Original prompt
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.