diff --git a/CHANGELOG.md b/CHANGELOG.md
index a78635816..8ebcf9fe6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,11 @@ All notable changes to Bambuddy will be documented in this file.
- Link button correctly disabled when no unlinked spools are available in Spoolman
- Toast notification shown on successful/failed spool linking
- Added `/api/v1/spoolman/spools/linked` endpoint returning map of linked spool tags to IDs
+- **Complete German Translations**:
+ - All UI strings now fully translated to German (1800+ translation keys)
+ - Pages translated: Settings, Archives, File Manager, Queue, Printers, Profiles, Projects, Stats, Maintenance, Camera, Groups, Users, Login, Setup, Stream Overlay
+ - Components translated: ConfirmModal, LinkSpoolModal, FilamentHoverCard, Layout
+ - Added locale parity test to ensure English and German stay in sync
## [0.1.6.2] - 2026-02-02
diff --git a/frontend/src/__tests__/components/ConfirmModal.test.tsx b/frontend/src/__tests__/components/ConfirmModal.test.tsx
index 4f360753e..a8882a11c 100644
--- a/frontend/src/__tests__/components/ConfirmModal.test.tsx
+++ b/frontend/src/__tests__/components/ConfirmModal.test.tsx
@@ -131,7 +131,7 @@ describe('ConfirmModal', () => {
it('shows default loading text when loadingText not provided', () => {
render(
- The selected printer is not connected. Power it on to view K-profiles. + {t('kProfiles.printerOfflineDesc')}
- No profiles match your search criteria + {t('kProfiles.noMatchingProfilesDesc')}
- No pressure advance profiles found for {nozzleDiameter}mm nozzle + {t('kProfiles.noKProfilesDesc', { diameter: nozzleDiameter })}
This cannot be undone
+{t('kProfiles.bulkDelete.cannotUndo')}
- Are you sure you want to delete {selectedProfiles.size} selected profiles from the printer? + {t('kProfiles.bulkDelete.message', { count: selectedProfiles.size })}
{plateDetectionAlert.printer_name}
- Objects detected on build plate. The print has been automatically paused. - Please clear the plate and resume the print. + {t('plateAlert.message')}
Passwords do not match
+{t('changePassword.passwordsDoNotMatch')}
)}Spool UUID:
+{t('spoolman.spoolId')}:
{trayUuid}
- Select a Spoolman spool to link: + {t('spoolman.selectSpool')}:
{isLoading ? ( @@ -148,8 +150,7 @@ export function LinkSpoolModal({ isOpen, onClose, trayUuid, trayInfo }: LinkSpooNo unlinked spools found in Spoolman.
-All spools are already linked to AMS trays.
+{t('spoolman.noUnlinkedSpools')}
{printerName}
No messages logged yet
+{t('mqttDebug.noMessages')}
{!loggingEnabled && ( -Click "Start Logging" to begin capturing MQTT messages
+{t('mqttDebug.startLoggingHint')}
)}No messages match your filter
-Try adjusting your search or filter criteria
+{t('mqttDebug.noMessagesMatch')}
+{t('mqttDebug.adjustFilterHint')}
- Drag & drop .3mf files here + {t('uploadModal.dragDrop')}
-or
+{t('uploadModal.or')}
- The printer model will be automatically extracted from the 3MF file metadata. + {t('uploadModal.extractionInfo')}
- {uploadResult.uploaded} uploaded + {uploadResult.uploaded} {t('uploadModal.uploaded')} {uploadResult.failed > 0 && ( - <>, {uploadResult.failed} failed> + <>, {uploadResult.failed} {t('uploadModal.failed')}> )}
{archive.print_name || archive.filename}
{archive.timelapse_path && ( - +Drop .3mf files here
-Release to upload
+{t('archives.releaseToUpload')}
- {search ? 'No archives match your search' : 'No archives yet'} + {search ? t('archives.noArchivesSearch') : t('archives.noArchivesYet')}
Archives are created automatically when prints complete @@ -2897,6 +2905,7 @@ export function ArchivesPage() { projects={projects} isHighlighted={archive.id === highlightedArchiveId} timeFormat={timeFormat} + t={t} /> ))}
Invalid printer ID
+{t('camera.invalidPrinterId')}
- {streamMode === 'stream' ? 'Connecting to camera...' : 'Capturing snapshot...'} + {streamMode === 'stream' ? t('camera.connectingToCamera') : t('camera.capturingSnapshot')}
Connection lost
+{t('camera.connectionLost')}
- Reconnecting in {reconnectCountdown}s... (attempt {reconnectAttempts + 1}/{MAX_RECONNECT_ATTEMPTS}) + {t('camera.reconnecting', { countdown: reconnectCountdown, attempt: reconnectAttempts + 1, max: MAX_RECONNECT_ATTEMPTS })}
Camera unavailable
+{t('camera.cameraUnavailable')}
- Make sure the printer is powered on and connected. + {t('camera.cameraUnavailableDesc')}
Link not found
+{t('common.linkNotFound')}
- Organize and manage your print files + {t('fileManager.subtitle')}
Low disk space warning
+{t('fileManager.lowDiskSpaceWarning')}
- Only {formatFileSize(stats.disk_free_bytes)} free of {formatFileSize(stats.disk_total_bytes)} total. - Threshold is set to {settings.library_disk_warning_gb} GB in settings. + {t('fileManager.lowDiskSpaceDetails', { free: formatFileSize(stats.disk_free_bytes), total: formatFileSize(stats.disk_total_bytes), threshold: settings.library_disk_warning_gb })}
Loading files...
+{t('fileManager.loadingFiles')}
{selectedFolderId !== null - ? 'Upload files or move files into this folder to get started.' - : 'Upload files to start organizing your print-related files.'} + ? t('fileManager.folderEmptyDescription') + : t('fileManager.noFilesDescription')}
- No files match your current search or filter criteria. + {t('fileManager.noMatchingFilesDescription')}
You do not have permission to access this page.
+{t('groups.noPermission')}
- Manage permission groups for access control + {t('groups.subtitle')}
{group.description || 'No description'}
+{group.description || t('groups.noDescription')}
System group names cannot be changed
+{t('groups.form.systemGroupWarning')}
)}- Sign in to your account + {t('login.subtitle')}
@@ -58,7 +60,7 @@ export function LoginPage() {- If you've forgotten your password, please contact your system administrator to reset it. + {t('login.forgotPasswordMessage')}
How to reset your password:
+{t('login.howToReset')}
System types and your custom maintenance tasks
+{t('maintenance.maintenanceTypesDescription')}