Some checks failed
ci/woodpecker/pr/pipeline Pipeline failed
Add i18n support to the blog web UI with 4 languages (en/ru/fr/de),
80 translation keys, automatic Accept-Language detection, persistent
locale cookie, and a language switcher dropdown in the header.
- Infrastructure: TranslationService, translation dicts, convenience _()
- Presentation: locale middleware, /web/lang/{locale} switcher route
- Templates: all 9 templates use {{ _(key, current_locale) }}
- Tests: 26 tests across TranslationService, locale detection helpers
- Docs: TEST_MODEL.md and FEATURE_INFRASTRUCTURE.md updated with TC-UNIT-811-821
126 lines
4.2 KiB
HTML
126 lines
4.2 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Profile - {{ user.username }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header" data-testid="page-header-profile">
|
|
<h1 class="page-title" data-testid="page-title-profile">{{ _('profile.title', current_locale) }}</h1>
|
|
</div>
|
|
|
|
<div class="card" data-testid="profile-card">
|
|
<div class="card-body" data-testid="profile-card-body">
|
|
<div class="profile-header" data-testid="profile-header">
|
|
<div class="avatar avatar-lg" data-testid="profile-avatar">
|
|
{{ user.username[0]|upper }}
|
|
</div>
|
|
<div class="profile-info" data-testid="profile-info">
|
|
<h2 class="profile-username" data-testid="profile-username">{{ user.username }}</h2>
|
|
<span class="badge {% if user_role == 'admin' %}badge-primary{% else %}badge-success{% endif %}" data-testid="profile-role">
|
|
{{ user_role|upper }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider" data-testid="profile-divider"></div>
|
|
|
|
<div class="profile-details" data-testid="profile-details">
|
|
<div class="profile-field" data-testid="profile-field-email">
|
|
<span class="profile-label" data-testid="profile-label-email">{{ _('profile.email', current_locale) }}</span>
|
|
<span class="profile-value" data-testid="profile-value-email">{{ user.email or _('profile.not_provided', current_locale) }}</span>
|
|
</div>
|
|
|
|
<div class="profile-field" data-testid="profile-field-userid">
|
|
<span class="profile-label" data-testid="profile-label-userid">{{ _('profile.user_id', current_locale) }}</span>
|
|
<span class="profile-value" data-testid="profile-value-userid">{{ user.user_id }}</span>
|
|
</div>
|
|
|
|
{% if user.first_name or user.last_name %}
|
|
<div class="profile-field" data-testid="profile-field-name">
|
|
<span class="profile-label" data-testid="profile-label-name">{{ _('profile.name', current_locale) }}</span>
|
|
<span class="profile-value" data-testid="profile-value-name">
|
|
{{ user.first_name or '' }} {{ user.last_name or '' }}
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer" data-testid="profile-card-footer">
|
|
<div class="flex justify-between items-center" data-testid="profile-actions">
|
|
<a href="/web/" class="btn" data-testid="btn-back-home">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
{{ _('profile.back_home', current_locale) }}
|
|
</a>
|
|
|
|
{% if can_create %}
|
|
<a href="/web/posts/new" class="btn btn-primary" data-testid="btn-create-post-profile">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M8 2v12M2 8h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
{{ _('profile.new_post', current_locale) }}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.profile-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.profile-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.profile-username {
|
|
margin: 0;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.profile-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.profile-field {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.profile-label {
|
|
font-weight: 600;
|
|
color: var(--color-text-light);
|
|
min-width: 80px;
|
|
}
|
|
|
|
.profile-value {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.profile-header {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
}
|
|
|
|
.profile-field {
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.profile-label {
|
|
min-width: auto;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|