i18n: перевод страницы чата (ru база, uk/en)
- Шаблон chat.html → русская база с {% trans %}/{% blocktrans %}
- JS-строки chat.js → gettext() + JavaScriptCatalog (/jsi18n/), подключён
в index.html; защитный fallback если каталог не загрузился
- Переводы uk/en для обоих доменов (django + djangojs), скомпилированы
- makemessages заодно извлёк непереведённые строки прошлых фич (2FA,
nextcloud, документы) — оставлены пустыми (рендерятся на базовом ru)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+25
-21
@@ -1,3 +1,7 @@
|
||||
// Переклади для JS приходять із JavaScriptCatalog (/jsi18n/). Якщо каталог не
|
||||
// підвантажився — не падаємо, показуємо базові (російські) рядки.
|
||||
if (typeof window.gettext !== 'function') { window.gettext = function (s) { return s; }; }
|
||||
|
||||
var CHAT_QUICK_EMOJIS = [
|
||||
['👍', 'thumbsup'], ['❤️', 'heart'], ['😂', 'joy'],
|
||||
['😮', 'open_mouth'], ['😢', 'cry'], ['🙏', 'pray'],
|
||||
@@ -303,7 +307,7 @@ function chatLoadChannels() {
|
||||
}).catch(function (e) {
|
||||
console.error(e);
|
||||
var el = document.getElementById('chat-channel-list');
|
||||
if (el) el.innerHTML = '<div style="padding:1rem; text-align:center; font-size:0.8125rem; color:var(--color-destructive);">Не вдалося завантажити канали</div>';
|
||||
if (el) el.innerHTML = '<div style="padding:1rem; text-align:center; font-size:0.8125rem; color:var(--color-destructive);">' + gettext('Не удалось загрузить каналы') + '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -395,7 +399,7 @@ function chatRenderChannelList() {
|
||||
});
|
||||
|
||||
if (!list.length) {
|
||||
el.innerHTML = '<div style="padding:1rem; text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">Немає каналів</div>';
|
||||
el.innerHTML = '<div style="padding:1rem; text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">' + gettext('Нет каналов') + '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,7 +420,7 @@ function chatRenderDmList() {
|
||||
});
|
||||
|
||||
if (!list.length) {
|
||||
el.innerHTML = '<div style="padding:0.75rem; text-align:center; font-size:0.75rem; color:var(--color-muted-foreground);">Немає повідомлень</div>';
|
||||
el.innerHTML = '<div style="padding:0.75rem; text-align:center; font-size:0.75rem; color:var(--color-muted-foreground);">' + gettext('Нет сообщений') + '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -425,7 +429,7 @@ function chatRenderDmList() {
|
||||
var otherId = chatDmOtherUserId[ch.id];
|
||||
var isBot = ch.type === 'D' && otherId && chatUserIsBot[otherId];
|
||||
var row = chatBuildChannelRow(ch.id, chatDmLabels[ch.id] || '…',
|
||||
ch.type === 'G' ? 'Групова розмова' : '', {avatar: true, bot: isBot});
|
||||
ch.type === 'G' ? gettext('Групповой разговор') : '', {avatar: true, bot: isBot});
|
||||
el.appendChild(row);
|
||||
// Якщо співрозмовник ЛС уже відомий — одразу показати його аватар.
|
||||
if (ch.type === 'D' && otherId) {
|
||||
@@ -542,7 +546,7 @@ function chatSelectChannel(channelId) {
|
||||
chatRenderComposerFileChips('chat-composer-files', chatComposerFileIds);
|
||||
|
||||
var messagesEl = document.getElementById('chat-messages');
|
||||
if (messagesEl) messagesEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">Завантаження…</div>';
|
||||
if (messagesEl) messagesEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">' + gettext('Загрузка…') + '</div>';
|
||||
chatRenderedPostIds = {};
|
||||
|
||||
chatApiFetch('/channels/' + channelId + '/posts?per_page=30').then(function (data) {
|
||||
@@ -554,7 +558,7 @@ function chatSelectChannel(channelId) {
|
||||
});
|
||||
}).catch(function (e) {
|
||||
console.error(e);
|
||||
if (messagesEl) messagesEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-destructive);">Не вдалося завантажити повідомлення</div>';
|
||||
if (messagesEl) messagesEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-destructive);">' + gettext('Не удалось загрузить сообщения') + '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -604,7 +608,7 @@ function chatSearchChannelLabel(channelId) {
|
||||
var ch = chatFindChannel(channelId);
|
||||
if (!ch) return '';
|
||||
if (ch.type === 'O' || ch.type === 'P') return '#' + ch.display_name;
|
||||
return chatDmLabels[ch.id] || 'Особисте';
|
||||
return chatDmLabels[ch.id] || gettext('Личное');
|
||||
}
|
||||
|
||||
function chatSearchMessages(terms) {
|
||||
@@ -622,7 +626,7 @@ function chatSearchMessages(terms) {
|
||||
|
||||
resultsEl.style.display = 'flex';
|
||||
if (messagesEl) messagesEl.style.display = 'none';
|
||||
resultsEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground); padding:1rem;">Пошук…</div>';
|
||||
resultsEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground); padding:1rem;">' + gettext('Поиск…') + '</div>';
|
||||
|
||||
var body = JSON.stringify({terms: terms, is_or_search: false, per_page: 30});
|
||||
var merged = {}; // id -> post (дедуплікація між командами)
|
||||
@@ -643,7 +647,7 @@ function chatSearchMessages(terms) {
|
||||
});
|
||||
}).catch(function (e) {
|
||||
console.error(e);
|
||||
resultsEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-destructive); padding:1rem;">Не вдалося виконати пошук</div>';
|
||||
resultsEl.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-destructive); padding:1rem;">' + gettext('Не удалось выполнить поиск') + '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -656,10 +660,10 @@ function chatRenderSearchResults(posts) {
|
||||
head.style.cssText = 'display:flex; align-items:center; justify-content:space-between; margin-bottom:0.25rem;';
|
||||
var title = document.createElement('span');
|
||||
title.style.cssText = 'font-size:0.75rem; color:var(--color-muted-foreground);';
|
||||
title.textContent = 'Знайдено: ' + posts.length;
|
||||
title.textContent = gettext('Найдено:') + ' ' + posts.length;
|
||||
var closeBtn = document.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.textContent = 'Закрити пошук';
|
||||
closeBtn.textContent = gettext('Закрыть поиск');
|
||||
closeBtn.style.cssText = 'background:none; border:none; cursor:pointer; font-size:0.75rem; color:var(--color-primary);';
|
||||
closeBtn.onclick = chatCloseSearch;
|
||||
head.appendChild(title);
|
||||
@@ -669,7 +673,7 @@ function chatRenderSearchResults(posts) {
|
||||
if (!posts.length) {
|
||||
var none = document.createElement('div');
|
||||
none.style.cssText = 'text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground); padding:1rem;';
|
||||
none.textContent = 'Нічого не знайдено';
|
||||
none.textContent = gettext('Ничего не найдено');
|
||||
el.appendChild(none);
|
||||
return;
|
||||
}
|
||||
@@ -711,7 +715,7 @@ function chatPostIsBot(post) {
|
||||
|
||||
function chatBotBadge() {
|
||||
var b = document.createElement('span');
|
||||
b.textContent = 'бот';
|
||||
b.textContent = gettext('бот');
|
||||
b.style.cssText = 'font-size:0.5625rem; font-weight:700; letter-spacing:0.03em; text-transform:uppercase; color:var(--color-muted-foreground); background:var(--color-muted); border:1px solid var(--color-border); border-radius:0.25rem; padding:0.0625rem 0.25rem; line-height:1.2;';
|
||||
return b;
|
||||
}
|
||||
@@ -736,7 +740,7 @@ function chatBuildMessageEl(post, context) {
|
||||
meta.style.cssText = 'display:flex; align-items:baseline; gap:0.5rem; margin-bottom:0.25rem;' + (isMine ? ' flex-direction:row-reverse;' : '');
|
||||
var nameEl = document.createElement('span');
|
||||
nameEl.style.cssText = 'font-size:0.8125rem; font-weight:600; color:var(--color-foreground);';
|
||||
nameEl.textContent = isMine ? 'Ви' : senderName;
|
||||
nameEl.textContent = isMine ? gettext('Вы') : senderName;
|
||||
var timeEl = document.createElement('span');
|
||||
timeEl.style.cssText = 'font-size:0.6875rem; color:var(--color-muted-foreground);';
|
||||
timeEl.textContent = post.create_at ? new Date(post.create_at).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}) : '';
|
||||
@@ -774,7 +778,7 @@ function chatBuildMessageEl(post, context) {
|
||||
var reactBtn = document.createElement('button');
|
||||
reactBtn.type = 'button';
|
||||
reactBtn.style.cssText = 'background:none; border:none; cursor:pointer; font-size:0.6875rem; color:var(--color-muted-foreground); padding:0;';
|
||||
reactBtn.textContent = '🙂 Реакція';
|
||||
reactBtn.textContent = '🙂 ' + gettext('Реакция');
|
||||
reactBtn.onclick = function (e) { chatToggleReactionPicker(e, post, context); };
|
||||
actions.appendChild(reactBtn);
|
||||
|
||||
@@ -783,7 +787,7 @@ function chatBuildMessageEl(post, context) {
|
||||
replyBtn.type = 'button';
|
||||
replyBtn.id = 'chat-reply-count-' + post.id;
|
||||
replyBtn.style.cssText = 'background:none; border:none; cursor:pointer; font-size:0.6875rem; color:var(--color-primary); padding:0;';
|
||||
replyBtn.textContent = post.reply_count ? ('💬 ' + post.reply_count + ' відповідей') : '↩ Відповісти';
|
||||
replyBtn.textContent = post.reply_count ? ('💬 ' + gettext('Ответов:') + ' ' + post.reply_count) : ('↩ ' + gettext('Ответить'));
|
||||
replyBtn.onclick = function () { chatOpenThread(post.id); };
|
||||
actions.appendChild(replyBtn);
|
||||
}
|
||||
@@ -945,7 +949,7 @@ function chatOpenThread(postId) {
|
||||
document.getElementById('chat-thread-composer-input').value = '';
|
||||
|
||||
var el = document.getElementById('chat-thread-messages');
|
||||
if (el) el.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">Завантаження…</div>';
|
||||
if (el) el.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">' + gettext('Загрузка…') + '</div>';
|
||||
|
||||
chatApiFetch('/posts/' + postId + '/thread?perPage=60').then(function (data) {
|
||||
var ids = (data.order || []).slice().reverse();
|
||||
@@ -957,7 +961,7 @@ function chatOpenThread(postId) {
|
||||
});
|
||||
}).catch(function (e) {
|
||||
console.error(e);
|
||||
if (el) el.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-destructive);">Не вдалося завантажити тред</div>';
|
||||
if (el) el.innerHTML = '<div style="text-align:center; font-size:0.8125rem; color:var(--color-destructive);">' + gettext('Не удалось загрузить обсуждение') + '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -995,7 +999,7 @@ function chatBumpReplyCount(rootId) {
|
||||
if (!root) return;
|
||||
root.reply_count = (root.reply_count || 0) + 1;
|
||||
var btn = document.getElementById('chat-reply-count-' + rootId);
|
||||
if (btn) btn.textContent = '💬 ' + root.reply_count + ' відповідей';
|
||||
if (btn) btn.textContent = '💬 ' + gettext('Ответов:') + ' ' + root.reply_count;
|
||||
}
|
||||
|
||||
function chatSendMessage() {
|
||||
@@ -1101,7 +1105,7 @@ function chatRenderDmPickerList() {
|
||||
|
||||
container.innerHTML = '';
|
||||
if (!filtered.length) {
|
||||
container.innerHTML = '<div style="padding:0.5rem; text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">Нікого не знайдено</div>';
|
||||
container.innerHTML = '<div style="padding:0.5rem; text-align:center; font-size:0.8125rem; color:var(--color-muted-foreground);">' + gettext('Никого не найдено') + '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1197,7 +1201,7 @@ function chatUpdateTitle() {
|
||||
function chatNotifyNewMessage(post) {
|
||||
try {
|
||||
if (window.Notification && Notification.permission === 'granted') {
|
||||
var who = chatUserNames[post.user_id] || 'Нове повідомлення';
|
||||
var who = chatUserNames[post.user_id] || gettext('Новое сообщение');
|
||||
new Notification(who, {body: (post.message || '').slice(0, 140)});
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
Reference in New Issue
Block a user