Files
rss-ntfy/app/static/i18n.js
T
dimon bf52bc3079
build-and-push / docker (push) Has been cancelled
RSS → ntfy bridge with modern web UI
Features: feed CRUD, per-feed ntfy target (incl. private servers),
Telegram/webhook channels, keyword filters, image attachments,
per-feed intervals, OPML import/export, notification history & stats,
users with roles, admin alerts, RU/EN i18n, light/dark theme,
notification preview, history search, activity chart. Dockerized.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:11:57 +08:00

337 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
/* Lightweight i18n: dictionaries + t() + applyI18n(). Shared by login & app. */
const I18N = {
ru: {
"nav.feeds": "Ленты",
"nav.history": "История",
"nav.users": "Пользователи",
"nav.settings": "Настройки",
"topbar.logout": "Выйти",
"theme.toggle": "Сменить тему",
"stats.feeds": "лент",
"stats.enabled": "активных",
"stats.failing": "с ошибками",
"stats.sent": "отправлено",
"stats.failed": "сбоев",
"chart.title": "Активность за 14 дней",
"chart.sent": "Отправлено",
"chart.failed": "Сбои",
"chart.empty": "Нет данных за период",
"feeds.heading": "RSS-ленты",
"feeds.checkAll": "↻ Проверить все",
"feeds.import": "⬆ Импорт OPML",
"feeds.export": "⬇ Экспорт OPML",
"feeds.add": "+ Добавить ленту",
"feeds.empty": "Пока нет ни одной ленты. Добавьте первую, чтобы начать получать уведомления.",
"feeds.never": "ещё не проверялась",
"feeds.noTopic": "— тема не задана —",
"history.heading": "История уведомлений",
"history.refresh": "↻ Обновить",
"history.clear": "Очистить",
"history.search": "Поиск по заголовку или ленте…",
"history.onlyErrors": "Только ошибки",
"history.empty": "История пуста.",
"users.heading": "Пользователи",
"users.add": "+ Добавить пользователя",
"users.admin": "👑 администратор",
"users.viewer": "👁 наблюдатель",
"settings.heading": "Настройки",
"settings.ntfy": "ntfy",
"settings.defaultServer": "Сервер ntfy по умолчанию",
"settings.defaultServerHint": "Используется для лент, у которых не задан собственный сервер.",
"settings.testPh": "тема для теста, напр. my-news",
"settings.testBtn": "Отправить тест",
"settings.check": "Проверка",
"settings.interval": "Интервал проверки по умолчанию (минуты)",
"settings.intervalHint": "Можно переопределить для каждой ленты отдельно.",
"settings.telegram": "Telegram",
"settings.tgEnable": "Включить доставку в Telegram",
"settings.tgToken": "Bot Token",
"settings.tgChat": "Chat ID",
"settings.tgHint": "Создайте бота через @BotFather, добавьте его в чат и укажите chat_id. Затем включите канал в нужных лентах.",
"settings.webhook": "Webhook",
"settings.whEnable": "Включить доставку через webhook",
"settings.whUrl": "URL webhook",
"settings.whHint": "POST с JSON: feed, feed_url, title, body, link, image.",
"settings.alerts": "Оповещения администратора",
"settings.alertEnable": "Уведомлять, если лента «упала»",
"settings.alertTopic": "Тема ntfy для алертов",
"settings.alertThreshold": "Порог (ошибок подряд)",
"settings.auth": "Авторизация",
"settings.authRequire": "Требовать вход в веб-панель",
"settings.authHint": "Учётные записи управляются во вкладке «Пользователи».",
"settings.save": "Сохранить настройки",
"modal.addFeed": "Добавить ленту",
"modal.editFeed": "Редактировать ленту",
"modal.cancel": "Отмена",
"modal.save": "Сохранить",
"feed.url": "URL ленты *",
"feed.title": "Название",
"feed.titleOpt": "(необязательно, определится автоматически)",
"feed.server": "Сервер ntfy",
"feed.serverHint": "(пусто = по умолчанию)",
"feed.topic": "Тема ntfy",
"feed.priv": "Приватный ntfy-сервер (авторизация)",
"feed.token": "Access token",
"feed.tokenHint": "(tk_…, приоритетнее логина)",
"feed.login": "Логин",
"feed.password": "Пароль",
"feed.priority": "Приоритет",
"feed.p1": "1 — минимальный",
"feed.p2": "2 — низкий",
"feed.p3": "3 — обычный",
"feed.p4": "4 — высокий",
"feed.p5": "5 — максимальный",
"feed.intervalMin": "Интервал, мин",
"feed.intervalHint": "(0 = общий)",
"feed.tags": "Теги / эмодзи",
"feed.commaHint": "(через запятую)",
"feed.filterInc": "Фильтр: только с этими словами",
"feed.filterExc": "Фильтр: исключить слова",
"feed.attach": "Прикреплять картинку",
"feed.dupTg": "Дублировать в Telegram",
"feed.toWebhook": "Отправлять в webhook",
"feed.enabled": "Лента включена",
"feed.preview": "👁 Предпросмотр",
"feed.previewLoading": "Загрузка…",
"feed.previewHint": "Введите URL и нажмите «Предпросмотр», чтобы увидеть последнюю запись.",
"user.addTitle": "Добавить пользователя",
"user.editTitle": "Редактировать пользователя",
"user.login": "Логин *",
"user.password": "Пароль",
"user.pwReq": "*",
"user.pwKeep": "(пусто = не менять)",
"user.role": "Роль",
"user.roleAdmin": "Администратор (полный доступ)",
"user.roleViewer": "Наблюдатель (только просмотр)",
"toast.feedDeleted": "Лента удалена",
"toast.feedAdded": "Лента добавлена",
"toast.feedUpdated": "Лента обновлена",
"toast.saved": "Сохранено",
"toast.deleted": "Удалён",
"toast.checkDone": "Проверка завершена",
"toast.historyCleared": "История очищена",
"toast.settingsSaved": "Настройки сохранены",
"toast.sentTo": "Отправлено в {dest}",
"toast.imported": "Импортировано {added} из {total}",
"toast.needTestTopic": "Укажите тему для теста",
"toast.needUrl": "Сначала укажите URL ленты",
"confirm.deleteFeed": "Удалить ленту «{name}»?",
"confirm.deleteUser": "Удалить пользователя «{name}»?",
"confirm.clearHistory": "Очистить всю историю?",
"status.init": "Инициализировано ({n} записей)",
"status.sent": "Отправлено {n} новых",
"status.sentSkip": "Отправлено {n} новых, пропущено {s}",
"status.filtered": "Без изменений (отфильтровано {s})",
"status.nochange": "Без изменений",
"status.parseError": "Ошибка: {msg}",
"status.sendError": "Ошибка отправки: {msg}",
"status.dash": "—",
"role.admin": "админ",
"role.viewer": "наблюдатель",
"login.subtitle": "Войдите, чтобы продолжить",
"login.user": "Логин",
"login.pass": "Пароль",
"login.submit": "Войти",
"login.error": "Неверный логин или пароль",
},
en: {
"nav.feeds": "Feeds",
"nav.history": "History",
"nav.users": "Users",
"nav.settings": "Settings",
"topbar.logout": "Log out",
"theme.toggle": "Toggle theme",
"stats.feeds": "feeds",
"stats.enabled": "active",
"stats.failing": "failing",
"stats.sent": "sent",
"stats.failed": "failed",
"chart.title": "Activity (last 14 days)",
"chart.sent": "Sent",
"chart.failed": "Failed",
"chart.empty": "No data for this period",
"feeds.heading": "RSS feeds",
"feeds.checkAll": "↻ Check all",
"feeds.import": "⬆ Import OPML",
"feeds.export": "⬇ Export OPML",
"feeds.add": "+ Add feed",
"feeds.empty": "No feeds yet. Add your first one to start receiving notifications.",
"feeds.never": "not checked yet",
"feeds.noTopic": "— no topic set —",
"history.heading": "Notification history",
"history.refresh": "↻ Refresh",
"history.clear": "Clear",
"history.search": "Search by title or feed…",
"history.onlyErrors": "Errors only",
"history.empty": "History is empty.",
"users.heading": "Users",
"users.add": "+ Add user",
"users.admin": "👑 administrator",
"users.viewer": "👁 viewer",
"settings.heading": "Settings",
"settings.ntfy": "ntfy",
"settings.defaultServer": "Default ntfy server",
"settings.defaultServerHint": "Used for feeds that don't define their own server.",
"settings.testPh": "topic to test, e.g. my-news",
"settings.testBtn": "Send test",
"settings.check": "Polling",
"settings.interval": "Default poll interval (minutes)",
"settings.intervalHint": "Can be overridden per feed.",
"settings.telegram": "Telegram",
"settings.tgEnable": "Enable Telegram delivery",
"settings.tgToken": "Bot Token",
"settings.tgChat": "Chat ID",
"settings.tgHint": "Create a bot via @BotFather, add it to a chat and set the chat_id. Then enable the channel on the feeds you want.",
"settings.webhook": "Webhook",
"settings.whEnable": "Enable webhook delivery",
"settings.whUrl": "Webhook URL",
"settings.whHint": "POST with JSON: feed, feed_url, title, body, link, image.",
"settings.alerts": "Admin alerts",
"settings.alertEnable": "Notify when a feed keeps failing",
"settings.alertTopic": "ntfy topic for alerts",
"settings.alertThreshold": "Threshold (consecutive errors)",
"settings.auth": "Authentication",
"settings.authRequire": "Require login to the web panel",
"settings.authHint": "Accounts are managed on the «Users» tab.",
"settings.save": "Save settings",
"modal.addFeed": "Add feed",
"modal.editFeed": "Edit feed",
"modal.cancel": "Cancel",
"modal.save": "Save",
"feed.url": "Feed URL *",
"feed.title": "Title",
"feed.titleOpt": "(optional, detected automatically)",
"feed.server": "ntfy server",
"feed.serverHint": "(empty = default)",
"feed.topic": "ntfy topic",
"feed.priv": "Private ntfy server (authentication)",
"feed.token": "Access token",
"feed.tokenHint": "(tk_…, takes precedence over login)",
"feed.login": "Username",
"feed.password": "Password",
"feed.priority": "Priority",
"feed.p1": "1 — min",
"feed.p2": "2 — low",
"feed.p3": "3 — default",
"feed.p4": "4 — high",
"feed.p5": "5 — max",
"feed.intervalMin": "Interval, min",
"feed.intervalHint": "(0 = global)",
"feed.tags": "Tags / emojis",
"feed.commaHint": "(comma separated)",
"feed.filterInc": "Filter: only with these words",
"feed.filterExc": "Filter: exclude words",
"feed.attach": "Attach image",
"feed.dupTg": "Mirror to Telegram",
"feed.toWebhook": "Send to webhook",
"feed.enabled": "Feed enabled",
"feed.preview": "👁 Preview",
"feed.previewLoading": "Loading…",
"feed.previewHint": "Enter a URL and click «Preview» to see the latest entry.",
"user.addTitle": "Add user",
"user.editTitle": "Edit user",
"user.login": "Username *",
"user.password": "Password",
"user.pwReq": "*",
"user.pwKeep": "(empty = keep current)",
"user.role": "Role",
"user.roleAdmin": "Administrator (full access)",
"user.roleViewer": "Viewer (read-only)",
"toast.feedDeleted": "Feed deleted",
"toast.feedAdded": "Feed added",
"toast.feedUpdated": "Feed updated",
"toast.saved": "Saved",
"toast.deleted": "Deleted",
"toast.checkDone": "Check complete",
"toast.historyCleared": "History cleared",
"toast.settingsSaved": "Settings saved",
"toast.sentTo": "Sent to {dest}",
"toast.imported": "Imported {added} of {total}",
"toast.needTestTopic": "Enter a topic to test",
"toast.needUrl": "Enter the feed URL first",
"confirm.deleteFeed": "Delete feed «{name}»?",
"confirm.deleteUser": "Delete user «{name}»?",
"confirm.clearHistory": "Clear the entire history?",
"status.init": "Initialized ({n} entries)",
"status.sent": "Sent {n} new",
"status.sentSkip": "Sent {n} new, skipped {s}",
"status.filtered": "No changes (filtered out {s})",
"status.nochange": "No changes",
"status.parseError": "Error: {msg}",
"status.sendError": "Send error: {msg}",
"status.dash": "—",
"role.admin": "admin",
"role.viewer": "viewer",
"login.subtitle": "Sign in to continue",
"login.user": "Username",
"login.pass": "Password",
"login.submit": "Sign in",
"login.error": "Wrong username or password",
},
};
function getLang() {
const l = localStorage.getItem("lang");
if (l === "ru" || l === "en") return l;
return (navigator.language || "en").startsWith("ru") ? "ru" : "en";
}
function setLang(lang) {
localStorage.setItem("lang", lang);
document.documentElement.lang = lang;
}
function t(key, params) {
let s = (I18N[getLang()] || I18N.en)[key] ?? key;
if (params) for (const k in params) s = s.replaceAll(`{${k}}`, params[k]);
return s;
}
function applyI18n(root = document) {
root.querySelectorAll("[data-i18n]").forEach(el => {
el.textContent = t(el.getAttribute("data-i18n"));
});
root.querySelectorAll("[data-i18n-ph]").forEach(el => {
el.setAttribute("placeholder", t(el.getAttribute("data-i18n-ph")));
});
root.querySelectorAll("[data-i18n-title]").forEach(el => {
el.setAttribute("title", t(el.getAttribute("data-i18n-title")));
});
}
/* Theme + locale helpers shared across pages. */
function getTheme() {
return localStorage.getItem("theme") === "light" ? "light" : "dark";
}
function setTheme(theme) {
localStorage.setItem("theme", theme);
document.documentElement.setAttribute("data-theme", theme);
}
function localeTag() {
return getLang() === "ru" ? "ru-RU" : "en-US";
}