RSS/Atom -> ntfy bridge with web UI, OPML import/export and RU/EN localization
Web-managed fork of nurefexc/rss-bridge-ntfy: Flask UI + REST API, background sync engine (SQLite dedup, quiet hours, filters, flood protection, images), OPML import/export and switchable interface/notification language. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
"use strict";
|
||||
|
||||
// Translation dictionary. Add a language by adding a key here and an <option>
|
||||
// to #lang-select in index.html.
|
||||
const I18N = {
|
||||
ru: {
|
||||
subtitle: "веб-панель управления мостом",
|
||||
sync_now: "⟳ Синхронизировать",
|
||||
pause: "Пауза",
|
||||
resume: "Возобновить",
|
||||
tab_dashboard: "Дашборд",
|
||||
tab_feeds: "Фиды",
|
||||
tab_settings: "Настройки",
|
||||
|
||||
// dashboard cards
|
||||
card_feeds: "Фидов (активно)",
|
||||
card_topics: "Топиков",
|
||||
card_sent: "Отправлено всего",
|
||||
card_history: "В истории",
|
||||
|
||||
// status panel
|
||||
status: "Состояние",
|
||||
engine: "Движок",
|
||||
last_sync: "Последняя синхронизация",
|
||||
next_sync: "Следующая",
|
||||
interval: "Интервал",
|
||||
error: "Ошибка",
|
||||
test_notification: "Тестовое уведомление",
|
||||
ph_topic: "топик (напр. news)",
|
||||
ph_message: "сообщение",
|
||||
send: "Отправить",
|
||||
|
||||
// log panel
|
||||
log: "Журнал",
|
||||
clear_history: "Очистить историю",
|
||||
|
||||
// feeds
|
||||
feeds: "Фиды",
|
||||
add_feed: "+ Добавить фид",
|
||||
import_opml: "Импорт OPML",
|
||||
export_opml: "Экспорт OPML",
|
||||
feeds_empty: "Фидов пока нет. Нажмите «Добавить фид».",
|
||||
no_name: "(без названия)",
|
||||
priority: "приоритет",
|
||||
quiet: "тихо",
|
||||
edit: "Изменить",
|
||||
delete: "Удалить",
|
||||
|
||||
// settings
|
||||
settings_global: "Глобальные настройки",
|
||||
s_ntfy_url: "ntfy сервер (URL)",
|
||||
s_ntfy_token: "ntfy токен (необязательно)",
|
||||
s_interval: "Интервал синхронизации (сек)",
|
||||
s_tz: "Часовой пояс (IANA)",
|
||||
s_batch: "Лимит новых записей за цикл",
|
||||
s_maxdesc: "Макс. длина описания",
|
||||
s_ua: "User-Agent",
|
||||
s_flood: "Флуд-защита (задержки для низкого приоритета)",
|
||||
save: "Сохранить",
|
||||
|
||||
// feed modal
|
||||
feed_new: "Новый фид",
|
||||
feed_edit: "Изменить фид",
|
||||
f_name: "Название",
|
||||
f_url: "URL фида *",
|
||||
f_topic: "Топик ntfy *",
|
||||
f_priority: "Приоритет (1–5)",
|
||||
f_icon: "Иконка (URL)",
|
||||
f_quiet_hours: "Тихие часы (напр. 22-7)",
|
||||
f_quiet_priority: "Приоритет в тихие часы",
|
||||
f_include: "Include regex (показывать только совпадения)",
|
||||
f_exclude: "Exclude regex (отбрасывать совпадения)",
|
||||
f_enabled: "Включён",
|
||||
check_feed: "Проверить фид",
|
||||
|
||||
// dynamic / toasts
|
||||
sync_started: "Синхронизация запущена",
|
||||
history_cleared: "История очищена",
|
||||
need_topic: "Укажите топик",
|
||||
notify_sent: "Уведомление отправлено",
|
||||
saved: "Сохранено",
|
||||
feed_deleted: "Фид удалён",
|
||||
feed_saved: "Фид сохранён",
|
||||
confirm_delete_feed: "Удалить этот фид?",
|
||||
confirm_clear_history: "Очистить историю отправленных записей? Старые записи могут прийти повторно.",
|
||||
checking: "Проверяю…",
|
||||
need_url: "Укажите URL",
|
||||
preview_ok: "OK:",
|
||||
preview_entries: "записей:",
|
||||
err_prefix: "Ошибка: ",
|
||||
no_connection: "нет связи",
|
||||
st_syncing: "синхронизация…",
|
||||
st_running: "работает",
|
||||
st_paused: "на паузе",
|
||||
pill_syncing: "● синхронизация",
|
||||
pill_running: "● работает",
|
||||
pill_paused: "‖ пауза",
|
||||
sec: "сек",
|
||||
err_suffix_sync_failed: " (ошибка)",
|
||||
import_done: "Импорт OPML: добавлено {n} из {total}",
|
||||
import_choose: "Выберите .opml файл",
|
||||
},
|
||||
|
||||
en: {
|
||||
subtitle: "web control panel for the bridge",
|
||||
sync_now: "⟳ Sync now",
|
||||
pause: "Pause",
|
||||
resume: "Resume",
|
||||
tab_dashboard: "Dashboard",
|
||||
tab_feeds: "Feeds",
|
||||
tab_settings: "Settings",
|
||||
|
||||
card_feeds: "Feeds (active)",
|
||||
card_topics: "Topics",
|
||||
card_sent: "Sent total",
|
||||
card_history: "In history",
|
||||
|
||||
status: "Status",
|
||||
engine: "Engine",
|
||||
last_sync: "Last sync",
|
||||
next_sync: "Next",
|
||||
interval: "Interval",
|
||||
error: "Error",
|
||||
test_notification: "Test notification",
|
||||
ph_topic: "topic (e.g. news)",
|
||||
ph_message: "message",
|
||||
send: "Send",
|
||||
|
||||
log: "Log",
|
||||
clear_history: "Clear history",
|
||||
|
||||
feeds: "Feeds",
|
||||
add_feed: "+ Add feed",
|
||||
import_opml: "Import OPML",
|
||||
export_opml: "Export OPML",
|
||||
feeds_empty: "No feeds yet. Click \"Add feed\".",
|
||||
no_name: "(no name)",
|
||||
priority: "priority",
|
||||
quiet: "quiet",
|
||||
edit: "Edit",
|
||||
delete: "Delete",
|
||||
|
||||
settings_global: "Global settings",
|
||||
s_ntfy_url: "ntfy server (URL)",
|
||||
s_ntfy_token: "ntfy token (optional)",
|
||||
s_interval: "Sync interval (sec)",
|
||||
s_tz: "Timezone (IANA)",
|
||||
s_batch: "New items per cycle limit",
|
||||
s_maxdesc: "Max description length",
|
||||
s_ua: "User-Agent",
|
||||
s_flood: "Flood protection (delay low-priority items)",
|
||||
save: "Save",
|
||||
|
||||
feed_new: "New feed",
|
||||
feed_edit: "Edit feed",
|
||||
f_name: "Name",
|
||||
f_url: "Feed URL *",
|
||||
f_topic: "ntfy topic *",
|
||||
f_priority: "Priority (1–5)",
|
||||
f_icon: "Icon (URL)",
|
||||
f_quiet_hours: "Quiet hours (e.g. 22-7)",
|
||||
f_quiet_priority: "Priority during quiet hours",
|
||||
f_include: "Include regex (only matching items)",
|
||||
f_exclude: "Exclude regex (drop matching items)",
|
||||
f_enabled: "Enabled",
|
||||
check_feed: "Check feed",
|
||||
|
||||
sync_started: "Sync started",
|
||||
history_cleared: "History cleared",
|
||||
need_topic: "Enter a topic",
|
||||
notify_sent: "Notification sent",
|
||||
saved: "Saved",
|
||||
feed_deleted: "Feed deleted",
|
||||
feed_saved: "Feed saved",
|
||||
confirm_delete_feed: "Delete this feed?",
|
||||
confirm_clear_history: "Clear the history of sent items? Old items may be delivered again.",
|
||||
checking: "Checking…",
|
||||
need_url: "Enter a URL",
|
||||
preview_ok: "OK:",
|
||||
preview_entries: "items:",
|
||||
err_prefix: "Error: ",
|
||||
no_connection: "no connection",
|
||||
st_syncing: "syncing…",
|
||||
st_running: "running",
|
||||
st_paused: "paused",
|
||||
pill_syncing: "● syncing",
|
||||
pill_running: "● running",
|
||||
pill_paused: "‖ paused",
|
||||
sec: "sec",
|
||||
err_suffix_sync_failed: " (error)",
|
||||
import_done: "OPML import: added {n} of {total}",
|
||||
import_choose: "Choose an .opml file",
|
||||
},
|
||||
};
|
||||
|
||||
let LANG = localStorage.getItem("lang") || "ru";
|
||||
|
||||
function t(key) {
|
||||
const dict = I18N[LANG] || I18N.ru;
|
||||
return (key in dict) ? dict[key] : (I18N.ru[key] || key);
|
||||
}
|
||||
|
||||
function locale() {
|
||||
return LANG === "ru" ? "ru-RU" : "en-US";
|
||||
}
|
||||
|
||||
function applyI18n() {
|
||||
document.documentElement.lang = LANG;
|
||||
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
||||
el.textContent = t(el.dataset.i18n);
|
||||
});
|
||||
document.querySelectorAll("[data-i18n-ph]").forEach((el) => {
|
||||
el.placeholder = t(el.dataset.i18nPh);
|
||||
});
|
||||
document.querySelectorAll("[data-i18n-title]").forEach((el) => {
|
||||
el.title = t(el.dataset.i18nTitle);
|
||||
});
|
||||
const sel = document.getElementById("lang-select");
|
||||
if (sel) sel.value = LANG;
|
||||
}
|
||||
|
||||
function setLang(lang) {
|
||||
LANG = lang;
|
||||
localStorage.setItem("lang", lang);
|
||||
applyI18n();
|
||||
}
|
||||
|
||||
// DOM is fully parsed (scripts sit at the end of <body>).
|
||||
applyI18n();
|
||||
Reference in New Issue
Block a user