feat(i18n): add English localization with per-user language preference and navbar toggle

This commit is contained in:
2026-07-06 22:07:05 +08:00
parent 172e33e121
commit f448a900f5
17 changed files with 539 additions and 418 deletions

View File

@@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block title %}总览 - 星笺{% endblock %}
{% block title %}{{ {'zh':'总览','en':'Dashboard'}[user.language] }} - {{ 'brand'|t(user.language) }}{% endblock %}
{% block content %}
<div class="section-header">
<h1>👋 欢迎,{{ user.username }}</h1>
<h1>{{ 'dash.welcome'|t(user.language) }}{{ user.username }}</h1>
</div>
{% if profiles %}
@@ -11,35 +11,35 @@
<div class="dash-stat-icon">📮</div>
<div class="dash-stat-body">
<span class="dash-stat-value">{{ total }}</span>
<span class="dash-stat-label">全部明信片</span>
<span class="dash-stat-label">{{ 'dash.total_cards'|t(user.language) }}</span>
</div>
</div>
<div class="dash-stat-card">
<div class="dash-stat-icon">📤</div>
<div class="dash-stat-body">
<span class="dash-stat-value dash-sent">{{ sent }}</span>
<span class="dash-stat-label">已寄出</span>
<span class="dash-stat-label">{{ 'dash.sent'|t(user.language) }}</span>
</div>
</div>
<div class="dash-stat-card">
<div class="dash-stat-icon">📬</div>
<div class="dash-stat-body">
<span class="dash-stat-value dash-delivered">{{ delivered }}</span>
<span class="dash-stat-label">已送达</span>
<span class="dash-stat-label">{{ 'dash.delivered'|t(user.language) }}</span>
</div>
</div>
<div class="dash-stat-card">
<div class="dash-stat-icon">💌</div>
<div class="dash-stat-body">
<span class="dash-stat-value dash-received">{{ received }}</span>
<span class="dash-stat-label">已收到</span>
<span class="dash-stat-label">{{ 'dash.received'|t(user.language) }}</span>
</div>
</div>
<div class="dash-stat-card">
<div class="dash-stat-icon">🌍</div>
<div class="dash-stat-body">
<span class="dash-stat-value">{{ countries }}</span>
<span class="dash-stat-label">国家/地区</span>
<span class="dash-stat-label">{{ 'dash.countries'|t(user.language) }}</span>
</div>
</div>
</div>
@@ -52,9 +52,9 @@
<div class="progress-seg progress-received" style="width:{{ received_pct }}%"></div>
</div>
<div class="progress-legend">
<span><i class="dot dot-sent"></i>已寄出 {{ sent_pct }}%</span>
<span><i class="dot dot-delivered"></i>已送达 {{ delivered_pct }}%</span>
<span><i class="dot dot-received"></i>已收到 {{ received_pct }}%</span>
<span><i class="dot dot-sent"></i>{{ 'dash.sent'|t(user.language) }} {{ sent_pct }}%</span>
<span><i class="dot dot-delivered"></i>{{ 'dash.delivered'|t(user.language) }} {{ delivered_pct }}%</span>
<span><i class="dot dot-received"></i>{{ 'dash.received'|t(user.language) }} {{ received_pct }}%</span>
</div>
</div>
{% endif %}
@@ -64,109 +64,62 @@
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1.5rem">
{% if sent_country_stats %}
<div style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:1.25rem">
<h2 style="margin-bottom:.75rem">📤 寄出</h2>
<h2 style="margin-bottom:.75rem">{{ 'dash.chart_sent'|t(user.language) }}</h2>
<canvas id="chart-sent"></canvas>
</div>
{% endif %}
{% if received_country_stats %}
<div style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:1.25rem">
<h2 style="margin-bottom:.75rem">📬 收到</h2>
<h2 style="margin-bottom:.75rem">{{ 'dash.chart_received'|t(user.language) }}</h2>
<canvas id="chart-received"></canvas>
</div>
{% endif %}
</div>
<script>
{% if sent_country_stats %}
new Chart(document.getElementById('chart-sent'), {
type: 'bar',
data: {
labels: [{% for code, _ in sent_country_stats %}'{{ code|flag }} {{ code }}',{% endfor %}],
datasets: [{ data: [{% for _, count in sent_country_stats %}{{ count }},{% endfor %}], backgroundColor: '#3b82f6', borderRadius: 4, maxBarThickness: 40 }]
},
options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } }, x: { grid: { display: false } } } }
});
{% endif %}
{% if received_country_stats %}
new Chart(document.getElementById('chart-received'), {
type: 'bar',
data: {
labels: [{% for code, _ in received_country_stats %}'{{ code|flag }} {{ code }}',{% endfor %}],
datasets: [{ data: [{% for _, count in received_country_stats %}{{ count }},{% endfor %}], backgroundColor: '#8b5cf6', borderRadius: 4, maxBarThickness: 40 }]
},
options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } }, x: { grid: { display: false } } } }
});
{% endif %}
(function() {
var labels, data, colors, elId;
{% for stat in sent_country_stats %}
labels = {{ stat.labels|tojson }};
data = {{ stat.data|tojson }};
colors = labels.map(function(l) { return '#f59e0b'; });
elId = 'chart-sent';
new Chart(document.getElementById(elId), {
type: 'bar',
data: { labels: labels, datasets: [{ data: data, backgroundColor: colors, borderRadius: 4 }] },
options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } } }
});
{% endfor %}
{% for stat in received_country_stats %}
labels = {{ stat.labels|tojson }};
data = {{ stat.data|tojson }};
colors = labels.map(function(l) { return '#8b5cf6'; });
elId = 'chart-received';
new Chart(document.getElementById(elId), {
type: 'bar',
data: { labels: labels, datasets: [{ data: data, backgroundColor: colors, borderRadius: 4 }] },
options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } } }
});
{% endfor %}
})();
</script>
{% endif %}
{% if profiles %}
<div class="dash-sections">
<div class="dash-section">
<h2>最近动态</h2>
{% if recent %}
<div class="recent-list">
{% for pc in recent %}
<a href="/postcards/{{ pc.id }}" class="recent-item">
<div class="recent-img">
{% if pc.image_front %}
<img src="{{ pc.image_front }}" alt="" loading="lazy">
{% else %}
<span>📷</span>
{% endif %}
</div>
<div class="recent-info">
<strong>{{ pc.card_number }}</strong>
<span class="badge badge-{{ pc.status }}">{{ {'sent':'已寄出','delivered':'已送达','received':'已收到'}.get(pc.status) }}</span>
</div>
<div class="recent-meta">
{% if pc.status in ('sent','delivered') %}
<span>{{ pc.country|flag }} {{ pc.country or '' }}</span>
<span>→ {{ pc.recipient_name or '-' }}</span>
{% else %}
<span>← {{ pc.sender_name or '-' }}</span>
{% endif %}
<span class="recent-time">
{% if pc.status == 'sent' and pc.send_time %}
{% set delta = (now.date() - pc.send_time.date()).days %}
{% if delta == 0 %}今天{% elif delta == 1 %}昨天{% else %}{{ delta }}天前{% endif %}
{% elif pc.status == 'delivered' and pc.arrival_time %}
{% set delta = (now.date() - pc.arrival_time.date()).days %}
{% if delta == 0 %}今天{% elif delta == 1 %}昨天{% else %}{{ delta }}天前{% endif %}
{% elif pc.status == 'received' and pc.receive_time %}
{% set delta = (now.date() - pc.receive_time.date()).days %}
{% if delta == 0 %}今天{% elif delta == 1 %}昨天{% else %}{{ delta }}天前{% endif %}
{% endif %}
</span>
</div>
</a>
{% endfor %}
{% for p in profiles %}
<a href="/profiles/{{ p.id }}/postcards" class="card dashboard-card">
<h3>{{ p.nickname }}</h3>
<div class="stats">
<span class="stat">{{ p.postcards|length }} {{ 'profiles.total'|t(user.language) }}</span>
</div>
{% else %}
<p class="empty">暂无明信片</p>
{% endif %}
</div>
<div class="dash-section">
<h2>Profile</h2>
<div class="dash-profiles">
{% for p in profiles %}
<a href="/profiles/{{ p.id }}/postcards" class="dash-profile-link">
<span class="dash-profile-name">{{ p.nickname }}</span>
<span class="dash-profile-arrow"></span>
</a>
{% endfor %}
<a href="/profiles" class="dash-profile-link dash-profile-more">
<span class="dash-profile-name">管理全部</span>
<span class="dash-profile-arrow"></span>
</a>
</div>
</div>
</a>
{% endfor %}
</div>
{% endif %}
{% else %}
<div class="empty-state">
<div class="empty-icon">📮</div>
<p>欢迎使用星笺!</p>
<p class="empty-sub">创建一个 <a href="/profiles">Profile</a> 开始管理你的明信片</p>
<p>{{ 'dash.welcome'|t(user.language) }}{{ user.username }} 👋</p>
</div>
{% endif %}
{% endblock %}