diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd1445d8..f8f5fcec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to Bambuddy will be documented in this file. - **AMS Info Card & Custom Labels** ([#570](https://github.com/maziggy/bambuddy/pull/570)) — Hovering an AMS label (e.g. "AMS-A") on the Printers page now shows a popover with serial number, firmware version, and an editable friendly name. Custom labels are stored by AMS serial number so they persist when the unit is moved to a different printer. Slot numbers are now displayed inside each filament color circle with auto-inverted contrast for readability. Labels also appear in the Inventory page's location column. Contributed by @cadtoolbox. ### Improved +- **i18n: Settings, Smart Plugs, Notifications, Backup/Restore** — Replaced all hardcoded English strings with translation keys (`t()` calls) across the Settings page, Smart Plug components (SmartPlugCard, AddSmartPlugModal, SwitchbarPopover), Notification components (NotificationProviderCard, AddNotificationModal, NotificationTemplateEditor, NotificationLogViewer), and Backup/Restore components (GitHubBackupSettings, RestoreModal). Added ~600 new translation keys to all 7 supported locales (en, de, ja, fr, it, pt-BR, zh-CN). Removed hardcoded label maps (`PROVIDER_LABELS`, `EVENT_LABELS`, `CATEGORY_LABELS`) in favor of dynamic translation key lookups with fallbacks. - **Install Script: Branch Selection** — The native install script (`install.sh`) now supports a `--branch` option and an interactive branch prompt (defaults to `main`). Previously the script hardcoded `origin/main`, so beta testers told to install from a beta branch would silently get the stable release instead. Fresh installs use `git clone --branch`, existing installs checkout and reset to the selected branch. The install summary highlights non-main branches in yellow with a "(beta)" label. Invalid branch names are caught early with an error message listing available branches. - **Print Queue Scheduler Diagnostics** ([#616](https://github.com/maziggy/bambuddy/issues/616)) — Added diagnostic logging to the print queue scheduler to help diagnose why queued prints aren't starting. After each queue check, the scheduler now logs a skip summary (how many items were skipped due to manual_start, scheduled_time, etc.) and for each busy printer, logs the exact state preventing it from being considered idle (connected status, printer state, plate_cleared flag). Previously the scheduler only logged "found N pending items" with no visibility into why items were skipped. diff --git a/frontend/src/components/AddNotificationModal.tsx b/frontend/src/components/AddNotificationModal.tsx index 8c5c3ad2b..b48c8e567 100644 --- a/frontend/src/components/AddNotificationModal.tsx +++ b/frontend/src/components/AddNotificationModal.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; import { X, Save, Loader2, Send, CheckCircle, XCircle } from 'lucide-react'; import { api } from '../api/client'; import type { NotificationProvider, NotificationProviderCreate, NotificationProviderUpdate, ProviderType } from '../api/client'; @@ -11,17 +12,10 @@ interface AddNotificationModalProps { onClose: () => void; } -const PROVIDER_OPTIONS: { value: ProviderType; label: string; description: string }[] = [ - { value: 'email', label: 'Email', description: 'SMTP email notifications' }, - { value: 'telegram', label: 'Telegram', description: 'Notifications via Telegram bot' }, - { value: 'discord', label: 'Discord', description: 'Send to Discord channel via webhook' }, - { value: 'ntfy', label: 'ntfy', description: 'Free, self-hostable push notifications' }, - { value: 'pushover', label: 'Pushover', description: 'Simple, reliable push notifications' }, - { value: 'callmebot', label: 'CallMeBot/WhatsApp', description: 'Free WhatsApp notifications via CallMeBot' }, - { value: 'webhook', label: 'Webhook', description: 'Generic HTTP POST to any URL' }, -]; +const PROVIDER_VALUES: ProviderType[] = ['email', 'telegram', 'discord', 'ntfy', 'pushover', 'callmebot', 'webhook']; export function AddNotificationModal({ provider, onClose }: AddNotificationModalProps) { + const { t } = useTranslation(); const queryClient = useQueryClient(); const isEditing = !!provider; @@ -112,7 +106,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal setError(null); if (!name.trim()) { - setError('Name is required'); + setError(t('notifications.nameRequired')); return; } @@ -120,7 +114,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal const requiredFields = getRequiredFields(providerType); for (const field of requiredFields) { if (!config[field.key]?.trim()) { - setError(`${field.label} is required`); + setError(t('notifications.fieldRequired', { field: field.label })); return; } } @@ -239,7 +233,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal {/* Header */}
- {PROVIDER_OPTIONS.find(o => o.value === providerType)?.description} + {t(`notifications.providerDescriptions.${providerType}`, '')}
Configuration
+{t('notifications.configuration')}
{configFields .filter((field) => !('showIf' in field) || (field as { showIf?: (cfg: Record- Only send notifications for events from this printer + {t('notifications.onlyFromPrinter')}
Batch notifications into a single daily summary
+ +{t('notifications.batchNotifications')}
- Events will be collected and sent as a single summary at this time + {t('notifications.digestCollected')}
Notification Events
+{t('notifications.notificationEvents')}
{/* Print Events */}Print Events
+{t('notifications.printEvents')}
Printer Status
+{t('notifications.printerStatus')}
Found {discoveredDevices.length} device(s) - click to select:
+{t('smartPlugs.foundDevices', { count: discoveredDevices.length })}
{debouncedSearch - ? `Searching all entities (${availableEntities.length} found)` - : `Showing switch, light, input_boolean (${availableEntities.length} available)`} + ? t('smartPlugs.searchingEntities', { count: availableEntities.length }) + : t('smartPlugs.showingEntities', { count: availableEntities.length })}
Energy Monitoring (Optional)
+{t('smartPlugs.energyMonitoringOptional')}
- Search and select sensors that provide power/energy data. + {t('smartPlugs.energyMonitoringHint')}
Monitor Only
+{t('smartPlugs.monitorOnly')}
- MQTT plugs receive power/energy data via MQTT subscription. On/off control is not available - use your MQTT broker or home automation system. + {t('smartPlugs.mqttMonitorOnlyDescription')}
Power Monitoring
+{t('smartPlugs.powerMonitoring')}
- JSON path extracts value from JSON payload (e.g., "power_l1"). Leave empty if topic publishes raw numeric values.
- Use multiplier 0.001 for mW→W, 1000 for kW→W.
+
+ {t('smartPlugs.mqttPowerHint')}
Energy Monitoring (optional)
+{t('smartPlugs.energyMonitoring')} ({t('smartPlugs.optional')})
- JSON path extracts value from JSON payload. Leave empty for raw values.
- Use multiplier 0.001 for Wh→kWh, 1000 for MWh→kWh.
+
+ {t('smartPlugs.mqttEnergyHint')}
State Monitoring (optional)
+{t('smartPlugs.stateMonitoring')} ({t('smartPlugs.optional')})
- JSON path extracts value from JSON payload. Leave empty for raw values.
- ON value: the exact string that means "ON". Leave empty for auto-detect (ON, true, 1).
+
+ {t('smartPlugs.mqttStateHint')}
Connected!
+{t('smartPlugs.connectedResult')}
- {testResult.device_name && `Device: ${testResult.device_name} - `} - State: {testResult.state} + {testResult.device_name && t('smartPlugs.deviceLabel', { name: testResult.device_name })} + {t('smartPlugs.stateLabel', { state: testResult.state })}
- Leave empty if your Tasmota device doesn't require authentication + {t('smartPlugs.authHint')}
> )} @@ -1182,13 +1179,13 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { {/* Link to Printer - not shown for MQTT plugs (monitor-only) */} {plugType !== 'mqtt' && (- Linking enables automatic on/off when prints start/complete + {t('smartPlugs.linkingDescription')}