Files
rss-ntfy/app/static/i18n.js
T
dimon 834092a3ec
build-and-push / docker (push) Has been cancelled
8 major features: trafilatura, digest, ntfy actions, templates, FTS5 search, backup/restore, proxy, RSS reader
- Full article extraction via trafilatura (fetch_full_article)
- Digest mode with configurable period (digest_enabled, digest_period_hours)
- ntfy Actions buttons (Open article, Open feed)
- Notification templates with {title}, {body}, {link}, {source}, {image_url}
- FTS5 full-text search in notification history
- Database backup/restore (download/upload .db)
- HTTP/SOCKS proxy for RSS feed fetching (proxy_url setting)
- Built-in RSS reader tab with categories, unread counts, article detail view
- Auto-category 'Общее' for feeds without a category
- Article storage (Article table) for reader
- DigestEntry model for pending digest entries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 20:47:46 +08:00

449 lines
19 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.ntfyAuth": "Авторизация на сервере ntfy",
"settings.ntfyAuthHint": "Нужно, если на сервере включён контроль доступа (ошибка 403 при отправке). Применяется к тесту, алертам и лентам без собственной авторизации.",
"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": "—",
"nav.categories": "Категории",
"categories.heading": "Категории",
"categories.add": "+ Добавить категорию",
"categories.empty": "Категорий пока нет.",
"cat.addTitle": "Добавить категорию",
"cat.editTitle": "Редактировать категорию",
"cat.name": "Название *",
"cat.sortOrder": "Порядок сортировки",
"feed.category": "Категория",
"feed.categoryNone": "— без категории —",
"feed.fullContent": "Отправлять полный контент",
"feed.fullContentHint": "Весь текст, все картинки и видео. Для ntfy — Markdown.",
"settings.feedDefaults": "Значения по умолчанию для новых лент",
"confirm.deleteCategory": "Удалить категорию «{name}»?",
"confirm.deleteCategoryFeeds": "Удалить категорию «{name}»? {n} лент будут откреплены.",
"toast.categoryAdded": "Категория добавлена",
"toast.categoryUpdated": "Категория обновлена",
"toast.categoryDeleted": "Категория удалена",
"nav.reader": "Чтение",
"reader.all": "Все",
"reader.markAll": "Отметить все прочитанными",
"reader.back": "← Назад",
"reader.open": "Открыть оригинал →",
"reader.empty": "Статей пока нет. Добавьте ленты, чтобы начать читать.",
"feed.digest": "Дайджест",
"feed.digestEnable": "Накапливать записи (дайджест)",
"feed.digestPeriod": "Период дайджеста (часы)",
"feed.fetchArticle": "Загружать полную статью (trafilatura)",
"feed.fetchArticleHint": "Загружает страницу статьи и извлекает основной текст.",
"settings.template": "Шаблон уведомлений",
"settings.templateHint": "Переменные: {title}, {body}, {link}, {source}, {image_url}",
"settings.proxyUrl": "URL прокси",
"settings.proxyHint": "Например: http://proxy:8080 или socks5://proxy:1080",
"feeds.backup": "💾 Бэкап",
"feeds.restore": "📥 Восстановить",
"confirm.restore": "Восстановление заменит всю текущую базу данных. Продолжить?",
"toast.restored": "База восстановлена. Перезагрузка...",
"toast.articlesMarked": "Все статьи отмечены прочитанными",
"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.ntfyAuth": "ntfy server authentication",
"settings.ntfyAuthHint": "Needed if the server has access control enabled (403 on publish). Applies to the test, alerts and feeds without their own auth.",
"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": "—",
"nav.categories": "Categories",
"categories.heading": "Categories",
"categories.add": "+ Add category",
"categories.empty": "No categories yet.",
"cat.addTitle": "Add category",
"cat.editTitle": "Edit category",
"cat.name": "Name *",
"cat.sortOrder": "Sort order",
"feed.category": "Category",
"feed.categoryNone": "— no category —",
"feed.fullContent": "Send full content",
"feed.fullContentHint": "Full text, all images and videos. For ntfy — Markdown.",
"settings.feedDefaults": "Default values for new feeds",
"confirm.deleteCategory": "Delete category «{name}»?",
"confirm.deleteCategoryFeeds": "Delete category «{name}»? {n} feeds will be uncategorized.",
"toast.categoryAdded": "Category added",
"toast.categoryUpdated": "Category updated",
"toast.categoryDeleted": "Category deleted",
"nav.reader": "Reader",
"reader.all": "All",
"reader.markAll": "Mark all read",
"reader.back": "← Back",
"reader.open": "Open original →",
"reader.empty": "No articles yet. Add feeds to start reading.",
"feed.digest": "Digest",
"feed.digestEnable": "Accumulate entries (digest)",
"feed.digestPeriod": "Digest period (hours)",
"feed.fetchArticle": "Fetch full article (trafilatura)",
"feed.fetchArticleHint": "Fetches the article page and extracts main text.",
"settings.template": "Notification template",
"settings.templateHint": "Variables: {title}, {body}, {link}, {source}, {image_url}",
"settings.proxyUrl": "Proxy URL",
"settings.proxyHint": "Example: http://proxy:8080 or socks5://proxy:1080",
"feeds.backup": "💾 Backup",
"feeds.restore": "📥 Restore",
"confirm.restore": "Restore will replace the entire database. Continue?",
"toast.restored": "Database restored. Reloading...",
"toast.articlesMarked": "All articles marked read",
"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";
}