Files
dimon 3f9b108482 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>
2026-06-06 19:34:53 +08:00

230 lines
7.4 KiB
JavaScript
Raw Permalink 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";
// 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: "Приоритет (15)",
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 (15)",
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();