feat(showcase): add temporary language switcher for visitors with cookie persistence

This commit is contained in:
2026-07-07 09:57:20 +08:00
parent 02269417a9
commit a619b919be
3 changed files with 33 additions and 2 deletions

View File

@@ -815,6 +815,16 @@ def public_showcase(request: Request, username: str, db: Session = Depends(get_d
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == username).first()
if not user: if not user:
return HTMLResponse("用户不存在", status_code=404) return HTMLResponse("用户不存在", status_code=404)
# Language: query param > cookie > owner's default
from app.i18n import get_languages
_avail = get_languages()
visitor_lang = request.query_params.get("lang")
if not visitor_lang or visitor_lang not in _avail:
visitor_lang = request.cookies.get("showcase_lang")
if not visitor_lang or visitor_lang not in _avail:
visitor_lang = user.language
profiles = db.query(Profile).filter(Profile.user_id == user.id, Profile.showcase_enabled == True).all() profiles = db.query(Profile).filter(Profile.user_id == user.id, Profile.showcase_enabled == True).all()
sent_all, received_all, profile_groups = [], [], [] sent_all, received_all, profile_groups = [], [], []
for p in profiles: for p in profiles:
@@ -837,11 +847,13 @@ def public_showcase(request: Request, username: str, db: Session = Depends(get_d
profile_groups.append({"profile": p, "sent": p_sent, "received": p_received}) profile_groups.append({"profile": p, "sent": p_sent, "received": p_received})
sent_all.sort(key=lambda c: c.send_time or c.created_at, reverse=True) sent_all.sort(key=lambda c: c.send_time or c.created_at, reverse=True)
received_all.sort(key=lambda c: c.receive_time or c.created_at, reverse=True) received_all.sort(key=lambda c: c.receive_time or c.created_at, reverse=True)
return templates.TemplateResponse("showcase.html", { resp = templates.TemplateResponse("showcase.html", {
"request": request, "profile_user": user, "mode": user.showcase_mode, "request": request, "profile_user": user, "mode": user.showcase_mode,
"profile_groups": profile_groups, "sent_all": sent_all, "received_all": received_all, "profile_groups": profile_groups, "sent_all": sent_all, "received_all": received_all,
"lang": user.language, "lang": visitor_lang, "available_languages": _avail,
}) })
resp.set_cookie("showcase_lang", visitor_lang, max_age=365 * 24 * 3600, httponly=True, samesite="lax")
return resp
# ---------- API Keys ---------- # ---------- API Keys ----------

View File

@@ -39,6 +39,7 @@ a:hover { text-decoration: underline; }
.lang-option { display: block; width: 100%; padding: .35rem .75rem; border: none; background: none; text-align: left; font-size: .8rem; color: var(--text); cursor: pointer; white-space: nowrap; } .lang-option { display: block; width: 100%; padding: .35rem .75rem; border: none; background: none; text-align: left; font-size: .8rem; color: var(--text); cursor: pointer; white-space: nowrap; }
.lang-option:hover { background: var(--bg); color: var(--primary); } .lang-option:hover { background: var(--bg); color: var(--primary); }
.lang-option.active { color: var(--primary); font-weight: 600; } .lang-option.active { color: var(--primary); font-weight: 600; }
.showcase-lang-bar { position: fixed; top: 1rem; right: 1.5rem; z-index: 100; }
/* Container */ /* Container */
.container { max-width: 960px; margin: 2rem auto; padding: 0 1.5rem; } .container { max-width: 960px; margin: 2rem auto; padding: 0 1.5rem; }

View File

@@ -2,6 +2,16 @@
{% block title %}{{ profile_user.username }} {{ 'showcase.title'|t(lang) }}{% endblock %} {% block title %}{{ profile_user.username }} {{ 'showcase.title'|t(lang) }}{% endblock %}
{% block nav %}{% endblock %} {% block nav %}{% endblock %}
{% block content %} {% block content %}
<div class="showcase-lang-bar">
<div class="lang-switcher" id="langSwitcher">
<button type="button" class="lang-globe" onclick="document.getElementById('langDropdown').classList.toggle('show')">🌐</button>
<div class="lang-dropdown" id="langDropdown">
{% for code, name in available_languages.items() %}
<a href="?lang={{ code }}" class="lang-option {% if lang == code %}active{% endif %}">{{ name }}</a>
{% endfor %}
</div>
</div>
</div>
<div class="section-header"> <div class="section-header">
<h1>📮 {{ profile_user.username }} {{ 'showcase.title'|t(lang) }}</h1> <h1>📮 {{ profile_user.username }} {{ 'showcase.title'|t(lang) }}</h1>
</div> </div>
@@ -184,5 +194,13 @@ document.addEventListener('keydown', function(e) {
else if (e.key === 'ArrowLeft') navLightbox(-1); else if (e.key === 'ArrowLeft') navLightbox(-1);
else if (e.key === 'ArrowRight') navLightbox(1); else if (e.key === 'ArrowRight') navLightbox(1);
}); });
// Close language dropdown when clicking outside
document.addEventListener('click', function(e) {
var sw = document.getElementById('langSwitcher');
var dd = document.getElementById('langDropdown');
if (sw && dd && !sw.contains(e.target)) {
dd.classList.remove('show');
}
});
</script> </script>
{% endblock %} {% endblock %}