From bfe84b3016d40ea644d3064c0542e57fa8d3cf4b Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 15 Apr 2026 13:59:25 +0200 Subject: [PATCH 1/2] feat(notifications): add ntfy as a first-class notification channel Adds ntfy.sh (and self-hosted instances) as a new push notification channel with full parity to the existing webhook channel. - Backend: NtfyConfig type, getUserNtfyConfig, getAdminNtfyConfig, resolveNtfyUrl, sendNtfy (header-based API with Title/Priority/Tags/ Click headers), testNtfy, NTFY_EVENT_META (priority + emoji tags per event), SSRF guard via existing checkSsrf + createPinnedDispatcher - notificationPreferencesService: ntfy added to NotifChannel union, IMPLEMENTED_COMBOS, getActiveChannels parser, getAvailableChannels, ADMIN_GLOBAL_CHANNELS, and AvailableChannels interface - notificationService: per-user ntfy dispatch after webhook block; admin-scoped ntfy via getAdminGlobalPref for version_available events - Routes: POST /api/notifications/test-ntfy with saved-token fallback - authService: admin_ntfy_server/topic/token in ADMIN_SETTINGS_KEYS, masked + encrypted on read/write - settingsService: ntfy_token added to ENCRYPTED_SETTING_KEYS - Frontend: ntfy topic/server/token inputs + Save/Test/Clear buttons in NotificationsTab; admin Ntfy panel in AdminPage; testNtfy API method - i18n: full English strings; English placeholders in 14 other locales - Tests: resolveNtfyUrl, sendNtfy, dispatch integration, UI tests, MSW handler for test-ntfy endpoint --- client/src/api/client.ts | 1 + .../Settings/NotificationsTab.test.tsx | 93 ++++++++++++ .../components/Settings/NotificationsTab.tsx | 130 +++++++++++++++- client/src/i18n/translations/ar.ts | 31 +++- client/src/i18n/translations/br.ts | 31 +++- client/src/i18n/translations/cs.ts | 31 +++- client/src/i18n/translations/de.ts | 31 +++- client/src/i18n/translations/en.ts | 31 +++- client/src/i18n/translations/es.ts | 31 +++- client/src/i18n/translations/fr.ts | 31 +++- client/src/i18n/translations/hu.ts | 31 +++- client/src/i18n/translations/id.ts | 31 +++- client/src/i18n/translations/it.ts | 31 +++- client/src/i18n/translations/nl.ts | 31 +++- client/src/i18n/translations/pl.ts | 31 +++- client/src/i18n/translations/ru.ts | 31 +++- client/src/i18n/translations/zh.ts | 31 +++- client/src/i18n/translations/zhTw.ts | 31 +++- client/src/pages/AdminPage.tsx | 115 ++++++++++++++- .../helpers/msw/handlers/notifications.ts | 4 + server/src/routes/notifications.ts | 22 ++- server/src/services/authService.ts | 7 +- .../notificationPreferencesService.ts | 41 +++--- server/src/services/notificationService.ts | 30 ++++ server/src/services/notifications.ts | 131 +++++++++++++++++ server/src/services/settingsService.ts | 2 +- .../tests/integration/notifications.test.ts | 37 +++++ .../notificationPreferencesService.test.ts | 7 +- .../unit/services/notificationService.test.ts | 69 +++++++++ .../tests/unit/services/notifications.test.ts | 139 +++++++++++++++++- 30 files changed, 1241 insertions(+), 52 deletions(-) diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 38203ee2..48e6889d 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -486,6 +486,7 @@ export const notificationsApi = { updatePreferences: (prefs: Record>) => apiClient.put('/notifications/preferences', prefs).then(r => r.data), testSmtp: (email?: string) => apiClient.post('/notifications/test-smtp', { email }).then(r => r.data), testWebhook: (url?: string) => apiClient.post('/notifications/test-webhook', { url }).then(r => r.data), + testNtfy: (payload: { topic?: string; server?: string | null; token?: string | null }) => apiClient.post('/notifications/test-ntfy', payload).then(r => r.data), } export const inAppNotificationsApi = { diff --git a/client/src/components/Settings/NotificationsTab.test.tsx b/client/src/components/Settings/NotificationsTab.test.tsx index b3b808a4..d712d5ff 100644 --- a/client/src/components/Settings/NotificationsTab.test.tsx +++ b/client/src/components/Settings/NotificationsTab.test.tsx @@ -347,6 +347,99 @@ describe('NotificationsTab', () => { }); }); + it('FE-COMP-NOTIFICATIONS-ntfy-001: ntfy topic input renders when ntfy channel is available', async () => { + server.use( + http.get('/api/notifications/preferences', () => + HttpResponse.json({ + preferences: { trip_invite: { inapp: true, ntfy: false } }, + available_channels: { email: false, webhook: false, inapp: true, ntfy: true }, + event_types: ['trip_invite'], + implemented_combos: { trip_invite: ['inapp', 'ntfy'] }, + }), + ), + ); + + render(); + await waitFor(() => { + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + }); + + // Ntfy topic input should be present (placeholder text from i18n key or EN default) + const inputs = await screen.findAllByRole('textbox'); + expect(inputs.length).toBeGreaterThan(0); + }); + + it('FE-COMP-NOTIFICATIONS-ntfy-002: ntfy test button disabled when no topic entered', async () => { + server.use( + http.get('/api/notifications/preferences', () => + HttpResponse.json({ + preferences: { trip_invite: { inapp: true, ntfy: false } }, + available_channels: { email: false, webhook: false, inapp: true, ntfy: true }, + event_types: ['trip_invite'], + implemented_combos: { trip_invite: ['inapp', 'ntfy'] }, + }), + ), + http.get('/api/settings', () => HttpResponse.json({ settings: { ntfy_topic: '' } })), + ); + + render(); + await waitFor(() => { + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + }); + + // Test button should be disabled when topic is empty + const allButtons = await screen.findAllByRole('button'); + const testBtn = allButtons.find(b => /test/i.test(b.textContent || '')); + expect(testBtn).toBeDefined(); + expect(testBtn).toBeDisabled(); + }); + + it('FE-COMP-NOTIFICATIONS-ntfy-003: entering topic and clicking Test calls test-ntfy API', async () => { + const user = userEvent.setup(); + let ntfyCalled = false; + server.use( + http.get('/api/notifications/preferences', () => + HttpResponse.json({ + preferences: { trip_invite: { inapp: true, ntfy: false } }, + available_channels: { email: false, webhook: false, inapp: true, ntfy: true }, + event_types: ['trip_invite'], + implemented_combos: { trip_invite: ['inapp', 'ntfy'] }, + }), + ), + http.post('/api/notifications/test-ntfy', () => { + ntfyCalled = true; + return HttpResponse.json({ success: true }); + }), + ); + + render( + <> + + + , + ); + + await waitFor(() => { + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + }); + + // Find the topic input (first textbox in the ntfy block) and type a topic + const inputs = await screen.findAllByRole('textbox'); + await user.type(inputs[0], 'my-test-topic'); + + // Test button should now be enabled + const allButtons = screen.getAllByRole('button'); + const testBtn = allButtons.find(b => /test/i.test(b.textContent || '')); + expect(testBtn).toBeDefined(); + expect(testBtn).not.toBeDisabled(); + + await user.click(testBtn!); + + await waitFor(() => { + expect(ntfyCalled).toBe(true); + }); + }); + it('FE-COMP-NOTIFICATIONS-014: failed test webhook shows error toast with message', async () => { const user = userEvent.setup(); server.use( diff --git a/client/src/components/Settings/NotificationsTab.tsx b/client/src/components/Settings/NotificationsTab.tsx index 2fa109eb..4978926b 100644 --- a/client/src/components/Settings/NotificationsTab.tsx +++ b/client/src/components/Settings/NotificationsTab.tsx @@ -8,7 +8,7 @@ import Section from './Section' interface PreferencesMatrix { preferences: Record> - available_channels: { email: boolean; webhook: boolean; inapp: boolean } + available_channels: { email: boolean; webhook: boolean; inapp: boolean; ntfy: boolean } event_types: string[] implemented_combos: Record } @@ -17,6 +17,7 @@ const CHANNEL_LABEL_KEYS: Record = { email: 'settings.notificationPreferences.email', webhook: 'settings.notificationPreferences.webhook', inapp: 'settings.notificationPreferences.inapp', + ntfy: 'settings.notificationPreferences.ntfy', } const EVENT_LABEL_KEYS: Record = { @@ -39,6 +40,12 @@ export default function NotificationsTab(): React.ReactElement { const [webhookIsSet, setWebhookIsSet] = useState(false) const [webhookSaving, setWebhookSaving] = useState(false) const [webhookTesting, setWebhookTesting] = useState(false) + const [ntfyTopic, setNtfyTopic] = useState('') + const [ntfyServer, setNtfyServer] = useState('') + const [ntfyToken, setNtfyToken] = useState('') + const [ntfyTokenIsSet, setNtfyTokenIsSet] = useState(false) + const [ntfySaving, setNtfySaving] = useState(false) + const [ntfyTesting, setNtfyTesting] = useState(false) useEffect(() => { notificationsApi.getPreferences().then((data: PreferencesMatrix) => setMatrix(data)).catch(() => {}) @@ -50,12 +57,21 @@ export default function NotificationsTab(): React.ReactElement { } else { setWebhookUrl(val) } + setNtfyTopic((data.settings?.ntfy_topic as string) || '') + setNtfyServer((data.settings?.ntfy_server as string) || '') + const rawToken = (data.settings?.ntfy_token as string) || '' + if (rawToken === '••••••••') { + setNtfyTokenIsSet(true) + setNtfyToken('') + } else { + setNtfyToken(rawToken) + } }).catch(() => {}) }, []) const visibleChannels = matrix - ? (['email', 'webhook', 'inapp'] as const).filter(ch => { - if (!matrix.available_channels[ch]) return false + ? (['email', 'webhook', 'ntfy', 'inapp'] as const).filter(ch => { + if (!matrix.available_channels[ch as keyof typeof matrix.available_channels]) return false return matrix.event_types.some(evt => matrix.implemented_combos[evt]?.includes(ch)) }) : [] @@ -106,6 +122,52 @@ export default function NotificationsTab(): React.ReactElement { } } + const saveNtfySettings = async () => { + setNtfySaving(true) + try { + await settingsApi.setBulk({ + ntfy_topic: ntfyTopic, + ntfy_server: ntfyServer, + ...(ntfyToken && ntfyToken !== '••••••••' ? { ntfy_token: ntfyToken } : {}), + }) + if (ntfyToken && ntfyToken !== '••••••••') setNtfyTokenIsSet(true) + toast.success(t('settings.ntfyUrl.saved')) + } catch { + toast.error(t('common.error')) + } finally { + setNtfySaving(false) + } + } + + const clearNtfyToken = async () => { + try { + await settingsApi.set('ntfy_token', '') + setNtfyToken('') + setNtfyTokenIsSet(false) + toast.success(t('settings.ntfyUrl.tokenCleared')) + } catch { + toast.error(t('common.error')) + } + } + + const testNtfySettings = async () => { + if (!ntfyTopic) return + setNtfyTesting(true) + try { + const result = await notificationsApi.testNtfy({ + topic: ntfyTopic, + server: ntfyServer || null, + token: ntfyToken && ntfyToken !== '••••••••' ? ntfyToken : null, + }) + if (result.success) toast.success(t('settings.ntfyUrl.testSuccess')) + else toast.error(result.error || t('settings.ntfyUrl.testFailed')) + } catch { + toast.error(t('settings.ntfyUrl.testFailed')) + } finally { + setNtfyTesting(false) + } + } + const renderContent = () => { if (!matrix) return

{t('common.loading')}

@@ -139,7 +201,7 @@ export default function NotificationsTab(): React.ReactElement { disabled={webhookSaving} style={{ fontSize: 12, padding: '6px 12px', background: 'var(--text-primary)', color: 'var(--bg-primary)', border: 'none', borderRadius: 6, cursor: webhookSaving ? 'not-allowed' : 'pointer', opacity: webhookSaving ? 0.6 : 1 }} > - {t('settings.webhookUrl.save')} + {t('common.save')} + )} + + + + + )} {/* Header row */}
'64px').join(' ')}`, gap: 4, paddingBottom: 6, marginBottom: 4, borderBottom: '1px solid var(--border-primary)' }}> diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts index 92c23eec..b422c6bd 100644 --- a/client/src/i18n/translations/ar.ts +++ b/client/src/i18n/translations/ar.ts @@ -1809,14 +1809,27 @@ const ar: Record = { 'settings.webhookUrl.label': 'رابط Webhook', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'أدخل رابط Webhook الخاص بـ Discord أو Slack أو المخصص لتلقي الإشعارات.', - 'settings.webhookUrl.save': 'حفظ', 'settings.webhookUrl.saved': 'تم حفظ رابط Webhook', 'settings.webhookUrl.test': 'اختبار', 'settings.webhookUrl.testSuccess': 'تم إرسال Webhook الاختباري بنجاح', 'settings.webhookUrl.testFailed': 'فشل إرسال Webhook الاختباري', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1827,6 +1840,22 @@ const ar: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'تم إرسال Webhook الاختباري بنجاح', 'admin.notifications.adminWebhookPanel.testFailed': 'فشل إرسال Webhook الاختباري', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'يُرسل Webhook المسؤول تلقائيًا عند تعيين رابط URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'حدد القنوات التي تُسلّم إشعارات المسؤول (مثل تنبيهات الإصدارات). يُرسل الـ Webhook تلقائيًا عند تعيين رابط URL لـ Webhook المسؤول.', 'admin.tabs.notifications': 'الإشعارات', 'notifications.versionAvailable.title': 'تحديث متاح', diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts index 346bafc9..ba320907 100644 --- a/client/src/i18n/translations/br.ts +++ b/client/src/i18n/translations/br.ts @@ -1758,14 +1758,27 @@ const br: Record = { 'settings.webhookUrl.label': 'URL do webhook', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Insira a URL do seu webhook do Discord, Slack ou personalizado para receber notificações.', - 'settings.webhookUrl.save': 'Salvar', 'settings.webhookUrl.saved': 'URL do webhook salva', 'settings.webhookUrl.test': 'Testar', 'settings.webhookUrl.testSuccess': 'Webhook de teste enviado com sucesso', 'settings.webhookUrl.testFailed': 'Falha no webhook de teste', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1776,6 +1789,22 @@ const br: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook de teste enviado com sucesso', 'admin.notifications.adminWebhookPanel.testFailed': 'Falha no webhook de teste', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'O webhook de admin dispara automaticamente quando uma URL está configurada', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Configure quais canais entregam notificações de admin (ex. alertas de versão). O webhook dispara automaticamente se uma URL de webhook de admin estiver definida.', 'admin.tabs.notifications': 'Notificações', 'notifications.versionAvailable.title': 'Atualização disponível', diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts index 283fb7e8..47d5dcee 100644 --- a/client/src/i18n/translations/cs.ts +++ b/client/src/i18n/translations/cs.ts @@ -1763,14 +1763,27 @@ const cs: Record = { 'settings.webhookUrl.label': 'URL webhooku', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Zadejte URL vašeho Discord, Slack nebo vlastního webhooku pro příjem oznámení.', - 'settings.webhookUrl.save': 'Uložit', 'settings.webhookUrl.saved': 'URL webhooku uložena', 'settings.webhookUrl.test': 'Otestovat', 'settings.webhookUrl.testSuccess': 'Testovací webhook byl úspěšně odeslán', 'settings.webhookUrl.testFailed': 'Testovací webhook selhal', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1781,6 +1794,22 @@ const cs: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Testovací webhook byl úspěšně odeslán', 'admin.notifications.adminWebhookPanel.testFailed': 'Testovací webhook selhal', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook odesílá automaticky, pokud je nastavena URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Nastavte, které kanály doručují admin oznámení (např. upozornění na verze). Webhook odesílá automaticky, pokud je nastavena URL admin webhooku.', 'admin.tabs.notifications': 'Oznámení', 'notifications.versionAvailable.title': 'Dostupná aktualizace', diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index 2c9e54ee..185577ed 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -1766,14 +1766,27 @@ const de: Record = { 'settings.webhookUrl.label': 'Webhook-URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Gib deine Discord-, Slack- oder benutzerdefinierte Webhook-URL ein, um Benachrichtigungen zu erhalten.', - 'settings.webhookUrl.save': 'Speichern', 'settings.webhookUrl.saved': 'Webhook-URL gespeichert', 'settings.webhookUrl.test': 'Testen', 'settings.webhookUrl.testSuccess': 'Test-Webhook erfolgreich gesendet', 'settings.webhookUrl.testFailed': 'Test-Webhook fehlgeschlagen', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1784,6 +1797,22 @@ const de: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Test-Webhook erfolgreich gesendet', 'admin.notifications.adminWebhookPanel.testFailed': 'Test-Webhook fehlgeschlagen', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin-Webhook sendet automatisch, wenn eine URL konfiguriert ist', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Konfiguriere, welche Kanäle Admin-Benachrichtigungen liefern (z. B. Versions-Updates). Der Webhook sendet automatisch, wenn eine Admin-Webhook-URL gesetzt ist.', 'admin.tabs.notifications': 'Benachrichtigungen', 'notifications.versionAvailable.title': 'Update verfügbar', diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts index 056e3773..d20e6243 100644 --- a/client/src/i18n/translations/en.ts +++ b/client/src/i18n/translations/en.ts @@ -189,25 +189,42 @@ const en: Record = { 'settings.notificationPreferences.email': 'Email', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.notificationPreferences.noChannels': 'No notification channels are configured. Ask an admin to set up email or webhook notifications.', 'settings.webhookUrl.label': 'Webhook URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Enter your Discord, Slack, or custom webhook URL to receive notifications.', - 'settings.webhookUrl.save': 'Save', 'settings.webhookUrl.saved': 'Webhook URL saved', 'settings.webhookUrl.test': 'Test', 'settings.webhookUrl.testSuccess': 'Test webhook sent successfully', 'settings.webhookUrl.testFailed': 'Test webhook failed', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'admin.notifications.title': 'Notifications', 'admin.notifications.hint': 'Choose one notification channel. Only one can be active at a time.', 'admin.notifications.none': 'Disabled', 'admin.notifications.email': 'Email (SMTP)', 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.ntfy': 'Ntfy', 'admin.notifications.save': 'Save notification settings', 'admin.notifications.saved': 'Notification settings saved', 'admin.notifications.testWebhook': 'Send test webhook', 'admin.notifications.testWebhookSuccess': 'Test webhook sent successfully', 'admin.notifications.testWebhookFailed': 'Test webhook failed', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -218,6 +235,18 @@ const en: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Test webhook sent successfully', 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook failed', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook always fires when a URL is configured', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Configure which channels deliver admin-only notifications (e.g. version alerts).', 'admin.smtp.title': 'Email & Notifications', 'admin.smtp.hint': 'SMTP configuration for sending email notifications.', diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts index 1a64e5e2..d7204fee 100644 --- a/client/src/i18n/translations/es.ts +++ b/client/src/i18n/translations/es.ts @@ -1768,14 +1768,27 @@ const es: Record = { 'settings.webhookUrl.label': 'URL del webhook', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Introduce tu URL de webhook de Discord, Slack o personalizada para recibir notificaciones.', - 'settings.webhookUrl.save': 'Guardar', 'settings.webhookUrl.saved': 'URL del webhook guardada', 'settings.webhookUrl.test': 'Probar', 'settings.webhookUrl.testSuccess': 'Webhook de prueba enviado correctamente', 'settings.webhookUrl.testFailed': 'Error al enviar el webhook de prueba', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1786,6 +1799,22 @@ const es: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook de prueba enviado correctamente', 'admin.notifications.adminWebhookPanel.testFailed': 'Error al enviar el webhook de prueba', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'El webhook de admin se activa automáticamente si hay una URL configurada', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Configura qué canales entregan notificaciones de admin (ej. alertas de versión). El webhook se activa automáticamente si hay una URL de webhook de admin configurada.', 'admin.tabs.notifications': 'Notificaciones', 'notifications.versionAvailable.title': 'Actualización disponible', diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts index 11ce8012..0b6095a3 100644 --- a/client/src/i18n/translations/fr.ts +++ b/client/src/i18n/translations/fr.ts @@ -1762,14 +1762,27 @@ const fr: Record = { 'settings.webhookUrl.label': 'URL du webhook', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Entrez votre URL de webhook Discord, Slack ou personnalisée pour recevoir des notifications.', - 'settings.webhookUrl.save': 'Enregistrer', 'settings.webhookUrl.saved': 'URL du webhook enregistrée', 'settings.webhookUrl.test': 'Tester', 'settings.webhookUrl.testSuccess': 'Webhook de test envoyé avec succès', 'settings.webhookUrl.testFailed': 'Échec du webhook de test', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1780,6 +1793,22 @@ const fr: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook de test envoyé avec succès', 'admin.notifications.adminWebhookPanel.testFailed': 'Échec du webhook de test', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Le webhook admin s\'active automatiquement si une URL est configurée', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Configurez quels canaux envoient les notifications admin (ex. alertes de version). Le webhook s\'active automatiquement si une URL webhook admin est définie.', 'admin.tabs.notifications': 'Notifications', 'notifications.versionAvailable.title': 'Mise à jour disponible', diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts index 2d64ae63..1e7185cd 100644 --- a/client/src/i18n/translations/hu.ts +++ b/client/src/i18n/translations/hu.ts @@ -1760,14 +1760,27 @@ const hu: Record = { 'settings.webhookUrl.label': 'Webhook URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Adja meg a Discord, Slack vagy egyéni webhook URL-jét az értesítések fogadásához.', - 'settings.webhookUrl.save': 'Mentés', 'settings.webhookUrl.saved': 'Webhook URL mentve', 'settings.webhookUrl.test': 'Teszt', 'settings.webhookUrl.testSuccess': 'Teszt webhook sikeresen elküldve', 'settings.webhookUrl.testFailed': 'Teszt webhook sikertelen', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1778,6 +1791,22 @@ const hu: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Teszt webhook sikeresen elküldve', 'admin.notifications.adminWebhookPanel.testFailed': 'Teszt webhook sikertelen', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Az admin webhook automatikusan küld, ha URL van beállítva', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Állítsa be, hogy mely csatornák szállítsák az admin értesítéseket (pl. verziófrissítési figyelmeztetések). A webhook automatikusan küld, ha admin webhook URL van megadva.', 'admin.tabs.notifications': 'Értesítések', 'notifications.versionAvailable.title': 'Elérhető frissítés', diff --git a/client/src/i18n/translations/id.ts b/client/src/i18n/translations/id.ts index e6bb27f5..5c577492 100644 --- a/client/src/i18n/translations/id.ts +++ b/client/src/i18n/translations/id.ts @@ -189,15 +189,28 @@ const id: Record = { 'settings.notificationPreferences.email': 'Email', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.notificationPreferences.noChannels': 'Belum ada saluran notifikasi yang dikonfigurasi. Minta admin untuk mengatur notifikasi email atau webhook.', 'settings.webhookUrl.label': 'Webhook URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Masukkan URL webhook Discord, Slack, atau kustom untuk menerima notifikasi.', - 'settings.webhookUrl.save': 'Simpan', 'settings.webhookUrl.saved': 'Webhook URL tersimpan', 'settings.webhookUrl.test': 'Uji', 'settings.webhookUrl.testSuccess': 'Test webhook berhasil dikirim', 'settings.webhookUrl.testFailed': 'Test webhook gagal', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'admin.notifications.title': 'Notifikasi', 'admin.notifications.hint': 'Pilih satu saluran notifikasi. Hanya satu yang bisa aktif sekaligus.', 'admin.notifications.none': 'Dinonaktifkan', @@ -218,6 +231,22 @@ const id: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Test webhook berhasil dikirim', 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook gagal', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook selalu berjalan jika URL dikonfigurasi', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Atur saluran mana yang mengirimkan notifikasi khusus admin (mis. peringatan versi).', 'admin.smtp.title': 'Email & Notifikasi', 'admin.smtp.hint': 'Konfigurasi SMTP untuk pengiriman notifikasi email.', diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts index 4c2716c4..f395709a 100644 --- a/client/src/i18n/translations/it.ts +++ b/client/src/i18n/translations/it.ts @@ -1763,14 +1763,27 @@ const it: Record = { 'settings.webhookUrl.label': 'URL webhook', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Inserisci il tuo URL webhook Discord, Slack o personalizzato per ricevere notifiche.', - 'settings.webhookUrl.save': 'Salva', 'settings.webhookUrl.saved': 'URL webhook salvato', 'settings.webhookUrl.test': 'Test', 'settings.webhookUrl.testSuccess': 'Webhook di test inviato con successo', 'settings.webhookUrl.testFailed': 'Invio webhook di test fallito', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1781,6 +1794,22 @@ const it: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook di test inviato con successo', 'admin.notifications.adminWebhookPanel.testFailed': 'Invio webhook di test fallito', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Il webhook admin si attiva automaticamente quando è configurato un URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Configura quali canali consegnano le notifiche admin (es. avvisi di versione). Il webhook si attiva automaticamente se è impostato un URL webhook admin.', 'admin.tabs.notifications': 'Notifiche', 'notifications.versionAvailable.title': 'Aggiornamento disponibile', diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts index 72c9a6aa..40cd4762 100644 --- a/client/src/i18n/translations/nl.ts +++ b/client/src/i18n/translations/nl.ts @@ -1762,14 +1762,27 @@ const nl: Record = { 'settings.webhookUrl.label': 'Webhook-URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Voer je Discord-, Slack- of aangepaste webhook-URL in om meldingen te ontvangen.', - 'settings.webhookUrl.save': 'Opslaan', 'settings.webhookUrl.saved': 'Webhook-URL opgeslagen', 'settings.webhookUrl.test': 'Testen', 'settings.webhookUrl.testSuccess': 'Test-webhook succesvol verzonden', 'settings.webhookUrl.testFailed': 'Test-webhook mislukt', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1780,6 +1793,22 @@ const nl: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Test-webhook succesvol verzonden', 'admin.notifications.adminWebhookPanel.testFailed': 'Test-webhook mislukt', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin-webhook verstuurt automatisch als er een URL is ingesteld', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Stel in via welke kanalen admin-meldingen worden bezorgd (bijv. versie-updates). De webhook verstuurt automatisch als er een admin-webhook-URL is ingesteld.', 'admin.tabs.notifications': 'Meldingen', 'notifications.versionAvailable.title': 'Update beschikbaar', diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts index 41d1d899..4e7ce40d 100644 --- a/client/src/i18n/translations/pl.ts +++ b/client/src/i18n/translations/pl.ts @@ -1596,6 +1596,22 @@ const pl: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Testowy webhook wysłany pomyślnie', 'admin.notifications.adminWebhookPanel.testFailed': 'Wysyłanie testowego webhooka nie powiodło się', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Webhook admina wysyła automatycznie, gdy URL jest skonfigurowany', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Skonfiguruj, które kanały dostarczają powiadomienia admina (np. alerty o wersjach). Webhook wysyła automatycznie, gdy ustawiony jest URL webhooka admina.', 'admin.webhook.hint': 'Pozwól użytkownikom konfigurować własne adresy URL webhooka dla powiadomień (Discord, Slack itp.).', 'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.', @@ -1603,14 +1619,27 @@ const pl: Record = { 'settings.webhookUrl.label': 'URL webhooka', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Wprowadź adres URL webhooka Discord, Slack lub własnego, aby otrzymywać powiadomienia.', - 'settings.webhookUrl.save': 'Zapisz', 'settings.webhookUrl.saved': 'URL webhooka zapisany', 'settings.webhookUrl.test': 'Testuj', 'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie', 'settings.webhookUrl.testFailed': 'Wysyłanie testowego webhooka nie powiodło się', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.notificationsActive': 'Aktywny kanał', 'settings.notificationsManagedByAdmin': 'Zdarzenia konfigurowane przez administratora.', 'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.', diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts index 18373f55..8e0ff013 100644 --- a/client/src/i18n/translations/ru.ts +++ b/client/src/i18n/translations/ru.ts @@ -1759,14 +1759,27 @@ const ru: Record = { 'settings.webhookUrl.label': 'URL вебхука', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': 'Введите URL вашего вебхука Discord, Slack или пользовательского для получения уведомлений.', - 'settings.webhookUrl.save': 'Сохранить', 'settings.webhookUrl.saved': 'URL вебхука сохранён', 'settings.webhookUrl.test': 'Тест', 'settings.webhookUrl.testSuccess': 'Тестовый вебхук успешно отправлен', 'settings.webhookUrl.testFailed': 'Ошибка тестового вебхука', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1777,6 +1790,22 @@ const ru: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': 'Тестовый вебхук успешно отправлен', 'admin.notifications.adminWebhookPanel.testFailed': 'Ошибка тестового вебхука', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Вебхук администратора отправляется автоматически при наличии URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': 'Настройте, какие каналы доставляют уведомления администратора (например, оповещения о версиях). Вебхук отправляется автоматически, если задан URL вебхука администратора.', 'admin.tabs.notifications': 'Уведомления', 'notifications.versionAvailable.title': 'Доступно обновление', diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts index 3312da83..e43d956c 100644 --- a/client/src/i18n/translations/zh.ts +++ b/client/src/i18n/translations/zh.ts @@ -1759,14 +1759,27 @@ const zh: Record = { 'settings.webhookUrl.label': 'Webhook URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': '输入您的 Discord、Slack 或自定义 Webhook URL 以接收通知。', - 'settings.webhookUrl.save': '保存', 'settings.webhookUrl.saved': 'Webhook URL 已保存', 'settings.webhookUrl.test': '测试', 'settings.webhookUrl.testSuccess': '测试 Webhook 发送成功', 'settings.webhookUrl.testFailed': '测试 Webhook 失败', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'admin.notifications.emailPanel.title': 'Email (SMTP)', 'admin.notifications.webhookPanel.title': 'Webhook', 'admin.notifications.inappPanel.title': 'In-App', @@ -1777,6 +1790,22 @@ const zh: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': '测试 Webhook 发送成功', 'admin.notifications.adminWebhookPanel.testFailed': '测试 Webhook 失败', 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 后管理员 Webhook 自动触发', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': '配置哪些渠道发送管理员通知(如版本更新提醒)。设置管理员 Webhook URL 后,Webhook 将自动触发。', 'admin.tabs.notifications': '通知', 'notifications.versionAvailable.title': '有可用更新', diff --git a/client/src/i18n/translations/zhTw.ts b/client/src/i18n/translations/zhTw.ts index 4e6acb6b..2637e0c8 100644 --- a/client/src/i18n/translations/zhTw.ts +++ b/client/src/i18n/translations/zhTw.ts @@ -186,15 +186,28 @@ const zhTw: Record = { 'settings.notificationPreferences.email': '電子郵件', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.inapp': '應用程式內', + 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.notificationPreferences.noChannels': '未配置通知渠道。請聯絡管理員設定電子郵件或 Webhook 通知。', 'settings.webhookUrl.label': 'Webhook URL', 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', 'settings.webhookUrl.hint': '輸入您的 Discord、Slack 或自訂 Webhook URL 以接收通知。', - 'settings.webhookUrl.save': '儲存', 'settings.webhookUrl.saved': 'Webhook URL 已儲存', 'settings.webhookUrl.test': '測試', 'settings.webhookUrl.testSuccess': '測試 Webhook 傳送成功', 'settings.webhookUrl.testFailed': '測試 Webhook 傳送失敗', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.clearToken': 'Clear', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', 'settings.notificationsDisabled': '通知尚未配置。請聯絡管理員啟用電子郵件或 Webhook 通知。', 'settings.notificationsActive': '活躍頻道', 'settings.notificationsManagedByAdmin': '通知事件由管理員配置。', @@ -218,6 +231,22 @@ const zhTw: Record = { 'admin.notifications.adminWebhookPanel.testSuccess': '測試 Webhook 傳送成功', 'admin.notifications.adminWebhookPanel.testFailed': '測試 Webhook 傳送失敗', 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 後,管理員 Webhook 始終觸發', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', 'admin.notifications.adminNotificationsHint': '配置哪些渠道傳遞僅管理員通知(例如版本提醒)。', 'admin.smtp.title': '郵件與通知', 'admin.smtp.hint': '用於傳送電子郵件通知的 SMTP 配置。', diff --git a/client/src/pages/AdminPage.tsx b/client/src/pages/AdminPage.tsx index da4e28ef..76728cd2 100644 --- a/client/src/pages/AdminPage.tsx +++ b/client/src/pages/AdminPage.tsx @@ -66,6 +66,7 @@ const ADMIN_CHANNEL_LABEL_KEYS: Record = { inapp: 'settings.notificationPreferences.inapp', email: 'settings.notificationPreferences.email', webhook: 'settings.notificationPreferences.webhook', + ntfy: 'settings.notificationPreferences.ntfy', } function AdminNotificationsPanel({ t, toast }: { t: (k: string) => string; toast: ReturnType }) { @@ -78,7 +79,7 @@ function AdminNotificationsPanel({ t, toast }: { t: (k: string) => string; toast if (!matrix) return

Loading…

- const visibleChannels = (['inapp', 'email', 'webhook'] as const).filter(ch => { + const visibleChannels = (['inapp', 'email', 'webhook', 'ntfy'] as const).filter(ch => { if (!matrix.available_channels[ch]) return false return matrix.event_types.some((evt: string) => matrix.implemented_combos[evt]?.includes(ch)) }) @@ -1168,15 +1169,16 @@ export default function AdminPage(): React.ReactElement { const activeChans = rawChannels === 'none' ? [] : rawChannels.split(',').map((c: string) => c.trim()) const emailActive = activeChans.includes('email') const webhookActive = activeChans.includes('webhook') + const ntfyActive = activeChans.includes('ntfy') - const setChannels = async (email: boolean, webhook: boolean) => { - const chans = [email && 'email', webhook && 'webhook'].filter(Boolean).join(',') || 'none' + const setChannels = async (email: boolean, webhook: boolean, ntfy: boolean) => { + const chans = [email && 'email', webhook && 'webhook', ntfy && 'ntfy'].filter(Boolean).join(',') || 'none' setSmtpValues(prev => ({ ...prev, notification_channels: chans })) try { await authApi.updateAppSettings({ notification_channels: chans }) } catch { // Revert state on failure - const reverted = [emailActive && 'email', webhookActive && 'webhook'].filter(Boolean).join(',') || 'none' + const reverted = [emailActive && 'email', webhookActive && 'webhook', ntfyActive && 'ntfy'].filter(Boolean).join(',') || 'none' setSmtpValues(prev => ({ ...prev, notification_channels: reverted })) toast.error(t('common.error')) } @@ -1207,7 +1209,7 @@ export default function AdminPage(): React.ReactElement {

{t('admin.smtp.hint')}

+ + + {/* In-App Panel */}
@@ -1358,6 +1378,89 @@ export default function AdminPage(): React.ReactElement {
+ {/* Admin Ntfy Panel */} +
+
+

{t('admin.notifications.adminNtfyPanel.title')}

+

{t('admin.notifications.adminNtfyPanel.hint')}

+
+
+ {smtpLoaded && ( + <> +
+ + setSmtpValues(prev => ({ ...prev, admin_ntfy_server: e.target.value }))} + placeholder={t('admin.notifications.adminNtfyPanel.serverPlaceholder')} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+
+ + setSmtpValues(prev => ({ ...prev, admin_ntfy_topic: e.target.value }))} + placeholder={t('admin.notifications.adminNtfyPanel.topicPlaceholder')} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+
+ + setSmtpValues(prev => ({ ...prev, admin_ntfy_token: e.target.value }))} + placeholder={smtpValues.admin_ntfy_token === '••••••••' ? '••••••••' : ''} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+ + )} +
+
+ + +
+
+
diff --git a/client/tests/helpers/msw/handlers/notifications.ts b/client/tests/helpers/msw/handlers/notifications.ts index 463f3e44..f009cee0 100644 --- a/client/tests/helpers/msw/handlers/notifications.ts +++ b/client/tests/helpers/msw/handlers/notifications.ts @@ -61,6 +61,10 @@ export const notificationHandlers = [ return HttpResponse.json({ success: true }); }), + http.post('/api/notifications/test-ntfy', async () => { + return HttpResponse.json({ success: true }); + }), + http.post('/api/notifications/in-app/:id/respond', async ({ request, params }) => { const body = await request.json() as { response: string }; return HttpResponse.json({ diff --git a/server/src/routes/notifications.ts b/server/src/routes/notifications.ts index 493561bd..9f8e7c27 100644 --- a/server/src/routes/notifications.ts +++ b/server/src/routes/notifications.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from 'express'; import { authenticate } from '../middleware/auth'; import { AuthRequest } from '../types'; -import { testSmtp, testWebhook, getAdminWebhookUrl, getUserWebhookUrl } from '../services/notifications'; +import { testSmtp, testWebhook, testNtfy, getAdminWebhookUrl, getUserWebhookUrl, getUserNtfyConfig, getAdminNtfyConfig } from '../services/notifications'; import { getNotifications, getUnreadCount, @@ -47,6 +47,26 @@ router.post('/test-webhook', authenticate, async (req: Request, res: Response) = res.json(await testWebhook(url)); }); +router.post('/test-ntfy', authenticate, async (req: Request, res: Response) => { + const authReq = req as AuthRequest; + const { topic, server, token } = req.body as { topic?: string; server?: string; token?: string }; + + // Always load saved config for fallbacks (token may be masked or absent in request) + const userCfg = getUserNtfyConfig(authReq.user.id); + const adminCfg = getAdminNtfyConfig(); + + const resolvedTopic = topic || userCfg?.topic || undefined; + const resolvedServer = server || userCfg?.server || adminCfg.server || undefined; + // Reuse saved token when request sends null, empty, or the masked placeholder + const resolvedToken = (token && token !== '••••••••') + ? token + : (userCfg?.token ?? adminCfg.token ?? null); + + if (!resolvedTopic) return res.status(400).json({ error: 'No ntfy topic configured' }); + + res.json(await testNtfy({ topic: resolvedTopic, server: resolvedServer ?? null, token: resolvedToken })); +}); + // ── In-app notifications ────────────────────────────────────────────────────── // GET /in-app — list notifications (paginated) diff --git a/server/src/services/authService.ts b/server/src/services/authService.ts index 5d475306..e15395d3 100644 --- a/server/src/services/authService.ts +++ b/server/src/services/authService.ts @@ -30,7 +30,7 @@ const MFA_BACKUP_CODE_COUNT = 10; const ADMIN_SETTINGS_KEYS = [ 'allow_registration', 'allowed_file_types', 'require_mfa', 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_from', 'smtp_skip_tls_verify', - 'notification_channels', 'admin_webhook_url', + 'notification_channels', 'admin_webhook_url', 'admin_ntfy_server', 'admin_ntfy_topic', 'admin_ntfy_token', 'password_login', 'password_registration', 'oidc_login', 'oidc_registration', ]; @@ -714,7 +714,7 @@ export function getAppSettings(userId: number): { error?: string; status?: numbe const result: Record = {}; for (const key of ADMIN_SETTINGS_KEYS) { const row = db.prepare("SELECT value FROM app_settings WHERE key = ?").get(key) as { value: string } | undefined; - if (row) result[key] = (key === 'smtp_pass' || key === 'admin_webhook_url') ? '••••••••' : row.value; + if (row) result[key] = (key === 'smtp_pass' || key === 'admin_webhook_url' || key === 'admin_ntfy_token') ? '••••••••' : row.value; } return { data: result }; } @@ -768,6 +768,8 @@ export function updateAppSettings( if (key === 'smtp_pass') val = encrypt_api_key(val); if (key === 'admin_webhook_url' && val === '••••••••') continue; if (key === 'admin_webhook_url' && val) val = maybe_encrypt_api_key(val) ?? val; + if (key === 'admin_ntfy_token' && val === '••••••••') continue; + if (key === 'admin_ntfy_token' && val) val = maybe_encrypt_api_key(val) ?? val; db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)").run(key, val); } } @@ -778,6 +780,7 @@ export function updateAppSettings( const smtpChanged = changedKeys.some(k => k.startsWith('smtp_')); if (changedKeys.includes('notification_channels')) summary.notification_channels = body.notification_channels; if (changedKeys.includes('admin_webhook_url')) summary.admin_webhook_url_updated = true; + if (changedKeys.some(k => k.startsWith('admin_ntfy_'))) summary.admin_ntfy_updated = true; if (smtpChanged) summary.smtp_settings_updated = true; if (changedKeys.includes('allow_registration')) summary.allow_registration = body.allow_registration; if (changedKeys.includes('allowed_file_types')) summary.allowed_file_types_updated = true; diff --git a/server/src/services/notificationPreferencesService.ts b/server/src/services/notificationPreferencesService.ts index 5b7ef73f..144d9c67 100644 --- a/server/src/services/notificationPreferencesService.ts +++ b/server/src/services/notificationPreferencesService.ts @@ -3,7 +3,7 @@ import { decrypt_api_key } from './apiKeyCrypto'; // ── Types ────────────────────────────────────────────────────────────────── -export type NotifChannel = 'email' | 'webhook' | 'inapp'; +export type NotifChannel = 'email' | 'webhook' | 'inapp' | 'ntfy'; export type NotifEventType = | 'trip_invite' @@ -20,19 +20,20 @@ export interface AvailableChannels { email: boolean; webhook: boolean; inapp: boolean; + ntfy: boolean; } // Which channels are implemented for each event type. // Only implemented combos show toggles in the user preferences UI. const IMPLEMENTED_COMBOS: Record = { - trip_invite: ['inapp', 'email', 'webhook'], - booking_change: ['inapp', 'email', 'webhook'], - trip_reminder: ['inapp', 'email', 'webhook'], - vacay_invite: ['inapp', 'email', 'webhook'], - photos_shared: ['inapp', 'email', 'webhook'], - collab_message: ['inapp', 'email', 'webhook'], - packing_tagged: ['inapp', 'email', 'webhook'], - version_available: ['inapp', 'email', 'webhook'], + trip_invite: ['inapp', 'email', 'webhook', 'ntfy'], + booking_change: ['inapp', 'email', 'webhook', 'ntfy'], + trip_reminder: ['inapp', 'email', 'webhook', 'ntfy'], + vacay_invite: ['inapp', 'email', 'webhook', 'ntfy'], + photos_shared: ['inapp', 'email', 'webhook', 'ntfy'], + collab_message: ['inapp', 'email', 'webhook', 'ntfy'], + packing_tagged: ['inapp', 'email', 'webhook', 'ntfy'], + version_available: ['inapp', 'email', 'webhook', 'ntfy'], synology_session_cleared: ['inapp'], }; @@ -55,7 +56,7 @@ function getAppSetting(key: string): string | null { export function getActiveChannels(): NotifChannel[] { const raw = getAppSetting('notification_channels') || getAppSetting('notification_channel') || 'none'; if (raw === 'none') return []; - return raw.split(',').map(c => c.trim()).filter((c): c is NotifChannel => c === 'email' || c === 'webhook'); + return raw.split(',').map(c => c.trim()).filter((c): c is NotifChannel => c === 'email' || c === 'webhook' || c === 'ntfy'); } /** @@ -64,8 +65,8 @@ export function getActiveChannels(): NotifChannel[] { */ export function getAvailableChannels(): AvailableChannels { const hasSmtp = !!(process.env.SMTP_HOST || getAppSetting('smtp_host')); - const hasWebhook = getActiveChannels().includes('webhook'); - return { email: hasSmtp, webhook: hasWebhook, inapp: true }; + const activeChannels = getActiveChannels(); + return { email: hasSmtp, webhook: activeChannels.includes('webhook'), ntfy: activeChannels.includes('ntfy'), inapp: true }; } // ── Per-user preference checks ───────────────────────────────────────────── @@ -115,8 +116,8 @@ export function getPreferencesMatrix(userId: number, userRole: string, scope: 'u const channels = IMPLEMENTED_COMBOS[eventType]; preferences[eventType] = {}; for (const channel of channels) { - // Admin-scoped events use global settings for email/webhook - if (scope === 'admin' && ADMIN_SCOPED_EVENTS.has(eventType) && (channel === 'email' || channel === 'webhook')) { + // Admin-scoped events use global settings for email/webhook/ntfy + if (scope === 'admin' && ADMIN_SCOPED_EVENTS.has(eventType) && (channel === 'email' || channel === 'webhook' || channel === 'ntfy')) { preferences[eventType]![channel] = getAdminGlobalPref(eventType, channel); } else { preferences[eventType]![channel] = stored[eventType]?.[channel] ?? true; @@ -134,12 +135,14 @@ export function getPreferencesMatrix(userId: number, userRole: string, scope: 'u if (scope === 'admin') { const hasSmtp = !!(process.env.SMTP_HOST || getAppSetting('smtp_host')); const hasAdminWebhook = !!(getAppSetting('admin_webhook_url')); - available_channels = { email: hasSmtp, webhook: hasAdminWebhook, inapp: true }; + const hasAdminNtfy = !!(getAppSetting('admin_ntfy_topic')); + available_channels = { email: hasSmtp, webhook: hasAdminWebhook, ntfy: hasAdminNtfy, inapp: true }; } else { const activeChannels = getActiveChannels(); available_channels = { email: activeChannels.includes('email'), webhook: activeChannels.includes('webhook'), + ntfy: activeChannels.includes('ntfy'), inapp: true, }; } @@ -154,19 +157,19 @@ export function getPreferencesMatrix(userId: number, userRole: string, scope: 'u // ── Admin global preferences (stored in app_settings) ───────────────────── -const ADMIN_GLOBAL_CHANNELS: NotifChannel[] = ['email', 'webhook']; +const ADMIN_GLOBAL_CHANNELS: NotifChannel[] = ['email', 'webhook', 'ntfy']; /** * Returns the global admin preference for an event+channel. * Stored in app_settings as `admin_notif_pref_{event}_{channel}`. * Defaults to true (enabled) when no row exists. */ -export function getAdminGlobalPref(event: NotifEventType, channel: 'email' | 'webhook'): boolean { +export function getAdminGlobalPref(event: NotifEventType, channel: 'email' | 'webhook' | 'ntfy'): boolean { const val = getAppSetting(`admin_notif_pref_${event}_${channel}`); return val !== '0'; } -function setAdminGlobalPref(event: NotifEventType, channel: 'email' | 'webhook', enabled: boolean): void { +function setAdminGlobalPref(event: NotifEventType, channel: 'email' | 'webhook' | 'ntfy', enabled: boolean): void { db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)').run( `admin_notif_pref_${event}_${channel}`, enabled ? '1' : '0' @@ -250,7 +253,7 @@ export function setAdminPreferences( for (const [eventType, channels] of Object.entries(globalPrefs)) { if (!channels) continue; for (const [channel, enabled] of Object.entries(channels)) { - setAdminGlobalPref(eventType as NotifEventType, channel as 'email' | 'webhook', enabled); + setAdminGlobalPref(eventType as NotifEventType, channel as 'email' | 'webhook' | 'ntfy', enabled); } } diff --git a/server/src/services/notificationService.ts b/server/src/services/notificationService.ts index 243faaf7..8a8a94cf 100644 --- a/server/src/services/notificationService.ts +++ b/server/src/services/notificationService.ts @@ -7,15 +7,20 @@ import { isSmtpConfigured, ADMIN_SCOPED_EVENTS, type NotifEventType, + type NotifChannel, } from './notificationPreferencesService'; import { getEventText, sendEmail, sendWebhook, + sendNtfy, getUserEmail, getUserLanguage, getUserWebhookUrl, getAdminWebhookUrl, + getUserNtfyConfig, + getAdminNtfyConfig, + resolveNtfyUrl, getAppUrl, } from './notifications'; import { @@ -270,6 +275,19 @@ export async function send(payload: NotificationPayload): Promise { } } + // ── Ntfy (per-user) — skip for admin-scoped events (handled globally below) ── + if (!ADMIN_SCOPED_EVENTS.has(event) && activeChannels.includes('ntfy') && isEnabledForEvent(recipientId, event, 'ntfy' as NotifChannel)) { + const userNtfyCfg = getUserNtfyConfig(recipientId); + const adminNtfyCfg = getAdminNtfyConfig(); + const ntfyUrl = resolveNtfyUrl(adminNtfyCfg, userNtfyCfg); + if (ntfyUrl) { + const lang = getUserLanguage(recipientId); + const { title, body } = getEventText(lang, event, params); + const token = userNtfyCfg?.token ?? adminNtfyCfg.token; + promises.push(sendNtfy(ntfyUrl, token, { event, title, body, link: fullLink })); + } + } + const results = await Promise.allSettled(promises); for (const result of results) { if (result.status === 'rejected') { @@ -288,4 +306,16 @@ export async function send(payload: NotificationPayload): Promise { }); } } + + // ── Admin ntfy (scope: admin) — global, respects global pref ───────── + if (scope === 'admin' && getAdminGlobalPref(event, 'ntfy')) { + const adminNtfyCfg = getAdminNtfyConfig(); + const adminNtfyUrl = resolveNtfyUrl(adminNtfyCfg, null); + if (adminNtfyUrl) { + const { title, body } = getEventText('en', event, params); + await sendNtfy(adminNtfyUrl, adminNtfyCfg.token, { event, title, body, link: fullLink }).catch((err: unknown) => { + logError(`notificationService.send admin ntfy failed event=${event}: ${err instanceof Error ? err.message : err}`); + }); + } + } } diff --git a/server/src/services/notifications.ts b/server/src/services/notifications.ts index ca0ad885..0773a279 100644 --- a/server/src/services/notifications.ts +++ b/server/src/services/notifications.ts @@ -442,3 +442,134 @@ export async function testWebhook(url: string): Promise<{ success: boolean; erro } } +// ── Ntfy ────────────────────────────────────────────────────────────────── + +export interface NtfyConfig { + server: string | null; + topic: string | null; + token: string | null; +} + +/** Priority and tags mapped to each notification event type. */ +const NTFY_EVENT_META: Partial> = { + trip_invite: { priority: 4, tags: ['loudspeaker'] }, + booking_change: { priority: 3, tags: ['calendar'] }, + trip_reminder: { priority: 4, tags: ['bell', 'alarm_clock'] }, + vacay_invite: { priority: 4, tags: ['palm_tree'] }, + photos_shared: { priority: 3, tags: ['camera'] }, + collab_message: { priority: 3, tags: ['speech_balloon'] }, + packing_tagged: { priority: 3, tags: ['luggage'] }, + version_available: { priority: 4, tags: ['package'] }, + synology_session_cleared: { priority: 3, tags: ['warning'] }, +}; +const NTFY_DEFAULT_META = { priority: 3 as const, tags: [] as string[] }; + +export function getUserNtfyConfig(userId: number): NtfyConfig | null { + const rows = db.prepare( + "SELECT key, value FROM settings WHERE user_id = ? AND key IN ('ntfy_topic', 'ntfy_server', 'ntfy_token')" + ).all(userId) as { key: string; value: string }[]; + if (rows.length === 0) return null; + const map: Record = {}; + for (const r of rows) map[r.key] = r.value; + return { + topic: map['ntfy_topic'] || null, + server: map['ntfy_server'] || null, + token: map['ntfy_token'] ? decrypt_api_key(map['ntfy_token']) : null, + }; +} + +export function getAdminNtfyConfig(): NtfyConfig { + const topic = getAppSetting('admin_ntfy_topic') || null; + const server = getAppSetting('admin_ntfy_server') || null; + const rawToken = getAppSetting('admin_ntfy_token') || null; + return { + topic, + server, + token: rawToken ? decrypt_api_key(rawToken) : null, + }; +} + +/** + * Resolve the ntfy POST URL from admin base config + user override. + * Returns null if topic cannot be determined. + */ +export function resolveNtfyUrl(adminCfg: NtfyConfig, userCfg: NtfyConfig | null): string | null { + const topic = userCfg?.topic || adminCfg.topic; + if (!topic) return null; + const base = (userCfg?.server || adminCfg.server || 'https://ntfy.sh').replace(/\/+$/, ''); + return `${base}/${encodeURIComponent(topic)}`; +} + +export function isNtfyConfiguredForUser(userId: number): boolean { + const cfg = getUserNtfyConfig(userId); + return !!(cfg?.topic); +} + +export function isNtfyConfiguredAdmin(): boolean { + return !!(getAppSetting('admin_ntfy_topic')); +} + +export async function sendNtfy( + url: string, + token: string | null, + payload: { event: string; title: string; body: string; link?: string }, +): Promise { + if (!url) return false; + + const ssrf = await checkSsrf(url); + if (!ssrf.allowed) { + logError(`Ntfy blocked by SSRF guard event=${payload.event} url=${url} reason=${ssrf.error}`); + return false; + } + + const meta = NTFY_EVENT_META[payload.event as NotifEventType] ?? NTFY_DEFAULT_META; + + // ntfy header-based API: POST to topic URL, body = plain text message, metadata in headers + const headers: Record = { + 'Title': payload.title, + 'Priority': String(meta.priority), + }; + if (meta.tags.length > 0) headers['Tags'] = meta.tags.join(','); + if (payload.link) headers['Click'] = payload.link; + if (token) headers['Authorization'] = `Bearer ${token}`; + + try { + const res = await fetch(url, { + method: 'POST', + headers, + body: payload.body, + signal: AbortSignal.timeout(10000), + dispatcher: createPinnedDispatcher(ssrf.resolvedIp!), + } as any); + + if (!res.ok) { + const errBody = await res.text().catch(() => ''); + logError(`Ntfy HTTP ${res.status}: ${errBody}`); + return false; + } + + logInfo(`Ntfy sent event=${payload.event}`); + logDebug(`Ntfy url=${url} priority=${meta.priority} tags=${meta.tags.join(',')}`); + return true; + } catch (err) { + logError(`Ntfy failed event=${payload.event}: ${err instanceof Error ? err.message : err}`); + return false; + } +} + +export async function testNtfy(cfg: { topic: string; server?: string | null; token?: string | null }): Promise<{ success: boolean; error?: string }> { + const adminCfg = getAdminNtfyConfig(); + const url = resolveNtfyUrl(adminCfg, { topic: cfg.topic, server: cfg.server ?? null, token: cfg.token ?? null }); + if (!url) return { success: false, error: 'Could not resolve ntfy URL — missing topic' }; + try { + const sent = await sendNtfy(url, cfg.token ?? null, { + event: 'test', + title: 'Test Notification', + body: 'This is a test notification from TREK. If you received this, your ntfy configuration is working correctly.', + }); + return sent ? { success: true } : { success: false, error: 'Failed to send ntfy notification' }; + } catch (err) { + return { success: false, error: err instanceof Error ? err.message : 'Unknown error' }; + } +} + diff --git a/server/src/services/settingsService.ts b/server/src/services/settingsService.ts index 12a0388e..e8b22af4 100644 --- a/server/src/services/settingsService.ts +++ b/server/src/services/settingsService.ts @@ -1,7 +1,7 @@ import { db } from '../db/database'; import { maybe_encrypt_api_key } from './apiKeyCrypto'; -const ENCRYPTED_SETTING_KEYS = new Set(['webhook_url']); +const ENCRYPTED_SETTING_KEYS = new Set(['webhook_url', 'ntfy_token']); export function getUserSettings(userId: number): Record { const rows = db.prepare('SELECT key, value FROM settings WHERE user_id = ?').all(userId) as { key: string; value: string }[]; diff --git a/server/tests/integration/notifications.test.ts b/server/tests/integration/notifications.test.ts index 28e38490..0eee9763 100644 --- a/server/tests/integration/notifications.test.ts +++ b/server/tests/integration/notifications.test.ts @@ -348,6 +348,43 @@ describe('Notification test endpoints', () => { expect(res.status).toBe(200); expect(res.body).toHaveProperty('success'); }); + + it('NOTIF-007 — POST /api/notifications/test-ntfy returns 400 when no topic configured', async () => { + const { user } = createUser(testDb); + + const res = await request(app) + .post('/api/notifications/test-ntfy') + .set('Cookie', authCookie(user.id)) + .send({}); + + expect(res.status).toBe(400); + expect(res.body).toHaveProperty('error'); + }); + + it('NOTIF-008 — POST /api/notifications/test-ntfy with explicit topic returns 200', async () => { + const { user } = createUser(testDb); + + const res = await request(app) + .post('/api/notifications/test-ntfy') + .set('Cookie', authCookie(user.id)) + .send({ topic: 'trek-integration-test-topic' }); + + expect(res.status).toBe(200); + expect(res.body).toHaveProperty('success'); + }); + + it('NOTIF-009 — POST /api/notifications/test-ntfy falls back to user saved topic', async () => { + const { user } = createUser(testDb); + testDb.prepare("INSERT OR REPLACE INTO settings (user_id, key, value) VALUES (?, 'ntfy_topic', 'saved-user-topic')").run(user.id); + + const res = await request(app) + .post('/api/notifications/test-ntfy') + .set('Cookie', authCookie(user.id)) + .send({}); + + expect(res.status).toBe(200); + expect(res.body).toHaveProperty('success'); + }); }); // ───────────────────────────────────────────────────────────────────────────── diff --git a/server/tests/unit/services/notificationPreferencesService.test.ts b/server/tests/unit/services/notificationPreferencesService.test.ts index 6126ff8d..3cca642f 100644 --- a/server/tests/unit/services/notificationPreferencesService.test.ts +++ b/server/tests/unit/services/notificationPreferencesService.test.ts @@ -153,14 +153,15 @@ describe('getPreferencesMatrix', () => { expect(available_channels.email).toBe(false); }); - it('NPREF-011 — implemented_combos maps version_available to [inapp, email, webhook]', () => { + it('NPREF-011 — implemented_combos maps version_available to [inapp, email, webhook, ntfy]', () => { const { user } = createAdmin(testDb); const { implemented_combos } = getPreferencesMatrix(user.id, 'admin', 'admin'); - expect(implemented_combos['version_available']).toEqual(['inapp', 'email', 'webhook']); - // All events now support all three channels + expect(implemented_combos['version_available']).toEqual(['inapp', 'email', 'webhook', 'ntfy']); + // All events now support all four channels expect(implemented_combos['trip_invite']).toContain('inapp'); expect(implemented_combos['trip_invite']).toContain('email'); expect(implemented_combos['trip_invite']).toContain('webhook'); + expect(implemented_combos['trip_invite']).toContain('ntfy'); }); }); diff --git a/server/tests/unit/services/notificationService.test.ts b/server/tests/unit/services/notificationService.test.ts index 3f9eeba2..bbef6ef6 100644 --- a/server/tests/unit/services/notificationService.test.ts +++ b/server/tests/unit/services/notificationService.test.ts @@ -458,3 +458,72 @@ describe('send() — channel failure resilience', () => { expect(countAllNotifications()).toBe(1); }); }); + +// ── Ntfy dispatch ───────────────────────────────────────────────────────────── + +function setUserNtfyTopic(userId: number, topic = 'my-trek-topic'): void { + testDb.prepare("INSERT OR REPLACE INTO settings (user_id, key, value) VALUES (?, 'ntfy_topic', ?)").run(userId, topic); +} + +function setAdminNtfyTopic(topic = 'trek-admin-alerts'): void { + setAppSetting(testDb, 'admin_ntfy_topic', topic); +} + +describe('send() — ntfy channel dispatch', () => { + beforeEach(() => { + fetchMock.mockResolvedValue({ ok: true, text: async () => '' }); + }); + + it('NTFY-SVCB-001 — ntfy fires when channel active and user has topic configured', async () => { + const { user } = createUser(testDb); + setUserNtfyTopic(user.id); + setNotificationChannels(testDb, 'ntfy'); + const tripId = (testDb.prepare('INSERT INTO trips (title, user_id) VALUES (?, ?)').run('Tokyo', user.id)).lastInsertRowid as number; + + await send({ event: 'trip_invite', actorId: null, scope: 'user', targetId: user.id, params: { trip: 'Tokyo', actor: 'Alice', invitee: 'Bob', tripId: String(tripId) } }); + + const ntfyCalls = fetchMock.mock.calls.filter(([url]: [string]) => url.includes('ntfy.sh')); + expect(ntfyCalls.length).toBeGreaterThan(0); + // Header-based API: metadata in headers, body = plain text + expect(ntfyCalls[0][1].headers['Priority']).toBe('4'); // trip_invite = high priority + expect(ntfyCalls[0][1].headers['Tags']).toContain('loudspeaker'); + }); + + it('NTFY-SVCB-002 — ntfy skips when channel not in active channels', async () => { + const { user } = createUser(testDb); + setUserNtfyTopic(user.id); + setNotificationChannels(testDb, 'none'); + + fetchMock.mockClear(); + await send({ event: 'trip_invite', actorId: null, scope: 'user', targetId: user.id, params: { trip: 'Paris', actor: 'Alice', invitee: 'Bob', tripId: '1' } }); + + const ntfyCalls = fetchMock.mock.calls.filter(([url]: [string]) => url.includes('ntfy.sh')); + expect(ntfyCalls.length).toBe(0); + }); + + it('NTFY-SVCB-003 — ntfy skips when user has no topic configured', async () => { + const { user } = createUser(testDb); + setNotificationChannels(testDb, 'ntfy'); + // No ntfy_topic set, but no admin_ntfy_server either — resolveNtfyUrl returns null + + fetchMock.mockClear(); + await send({ event: 'trip_invite', actorId: null, scope: 'user', targetId: user.id, params: { trip: 'Rome', actor: 'Alice', invitee: 'Bob', tripId: '1' } }); + + const ntfyCalls = fetchMock.mock.calls.filter(([url]: [string]) => url.includes('ntfy.sh')); + expect(ntfyCalls.length).toBe(0); + }); + + it('NTFY-SVCB-004 — admin-scoped version_available fires admin ntfy topic', async () => { + createAdmin(testDb); + setAdminNtfyTopic(); + setNotificationChannels(testDb, 'none'); + + fetchMock.mockClear(); + await send({ event: 'version_available', actorId: null, scope: 'admin', targetId: 0, params: { version: '3.0.0' } }); + + const ntfyCalls = fetchMock.mock.calls.filter(([url]: [string]) => url.includes('ntfy.sh')); + expect(ntfyCalls.length).toBeGreaterThan(0); + expect(ntfyCalls[0][1].headers['Priority']).toBe('4'); // version_available = high priority + expect(ntfyCalls[0][1].headers['Tags']).toContain('package'); + }); +}); diff --git a/server/tests/unit/services/notifications.test.ts b/server/tests/unit/services/notifications.test.ts index acf7c3d4..6744dd62 100644 --- a/server/tests/unit/services/notifications.test.ts +++ b/server/tests/unit/services/notifications.test.ts @@ -24,7 +24,7 @@ vi.mock('../../../src/utils/ssrfGuard', () => ({ createPinnedDispatcher: vi.fn(() => ({})), })); -import { getEventText, buildEmailHtml, buildWebhookBody, sendWebhook } from '../../../src/services/notifications'; +import { getEventText, buildEmailHtml, buildWebhookBody, sendWebhook, sendNtfy, resolveNtfyUrl, type NtfyConfig } from '../../../src/services/notifications'; import { checkSsrf } from '../../../src/utils/ssrfGuard'; import { logError } from '../../../src/services/auditLog'; @@ -319,3 +319,140 @@ describe('sendWebhook SSRF protection (SEC-017)', () => { }); afterAll(() => vi.unstubAllGlobals()); + +// ── resolveNtfyUrl ──────────────────────────────────────────────────────────── + +describe('resolveNtfyUrl', () => { + const adminCfg: NtfyConfig = { server: 'https://ntfy.sh', topic: 'admin-topic', token: null }; + + it('uses admin server + admin topic when no user config', () => { + expect(resolveNtfyUrl(adminCfg, null)).toBe('https://ntfy.sh/admin-topic'); + }); + + it('uses user topic over admin topic', () => { + const user: NtfyConfig = { server: null, topic: 'my-topic', token: null }; + expect(resolveNtfyUrl(adminCfg, user)).toBe('https://ntfy.sh/my-topic'); + }); + + it('uses user server override', () => { + const user: NtfyConfig = { server: 'https://ntfy.example.com', topic: 'my-topic', token: null }; + expect(resolveNtfyUrl(adminCfg, user)).toBe('https://ntfy.example.com/my-topic'); + }); + + it('strips trailing slash from server', () => { + const admin: NtfyConfig = { server: 'https://ntfy.sh/', topic: 'alerts', token: null }; + expect(resolveNtfyUrl(admin, null)).toBe('https://ntfy.sh/alerts'); + }); + + it('returns null when no topic in admin or user config', () => { + const noTopic: NtfyConfig = { server: 'https://ntfy.sh', topic: null, token: null }; + expect(resolveNtfyUrl(noTopic, null)).toBeNull(); + }); + + it('falls back to https://ntfy.sh when no server configured', () => { + const noServer: NtfyConfig = { server: null, topic: 'my-topic', token: null }; + expect(resolveNtfyUrl(noServer, null)).toBe('https://ntfy.sh/my-topic'); + }); +}); + +// ── sendNtfy ───────────────────────────────────────────────────────────────── + +describe('sendNtfy', () => { + const ntfyUrl = 'https://ntfy.sh/trek-test'; + const payload = { event: 'trip_invite', title: 'Test Title', body: 'Test body' }; + + beforeEach(() => { + vi.mocked(logError).mockClear(); + (globalThis.fetch as ReturnType).mockClear(); + vi.mocked(checkSsrf).mockResolvedValue({ allowed: true, isPrivate: false, resolvedIp: '1.2.3.4' }); + }); + + it('NTFY-001 — sends POST to topic URL with plain text body and metadata in headers', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never); + + const result = await sendNtfy(ntfyUrl, null, payload); + expect(result).toBe(true); + expect(mockFetch).toHaveBeenCalledOnce(); + + const [calledUrl, calledOpts] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(ntfyUrl); + // Body should be plain text, not JSON + expect(calledOpts.body).toBe('Test body'); + // Title, Priority, Tags go in headers + expect(calledOpts.headers['Title']).toBe('Test Title'); + expect(calledOpts.headers['Priority']).toBe('4'); // trip_invite maps to priority 4 + expect(calledOpts.headers['Tags']).toContain('loudspeaker'); + }); + + it('NTFY-002 — attaches Bearer token when token provided', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never); + + await sendNtfy(ntfyUrl, 'my-secret-token', payload); + + const [, calledOpts] = mockFetch.mock.calls[0]; + expect(calledOpts.headers['Authorization']).toBe('Bearer my-secret-token'); + }); + + it('NTFY-003 — no Authorization header when token is null', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never); + + await sendNtfy(ntfyUrl, null, payload); + + const [, calledOpts] = mockFetch.mock.calls[0]; + expect(calledOpts.headers['Authorization']).toBeUndefined(); + }); + + it('NTFY-004 — includes Click header when link is provided', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never); + + await sendNtfy(ntfyUrl, null, { ...payload, link: 'https://trek.example.com/trips/5' }); + + const [, calledOpts] = mockFetch.mock.calls[0]; + expect(calledOpts.headers['Click']).toBe('https://trek.example.com/trips/5'); + }); + + it('NTFY-005 — SSRF guard blocks private URL and returns false', async () => { + vi.mocked(checkSsrf).mockResolvedValueOnce({ + allowed: false, isPrivate: true, resolvedIp: '192.168.1.1', + error: 'Requests to private/internal network addresses are not allowed', + }); + + const result = await sendNtfy('http://192.168.1.1/ntfy', null, payload); + expect(result).toBe(false); + expect(vi.mocked(logError)).toHaveBeenCalledWith(expect.stringContaining('SSRF')); + expect(globalThis.fetch as ReturnType).not.toHaveBeenCalled(); + }); + + it('NTFY-006 — HTTP non-2xx response returns false and logs error', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockResolvedValueOnce({ ok: false, status: 403, text: async () => 'Forbidden' } as never); + + const result = await sendNtfy(ntfyUrl, null, payload); + expect(result).toBe(false); + expect(vi.mocked(logError)).toHaveBeenCalledWith(expect.stringContaining('403')); + }); + + it('NTFY-007 — network error returns false', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockRejectedValueOnce(new Error('Network failure')); + + const result = await sendNtfy(ntfyUrl, null, payload); + expect(result).toBe(false); + expect(vi.mocked(logError)).toHaveBeenCalledWith(expect.stringContaining('Network failure')); + }); + + it('NTFY-008 — unknown event falls back to priority 3 and no Tags header', async () => { + const mockFetch = globalThis.fetch as unknown as ReturnType; + mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never); + + await sendNtfy(ntfyUrl, null, { event: 'unknown_event', title: 'T', body: 'B' }); + + const [, calledOpts] = mockFetch.mock.calls[0]; + expect(calledOpts.headers['Priority']).toBe('3'); + expect(calledOpts.headers['Tags']).toBeUndefined(); // empty tags = no header + }); +}); From 7c4ac70db34fcb5edc39e888269af92013261df8 Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 15 Apr 2026 14:08:04 +0200 Subject: [PATCH 2/2] feat(i18n): translate ntfy notification strings into 14 languages Properly translate all ntfy-related UI strings added in the previous commit for ar, br, cs, de, es, fr, hu, id, it, nl, pl, ru, zh, zhTw. Product name 'Ntfy' and placeholder values kept as-is. --- client/src/i18n/translations/ar.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/br.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/cs.ts | 46 +++++++++++++------------- client/src/i18n/translations/de.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/es.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/fr.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/hu.ts | 46 +++++++++++++------------- client/src/i18n/translations/id.ts | 46 +++++++++++++------------- client/src/i18n/translations/it.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/nl.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/pl.ts | 46 +++++++++++++------------- client/src/i18n/translations/ru.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/zh.ts | 48 ++++++++++++++-------------- client/src/i18n/translations/zhTw.ts | 48 ++++++++++++++-------------- 14 files changed, 332 insertions(+), 332 deletions(-) diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts index b422c6bd..271cc672 100644 --- a/client/src/i18n/translations/ar.ts +++ b/client/src/i18n/translations/ar.ts @@ -1813,19 +1813,19 @@ const ar: Record = { 'settings.webhookUrl.test': 'اختبار', 'settings.webhookUrl.testSuccess': 'تم إرسال Webhook الاختباري بنجاح', 'settings.webhookUrl.testFailed': 'فشل إرسال Webhook الاختباري', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'موضوع Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'عنوان URL خادم Ntfy (اختياري)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'أدخل موضوع Ntfy الخاص بك لتلقي الإشعارات الفورية. اترك حقل الخادم فارغاً لاستخدام الإعداد الافتراضي الذي حدده المسؤول.', + 'settings.ntfyUrl.tokenLabel': 'رمز الوصول (اختياري)', + 'settings.ntfyUrl.tokenHint': 'مطلوب للمواضيع المحمية بكلمة مرور.', + 'settings.ntfyUrl.saved': 'تم حفظ إعدادات Ntfy', + 'settings.ntfyUrl.test': 'اختبار', + 'settings.ntfyUrl.testSuccess': 'تم إرسال إشعار Ntfy التجريبي بنجاح', + 'settings.ntfyUrl.testFailed': 'فشل إشعار Ntfy التجريبي', + 'settings.ntfyUrl.clearToken': 'مسح', + 'settings.ntfyUrl.tokenCleared': 'تم مسح رمز الوصول', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1841,21 +1841,21 @@ const ar: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'فشل إرسال Webhook الاختباري', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'يُرسل Webhook المسؤول تلقائيًا عند تعيين رابط URL', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'إرسال Ntfy تجريبي', + 'admin.notifications.testNtfySuccess': 'تم إرسال Ntfy التجريبي بنجاح', + 'admin.notifications.testNtfyFailed': 'فشل إرسال Ntfy التجريبي', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy المسؤول', + 'admin.notifications.adminNtfyPanel.hint': 'يُستخدم موضوع Ntfy هذا حصريًا لإشعارات المسؤول (مثل تنبيهات الإصدارات). وهو مستقل عن مواضيع المستخدمين ويُرسل دائمًا عند تهيئته.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'عنوان URL خادم Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'موضوع المسؤول', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'رمز الوصول (اختياري)', + 'admin.notifications.adminNtfyPanel.saved': 'تم حفظ إعدادات Ntfy للمسؤول', + 'admin.notifications.adminNtfyPanel.test': 'إرسال Ntfy تجريبي', + 'admin.notifications.adminNtfyPanel.testSuccess': 'تم إرسال Ntfy التجريبي بنجاح', + 'admin.notifications.adminNtfyPanel.testFailed': 'فشل إرسال Ntfy التجريبي', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'يُرسل Ntfy للمسؤول دائمًا عند تهيئة موضوع', 'admin.notifications.adminNotificationsHint': 'حدد القنوات التي تُسلّم إشعارات المسؤول (مثل تنبيهات الإصدارات). يُرسل الـ Webhook تلقائيًا عند تعيين رابط URL لـ Webhook المسؤول.', 'admin.tabs.notifications': 'الإشعارات', 'notifications.versionAvailable.title': 'تحديث متاح', diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts index ba320907..ef41ec53 100644 --- a/client/src/i18n/translations/br.ts +++ b/client/src/i18n/translations/br.ts @@ -1762,19 +1762,19 @@ const br: Record = { 'settings.webhookUrl.test': 'Testar', 'settings.webhookUrl.testSuccess': 'Webhook de teste enviado com sucesso', 'settings.webhookUrl.testFailed': 'Falha no webhook de teste', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Tópico Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL do servidor Ntfy (opcional)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Insira seu tópico Ntfy para receber notificações push. Deixe o servidor em branco para usar o padrão configurado pelo seu administrador.', + 'settings.ntfyUrl.tokenLabel': 'Token de acesso (opcional)', + 'settings.ntfyUrl.tokenHint': 'Necessário para tópicos protegidos por senha.', + 'settings.ntfyUrl.saved': 'Configurações do Ntfy salvas', + 'settings.ntfyUrl.test': 'Testar', + 'settings.ntfyUrl.testSuccess': 'Notificação de teste do Ntfy enviada com sucesso', + 'settings.ntfyUrl.testFailed': 'Falha na notificação de teste do Ntfy', + 'settings.ntfyUrl.clearToken': 'Limpar', + 'settings.ntfyUrl.tokenCleared': 'Token de acesso removido', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1790,21 +1790,21 @@ const br: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Falha no webhook de teste', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'O webhook de admin dispara automaticamente quando uma URL está configurada', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Enviar Ntfy de teste', + 'admin.notifications.testNtfySuccess': 'Ntfy de teste enviado com sucesso', + 'admin.notifications.testNtfyFailed': 'Falha ao enviar Ntfy de teste', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy de admin', + 'admin.notifications.adminNtfyPanel.hint': 'Este tópico Ntfy é usado exclusivamente para notificações de admin (ex. alertas de versão). É independente dos tópicos por usuário e sempre dispara quando configurado.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL do servidor Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Tópico de admin', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token de acesso (opcional)', + 'admin.notifications.adminNtfyPanel.saved': 'Configurações de Ntfy de admin salvas', + 'admin.notifications.adminNtfyPanel.test': 'Enviar Ntfy de teste', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy de teste enviado com sucesso', + 'admin.notifications.adminNtfyPanel.testFailed': 'Falha ao enviar Ntfy de teste', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'O Ntfy de admin sempre dispara quando um tópico está configurado', 'admin.notifications.adminNotificationsHint': 'Configure quais canais entregam notificações de admin (ex. alertas de versão). O webhook dispara automaticamente se uma URL de webhook de admin estiver definida.', 'admin.tabs.notifications': 'Notificações', 'notifications.versionAvailable.title': 'Atualização disponível', diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts index 47d5dcee..e4ab10f9 100644 --- a/client/src/i18n/translations/cs.ts +++ b/client/src/i18n/translations/cs.ts @@ -1767,19 +1767,19 @@ const cs: Record = { 'settings.webhookUrl.test': 'Otestovat', 'settings.webhookUrl.testSuccess': 'Testovací webhook byl úspěšně odeslán', 'settings.webhookUrl.testFailed': 'Testovací webhook selhal', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Téma Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL serveru Ntfy (volitelné)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Zadejte své téma Ntfy pro příjem push notifikací. Pole serveru ponechte prázdné pro použití výchozího nastavení správce.', + 'settings.ntfyUrl.tokenLabel': 'Přístupový token (volitelné)', + 'settings.ntfyUrl.tokenHint': 'Vyžadováno pro témata chráněná heslem.', + 'settings.ntfyUrl.saved': 'Nastavení Ntfy uloženo', + 'settings.ntfyUrl.test': 'Otestovat', + 'settings.ntfyUrl.testSuccess': 'Testovací notifikace Ntfy byla úspěšně odeslána', + 'settings.ntfyUrl.testFailed': 'Testovací notifikace Ntfy selhala', + 'settings.ntfyUrl.clearToken': 'Vymazat', + 'settings.ntfyUrl.tokenCleared': 'Přístupový token byl vymazán', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1795,21 +1795,21 @@ const cs: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Testovací webhook selhal', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook odesílá automaticky, pokud je nastavena URL', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.testNtfy': 'Odeslat testovací Ntfy', + 'admin.notifications.testNtfySuccess': 'Testovací Ntfy bylo úspěšně odesláno', + 'admin.notifications.testNtfyFailed': 'Testovací Ntfy selhalo', 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.hint': 'Toto téma Ntfy se používá výhradně pro admin oznámení (např. upozornění na verze). Je nezávislé na tématech uživatelů a odesílá vždy, když je nakonfigurováno.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serveru Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Přístupový token (volitelné)', + 'admin.notifications.adminNtfyPanel.saved': 'Nastavení admin Ntfy uloženo', + 'admin.notifications.adminNtfyPanel.test': 'Odeslat testovací Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Testovací Ntfy bylo úspěšně odesláno', + 'admin.notifications.adminNtfyPanel.testFailed': 'Testovací Ntfy selhalo', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin Ntfy odesílá vždy, když je nakonfigurováno téma', 'admin.notifications.adminNotificationsHint': 'Nastavte, které kanály doručují admin oznámení (např. upozornění na verze). Webhook odesílá automaticky, pokud je nastavena URL admin webhooku.', 'admin.tabs.notifications': 'Oznámení', 'notifications.versionAvailable.title': 'Dostupná aktualizace', diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index 185577ed..78af3e2a 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -1770,19 +1770,19 @@ const de: Record = { 'settings.webhookUrl.test': 'Testen', 'settings.webhookUrl.testSuccess': 'Test-Webhook erfolgreich gesendet', 'settings.webhookUrl.testFailed': 'Test-Webhook fehlgeschlagen', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Ntfy-Thema', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'Ntfy-Server-URL (optional)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Gib dein Ntfy-Thema ein, um Push-Benachrichtigungen zu erhalten. Lasse das Server-Feld leer, um den vom Administrator konfigurierten Standard zu verwenden.', + 'settings.ntfyUrl.tokenLabel': 'Zugriffstoken (optional)', + 'settings.ntfyUrl.tokenHint': 'Erforderlich für passwortgeschützte Themen.', + 'settings.ntfyUrl.saved': 'Ntfy-Einstellungen gespeichert', + 'settings.ntfyUrl.test': 'Testen', + 'settings.ntfyUrl.testSuccess': 'Test-Ntfy-Benachrichtigung erfolgreich gesendet', + 'settings.ntfyUrl.testFailed': 'Test-Ntfy-Benachrichtigung fehlgeschlagen', + 'settings.ntfyUrl.clearToken': 'Löschen', + 'settings.ntfyUrl.tokenCleared': 'Zugriffstoken gelöscht', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1798,21 +1798,21 @@ const de: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Test-Webhook fehlgeschlagen', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin-Webhook sendet automatisch, wenn eine URL konfiguriert ist', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Test-Ntfy senden', + 'admin.notifications.testNtfySuccess': 'Test-Ntfy erfolgreich gesendet', + 'admin.notifications.testNtfyFailed': 'Test-Ntfy fehlgeschlagen', + 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'Dieses Ntfy-Thema wird ausschließlich für Admin-Benachrichtigungen verwendet (z. B. Versions-Updates). Es ist unabhängig von Benutzer-Themen und sendet immer, wenn es konfiguriert ist.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-Server-URL', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-Thema', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Zugriffstoken (optional)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin-Ntfy-Einstellungen gespeichert', + 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy senden', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test-Ntfy erfolgreich gesendet', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy fehlgeschlagen', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin-Ntfy sendet immer, wenn ein Thema konfiguriert ist', 'admin.notifications.adminNotificationsHint': 'Konfiguriere, welche Kanäle Admin-Benachrichtigungen liefern (z. B. Versions-Updates). Der Webhook sendet automatisch, wenn eine Admin-Webhook-URL gesetzt ist.', 'admin.tabs.notifications': 'Benachrichtigungen', 'notifications.versionAvailable.title': 'Update verfügbar', diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts index d7204fee..0b13fd92 100644 --- a/client/src/i18n/translations/es.ts +++ b/client/src/i18n/translations/es.ts @@ -1772,19 +1772,19 @@ const es: Record = { 'settings.webhookUrl.test': 'Probar', 'settings.webhookUrl.testSuccess': 'Webhook de prueba enviado correctamente', 'settings.webhookUrl.testFailed': 'Error al enviar el webhook de prueba', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Tema de Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL del servidor Ntfy (opcional)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Introduce tu tema de Ntfy para recibir notificaciones push. Deja el servidor en blanco para usar el predeterminado configurado por tu administrador.', + 'settings.ntfyUrl.tokenLabel': 'Token de acceso (opcional)', + 'settings.ntfyUrl.tokenHint': 'Requerido para temas protegidos con contraseña.', + 'settings.ntfyUrl.saved': 'Configuración de Ntfy guardada', + 'settings.ntfyUrl.test': 'Probar', + 'settings.ntfyUrl.testSuccess': 'Notificación de prueba de Ntfy enviada correctamente', + 'settings.ntfyUrl.testFailed': 'Error en la notificación de prueba de Ntfy', + 'settings.ntfyUrl.clearToken': 'Borrar', + 'settings.ntfyUrl.tokenCleared': 'Token de acceso eliminado', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1800,21 +1800,21 @@ const es: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Error al enviar el webhook de prueba', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'El webhook de admin se activa automáticamente si hay una URL configurada', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Enviar Ntfy de prueba', + 'admin.notifications.testNtfySuccess': 'Ntfy de prueba enviado correctamente', + 'admin.notifications.testNtfyFailed': 'Error al enviar el Ntfy de prueba', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy de admin', + 'admin.notifications.adminNtfyPanel.hint': 'Este tema Ntfy se usa exclusivamente para notificaciones de admin (ej. alertas de versión). Es independiente de los temas por usuario y siempre se activa cuando está configurado.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL del servidor Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Tema de admin', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token de acceso (opcional)', + 'admin.notifications.adminNtfyPanel.saved': 'Configuración de Ntfy de admin guardada', + 'admin.notifications.adminNtfyPanel.test': 'Enviar Ntfy de prueba', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy de prueba enviado correctamente', + 'admin.notifications.adminNtfyPanel.testFailed': 'Error al enviar el Ntfy de prueba', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'El Ntfy de admin siempre se activa cuando hay un tema configurado', 'admin.notifications.adminNotificationsHint': 'Configura qué canales entregan notificaciones de admin (ej. alertas de versión). El webhook se activa automáticamente si hay una URL de webhook de admin configurada.', 'admin.tabs.notifications': 'Notificaciones', 'notifications.versionAvailable.title': 'Actualización disponible', diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts index 0b6095a3..afe7146e 100644 --- a/client/src/i18n/translations/fr.ts +++ b/client/src/i18n/translations/fr.ts @@ -1766,19 +1766,19 @@ const fr: Record = { 'settings.webhookUrl.test': 'Tester', 'settings.webhookUrl.testSuccess': 'Webhook de test envoyé avec succès', 'settings.webhookUrl.testFailed': 'Échec du webhook de test', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Sujet Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': "URL du serveur Ntfy (optionnel)", 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': "Entrez votre sujet Ntfy pour recevoir des notifications push. Laissez le serveur vide pour utiliser la valeur par défaut configurée par votre administrateur.", + 'settings.ntfyUrl.tokenLabel': "Jeton d'accès (optionnel)", + 'settings.ntfyUrl.tokenHint': 'Requis pour les sujets protégés par mot de passe.', + 'settings.ntfyUrl.saved': 'Paramètres Ntfy enregistrés', + 'settings.ntfyUrl.test': 'Tester', + 'settings.ntfyUrl.testSuccess': 'Notification de test Ntfy envoyée avec succès', + 'settings.ntfyUrl.testFailed': 'Échec de la notification de test Ntfy', + 'settings.ntfyUrl.clearToken': 'Effacer', + 'settings.ntfyUrl.tokenCleared': "Jeton d'accès effacé", 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1794,21 +1794,21 @@ const fr: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Échec du webhook de test', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Le webhook admin s\'active automatiquement si une URL est configurée', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Envoyer un Ntfy de test', + 'admin.notifications.testNtfySuccess': 'Ntfy de test envoyé avec succès', + 'admin.notifications.testNtfyFailed': 'Échec de l\'envoi du Ntfy de test', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin', + 'admin.notifications.adminNtfyPanel.hint': 'Ce sujet Ntfy est utilisé exclusivement pour les notifications admin (ex. alertes de version). Il est séparé des sujets par utilisateur et s\'active toujours lorsqu\'il est configuré.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL du serveur Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Sujet admin', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': "Jeton d'accès (optionnel)", + 'admin.notifications.adminNtfyPanel.saved': 'Paramètres Ntfy admin enregistrés', + 'admin.notifications.adminNtfyPanel.test': 'Envoyer un Ntfy de test', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy de test envoyé avec succès', + 'admin.notifications.adminNtfyPanel.testFailed': 'Échec de l\'envoi du Ntfy de test', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Le Ntfy admin s\'active toujours lorsqu\'un sujet est configuré', 'admin.notifications.adminNotificationsHint': 'Configurez quels canaux envoient les notifications admin (ex. alertes de version). Le webhook s\'active automatiquement si une URL webhook admin est définie.', 'admin.tabs.notifications': 'Notifications', 'notifications.versionAvailable.title': 'Mise à jour disponible', diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts index 1e7185cd..93dce675 100644 --- a/client/src/i18n/translations/hu.ts +++ b/client/src/i18n/translations/hu.ts @@ -1764,19 +1764,19 @@ const hu: Record = { 'settings.webhookUrl.test': 'Teszt', 'settings.webhookUrl.testSuccess': 'Teszt webhook sikeresen elküldve', 'settings.webhookUrl.testFailed': 'Teszt webhook sikertelen', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Ntfy téma', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'Ntfy szerver URL (opcionális)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Add meg az Ntfy témádat push értesítések fogadásához. Hagyd üresen a szervert a rendszergazda által beállított alapértelmezett használatához.', + 'settings.ntfyUrl.tokenLabel': 'Hozzáférési token (opcionális)', + 'settings.ntfyUrl.tokenHint': 'Jelszóval védett témákhoz szükséges.', + 'settings.ntfyUrl.saved': 'Ntfy beállítások mentve', + 'settings.ntfyUrl.test': 'Teszt', + 'settings.ntfyUrl.testSuccess': 'Teszt Ntfy értesítés sikeresen elküldve', + 'settings.ntfyUrl.testFailed': 'Teszt Ntfy értesítés sikertelen', + 'settings.ntfyUrl.clearToken': 'Törlés', + 'settings.ntfyUrl.tokenCleared': 'Hozzáférési token törölve', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1792,21 +1792,21 @@ const hu: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Teszt webhook sikertelen', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Az admin webhook automatikusan küld, ha URL van beállítva', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.testNtfy': 'Teszt Ntfy küldése', + 'admin.notifications.testNtfySuccess': 'Teszt Ntfy sikeresen elküldve', + 'admin.notifications.testNtfyFailed': 'Teszt Ntfy sikertelen', 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.hint': 'Ez az Ntfy téma kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói témáktól, és mindig küld, ha konfigurálva van.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy szerver URL', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Hozzáférési token (opcionális)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin Ntfy beállítások mentve', + 'admin.notifications.adminNtfyPanel.test': 'Teszt Ntfy küldése', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Teszt Ntfy sikeresen elküldve', + 'admin.notifications.adminNtfyPanel.testFailed': 'Teszt Ntfy sikertelen', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Az admin Ntfy mindig küld, ha egy téma konfigurálva van', 'admin.notifications.adminNotificationsHint': 'Állítsa be, hogy mely csatornák szállítsák az admin értesítéseket (pl. verziófrissítési figyelmeztetések). A webhook automatikusan küld, ha admin webhook URL van megadva.', 'admin.tabs.notifications': 'Értesítések', 'notifications.versionAvailable.title': 'Elérhető frissítés', diff --git a/client/src/i18n/translations/id.ts b/client/src/i18n/translations/id.ts index 5c577492..03eff31d 100644 --- a/client/src/i18n/translations/id.ts +++ b/client/src/i18n/translations/id.ts @@ -198,19 +198,19 @@ const id: Record = { 'settings.webhookUrl.test': 'Uji', 'settings.webhookUrl.testSuccess': 'Test webhook berhasil dikirim', 'settings.webhookUrl.testFailed': 'Test webhook gagal', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Topik Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL Server Ntfy (opsional)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Masukkan topik Ntfy Anda untuk menerima notifikasi push. Kosongkan bidang server untuk menggunakan default yang dikonfigurasi oleh admin Anda.', + 'settings.ntfyUrl.tokenLabel': 'Token Akses (opsional)', + 'settings.ntfyUrl.tokenHint': 'Diperlukan untuk topik yang dilindungi kata sandi.', + 'settings.ntfyUrl.saved': 'Pengaturan Ntfy tersimpan', + 'settings.ntfyUrl.test': 'Uji', + 'settings.ntfyUrl.testSuccess': 'Notifikasi uji Ntfy berhasil dikirim', + 'settings.ntfyUrl.testFailed': 'Notifikasi uji Ntfy gagal', + 'settings.ntfyUrl.clearToken': 'Hapus', + 'settings.ntfyUrl.tokenCleared': 'Token akses dihapus', 'admin.notifications.title': 'Notifikasi', 'admin.notifications.hint': 'Pilih satu saluran notifikasi. Hanya satu yang bisa aktif sekaligus.', 'admin.notifications.none': 'Dinonaktifkan', @@ -232,21 +232,21 @@ const id: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook gagal', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook selalu berjalan jika URL dikonfigurasi', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.testNtfy': 'Kirim uji Ntfy', + 'admin.notifications.testNtfySuccess': 'Uji Ntfy berhasil dikirim', + 'admin.notifications.testNtfyFailed': 'Uji Ntfy gagal', 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.hint': 'Topik Ntfy ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari topik per pengguna dan selalu berjalan jika dikonfigurasi.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL Server Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Topik Admin', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token Akses (opsional)', + 'admin.notifications.adminNtfyPanel.saved': 'Pengaturan Ntfy admin tersimpan', + 'admin.notifications.adminNtfyPanel.test': 'Kirim uji Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Uji Ntfy berhasil dikirim', + 'admin.notifications.adminNtfyPanel.testFailed': 'Uji Ntfy gagal', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin Ntfy selalu berjalan jika topik dikonfigurasi', 'admin.notifications.adminNotificationsHint': 'Atur saluran mana yang mengirimkan notifikasi khusus admin (mis. peringatan versi).', 'admin.smtp.title': 'Email & Notifikasi', 'admin.smtp.hint': 'Konfigurasi SMTP untuk pengiriman notifikasi email.', diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts index f395709a..7da4fea7 100644 --- a/client/src/i18n/translations/it.ts +++ b/client/src/i18n/translations/it.ts @@ -1767,19 +1767,19 @@ const it: Record = { 'settings.webhookUrl.test': 'Test', 'settings.webhookUrl.testSuccess': 'Webhook di test inviato con successo', 'settings.webhookUrl.testFailed': 'Invio webhook di test fallito', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Argomento Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL server Ntfy (opzionale)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': "Inserisci il tuo argomento Ntfy per ricevere notifiche push. Lascia il server vuoto per usare il valore predefinito configurato dall'amministratore.", + 'settings.ntfyUrl.tokenLabel': 'Token di accesso (opzionale)', + 'settings.ntfyUrl.tokenHint': 'Richiesto per gli argomenti protetti da password.', + 'settings.ntfyUrl.saved': 'Impostazioni Ntfy salvate', + 'settings.ntfyUrl.test': 'Testa', + 'settings.ntfyUrl.testSuccess': 'Notifica di test Ntfy inviata con successo', + 'settings.ntfyUrl.testFailed': 'Notifica di test Ntfy fallita', + 'settings.ntfyUrl.clearToken': 'Cancella', + 'settings.ntfyUrl.tokenCleared': 'Token di accesso rimosso', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1795,21 +1795,21 @@ const it: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Invio webhook di test fallito', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Il webhook admin si attiva automaticamente quando è configurato un URL', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Invia Ntfy di test', + 'admin.notifications.testNtfySuccess': 'Ntfy di test inviato con successo', + 'admin.notifications.testNtfyFailed': 'Invio Ntfy di test fallito', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin', + 'admin.notifications.adminNtfyPanel.hint': 'Questo argomento Ntfy viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dagli argomenti per utente e si attiva sempre quando è configurato.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL server Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Argomento admin', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token di accesso (opzionale)', + 'admin.notifications.adminNtfyPanel.saved': 'Impostazioni Ntfy admin salvate', + 'admin.notifications.adminNtfyPanel.test': 'Invia Ntfy di test', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy di test inviato con successo', + 'admin.notifications.adminNtfyPanel.testFailed': 'Invio Ntfy di test fallito', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Il Ntfy admin si attiva sempre quando un argomento è configurato', 'admin.notifications.adminNotificationsHint': 'Configura quali canali consegnano le notifiche admin (es. avvisi di versione). Il webhook si attiva automaticamente se è impostato un URL webhook admin.', 'admin.tabs.notifications': 'Notifiche', 'notifications.versionAvailable.title': 'Aggiornamento disponibile', diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts index 40cd4762..5a9e83ba 100644 --- a/client/src/i18n/translations/nl.ts +++ b/client/src/i18n/translations/nl.ts @@ -1766,19 +1766,19 @@ const nl: Record = { 'settings.webhookUrl.test': 'Testen', 'settings.webhookUrl.testSuccess': 'Test-webhook succesvol verzonden', 'settings.webhookUrl.testFailed': 'Test-webhook mislukt', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Ntfy-onderwerp', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'Ntfy-server-URL (optioneel)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Voer je Ntfy-onderwerp in om pushmeldingen te ontvangen. Laat het serverveld leeg om de standaard te gebruiken die door je beheerder is ingesteld.', + 'settings.ntfyUrl.tokenLabel': 'Toegangstoken (optioneel)', + 'settings.ntfyUrl.tokenHint': 'Vereist voor onderwerpen die met een wachtwoord zijn beveiligd.', + 'settings.ntfyUrl.saved': 'Ntfy-instellingen opgeslagen', + 'settings.ntfyUrl.test': 'Testen', + 'settings.ntfyUrl.testSuccess': 'Test-Ntfy-melding succesvol verzonden', + 'settings.ntfyUrl.testFailed': 'Test-Ntfy-melding mislukt', + 'settings.ntfyUrl.clearToken': 'Wissen', + 'settings.ntfyUrl.tokenCleared': 'Toegangstoken gewist', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1794,21 +1794,21 @@ const nl: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Test-webhook mislukt', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin-webhook verstuurt automatisch als er een URL is ingesteld', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Test-Ntfy verzenden', + 'admin.notifications.testNtfySuccess': 'Test-Ntfy succesvol verzonden', + 'admin.notifications.testNtfyFailed': 'Test-Ntfy mislukt', + 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy', + 'admin.notifications.adminNtfyPanel.hint': 'Dit Ntfy-onderwerp wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Het staat los van onderwerpen per gebruiker en verstuurt altijd wanneer het geconfigureerd is.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-server-URL', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-onderwerp', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Toegangstoken (optioneel)', + 'admin.notifications.adminNtfyPanel.saved': 'Admin-Ntfy-instellingen opgeslagen', + 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy verzenden', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Test-Ntfy succesvol verzonden', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy mislukt', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin-Ntfy verstuurt altijd wanneer een onderwerp is geconfigureerd', 'admin.notifications.adminNotificationsHint': 'Stel in via welke kanalen admin-meldingen worden bezorgd (bijv. versie-updates). De webhook verstuurt automatisch als er een admin-webhook-URL is ingesteld.', 'admin.tabs.notifications': 'Meldingen', 'notifications.versionAvailable.title': 'Update beschikbaar', diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts index 4e7ce40d..7f264f55 100644 --- a/client/src/i18n/translations/pl.ts +++ b/client/src/i18n/translations/pl.ts @@ -1597,21 +1597,21 @@ const pl: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Wysyłanie testowego webhooka nie powiodło się', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Webhook admina wysyła automatycznie, gdy URL jest skonfigurowany', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.testNtfy': 'Wyślij testowe Ntfy', + 'admin.notifications.testNtfySuccess': 'Testowe Ntfy wysłane pomyślnie', + 'admin.notifications.testNtfyFailed': 'Wysyłanie testowego Ntfy nie powiodło się', 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.hint': 'Ten temat Ntfy jest używany wyłącznie do powiadomień admina (np. alertów o wersjach). Jest niezależny od tematów użytkowników i zawsze wysyła po skonfigurowaniu.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serwera Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Temat admina', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token dostępu (opcjonalne)', + 'admin.notifications.adminNtfyPanel.saved': 'Ustawienia admin Ntfy zapisane', + 'admin.notifications.adminNtfyPanel.test': 'Wyślij testowe Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Testowe Ntfy wysłane pomyślnie', + 'admin.notifications.adminNtfyPanel.testFailed': 'Wysyłanie testowego Ntfy nie powiodło się', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin Ntfy zawsze wysyła po skonfigurowaniu tematu', 'admin.notifications.adminNotificationsHint': 'Skonfiguruj, które kanały dostarczają powiadomienia admina (np. alerty o wersjach). Webhook wysyła automatycznie, gdy ustawiony jest URL webhooka admina.', 'admin.webhook.hint': 'Pozwól użytkownikom konfigurować własne adresy URL webhooka dla powiadomień (Discord, Slack itp.).', 'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.', @@ -1623,19 +1623,19 @@ const pl: Record = { 'settings.webhookUrl.test': 'Testuj', 'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie', 'settings.webhookUrl.testFailed': 'Wysyłanie testowego webhooka nie powiodło się', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Temat Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL serwera Ntfy (opcjonalne)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Wprowadź swój temat Ntfy, aby otrzymywać powiadomienia push. Pozostaw pole serwera puste, aby użyć domyślnego ustawienia skonfigurowanego przez administratora.', + 'settings.ntfyUrl.tokenLabel': 'Token dostępu (opcjonalne)', + 'settings.ntfyUrl.tokenHint': 'Wymagane dla tematów chronionych hasłem.', + 'settings.ntfyUrl.saved': 'Ustawienia Ntfy zapisane', + 'settings.ntfyUrl.test': 'Testuj', + 'settings.ntfyUrl.testSuccess': 'Testowe powiadomienie Ntfy wysłane pomyślnie', + 'settings.ntfyUrl.testFailed': 'Testowe powiadomienie Ntfy nie powiodło się', + 'settings.ntfyUrl.clearToken': 'Wyczyść', + 'settings.ntfyUrl.tokenCleared': 'Token dostępu wyczyszczony', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts index 8e0ff013..63cb68ce 100644 --- a/client/src/i18n/translations/ru.ts +++ b/client/src/i18n/translations/ru.ts @@ -1763,19 +1763,19 @@ const ru: Record = { 'settings.webhookUrl.test': 'Тест', 'settings.webhookUrl.testSuccess': 'Тестовый вебхук успешно отправлен', 'settings.webhookUrl.testFailed': 'Ошибка тестового вебхука', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Тема Ntfy', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'URL сервера Ntfy (необязательно)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': 'Введите тему Ntfy для получения push-уведомлений. Оставьте поле сервера пустым, чтобы использовать настройку по умолчанию, заданную администратором.', + 'settings.ntfyUrl.tokenLabel': 'Токен доступа (необязательно)', + 'settings.ntfyUrl.tokenHint': 'Требуется для тем, защищённых паролем.', + 'settings.ntfyUrl.saved': 'Настройки Ntfy сохранены', + 'settings.ntfyUrl.test': 'Тест', + 'settings.ntfyUrl.testSuccess': 'Тестовое уведомление Ntfy успешно отправлено', + 'settings.ntfyUrl.testFailed': 'Ошибка отправки тестового уведомления Ntfy', + 'settings.ntfyUrl.clearToken': 'Очистить', + 'settings.ntfyUrl.tokenCleared': 'Токен доступа очищен', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1791,21 +1791,21 @@ const ru: Record = { 'admin.notifications.adminWebhookPanel.testFailed': 'Ошибка тестового вебхука', 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Вебхук администратора отправляется автоматически при наличии URL', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': 'Отправить тестовое Ntfy', + 'admin.notifications.testNtfySuccess': 'Тестовое Ntfy успешно отправлено', + 'admin.notifications.testNtfyFailed': 'Ошибка отправки тестового Ntfy', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy администратора', + 'admin.notifications.adminNtfyPanel.hint': 'Эта тема Ntfy используется исключительно для уведомлений администратора (например, оповещения о версиях). Она независима от тем пользователей и всегда отправляется при наличии настройки.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL сервера Ntfy', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Тема администратора', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Токен доступа (необязательно)', + 'admin.notifications.adminNtfyPanel.saved': 'Настройки Ntfy администратора сохранены', + 'admin.notifications.adminNtfyPanel.test': 'Отправить тестовое Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Тестовое Ntfy успешно отправлено', + 'admin.notifications.adminNtfyPanel.testFailed': 'Ошибка отправки тестового Ntfy', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Ntfy администратора всегда отправляется при наличии настроенной темы', 'admin.notifications.adminNotificationsHint': 'Настройте, какие каналы доставляют уведомления администратора (например, оповещения о версиях). Вебхук отправляется автоматически, если задан URL вебхука администратора.', 'admin.tabs.notifications': 'Уведомления', 'notifications.versionAvailable.title': 'Доступно обновление', diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts index e43d956c..809cf136 100644 --- a/client/src/i18n/translations/zh.ts +++ b/client/src/i18n/translations/zh.ts @@ -1763,19 +1763,19 @@ const zh: Record = { 'settings.webhookUrl.test': '测试', 'settings.webhookUrl.testSuccess': '测试 Webhook 发送成功', 'settings.webhookUrl.testFailed': '测试 Webhook 失败', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Ntfy 主题', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'Ntfy 服务器 URL(可选)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': '输入您的 Ntfy 主题以接收推送通知。将服务器留空以使用管理员配置的默认值。', + 'settings.ntfyUrl.tokenLabel': '访问令牌(可选)', + 'settings.ntfyUrl.tokenHint': '受密码保护的主题需要此项。', + 'settings.ntfyUrl.saved': 'Ntfy 设置已保存', + 'settings.ntfyUrl.test': '测试', + 'settings.ntfyUrl.testSuccess': '测试 Ntfy 通知发送成功', + 'settings.ntfyUrl.testFailed': '测试 Ntfy 通知失败', + 'settings.ntfyUrl.clearToken': '清除', + 'settings.ntfyUrl.tokenCleared': '访问令牌已清除', 'settings.notificationPreferences.inapp': 'In-App', 'settings.notificationPreferences.webhook': 'Webhook', 'settings.notificationPreferences.email': 'Email', @@ -1791,21 +1791,21 @@ const zh: Record = { 'admin.notifications.adminWebhookPanel.testFailed': '测试 Webhook 失败', 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 后管理员 Webhook 自动触发', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': '发送测试 Ntfy', + 'admin.notifications.testNtfySuccess': '测试 Ntfy 发送成功', + 'admin.notifications.testNtfyFailed': '测试 Ntfy 失败', + 'admin.notifications.adminNtfyPanel.title': '管理员 Ntfy', + 'admin.notifications.adminNtfyPanel.hint': '此 Ntfy 主题专用于管理员通知(如版本更新提醒)。它与每用户主题相互独立,配置后始终触发。', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 服务器 URL', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': '管理员主题', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': '访问令牌(可选)', + 'admin.notifications.adminNtfyPanel.saved': '管理员 Ntfy 设置已保存', + 'admin.notifications.adminNtfyPanel.test': '发送测试 Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': '测试 Ntfy 发送成功', + 'admin.notifications.adminNtfyPanel.testFailed': '测试 Ntfy 失败', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': '配置主题后管理员 Ntfy 始终触发', 'admin.notifications.adminNotificationsHint': '配置哪些渠道发送管理员通知(如版本更新提醒)。设置管理员 Webhook URL 后,Webhook 将自动触发。', 'admin.tabs.notifications': '通知', 'notifications.versionAvailable.title': '有可用更新', diff --git a/client/src/i18n/translations/zhTw.ts b/client/src/i18n/translations/zhTw.ts index 2637e0c8..bda7dc91 100644 --- a/client/src/i18n/translations/zhTw.ts +++ b/client/src/i18n/translations/zhTw.ts @@ -195,19 +195,19 @@ const zhTw: Record = { 'settings.webhookUrl.test': '測試', 'settings.webhookUrl.testSuccess': '測試 Webhook 傳送成功', 'settings.webhookUrl.testFailed': '測試 Webhook 傳送失敗', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicLabel': 'Ntfy 主題', 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverLabel': 'Ntfy 伺服器 URL(選填)', 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.clearToken': 'Clear', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.ntfyUrl.hint': '輸入您的 Ntfy 主題以接收推播通知。將伺服器留空以使用管理員設定的預設值。', + 'settings.ntfyUrl.tokenLabel': '存取權杖(選填)', + 'settings.ntfyUrl.tokenHint': '受密碼保護的主題需要此項目。', + 'settings.ntfyUrl.saved': 'Ntfy 設定已儲存', + 'settings.ntfyUrl.test': '測試', + 'settings.ntfyUrl.testSuccess': '測試 Ntfy 通知傳送成功', + 'settings.ntfyUrl.testFailed': '測試 Ntfy 通知失敗', + 'settings.ntfyUrl.clearToken': '清除', + 'settings.ntfyUrl.tokenCleared': '存取權杖已清除', 'settings.notificationsDisabled': '通知尚未配置。請聯絡管理員啟用電子郵件或 Webhook 通知。', 'settings.notificationsActive': '活躍頻道', 'settings.notificationsManagedByAdmin': '通知事件由管理員配置。', @@ -232,21 +232,21 @@ const zhTw: Record = { 'admin.notifications.adminWebhookPanel.testFailed': '測試 Webhook 傳送失敗', 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 後,管理員 Webhook 始終觸發', 'admin.notifications.ntfy': 'Ntfy', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.testNtfy': '傳送測試 Ntfy', + 'admin.notifications.testNtfySuccess': '測試 Ntfy 傳送成功', + 'admin.notifications.testNtfyFailed': '測試 Ntfy 失敗', + 'admin.notifications.adminNtfyPanel.title': '管理員 Ntfy', + 'admin.notifications.adminNtfyPanel.hint': '此 Ntfy 主題專用於管理員通知(例如版本提醒)。它與每位使用者的主題分開,設定後始終會觸發。', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 伺服器 URL', 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicLabel': '管理員主題', 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNtfyPanel.tokenLabel': '存取權杖(選填)', + 'admin.notifications.adminNtfyPanel.saved': '管理員 Ntfy 設定已儲存', + 'admin.notifications.adminNtfyPanel.test': '傳送測試 Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': '測試 Ntfy 傳送成功', + 'admin.notifications.adminNtfyPanel.testFailed': '測試 Ntfy 失敗', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': '設定主題後管理員 Ntfy 始終觸發', 'admin.notifications.adminNotificationsHint': '配置哪些渠道傳遞僅管理員通知(例如版本提醒)。', 'admin.smtp.title': '郵件與通知', 'admin.smtp.hint': '用於傳送電子郵件通知的 SMTP 配置。',