feat: use i18n
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -11,6 +11,14 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Twemoji Country Flags';
|
||||
unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
|
||||
src: url('https://cdn.jsdelivr.net/npm/country-flag-emoji-polyfill@0.1/dist/TwemojiCountryFlags.woff2') format('woff2');
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
<script src="./telegram-web-app.js"></script>
|
||||
<link rel="stylesheet" href="./subscription_webapp.css">
|
||||
</head>
|
||||
@@ -28,6 +36,16 @@
|
||||
<div id="brand-title" class="brand-title" data-brand-title>Моя подписка</div>
|
||||
</div>
|
||||
<div class="app-header-actions">
|
||||
<div class="lang-menu">
|
||||
<button id="lang-chip" class="lang-chip" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="lang-dropdown" aria-label="Language" onclick="toggleLangMenu()">
|
||||
<span id="lang-chip-flag" class="lang-chip-flag" aria-hidden="true">🇷🇺</span>
|
||||
<span id="lang-chip-label" class="lang-chip-label">RU</span>
|
||||
<svg class="lang-chip-caret" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" aria-hidden="true">
|
||||
<path d="M297.4 470.6C309.9 483.1 330.2 483.1 342.7 470.6L534.7 278.6C547.2 266.1 547.2 245.8 534.7 233.3C522.2 220.8 501.9 220.8 489.4 233.3L320 402.7L150.6 233.4C138.1 220.9 117.8 220.9 105.3 233.4C92.8 245.9 92.8 266.2 105.3 278.7L297.3 470.7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div id="lang-dropdown" class="lang-dropdown hidden" role="menu"></div>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<button id="user-chip" class="user-chip" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="user-dropdown" onclick="toggleUserMenu()">
|
||||
<img id="user-chip-avatar" class="user-chip-avatar" alt="" aria-hidden="true">
|
||||
|
||||
@@ -109,9 +109,19 @@ const MOCK = (() => {
|
||||
telegramLinkInProgress: false,
|
||||
toastTimer: null,
|
||||
userMenuOpen: false,
|
||||
userMenuCloseTimer: null
|
||||
userMenuCloseTimer: null,
|
||||
langOverride: (function() {
|
||||
try { return localStorage.getItem('rw_webapp_lang') || ''; } catch (e) { return ''; }
|
||||
})(),
|
||||
langMenuOpen: false,
|
||||
langMenuCloseTimer: null
|
||||
};
|
||||
|
||||
const LANG_OPTIONS = [
|
||||
{ code: 'ru', label: 'Русский', short: 'RU', flag: '🇷🇺' },
|
||||
{ code: 'en', label: 'English', short: 'EN', flag: '🇬🇧' }
|
||||
];
|
||||
|
||||
const TW = {
|
||||
iconBtn: 'icon-btn',
|
||||
btnBase: 'btn',
|
||||
@@ -459,6 +469,8 @@ const MOCK = (() => {
|
||||
});
|
||||
|
||||
document.addEventListener('click', handleDocumentClickForUserMenu);
|
||||
document.addEventListener('click', handleDocumentClickForLangMenu);
|
||||
renderLangMenu();
|
||||
|
||||
boot();
|
||||
|
||||
@@ -1211,6 +1223,64 @@ const MOCK = (() => {
|
||||
if (event.key === 'Escape' && state.userMenuOpen) {
|
||||
toggleUserMenu(false);
|
||||
}
|
||||
if (event.key === 'Escape' && state.langMenuOpen) {
|
||||
toggleLangMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLangMenu() {
|
||||
const current = getLanguage();
|
||||
const chipLabel = document.getElementById('lang-chip-label');
|
||||
const chipFlag = document.getElementById('lang-chip-flag');
|
||||
const opt = LANG_OPTIONS.find(o => o.code === current) || LANG_OPTIONS[0];
|
||||
if (chipLabel) chipLabel.textContent = opt.short;
|
||||
if (chipFlag) chipFlag.textContent = opt.flag;
|
||||
const list = document.getElementById('lang-dropdown');
|
||||
if (!list) return;
|
||||
list.innerHTML = LANG_OPTIONS.map(o => (
|
||||
'<button type="button" role="menuitem" class="lang-dropdown-item' +
|
||||
(o.code === current ? ' is-active' : '') +
|
||||
'" data-lang="' + o.code + '">' +
|
||||
'<span class="lang-dropdown-flag" aria-hidden="true">' + o.flag + '</span>' +
|
||||
'<span class="lang-dropdown-label">' + escapeHtml(o.label) + '</span>' +
|
||||
'</button>'
|
||||
)).join('');
|
||||
list.querySelectorAll('[data-lang]').forEach(btn => {
|
||||
btn.addEventListener('click', () => setLanguage(btn.dataset.lang));
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLangMenu(force) {
|
||||
const open = typeof force === 'boolean' ? force : !state.langMenuOpen;
|
||||
state.langMenuOpen = open;
|
||||
const chip = document.getElementById('lang-chip');
|
||||
const dropdown = document.getElementById('lang-dropdown');
|
||||
if (!chip || !dropdown) return;
|
||||
if (state.langMenuCloseTimer) {
|
||||
window.clearTimeout(state.langMenuCloseTimer);
|
||||
state.langMenuCloseTimer = null;
|
||||
}
|
||||
chip.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
if (open) {
|
||||
dropdown.classList.remove('hidden');
|
||||
dropdown.classList.remove('show');
|
||||
window.requestAnimationFrame(() => {
|
||||
if (state.langMenuOpen) dropdown.classList.add('show');
|
||||
});
|
||||
return;
|
||||
}
|
||||
dropdown.classList.remove('show');
|
||||
state.langMenuCloseTimer = window.setTimeout(() => {
|
||||
if (!state.langMenuOpen) dropdown.classList.add('hidden');
|
||||
state.langMenuCloseTimer = null;
|
||||
}, 180);
|
||||
}
|
||||
|
||||
function handleDocumentClickForLangMenu(event) {
|
||||
if (!state.langMenuOpen) return;
|
||||
const menu = document.querySelector('.lang-menu');
|
||||
if (menu && menu.contains(event.target)) return;
|
||||
toggleLangMenu(false);
|
||||
}
|
||||
|
||||
function openPromoModal() {
|
||||
@@ -2253,13 +2323,39 @@ const MOCK = (() => {
|
||||
}
|
||||
|
||||
function getLanguage() {
|
||||
const raw = (
|
||||
state.data && state.data.user && state.data.user.language_code
|
||||
? state.data.user.language_code
|
||||
: (CFG.language || document.documentElement.lang || 'ru')
|
||||
).toLowerCase();
|
||||
const short = raw.split('-')[0];
|
||||
return I18N[short] ? short : 'ru';
|
||||
const candidates = [];
|
||||
if (state.langOverride) candidates.push(state.langOverride);
|
||||
if (state.data && state.data.user && state.data.user.language_code) {
|
||||
candidates.push(state.data.user.language_code);
|
||||
}
|
||||
const tgUser = tg && tg.initDataUnsafe && tg.initDataUnsafe.user;
|
||||
if (tgUser && tgUser.language_code) candidates.push(tgUser.language_code);
|
||||
if (typeof navigator !== 'undefined') {
|
||||
if (Array.isArray(navigator.languages)) {
|
||||
navigator.languages.forEach(l => l && candidates.push(l));
|
||||
}
|
||||
if (navigator.language) candidates.push(navigator.language);
|
||||
}
|
||||
if (CFG.language) candidates.push(CFG.language);
|
||||
if (document.documentElement.lang) candidates.push(document.documentElement.lang);
|
||||
for (const raw of candidates) {
|
||||
const short = String(raw).toLowerCase().split('-')[0];
|
||||
if (I18N[short]) return short;
|
||||
}
|
||||
return 'ru';
|
||||
}
|
||||
|
||||
function setLanguage(lang) {
|
||||
const short = String(lang || '').toLowerCase().split('-')[0];
|
||||
if (!I18N[short]) return;
|
||||
state.langOverride = short;
|
||||
try { localStorage.setItem('rw_webapp_lang', short); } catch (e) { }
|
||||
applyI18n();
|
||||
if (state.data) {
|
||||
try { render(); } catch (e) { }
|
||||
}
|
||||
renderLangMenu();
|
||||
toggleLangMenu(false);
|
||||
}
|
||||
|
||||
function t(key, params = {}) {
|
||||
|
||||
@@ -180,6 +180,66 @@
|
||||
@apply relative inline-flex;
|
||||
}
|
||||
|
||||
.lang-menu {
|
||||
@apply relative inline-flex;
|
||||
}
|
||||
|
||||
.lang-chip {
|
||||
@apply inline-flex min-h-[38px] cursor-pointer items-center gap-1.5 rounded-[var(--radius-md)] border border-[var(--border)] bg-[rgba(255,255,255,0.03)] px-2 text-left font-extrabold text-[12px] text-[var(--text-primary)] no-underline transition-[transform,border-color,background,color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] hover:border-[color-mix(in_srgb,var(--accent)_42%,var(--border))] hover:bg-[var(--bg-card-hover)];
|
||||
}
|
||||
|
||||
.lang-chip[aria-expanded="true"] {
|
||||
@apply border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] bg-[var(--bg-card-hover)];
|
||||
}
|
||||
|
||||
.lang-chip-flag {
|
||||
@apply text-[14px] leading-none;
|
||||
font-family: 'Twemoji Country Flags', 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
|
||||
}
|
||||
|
||||
.lang-chip-label {
|
||||
@apply font-[family-name:var(--font-mono)] text-[11px] tracking-wide;
|
||||
}
|
||||
|
||||
.lang-chip-caret {
|
||||
@apply h-3 w-3 flex-none fill-[var(--text-secondary)] transition-transform;
|
||||
}
|
||||
|
||||
.lang-chip[aria-expanded="true"] .lang-chip-caret {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.lang-dropdown {
|
||||
@apply absolute right-0 top-[calc(100%+8px)] z-30 grid w-[160px] origin-top-right gap-0 rounded-[var(--radius-md)] border border-[var(--border)] bg-[var(--bg-card)] opacity-0 shadow-[0_24px_48px_rgba(0,0,0,0.45)] transition-[opacity,transform] duration-200 ease-[cubic-bezier(0.2,0.8,0.2,1)] pointer-events-none -translate-y-2 scale-[0.96];
|
||||
backdrop-filter: blur(14px);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.lang-dropdown.show {
|
||||
@apply opacity-100 pointer-events-auto translate-y-0 scale-100;
|
||||
}
|
||||
|
||||
.lang-dropdown-item {
|
||||
@apply flex cursor-pointer items-center gap-2.5 border-0 bg-transparent px-3 py-2.5 text-left text-[13px] font-bold text-[var(--text-secondary)] transition-colors hover:bg-[var(--bg-card-hover)] hover:text-[var(--text-primary)];
|
||||
}
|
||||
|
||||
.lang-dropdown-item + .lang-dropdown-item {
|
||||
@apply border-t border-[var(--border)];
|
||||
}
|
||||
|
||||
.lang-dropdown-item.is-active {
|
||||
@apply text-[var(--accent)];
|
||||
}
|
||||
|
||||
.lang-dropdown-flag {
|
||||
@apply text-[16px] leading-none;
|
||||
font-family: 'Twemoji Country Flags', 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
|
||||
}
|
||||
|
||||
.lang-dropdown-label {
|
||||
@apply flex-1 overflow-hidden text-ellipsis whitespace-nowrap;
|
||||
}
|
||||
|
||||
.user-chip {
|
||||
@apply inline-flex min-h-[38px] min-w-0 max-w-[200px] cursor-pointer items-center gap-2 rounded-[var(--radius-md)] border border-[var(--border)] bg-[rgba(255,255,255,0.03)] py-1 pl-1 pr-2.5 text-left font-extrabold text-[13px] text-[var(--text-primary)] no-underline transition-[transform,border-color,background,color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] hover:border-[color-mix(in_srgb,var(--accent)_42%,var(--border))] hover:bg-[var(--bg-card-hover)];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user