diff --git a/frontend/js/i18n/de.js b/frontend/js/i18n/de.js index 7b9769a..0a578f3 100644 --- a/frontend/js/i18n/de.js +++ b/frontend/js/i18n/de.js @@ -194,4 +194,250 @@ export default { 'content.toast.folder_deleted': 'Ordner gelöscht', 'content.toast.moved': 'Verschoben', 'content.toast.moved_to_root': 'In den Hauptordner verschoben', + + // Device detail + 'device.back': 'Zurück zu Bildschirmen', + 'device.owner_label': 'Besitzer: {owner}', + 'device.rename': 'Umbenennen', + 'device.screenshot_btn': 'Screenshot', + 'device.remove': 'Entfernen', + 'device.click_to_confirm': 'Erneut klicken zum Bestätigen', + 'device.prompt_new_name': 'Neuen Namen eingeben:', + 'device.confirm_discard_draft': 'Alle nicht veröffentlichten Änderungen verwerfen und zur letzten veröffentlichten Version zurückkehren?', + 'device.failed_load': 'Gerät konnte nicht geladen werden', + 'device.no_screenshot': 'Kein Screenshot verfügbar. Klicken Sie auf „Screenshot", um einen aufzunehmen.', + 'device.no_content_assigned': 'Kein Inhalt zugewiesen', + 'device.now_playing_id': 'Wiedergabe: {id}', + 'device.playlist_count_one': '1 Element in der Playlist', + 'device.playlist_count_other': '{n} Elemente in der Playlist', + 'device.tab.now_playing': 'Aktuelle Wiedergabe', + 'device.tab.now_playing_tip': 'Live-Screenshot dessen, was aktuell auf diesem Gerät angezeigt wird.', + 'device.tab.playlist': 'Playlist', + 'device.tab.playlist_tip': 'Diesem Gerät zugewiesener Inhalt. Ziehen zum Sortieren. Medien, Widgets oder Kioskseiten hinzufügen.', + 'device.tab.info': 'Geräteinfos', + 'device.tab.info_tip': 'Hardware-Telemetrie, Ausrichtung, Notizen und Gerätesteuerung.', + 'device.tab.remote': 'Fernsteuerung', + 'device.tab.remote_tip': 'Bildschirm in Echtzeit anzeigen und Tastendrücke senden. Funktioniert mit Android-APK und Web-Player.', + 'device.draft.banner_title': 'Unveröffentlichte Änderungen', + 'device.draft.devices_showing_published': 'Geräte zeigen weiterhin die zuletzt veröffentlichte Version.', + 'device.draft.never_published': 'Diese Playlist wurde noch nie veröffentlicht. Geräte zeigen nichts an, bis Sie veröffentlichen.', + 'device.draft.discard': 'Verwerfen', + 'device.draft.publish': 'Veröffentlichen', + 'device.draft.publishing': 'Wird veröffentlicht...', + 'device.layout.label': 'Bildschirm-Layout', + 'device.layout.fullscreen_default': 'Vollbild (Standard)', + 'device.layout.zones_count': '{name} ({n} Zonen)', + 'device.layout.template_zones_count': '[Vorlage] {name} ({n} Zonen)', + 'device.layout.apply': 'Anwenden', + 'device.playlist.label': 'Playlist', + 'device.playlist.no_playlist': 'Keine Playlist', + 'device.playlist.copy_to_btn': 'Kopieren nach...', + 'device.playlist.add_content_btn': 'Inhalt hinzufügen', + 'device.playlist.empty_title': 'Kein Inhalt zugewiesen', + 'device.playlist.empty_desc': 'Fügen Sie Inhalt aus Ihrer Bibliothek zur Playlist dieses Bildschirms hinzu.', + 'device.playlist_picker.with_count': '{name} — {n} Elemente', + 'device.playlist_picker.with_auto': '{name} (auto) — {n} Elemente', + 'device.info.status': 'Status', + 'device.info.ip_address': 'IP-Adresse', + 'device.info.battery': 'Akku', + 'device.info.storage': 'Speicher', + 'device.info.size_free': '{size} frei', + 'device.info.player_type': 'Player-Typ', + 'device.info.web_player': 'Web-Player', + 'device.info.wifi': 'WLAN', + 'device.info.uptime': 'Betriebszeit', + 'device.info.android_version': 'Android-Version', + 'device.info.app_version': 'App-Version', + 'device.info.screen_resolution': 'Bildschirmauflösung', + 'device.info.ram': 'RAM', + 'device.info.cpu_usage': 'CPU-Auslastung', + 'device.timeline.title': 'Verfügbarkeit (letzte 24 Stunden)', + 'device.timeline.h24_ago': 'vor 24h', + 'device.timeline.now': 'Jetzt', + 'device.timeline.online': 'Online', + 'device.timeline.offline': 'Offline', + 'device.timeline.no_data': 'Keine Daten', + 'device.timeline.uptime_pct_tracked': '{pct}% Verfügbarkeit ({n}min erfasst)', + 'device.timeline.uptime_pct_no_data': '{pct}% Verfügbarkeit (keine Daten)', + 'device.form.orientation_label': 'Ausrichtung / Drehung', + 'device.form.orientation.landscape': 'Querformat (0°)', + 'device.form.orientation.portrait': 'Hochformat (90° im UZS)', + 'device.form.orientation.landscape_flipped': 'Querformat gedreht (180°)', + 'device.form.orientation.portrait_flipped': 'Hochformat gedreht (270° im UZS)', + 'device.form.default_content_label': 'Standardinhalt', + 'device.form.default_content_none': 'Keiner ("Warten..." anzeigen)', + 'device.form.notes_label': 'Notizen', + 'device.form.notes_placeholder': 'Standort, Setup-Details usw.', + 'device.form.save_settings': 'Einstellungen speichern', + 'device.ctl.reboot_device': 'Gerät neu starten', + 'device.ctl.screen_off': 'Bildschirm aus', + 'device.ctl.screen_on': 'Bildschirm an', + 'device.ctl.launch_player': 'Player starten', + 'device.ctl.force_update': 'Update erzwingen', + 'device.ctl.shutdown': 'Herunterfahren', + 'device.remote.start_prompt': 'Auf „Fernsteuerung starten" klicken zum Beginnen', + 'device.remote.start': 'Fernsteuerung starten', + 'device.remote.stop': 'Fernsteuerung beenden', + 'device.remote.vol_up': 'Vol +', + 'device.remote.vol_down': 'Vol -', + 'device.remote.home': 'Start', + 'device.remote.back': 'Zurück', + 'device.remote.recents': 'Zuletzt', + 'device.remote.power': 'Power', + 'device.remote.ok': 'OK', + 'device.remote.settings': 'Einstellungen', + 'device.remote.scrn_off': 'Bs. aus', + 'device.remote.scrn_on': 'Bs. an', + 'device.remote.enable_system_view': 'Systemansicht aktivieren', + 'device.remote.system_view_tooltip': 'Fordert den Gerätenutzer auf, Vollbildaufnahme zu erlauben - aktiviert Fernansicht von Startbildschirm, Einstellungen und anderen Apps', + 'device.remote.system_view_hint': 'Einmalige Genehmigung am Gerät erforderlich', + 'device.remote.waiting_for_approval': 'Warte auf Genehmigung am Gerät...', + 'device.remote.system_view_enabled': 'Systemansicht aktiviert', + 'device.remote.unlocked_hint': 'Navigation und Systemsteuerung freigeschaltet', + 'device.pl_item.widget_with_type': 'Widget ({type})', + 'device.pl_item.youtube': 'YouTube', + 'device.pl_item.video': 'Video', + 'device.pl_item.image': 'Bild', + 'device.pl_item.zone_label': 'Zone: {id}', + 'device.pl_item.no_zone': 'Keine Zone', + 'device.pl_item.mute': 'Stummschalten', + 'device.pl_item.unmute': 'Stummschaltung aufheben', + 'device.pl_item.remove': 'Entfernen', + 'device.copy.no_other_devices': 'Keine anderen Geräte zum Kopieren', + 'device.copy.prompt': 'Playlist auf welches Gerät kopieren?\n\n{list}\n\nNummer eingeben:', + 'device.copy.invalid_selection': 'Ungültige Auswahl', + 'device.copy.toast': '{n} Elemente nach {device} kopiert', + 'device.assign.empty_all': 'Noch keine Inhalte, Widgets oder Kioskseiten. Erstellen Sie zuerst etwas!', + 'device.assign.modal_title': 'Zur Playlist hinzufügen', + 'device.assign.zone_label': 'Zone', + 'device.assign.zone_default': 'Standard (Vollbild)', + 'device.assign.duration_label': 'Anzeigedauer (Sekunden, für Bilder/Widgets)', + 'device.assign.tab.media': 'Medien ({n})', + 'device.assign.tab.widgets': 'Widgets ({n})', + 'device.assign.tab.kiosk': 'Kiosk ({n})', + 'device.assign.no_media': 'Noch keine Medien hochgeladen', + 'device.assign.no_widgets': 'Noch keine Widgets erstellt.', + 'device.assign.no_kiosk': 'Noch keine Kioskseiten.', + 'device.assign.create_one': 'Eines erstellen', + 'device.assign.add_selected': 'Auswahl hinzufügen', + 'device.assign.select_first': 'Erst etwas auswählen', + 'device.assign.kiosk_widget_name': 'Kiosk: {name}', + 'device.toast.screenshot_requested': 'Screenshot angefordert', + 'device.toast.renamed': 'Bildschirm umbenannt', + 'device.toast.removing': 'Wird entfernt...', + 'device.toast.removed': 'Bildschirm entfernt', + 'device.toast.settings_saved': 'Einstellungen gespeichert', + 'device.toast.published': 'Playlist veröffentlicht — Geräte aktualisiert', + 'device.toast.draft_discarded': 'Entwurfsänderungen verworfen', + 'device.toast.playlist_changed': 'Playlist geändert', + 'device.toast.layout_applied': 'Layout angewendet', + 'device.toast.switched_to_fullscreen': 'Auf Vollbild umgestellt', + 'device.toast.added_to_playlist': 'Zur Playlist hinzugefügt', + 'device.toast.unmuted': 'Stummschaltung aufgehoben', + 'device.toast.muted': 'Stummgeschaltet', + 'device.toast.zone_updated': 'Zone aktualisiert', + 'device.toast.removed_from_playlist': 'Inhalt aus Playlist entfernt', + 'device.toast.playlist_reordered': 'Playlist neu sortiert', + 'device.toast.reboot_sent': 'Neustart-Befehl gesendet', + 'device.toast.shutdown_sent': 'Herunterfahren-Befehl gesendet', + 'device.toast.screen_off_sent': 'Befehl Bildschirm aus gesendet', + 'device.toast.screen_on_sent': 'Befehl Bildschirm an gesendet', + 'device.toast.launch_sent': 'Start-Befehl gesendet', + 'device.toast.update_triggered': 'Update-Prüfung ausgelöst', + 'device.toast.remote_started': 'Fernsteuerungssitzung gestartet', + + // Settings + 'settings.title': 'Einstellungen', + 'settings.subtitle': 'Serverkonfiguration und Setup-Informationen', + 'settings.account': 'Konto', + 'settings.save_profile': 'Profil speichern', + 'settings.change_password': 'Passwort ändern', + 'settings.password_min_8': 'Muss mindestens 8 Zeichen lang sein.', + 'settings.current_password': 'Aktuelles Passwort', + 'settings.new_password': 'Neues Passwort', + 'settings.confirm_new_password': 'Neues Passwort bestätigen', + 'settings.sso_note': 'Sie melden sich über {provider} an. Verwalten Sie Ihr Passwort dort.', + 'settings.license': 'Lizenz', + 'settings.license_mit': 'MIT-Lizenz - alle Funktionen enthalten.', + 'settings.platform_admin_link': 'Plattform-Admin-Tools sind auf der Seite', + 'settings.platform_admin_page_suffix': '.', + 'settings.user_management': 'Benutzerverwaltung', + 'settings.loading_users': 'Benutzer werden geladen...', + 'settings.white_label': 'White Label / Branding', + 'settings.white_label_desc': 'Passen Sie das Erscheinungsbild des Dashboards und Players für Ihre Kunden an.', + 'settings.brand_name': 'Markenname', + 'settings.logo_url': 'Logo-URL', + 'settings.primary_color': 'Primärfarbe', + 'settings.bg_color': 'Hintergrundfarbe', + 'settings.custom_domain': 'Benutzerdefinierte Domain', + 'settings.favicon_url': 'Favicon-URL', + 'settings.custom_css': 'Benutzerdefiniertes CSS (optional)', + 'settings.hide_branding': '„ScreenTinker"-Branding ausblenden', + 'settings.save_branding': 'Branding speichern', + 'settings.preview': 'Vorschau', + 'settings.white_label_enterprise_only': 'Benutzerdefiniertes Branding ist im Enterprise-Plan verfügbar', + 'settings.view_plans': 'Pläne ansehen', + 'settings.server_info': 'Serverinformationen', + 'settings.server_url': 'Server-URL', + 'settings.api_endpoint': 'API-Endpunkt', + 'settings.server_url_hint': 'Verwenden Sie diese URL beim Einrichten der Android-App', + 'settings.setup_guide': 'Setup-Anleitung', + 'settings.setup_step_1': 'Installieren Sie die ScreenTinker APK auf Ihrem TV via Sideloading', + 'settings.setup_step_2_prefix': 'Öffnen Sie die App und geben Sie diese Server-URL ein:', + 'settings.setup_step_3': 'Die App zeigt einen 6-stelligen Kopplungscode an', + 'settings.setup_step_4': 'Klicken Sie auf „Bildschirm hinzufügen" und geben Sie den Code ein', + 'settings.setup_step_5': 'Inhalte in die Inhaltsbibliothek hochladen', + 'settings.setup_step_6': 'Inhalte der Playlist des Bildschirms zuweisen', + 'settings.your_data': 'Ihre Daten', + 'settings.your_data_desc': 'Exportieren oder importieren Sie Ihre Geräte, Inhalte, Layouts, Zeitpläne und alle Einstellungen. Nutzen Sie dies zur Migration zwischen Cloud und Selfhosted.', + 'settings.export_my_data': 'Meine Daten exportieren', + 'settings.include_media_zip': 'Mediendateien einschließen (ZIP)', + 'settings.import_data': 'Daten importieren', + 'settings.language': 'Sprache', + 'settings.about': 'Über', + 'settings.about_tagline': 'Digital-Signage-Verwaltungssystem.', + 'settings.third_party_licenses': 'Drittanbieter-Lizenzen', + 'settings.import.reading_file': 'Datei wird gelesen...', + 'settings.import.zip_detected': 'ZIP-Export erkannt: {name} ({size} MB)
Enthält Daten + Mediendateien.', + 'settings.import.confirm': 'Import bestätigen', + 'settings.import.invalid_file': 'Ungültige Datei. Muss ein ScreenTinker-Export-JSON oder ZIP sein.', + 'settings.import.summary_devices': '{n} Geräte', + 'settings.import.summary_content': '{n} Inhalte', + 'settings.import.summary_widgets': '{n} Widgets', + 'settings.import.summary_layouts': '{n} Layouts', + 'settings.import.summary_schedules': '{n} Zeitpläne', + 'settings.import.summary_walls': '{n} Videowände', + 'settings.import.summary_kiosk': '{n} Kioskseiten', + 'settings.import.found_summary': 'Gefunden: {summary}.
Von: {email} (exportiert {date})', + 'settings.import.empty_export': 'leerer Export', + 'settings.import.uploading_zip': 'Wird hochgeladen und importiert... Bei großen Dateien kann dies einen Moment dauern.', + 'settings.import.importing': 'Wird importiert...', + 'settings.import.complete': 'Import abgeschlossen: {imported}.', + 'settings.import.pairing_codes_title': 'Geräte-Kopplungscodes:', + 'settings.import.pairing_codes_hint': 'Geben Sie diese Codes an jedem Gerät ein, um sie neu zu verknüpfen. Zuweisungen und Zeitpläne bleiben erhalten.', + 'settings.import.failed': 'Import fehlgeschlagen', + 'settings.import.failed_with_error': 'Import fehlgeschlagen: {error}', + 'settings.import.read_failed': 'Datei konnte nicht gelesen werden: {error}', + 'settings.toast.support_token_generated': 'Support-Token erstellt (gültig {hours}h)', + 'settings.toast.import_success': 'Daten erfolgreich importiert', + 'settings.toast.name_required': 'Name darf nicht leer sein', + 'settings.toast.profile_saved': 'Profil gespeichert', + 'settings.toast.current_password_required': 'Geben Sie Ihr aktuelles Passwort ein', + 'settings.toast.new_password_min_8': 'Neues Passwort muss mindestens 8 Zeichen lang sein', + 'settings.toast.passwords_dont_match': 'Neue Passwörter stimmen nicht überein', + 'settings.toast.password_changed': 'Passwort geändert', + 'settings.toast.branding_saved': 'Branding gespeichert', + 'settings.toast.preview_applied': 'Vorschau angewendet (zum Zurücksetzen neu laden)', + 'settings.toast.plan_updated': 'Plan aktualisiert', + 'settings.toast.user_removed': 'Benutzer entfernt', + 'settings.user.col_user': 'Benutzer', + 'settings.user.col_auth': 'Auth', + 'settings.user.col_role': 'Rolle', + 'settings.user.col_plan': 'Plan', + 'settings.user.col_actions': 'Aktionen', + 'settings.user.remove': 'Entfernen', + 'settings.user.you': 'Sie', + 'settings.user.confirm': 'Bestätigen?', + 'settings.user.count_one': '1 Benutzer registriert', + 'settings.user.count_other': '{n} Benutzer registriert', }; diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index 3b1a799..dc73d72 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -205,4 +205,266 @@ export default { 'content.toast.folder_deleted': 'Folder deleted', 'content.toast.moved': 'Moved', 'content.toast.moved_to_root': 'Moved to root', + + // Device detail + 'device.back': 'Back to Displays', + 'device.owner_label': 'Owner: {owner}', + 'device.rename': 'Rename', + 'device.screenshot_btn': 'Screenshot', + 'device.remove': 'Remove', + 'device.click_to_confirm': 'Click again to confirm', + 'device.prompt_new_name': 'Enter new name:', + 'device.confirm_discard_draft': 'Discard all unpublished changes and revert to the last published version?', + 'device.failed_load': 'Failed to load device', + 'device.no_screenshot': 'No screenshot available. Click "Screenshot" to capture one.', + 'device.no_content_assigned': 'No content assigned', + 'device.now_playing_id': 'Playing: {id}', + 'device.playlist_count_one': '1 item in playlist', + 'device.playlist_count_other': '{n} items in playlist', + // Tabs + 'device.tab.now_playing': 'Now Playing', + 'device.tab.now_playing_tip': "Live screenshot of what's currently displaying on this device.", + 'device.tab.playlist': 'Playlist', + 'device.tab.playlist_tip': 'Content assigned to this device. Drag items to reorder. Add media, widgets, or kiosk pages.', + 'device.tab.info': 'Device Info', + 'device.tab.info_tip': 'Hardware telemetry, orientation settings, notes, and device controls.', + 'device.tab.remote': 'Remote Control', + 'device.tab.remote_tip': 'View the device screen in real-time and send key presses. Works on Android APK and web player.', + // Draft banner + 'device.draft.banner_title': 'Unpublished changes', + 'device.draft.devices_showing_published': 'Devices are still showing the last published version.', + 'device.draft.never_published': 'This playlist has never been published. Devices will show nothing until you publish.', + 'device.draft.discard': 'Discard', + 'device.draft.publish': 'Publish', + 'device.draft.publishing': 'Publishing...', + // Layout selector + 'device.layout.label': 'Screen Layout', + 'device.layout.fullscreen_default': 'Fullscreen (default)', + 'device.layout.zones_count': '{name} ({n} zones)', + 'device.layout.template_zones_count': '[Template] {name} ({n} zones)', + 'device.layout.apply': 'Apply', + // Playlist tab + 'device.playlist.label': 'Playlist', + 'device.playlist.no_playlist': 'No playlist', + 'device.playlist.copy_to_btn': 'Copy To...', + 'device.playlist.add_content_btn': 'Add Content', + 'device.playlist.empty_title': 'No content assigned', + 'device.playlist.empty_desc': "Add content from your library to this display's playlist.", + 'device.playlist_picker.with_count': '{name} — {n} items', + 'device.playlist_picker.with_auto': '{name} (auto) — {n} items', + // Info cards + 'device.info.status': 'Status', + 'device.info.ip_address': 'IP Address', + 'device.info.battery': 'Battery', + 'device.info.storage': 'Storage', + 'device.info.size_free': '{size} free', + 'device.info.player_type': 'Player Type', + 'device.info.web_player': 'Web Player', + 'device.info.wifi': 'WiFi', + 'device.info.uptime': 'Uptime', + 'device.info.android_version': 'Android Version', + 'device.info.app_version': 'App Version', + 'device.info.screen_resolution': 'Screen Resolution', + 'device.info.ram': 'RAM', + 'device.info.cpu_usage': 'CPU Usage', + // Uptime timeline + 'device.timeline.title': 'Uptime Timeline (Last 24 Hours)', + 'device.timeline.h24_ago': '24h ago', + 'device.timeline.now': 'Now', + 'device.timeline.online': 'Online', + 'device.timeline.offline': 'Offline', + 'device.timeline.no_data': 'No data', + 'device.timeline.uptime_pct_tracked': '{pct}% uptime ({n}min tracked)', + 'device.timeline.uptime_pct_no_data': '{pct}% uptime (no data)', + // Form + 'device.form.orientation_label': 'Orientation / Rotation', + 'device.form.orientation.landscape': 'Landscape (0°)', + 'device.form.orientation.portrait': 'Portrait (90° CW)', + 'device.form.orientation.landscape_flipped': 'Landscape Flipped (180°)', + 'device.form.orientation.portrait_flipped': 'Portrait Flipped (270° CW)', + 'device.form.default_content_label': 'Default Content', + 'device.form.default_content_none': 'None (show "Waiting...")', + 'device.form.notes_label': 'Notes', + 'device.form.notes_placeholder': 'Location, setup details, etc.', + 'device.form.save_settings': 'Save Settings', + // Control buttons + 'device.ctl.reboot_device': 'Reboot Device', + 'device.ctl.screen_off': 'Screen Off', + 'device.ctl.screen_on': 'Screen On', + 'device.ctl.launch_player': 'Launch Player', + 'device.ctl.force_update': 'Force Update', + 'device.ctl.shutdown': 'Shutdown', + // Remote tab + 'device.remote.start_prompt': 'Click "Start Remote" to begin', + 'device.remote.start': 'Start Remote', + 'device.remote.stop': 'Stop Remote', + 'device.remote.vol_up': 'Vol +', + 'device.remote.vol_down': 'Vol -', + 'device.remote.home': 'Home', + 'device.remote.back': 'Back', + 'device.remote.recents': 'Recents', + 'device.remote.power': 'Power', + 'device.remote.ok': 'OK', + 'device.remote.settings': 'Settings', + 'device.remote.scrn_off': 'Scrn Off', + 'device.remote.scrn_on': 'Scrn On', + 'device.remote.enable_system_view': 'Enable System View', + 'device.remote.system_view_tooltip': 'Prompts the device user to allow full screen capture - enables remote view of home screen, settings, and other apps', + 'device.remote.system_view_hint': 'Requires one-time approval on device', + 'device.remote.waiting_for_approval': 'Waiting for device approval...', + 'device.remote.system_view_enabled': 'System View Enabled', + 'device.remote.unlocked_hint': 'Navigation and system controls unlocked', + // Playlist item + 'device.pl_item.widget_with_type': 'Widget ({type})', + 'device.pl_item.youtube': 'YouTube', + 'device.pl_item.video': 'Video', + 'device.pl_item.image': 'Image', + 'device.pl_item.zone_label': 'Zone: {id}', + 'device.pl_item.no_zone': 'No zone', + 'device.pl_item.mute': 'Mute', + 'device.pl_item.unmute': 'Unmute', + 'device.pl_item.remove': 'Remove', + // Copy playlist + 'device.copy.no_other_devices': 'No other devices to copy to', + 'device.copy.prompt': 'Copy playlist to which device?\n\n{list}\n\nEnter number:', + 'device.copy.invalid_selection': 'Invalid selection', + 'device.copy.toast': 'Copied {n} items to {device}', + // Add-content modal + 'device.assign.empty_all': 'No content, widgets, or kiosk pages yet. Create something first!', + 'device.assign.modal_title': 'Add to Playlist', + 'device.assign.zone_label': 'Zone', + 'device.assign.zone_default': 'Default (fullscreen)', + 'device.assign.duration_label': 'Display Duration (seconds, for images/widgets)', + 'device.assign.tab.media': 'Media ({n})', + 'device.assign.tab.widgets': 'Widgets ({n})', + 'device.assign.tab.kiosk': 'Kiosk ({n})', + 'device.assign.no_media': 'No media uploaded yet', + 'device.assign.no_widgets': 'No widgets created yet.', + 'device.assign.no_kiosk': 'No kiosk pages yet.', + 'device.assign.create_one': 'Create one', + 'device.assign.add_selected': 'Add Selected', + 'device.assign.select_first': 'Select something first', + 'device.assign.kiosk_widget_name': 'Kiosk: {name}', + // Toasts + 'device.toast.screenshot_requested': 'Screenshot requested', + 'device.toast.renamed': 'Display renamed', + 'device.toast.removing': 'Removing...', + 'device.toast.removed': 'Display removed', + 'device.toast.settings_saved': 'Settings saved', + 'device.toast.published': 'Playlist published — devices updated', + 'device.toast.draft_discarded': 'Draft changes discarded', + 'device.toast.playlist_changed': 'Playlist changed', + 'device.toast.layout_applied': 'Layout applied', + 'device.toast.switched_to_fullscreen': 'Switched to fullscreen', + 'device.toast.added_to_playlist': 'Added to playlist', + 'device.toast.unmuted': 'Unmuted', + 'device.toast.muted': 'Muted', + 'device.toast.zone_updated': 'Zone updated', + 'device.toast.removed_from_playlist': 'Content removed from playlist', + 'device.toast.playlist_reordered': 'Playlist reordered', + 'device.toast.reboot_sent': 'Reboot command sent', + 'device.toast.shutdown_sent': 'Shutdown command sent', + 'device.toast.screen_off_sent': 'Screen off command sent', + 'device.toast.screen_on_sent': 'Screen on command sent', + 'device.toast.launch_sent': 'Launch command sent', + 'device.toast.update_triggered': 'Update check triggered', + 'device.toast.remote_started': 'Remote session started', + + // Settings + 'settings.title': 'Settings', + 'settings.subtitle': 'Server configuration and setup information', + 'settings.account': 'Account', + 'settings.save_profile': 'Save Profile', + 'settings.change_password': 'Change Password', + 'settings.password_min_8': 'Must be at least 8 characters.', + 'settings.current_password': 'Current Password', + 'settings.new_password': 'New Password', + 'settings.confirm_new_password': 'Confirm New Password', + 'settings.sso_note': 'You sign in via {provider}. Manage your password there.', + 'settings.license': 'License', + 'settings.license_mit': 'MIT License - all features included.', + 'settings.platform_admin_link': 'Platform admin tools are in the', + 'settings.platform_admin_page_suffix': 'page.', + 'settings.user_management': 'User Management', + 'settings.loading_users': 'Loading users...', + 'settings.white_label': 'White Label / Branding', + 'settings.white_label_desc': 'Customize the look of your dashboard and player for your clients.', + 'settings.brand_name': 'Brand Name', + 'settings.logo_url': 'Logo URL', + 'settings.primary_color': 'Primary Color', + 'settings.bg_color': 'Background Color', + 'settings.custom_domain': 'Custom Domain', + 'settings.favicon_url': 'Favicon URL', + 'settings.custom_css': 'Custom CSS (optional)', + 'settings.hide_branding': 'Hide "ScreenTinker" branding', + 'settings.save_branding': 'Save Branding', + 'settings.preview': 'Preview', + 'settings.white_label_enterprise_only': 'Custom branding is available on the Enterprise plan', + 'settings.view_plans': 'View Plans', + 'settings.server_info': 'Server Information', + 'settings.server_url': 'Server URL', + 'settings.api_endpoint': 'API Endpoint', + 'settings.server_url_hint': 'Use this URL when setting up the Android app', + 'settings.setup_guide': 'Setup Guide', + 'settings.setup_step_1': 'Install the ScreenTinker APK on your TV via sideloading', + 'settings.setup_step_2_prefix': 'Open the app and enter this server URL:', + 'settings.setup_step_3': 'The app will display a 6-digit pairing code', + 'settings.setup_step_4': 'Click "Add Display" on the dashboard and enter the pairing code', + 'settings.setup_step_5': 'Upload content in the Content Library', + 'settings.setup_step_6': "Assign content to the display's Playlist", + 'settings.your_data': 'Your Data', + 'settings.your_data_desc': 'Export or import your devices, content, layouts, schedules, and all settings. Use this to migrate between cloud and self-hosted instances.', + 'settings.export_my_data': 'Export My Data', + 'settings.include_media_zip': 'Include media files (ZIP)', + 'settings.import_data': 'Import Data', + 'settings.language': 'Language', + 'settings.about': 'About', + 'settings.about_tagline': 'Digital signage management system.', + 'settings.third_party_licenses': 'Third-Party Licenses', + // Import flow + 'settings.import.reading_file': 'Reading file...', + 'settings.import.zip_detected': 'ZIP export detected: {name} ({size} MB)
Contains data + media files.', + 'settings.import.confirm': 'Confirm Import', + 'settings.import.invalid_file': 'Invalid file. Must be a ScreenTinker export JSON or ZIP.', + 'settings.import.summary_devices': '{n} devices', + 'settings.import.summary_content': '{n} content items', + 'settings.import.summary_widgets': '{n} widgets', + 'settings.import.summary_layouts': '{n} layouts', + 'settings.import.summary_schedules': '{n} schedules', + 'settings.import.summary_walls': '{n} video walls', + 'settings.import.summary_kiosk': '{n} kiosk pages', + 'settings.import.found_summary': 'Found: {summary}.
From: {email} (exported {date})', + 'settings.import.empty_export': 'empty export', + 'settings.import.uploading_zip': 'Uploading and importing... This may take a moment for large files.', + 'settings.import.importing': 'Importing...', + 'settings.import.complete': 'Import complete: {imported}.', + 'settings.import.pairing_codes_title': 'Device Pairing Codes:', + 'settings.import.pairing_codes_hint': 'Enter these codes on each device to re-link them. All assignments and schedules will be preserved.', + 'settings.import.failed': 'Import failed', + 'settings.import.failed_with_error': 'Import failed: {error}', + 'settings.import.read_failed': 'Failed to read file: {error}', + // Settings toasts + 'settings.toast.support_token_generated': 'Support token generated (valid {hours}h)', + 'settings.toast.import_success': 'Data imported successfully', + 'settings.toast.name_required': 'Name cannot be empty', + 'settings.toast.profile_saved': 'Profile saved', + 'settings.toast.current_password_required': 'Enter your current password', + 'settings.toast.new_password_min_8': 'New password must be at least 8 characters', + 'settings.toast.passwords_dont_match': 'New passwords do not match', + 'settings.toast.password_changed': 'Password changed', + 'settings.toast.branding_saved': 'Branding saved', + 'settings.toast.preview_applied': 'Preview applied (refresh to reset)', + 'settings.toast.plan_updated': 'Plan updated', + 'settings.toast.user_removed': 'User removed', + // User management table + 'settings.user.col_user': 'User', + 'settings.user.col_auth': 'Auth', + 'settings.user.col_role': 'Role', + 'settings.user.col_plan': 'Plan', + 'settings.user.col_actions': 'Actions', + 'settings.user.remove': 'Remove', + 'settings.user.you': 'You', + 'settings.user.confirm': 'Confirm?', + 'settings.user.count_one': '1 user registered', + 'settings.user.count_other': '{n} users registered', }; diff --git a/frontend/js/i18n/es.js b/frontend/js/i18n/es.js index b117b4c..827007e 100644 --- a/frontend/js/i18n/es.js +++ b/frontend/js/i18n/es.js @@ -193,4 +193,250 @@ export default { 'content.toast.folder_deleted': 'Carpeta eliminada', 'content.toast.moved': 'Movido', 'content.toast.moved_to_root': 'Movido a la raíz', + + // Device detail + 'device.back': 'Volver a Pantallas', + 'device.owner_label': 'Propietario: {owner}', + 'device.rename': 'Renombrar', + 'device.screenshot_btn': 'Captura', + 'device.remove': 'Eliminar', + 'device.click_to_confirm': 'Haz clic de nuevo para confirmar', + 'device.prompt_new_name': 'Ingresa el nuevo nombre:', + 'device.confirm_discard_draft': '¿Descartar todos los cambios no publicados y volver a la última versión publicada?', + 'device.failed_load': 'Error al cargar el dispositivo', + 'device.no_screenshot': 'No hay captura disponible. Haz clic en "Captura" para tomar una.', + 'device.no_content_assigned': 'Sin contenido asignado', + 'device.now_playing_id': 'Reproduciendo: {id}', + 'device.playlist_count_one': '1 elemento en la lista', + 'device.playlist_count_other': '{n} elementos en la lista', + 'device.tab.now_playing': 'Reproducción actual', + 'device.tab.now_playing_tip': 'Captura en vivo de lo que se muestra ahora en este dispositivo.', + 'device.tab.playlist': 'Lista', + 'device.tab.playlist_tip': 'Contenido asignado a este dispositivo. Arrastra para reordenar. Agrega medios, widgets o páginas de kiosco.', + 'device.tab.info': 'Información del dispositivo', + 'device.tab.info_tip': 'Telemetría de hardware, orientación, notas y controles del dispositivo.', + 'device.tab.remote': 'Control remoto', + 'device.tab.remote_tip': 'Visualiza la pantalla del dispositivo en tiempo real y envía pulsaciones. Funciona en la APK de Android y el reproductor web.', + 'device.draft.banner_title': 'Cambios sin publicar', + 'device.draft.devices_showing_published': 'Los dispositivos siguen mostrando la última versión publicada.', + 'device.draft.never_published': 'Esta lista nunca se ha publicado. Los dispositivos no mostrarán nada hasta que la publiques.', + 'device.draft.discard': 'Descartar', + 'device.draft.publish': 'Publicar', + 'device.draft.publishing': 'Publicando...', + 'device.layout.label': 'Diseño de pantalla', + 'device.layout.fullscreen_default': 'Pantalla completa (predeterminado)', + 'device.layout.zones_count': '{name} ({n} zonas)', + 'device.layout.template_zones_count': '[Plantilla] {name} ({n} zonas)', + 'device.layout.apply': 'Aplicar', + 'device.playlist.label': 'Lista', + 'device.playlist.no_playlist': 'Sin lista', + 'device.playlist.copy_to_btn': 'Copiar a...', + 'device.playlist.add_content_btn': 'Agregar contenido', + 'device.playlist.empty_title': 'Sin contenido asignado', + 'device.playlist.empty_desc': 'Agrega contenido de tu biblioteca a la lista de esta pantalla.', + 'device.playlist_picker.with_count': '{name} — {n} elementos', + 'device.playlist_picker.with_auto': '{name} (auto) — {n} elementos', + 'device.info.status': 'Estado', + 'device.info.ip_address': 'Dirección IP', + 'device.info.battery': 'Batería', + 'device.info.storage': 'Almacenamiento', + 'device.info.size_free': '{size} libres', + 'device.info.player_type': 'Tipo de reproductor', + 'device.info.web_player': 'Reproductor web', + 'device.info.wifi': 'WiFi', + 'device.info.uptime': 'Tiempo activo', + 'device.info.android_version': 'Versión de Android', + 'device.info.app_version': 'Versión de la app', + 'device.info.screen_resolution': 'Resolución de pantalla', + 'device.info.ram': 'RAM', + 'device.info.cpu_usage': 'Uso de CPU', + 'device.timeline.title': 'Línea de tiempo (últimas 24 horas)', + 'device.timeline.h24_ago': 'hace 24h', + 'device.timeline.now': 'Ahora', + 'device.timeline.online': 'En línea', + 'device.timeline.offline': 'Desconectado', + 'device.timeline.no_data': 'Sin datos', + 'device.timeline.uptime_pct_tracked': '{pct}% activo ({n}min registrados)', + 'device.timeline.uptime_pct_no_data': '{pct}% activo (sin datos)', + 'device.form.orientation_label': 'Orientación / Rotación', + 'device.form.orientation.landscape': 'Horizontal (0°)', + 'device.form.orientation.portrait': 'Vertical (90° SR)', + 'device.form.orientation.landscape_flipped': 'Horizontal invertido (180°)', + 'device.form.orientation.portrait_flipped': 'Vertical invertido (270° SR)', + 'device.form.default_content_label': 'Contenido predeterminado', + 'device.form.default_content_none': 'Ninguno (mostrar "Esperando...")', + 'device.form.notes_label': 'Notas', + 'device.form.notes_placeholder': 'Ubicación, detalles de instalación, etc.', + 'device.form.save_settings': 'Guardar configuración', + 'device.ctl.reboot_device': 'Reiniciar dispositivo', + 'device.ctl.screen_off': 'Apagar pantalla', + 'device.ctl.screen_on': 'Encender pantalla', + 'device.ctl.launch_player': 'Iniciar reproductor', + 'device.ctl.force_update': 'Forzar actualización', + 'device.ctl.shutdown': 'Apagar', + 'device.remote.start_prompt': 'Haz clic en "Iniciar control remoto" para comenzar', + 'device.remote.start': 'Iniciar control remoto', + 'device.remote.stop': 'Detener control remoto', + 'device.remote.vol_up': 'Vol +', + 'device.remote.vol_down': 'Vol -', + 'device.remote.home': 'Inicio', + 'device.remote.back': 'Atrás', + 'device.remote.recents': 'Recientes', + 'device.remote.power': 'Encendido', + 'device.remote.ok': 'OK', + 'device.remote.settings': 'Ajustes', + 'device.remote.scrn_off': 'Pant. off', + 'device.remote.scrn_on': 'Pant. on', + 'device.remote.enable_system_view': 'Habilitar vista de sistema', + 'device.remote.system_view_tooltip': 'Solicita al usuario del dispositivo permitir captura de pantalla completa - habilita ver pantalla de inicio, ajustes y otras apps', + 'device.remote.system_view_hint': 'Requiere aprobación única en el dispositivo', + 'device.remote.waiting_for_approval': 'Esperando aprobación del dispositivo...', + 'device.remote.system_view_enabled': 'Vista de sistema habilitada', + 'device.remote.unlocked_hint': 'Navegación y controles del sistema desbloqueados', + 'device.pl_item.widget_with_type': 'Widget ({type})', + 'device.pl_item.youtube': 'YouTube', + 'device.pl_item.video': 'Video', + 'device.pl_item.image': 'Imagen', + 'device.pl_item.zone_label': 'Zona: {id}', + 'device.pl_item.no_zone': 'Sin zona', + 'device.pl_item.mute': 'Silenciar', + 'device.pl_item.unmute': 'Activar audio', + 'device.pl_item.remove': 'Eliminar', + 'device.copy.no_other_devices': 'No hay otros dispositivos a los que copiar', + 'device.copy.prompt': '¿A qué dispositivo copiar la lista?\n\n{list}\n\nIngresa el número:', + 'device.copy.invalid_selection': 'Selección no válida', + 'device.copy.toast': 'Se copiaron {n} elementos a {device}', + 'device.assign.empty_all': 'Aún no hay contenido, widgets ni páginas de kiosco. ¡Crea algo primero!', + 'device.assign.modal_title': 'Agregar a la lista', + 'device.assign.zone_label': 'Zona', + 'device.assign.zone_default': 'Predeterminada (pantalla completa)', + 'device.assign.duration_label': 'Duración (segundos, para imágenes/widgets)', + 'device.assign.tab.media': 'Medios ({n})', + 'device.assign.tab.widgets': 'Widgets ({n})', + 'device.assign.tab.kiosk': 'Kiosco ({n})', + 'device.assign.no_media': 'Aún no se ha subido ningún medio', + 'device.assign.no_widgets': 'Aún no hay widgets creados.', + 'device.assign.no_kiosk': 'Aún no hay páginas de kiosco.', + 'device.assign.create_one': 'Crea uno', + 'device.assign.add_selected': 'Agregar selección', + 'device.assign.select_first': 'Primero selecciona algo', + 'device.assign.kiosk_widget_name': 'Kiosco: {name}', + 'device.toast.screenshot_requested': 'Captura solicitada', + 'device.toast.renamed': 'Pantalla renombrada', + 'device.toast.removing': 'Eliminando...', + 'device.toast.removed': 'Pantalla eliminada', + 'device.toast.settings_saved': 'Configuración guardada', + 'device.toast.published': 'Lista publicada — dispositivos actualizados', + 'device.toast.draft_discarded': 'Cambios del borrador descartados', + 'device.toast.playlist_changed': 'Lista cambiada', + 'device.toast.layout_applied': 'Diseño aplicado', + 'device.toast.switched_to_fullscreen': 'Cambiado a pantalla completa', + 'device.toast.added_to_playlist': 'Agregado a la lista', + 'device.toast.unmuted': 'Audio activado', + 'device.toast.muted': 'Silenciado', + 'device.toast.zone_updated': 'Zona actualizada', + 'device.toast.removed_from_playlist': 'Contenido eliminado de la lista', + 'device.toast.playlist_reordered': 'Lista reordenada', + 'device.toast.reboot_sent': 'Comando de reinicio enviado', + 'device.toast.shutdown_sent': 'Comando de apagado enviado', + 'device.toast.screen_off_sent': 'Comando para apagar pantalla enviado', + 'device.toast.screen_on_sent': 'Comando para encender pantalla enviado', + 'device.toast.launch_sent': 'Comando de inicio enviado', + 'device.toast.update_triggered': 'Verificación de actualización iniciada', + 'device.toast.remote_started': 'Sesión de control remoto iniciada', + + // Settings + 'settings.title': 'Configuración', + 'settings.subtitle': 'Configuración del servidor e información de instalación', + 'settings.account': 'Cuenta', + 'settings.save_profile': 'Guardar perfil', + 'settings.change_password': 'Cambiar contraseña', + 'settings.password_min_8': 'Debe tener al menos 8 caracteres.', + 'settings.current_password': 'Contraseña actual', + 'settings.new_password': 'Contraseña nueva', + 'settings.confirm_new_password': 'Confirmar contraseña nueva', + 'settings.sso_note': 'Inicias sesión con {provider}. Gestiona tu contraseña ahí.', + 'settings.license': 'Licencia', + 'settings.license_mit': 'Licencia MIT - todas las funciones incluidas.', + 'settings.platform_admin_link': 'Las herramientas de administración están en la', + 'settings.platform_admin_page_suffix': '.', + 'settings.user_management': 'Gestión de usuarios', + 'settings.loading_users': 'Cargando usuarios...', + 'settings.white_label': 'Marca blanca / Branding', + 'settings.white_label_desc': 'Personaliza la apariencia del panel y el reproductor para tus clientes.', + 'settings.brand_name': 'Nombre de la marca', + 'settings.logo_url': 'URL del logotipo', + 'settings.primary_color': 'Color principal', + 'settings.bg_color': 'Color de fondo', + 'settings.custom_domain': 'Dominio personalizado', + 'settings.favicon_url': 'URL del favicon', + 'settings.custom_css': 'CSS personalizado (opcional)', + 'settings.hide_branding': 'Ocultar la marca "ScreenTinker"', + 'settings.save_branding': 'Guardar branding', + 'settings.preview': 'Previsualizar', + 'settings.white_label_enterprise_only': 'El branding personalizado está disponible en el plan Enterprise', + 'settings.view_plans': 'Ver planes', + 'settings.server_info': 'Información del servidor', + 'settings.server_url': 'URL del servidor', + 'settings.api_endpoint': 'Endpoint de la API', + 'settings.server_url_hint': 'Usa esta URL al configurar la app de Android', + 'settings.setup_guide': 'Guía de instalación', + 'settings.setup_step_1': 'Instala el APK de ScreenTinker en tu TV mediante sideloading', + 'settings.setup_step_2_prefix': 'Abre la app e ingresa esta URL del servidor:', + 'settings.setup_step_3': 'La app mostrará un código de vinculación de 6 dígitos', + 'settings.setup_step_4': 'Haz clic en "Agregar pantalla" en el panel e ingresa el código', + 'settings.setup_step_5': 'Sube contenido en la Biblioteca de contenido', + 'settings.setup_step_6': 'Asigna contenido a la lista de la pantalla', + 'settings.your_data': 'Tus datos', + 'settings.your_data_desc': 'Exporta o importa tus dispositivos, contenido, diseños, horarios y toda la configuración. Úsalo para migrar entre instancias en la nube y autoalojadas.', + 'settings.export_my_data': 'Exportar mis datos', + 'settings.include_media_zip': 'Incluir archivos multimedia (ZIP)', + 'settings.import_data': 'Importar datos', + 'settings.language': 'Idioma', + 'settings.about': 'Acerca de', + 'settings.about_tagline': 'Sistema de gestión de señalización digital.', + 'settings.third_party_licenses': 'Licencias de terceros', + 'settings.import.reading_file': 'Leyendo archivo...', + 'settings.import.zip_detected': 'Exportación ZIP detectada: {name} ({size} MB)
Contiene datos + archivos multimedia.', + 'settings.import.confirm': 'Confirmar importación', + 'settings.import.invalid_file': 'Archivo no válido. Debe ser un JSON o ZIP de exportación de ScreenTinker.', + 'settings.import.summary_devices': '{n} dispositivos', + 'settings.import.summary_content': '{n} elementos de contenido', + 'settings.import.summary_widgets': '{n} widgets', + 'settings.import.summary_layouts': '{n} diseños', + 'settings.import.summary_schedules': '{n} horarios', + 'settings.import.summary_walls': '{n} muros de video', + 'settings.import.summary_kiosk': '{n} páginas de kiosco', + 'settings.import.found_summary': 'Encontrado: {summary}.
De: {email} (exportado {date})', + 'settings.import.empty_export': 'exportación vacía', + 'settings.import.uploading_zip': 'Subiendo e importando... Esto puede tardar para archivos grandes.', + 'settings.import.importing': 'Importando...', + 'settings.import.complete': 'Importación completa: {imported}.', + 'settings.import.pairing_codes_title': 'Códigos de vinculación:', + 'settings.import.pairing_codes_hint': 'Ingresa estos códigos en cada dispositivo para volver a vincularlos. Las asignaciones y horarios se conservarán.', + 'settings.import.failed': 'Error al importar', + 'settings.import.failed_with_error': 'Error al importar: {error}', + 'settings.import.read_failed': 'Error al leer el archivo: {error}', + 'settings.toast.support_token_generated': 'Token de soporte generado (válido {hours}h)', + 'settings.toast.import_success': 'Datos importados correctamente', + 'settings.toast.name_required': 'El nombre no puede estar vacío', + 'settings.toast.profile_saved': 'Perfil guardado', + 'settings.toast.current_password_required': 'Ingresa tu contraseña actual', + 'settings.toast.new_password_min_8': 'La nueva contraseña debe tener al menos 8 caracteres', + 'settings.toast.passwords_dont_match': 'Las nuevas contraseñas no coinciden', + 'settings.toast.password_changed': 'Contraseña cambiada', + 'settings.toast.branding_saved': 'Branding guardado', + 'settings.toast.preview_applied': 'Previsualización aplicada (recarga para restablecer)', + 'settings.toast.plan_updated': 'Plan actualizado', + 'settings.toast.user_removed': 'Usuario eliminado', + 'settings.user.col_user': 'Usuario', + 'settings.user.col_auth': 'Auth', + 'settings.user.col_role': 'Rol', + 'settings.user.col_plan': 'Plan', + 'settings.user.col_actions': 'Acciones', + 'settings.user.remove': 'Eliminar', + 'settings.user.you': 'Tú', + 'settings.user.confirm': '¿Confirmar?', + 'settings.user.count_one': '1 usuario registrado', + 'settings.user.count_other': '{n} usuarios registrados', }; diff --git a/frontend/js/i18n/fr.js b/frontend/js/i18n/fr.js index ed24b1b..556a2ac 100644 --- a/frontend/js/i18n/fr.js +++ b/frontend/js/i18n/fr.js @@ -194,4 +194,250 @@ export default { 'content.toast.folder_deleted': 'Dossier supprimé', 'content.toast.moved': 'Déplacé', 'content.toast.moved_to_root': 'Déplacé à la racine', + + // Device detail + 'device.back': 'Retour aux écrans', + 'device.owner_label': 'Propriétaire : {owner}', + 'device.rename': 'Renommer', + 'device.screenshot_btn': 'Capture', + 'device.remove': 'Retirer', + 'device.click_to_confirm': 'Cliquez à nouveau pour confirmer', + 'device.prompt_new_name': 'Saisissez le nouveau nom :', + 'device.confirm_discard_draft': 'Annuler toutes les modifications non publiées et revenir à la dernière version publiée ?', + 'device.failed_load': 'Échec du chargement de l\'appareil', + 'device.no_screenshot': 'Aucune capture disponible. Cliquez sur « Capture » pour en prendre une.', + 'device.no_content_assigned': 'Aucun contenu attribué', + 'device.now_playing_id': 'En lecture : {id}', + 'device.playlist_count_one': '1 élément dans la liste', + 'device.playlist_count_other': '{n} éléments dans la liste', + 'device.tab.now_playing': 'En cours de lecture', + 'device.tab.now_playing_tip': 'Capture en direct de ce qui s\'affiche sur cet appareil.', + 'device.tab.playlist': 'Liste de lecture', + 'device.tab.playlist_tip': 'Contenu attribué à cet appareil. Glissez pour réorganiser. Ajoutez des médias, widgets ou pages kiosque.', + 'device.tab.info': 'Infos appareil', + 'device.tab.info_tip': 'Télémétrie matérielle, orientation, notes et contrôles de l\'appareil.', + 'device.tab.remote': 'Contrôle à distance', + 'device.tab.remote_tip': 'Visualisez l\'écran en temps réel et envoyez des touches. Fonctionne sur l\'APK Android et le lecteur web.', + 'device.draft.banner_title': 'Modifications non publiées', + 'device.draft.devices_showing_published': 'Les appareils affichent encore la dernière version publiée.', + 'device.draft.never_published': 'Cette liste n\'a jamais été publiée. Les appareils n\'afficheront rien jusqu\'à publication.', + 'device.draft.discard': 'Annuler', + 'device.draft.publish': 'Publier', + 'device.draft.publishing': 'Publication...', + 'device.layout.label': 'Mise en page', + 'device.layout.fullscreen_default': 'Plein écran (par défaut)', + 'device.layout.zones_count': '{name} ({n} zones)', + 'device.layout.template_zones_count': '[Modèle] {name} ({n} zones)', + 'device.layout.apply': 'Appliquer', + 'device.playlist.label': 'Liste de lecture', + 'device.playlist.no_playlist': 'Aucune liste', + 'device.playlist.copy_to_btn': 'Copier vers...', + 'device.playlist.add_content_btn': 'Ajouter du contenu', + 'device.playlist.empty_title': 'Aucun contenu attribué', + 'device.playlist.empty_desc': 'Ajoutez du contenu de votre bibliothèque à la liste de cet écran.', + 'device.playlist_picker.with_count': '{name} — {n} éléments', + 'device.playlist_picker.with_auto': '{name} (auto) — {n} éléments', + 'device.info.status': 'Statut', + 'device.info.ip_address': 'Adresse IP', + 'device.info.battery': 'Batterie', + 'device.info.storage': 'Stockage', + 'device.info.size_free': '{size} libres', + 'device.info.player_type': 'Type de lecteur', + 'device.info.web_player': 'Lecteur web', + 'device.info.wifi': 'WiFi', + 'device.info.uptime': 'Disponibilité', + 'device.info.android_version': 'Version d\'Android', + 'device.info.app_version': 'Version de l\'app', + 'device.info.screen_resolution': 'Résolution', + 'device.info.ram': 'RAM', + 'device.info.cpu_usage': 'Utilisation CPU', + 'device.timeline.title': 'Disponibilité (24 dernières heures)', + 'device.timeline.h24_ago': 'il y a 24h', + 'device.timeline.now': 'Maintenant', + 'device.timeline.online': 'En ligne', + 'device.timeline.offline': 'Hors ligne', + 'device.timeline.no_data': 'Aucune donnée', + 'device.timeline.uptime_pct_tracked': '{pct}% disponible ({n}min suivies)', + 'device.timeline.uptime_pct_no_data': '{pct}% disponible (aucune donnée)', + 'device.form.orientation_label': 'Orientation / Rotation', + 'device.form.orientation.landscape': 'Paysage (0°)', + 'device.form.orientation.portrait': 'Portrait (90° SH)', + 'device.form.orientation.landscape_flipped': 'Paysage retourné (180°)', + 'device.form.orientation.portrait_flipped': 'Portrait retourné (270° SH)', + 'device.form.default_content_label': 'Contenu par défaut', + 'device.form.default_content_none': 'Aucun (afficher « En attente... »)', + 'device.form.notes_label': 'Notes', + 'device.form.notes_placeholder': 'Emplacement, détails d\'installation, etc.', + 'device.form.save_settings': 'Enregistrer les paramètres', + 'device.ctl.reboot_device': 'Redémarrer l\'appareil', + 'device.ctl.screen_off': 'Éteindre l\'écran', + 'device.ctl.screen_on': 'Allumer l\'écran', + 'device.ctl.launch_player': 'Lancer le lecteur', + 'device.ctl.force_update': 'Forcer la mise à jour', + 'device.ctl.shutdown': 'Arrêter', + 'device.remote.start_prompt': 'Cliquez sur « Démarrer » pour commencer', + 'device.remote.start': 'Démarrer', + 'device.remote.stop': 'Arrêter', + 'device.remote.vol_up': 'Vol +', + 'device.remote.vol_down': 'Vol -', + 'device.remote.home': 'Accueil', + 'device.remote.back': 'Retour', + 'device.remote.recents': 'Récents', + 'device.remote.power': 'Marche', + 'device.remote.ok': 'OK', + 'device.remote.settings': 'Paramètres', + 'device.remote.scrn_off': 'Écr. off', + 'device.remote.scrn_on': 'Écr. on', + 'device.remote.enable_system_view': 'Activer la vue système', + 'device.remote.system_view_tooltip': 'Demande à l\'utilisateur d\'autoriser la capture plein écran - permet de voir l\'écran d\'accueil, les paramètres et d\'autres apps', + 'device.remote.system_view_hint': 'Approbation unique requise sur l\'appareil', + 'device.remote.waiting_for_approval': 'Attente de l\'approbation de l\'appareil...', + 'device.remote.system_view_enabled': 'Vue système activée', + 'device.remote.unlocked_hint': 'Navigation et contrôles système débloqués', + 'device.pl_item.widget_with_type': 'Widget ({type})', + 'device.pl_item.youtube': 'YouTube', + 'device.pl_item.video': 'Vidéo', + 'device.pl_item.image': 'Image', + 'device.pl_item.zone_label': 'Zone : {id}', + 'device.pl_item.no_zone': 'Aucune zone', + 'device.pl_item.mute': 'Muet', + 'device.pl_item.unmute': 'Réactiver le son', + 'device.pl_item.remove': 'Retirer', + 'device.copy.no_other_devices': 'Aucun autre appareil vers lequel copier', + 'device.copy.prompt': 'Copier la liste vers quel appareil ?\n\n{list}\n\nSaisissez le numéro :', + 'device.copy.invalid_selection': 'Sélection invalide', + 'device.copy.toast': '{n} éléments copiés vers {device}', + 'device.assign.empty_all': 'Pas encore de contenu, widgets ou pages kiosque. Créez-en un d\'abord !', + 'device.assign.modal_title': 'Ajouter à la liste', + 'device.assign.zone_label': 'Zone', + 'device.assign.zone_default': 'Par défaut (plein écran)', + 'device.assign.duration_label': 'Durée (secondes, pour images/widgets)', + 'device.assign.tab.media': 'Médias ({n})', + 'device.assign.tab.widgets': 'Widgets ({n})', + 'device.assign.tab.kiosk': 'Kiosque ({n})', + 'device.assign.no_media': 'Aucun média téléversé', + 'device.assign.no_widgets': 'Aucun widget créé.', + 'device.assign.no_kiosk': 'Aucune page kiosque.', + 'device.assign.create_one': 'Créez-en un', + 'device.assign.add_selected': 'Ajouter la sélection', + 'device.assign.select_first': 'Sélectionnez d\'abord un élément', + 'device.assign.kiosk_widget_name': 'Kiosque : {name}', + 'device.toast.screenshot_requested': 'Capture demandée', + 'device.toast.renamed': 'Écran renommé', + 'device.toast.removing': 'Suppression...', + 'device.toast.removed': 'Écran retiré', + 'device.toast.settings_saved': 'Paramètres enregistrés', + 'device.toast.published': 'Liste publiée — appareils mis à jour', + 'device.toast.draft_discarded': 'Modifications du brouillon annulées', + 'device.toast.playlist_changed': 'Liste modifiée', + 'device.toast.layout_applied': 'Mise en page appliquée', + 'device.toast.switched_to_fullscreen': 'Passé en plein écran', + 'device.toast.added_to_playlist': 'Ajouté à la liste', + 'device.toast.unmuted': 'Son réactivé', + 'device.toast.muted': 'Son coupé', + 'device.toast.zone_updated': 'Zone mise à jour', + 'device.toast.removed_from_playlist': 'Contenu retiré de la liste', + 'device.toast.playlist_reordered': 'Liste réorganisée', + 'device.toast.reboot_sent': 'Commande de redémarrage envoyée', + 'device.toast.shutdown_sent': 'Commande d\'arrêt envoyée', + 'device.toast.screen_off_sent': 'Commande d\'extinction de l\'écran envoyée', + 'device.toast.screen_on_sent': 'Commande d\'allumage de l\'écran envoyée', + 'device.toast.launch_sent': 'Commande de lancement envoyée', + 'device.toast.update_triggered': 'Vérification de mise à jour déclenchée', + 'device.toast.remote_started': 'Session de contrôle à distance démarrée', + + // Settings + 'settings.title': 'Paramètres', + 'settings.subtitle': 'Configuration du serveur et informations d\'installation', + 'settings.account': 'Compte', + 'settings.save_profile': 'Enregistrer le profil', + 'settings.change_password': 'Changer le mot de passe', + 'settings.password_min_8': 'Doit contenir au moins 8 caractères.', + 'settings.current_password': 'Mot de passe actuel', + 'settings.new_password': 'Nouveau mot de passe', + 'settings.confirm_new_password': 'Confirmer le nouveau mot de passe', + 'settings.sso_note': 'Vous vous connectez via {provider}. Gérez votre mot de passe là-bas.', + 'settings.license': 'Licence', + 'settings.license_mit': 'Licence MIT - toutes les fonctionnalités incluses.', + 'settings.platform_admin_link': 'Les outils d\'administration sont sur la page', + 'settings.platform_admin_page_suffix': '.', + 'settings.user_management': 'Gestion des utilisateurs', + 'settings.loading_users': 'Chargement des utilisateurs...', + 'settings.white_label': 'Marque blanche / Branding', + 'settings.white_label_desc': 'Personnalisez l\'apparence du tableau de bord et du lecteur pour vos clients.', + 'settings.brand_name': 'Nom de la marque', + 'settings.logo_url': 'URL du logo', + 'settings.primary_color': 'Couleur principale', + 'settings.bg_color': 'Couleur de fond', + 'settings.custom_domain': 'Domaine personnalisé', + 'settings.favicon_url': 'URL du favicon', + 'settings.custom_css': 'CSS personnalisé (facultatif)', + 'settings.hide_branding': 'Masquer la marque « ScreenTinker »', + 'settings.save_branding': 'Enregistrer le branding', + 'settings.preview': 'Aperçu', + 'settings.white_label_enterprise_only': 'Le branding personnalisé est disponible sur le plan Enterprise', + 'settings.view_plans': 'Voir les plans', + 'settings.server_info': 'Informations du serveur', + 'settings.server_url': 'URL du serveur', + 'settings.api_endpoint': 'Point de terminaison API', + 'settings.server_url_hint': 'Utilisez cette URL pour configurer l\'app Android', + 'settings.setup_guide': 'Guide d\'installation', + 'settings.setup_step_1': 'Installez l\'APK ScreenTinker sur votre TV via le sideloading', + 'settings.setup_step_2_prefix': 'Ouvrez l\'app et saisissez cette URL du serveur :', + 'settings.setup_step_3': 'L\'app affichera un code d\'appairage à 6 chiffres', + 'settings.setup_step_4': 'Cliquez sur « Ajouter un écran » et saisissez le code', + 'settings.setup_step_5': 'Téléversez du contenu dans la Bibliothèque', + 'settings.setup_step_6': 'Attribuez du contenu à la liste de l\'écran', + 'settings.your_data': 'Vos données', + 'settings.your_data_desc': 'Exportez ou importez vos appareils, contenu, mises en page, calendriers et tous les paramètres. Utilisez cela pour migrer entre cloud et auto-hébergé.', + 'settings.export_my_data': 'Exporter mes données', + 'settings.include_media_zip': 'Inclure les fichiers médias (ZIP)', + 'settings.import_data': 'Importer des données', + 'settings.language': 'Langue', + 'settings.about': 'À propos', + 'settings.about_tagline': 'Système de gestion d\'affichage dynamique.', + 'settings.third_party_licenses': 'Licences tierces', + 'settings.import.reading_file': 'Lecture du fichier...', + 'settings.import.zip_detected': 'Export ZIP détecté : {name} ({size} Mo)
Contient données + fichiers médias.', + 'settings.import.confirm': 'Confirmer l\'import', + 'settings.import.invalid_file': 'Fichier invalide. Doit être un JSON ou ZIP d\'export ScreenTinker.', + 'settings.import.summary_devices': '{n} appareils', + 'settings.import.summary_content': '{n} contenus', + 'settings.import.summary_widgets': '{n} widgets', + 'settings.import.summary_layouts': '{n} mises en page', + 'settings.import.summary_schedules': '{n} calendriers', + 'settings.import.summary_walls': '{n} murs vidéo', + 'settings.import.summary_kiosk': '{n} pages kiosque', + 'settings.import.found_summary': 'Trouvé : {summary}.
De : {email} (exporté {date})', + 'settings.import.empty_export': 'export vide', + 'settings.import.uploading_zip': 'Téléversement et import... Cela peut prendre du temps pour les gros fichiers.', + 'settings.import.importing': 'Import...', + 'settings.import.complete': 'Import terminé : {imported}.', + 'settings.import.pairing_codes_title': 'Codes d\'appairage :', + 'settings.import.pairing_codes_hint': 'Saisissez ces codes sur chaque appareil pour les relier. Les attributions et calendriers seront préservés.', + 'settings.import.failed': 'Échec de l\'import', + 'settings.import.failed_with_error': 'Échec de l\'import : {error}', + 'settings.import.read_failed': 'Échec de lecture du fichier : {error}', + 'settings.toast.support_token_generated': 'Jeton de support généré (valide {hours}h)', + 'settings.toast.import_success': 'Données importées avec succès', + 'settings.toast.name_required': 'Le nom ne peut pas être vide', + 'settings.toast.profile_saved': 'Profil enregistré', + 'settings.toast.current_password_required': 'Saisissez votre mot de passe actuel', + 'settings.toast.new_password_min_8': 'Le nouveau mot de passe doit comporter au moins 8 caractères', + 'settings.toast.passwords_dont_match': 'Les nouveaux mots de passe ne correspondent pas', + 'settings.toast.password_changed': 'Mot de passe changé', + 'settings.toast.branding_saved': 'Branding enregistré', + 'settings.toast.preview_applied': 'Aperçu appliqué (rechargez pour réinitialiser)', + 'settings.toast.plan_updated': 'Plan mis à jour', + 'settings.toast.user_removed': 'Utilisateur retiré', + 'settings.user.col_user': 'Utilisateur', + 'settings.user.col_auth': 'Auth', + 'settings.user.col_role': 'Rôle', + 'settings.user.col_plan': 'Plan', + 'settings.user.col_actions': 'Actions', + 'settings.user.remove': 'Retirer', + 'settings.user.you': 'Vous', + 'settings.user.confirm': 'Confirmer ?', + 'settings.user.count_one': '1 utilisateur inscrit', + 'settings.user.count_other': '{n} utilisateurs inscrits', }; diff --git a/frontend/js/i18n/pt.js b/frontend/js/i18n/pt.js index efcdc58..623d612 100644 --- a/frontend/js/i18n/pt.js +++ b/frontend/js/i18n/pt.js @@ -194,4 +194,250 @@ export default { 'content.toast.folder_deleted': 'Pasta excluída', 'content.toast.moved': 'Movido', 'content.toast.moved_to_root': 'Movido para a raiz', + + // Device detail + 'device.back': 'Voltar para Telas', + 'device.owner_label': 'Proprietário: {owner}', + 'device.rename': 'Renomear', + 'device.screenshot_btn': 'Captura', + 'device.remove': 'Remover', + 'device.click_to_confirm': 'Clique novamente para confirmar', + 'device.prompt_new_name': 'Digite o novo nome:', + 'device.confirm_discard_draft': 'Descartar todas as alterações não publicadas e voltar à última versão publicada?', + 'device.failed_load': 'Falha ao carregar o dispositivo', + 'device.no_screenshot': 'Sem captura disponível. Clique em "Captura" para tirar uma.', + 'device.no_content_assigned': 'Sem conteúdo atribuído', + 'device.now_playing_id': 'Reproduzindo: {id}', + 'device.playlist_count_one': '1 item na playlist', + 'device.playlist_count_other': '{n} itens na playlist', + 'device.tab.now_playing': 'Reproduzindo agora', + 'device.tab.now_playing_tip': 'Captura ao vivo do que está sendo exibido neste dispositivo.', + 'device.tab.playlist': 'Playlist', + 'device.tab.playlist_tip': 'Conteúdo atribuído a este dispositivo. Arraste para reordenar. Adicione mídia, widgets ou páginas de quiosque.', + 'device.tab.info': 'Informações do dispositivo', + 'device.tab.info_tip': 'Telemetria de hardware, orientação, notas e controles do dispositivo.', + 'device.tab.remote': 'Controle remoto', + 'device.tab.remote_tip': 'Visualize a tela em tempo real e envie pressionamentos de tecla. Funciona no APK do Android e no player web.', + 'device.draft.banner_title': 'Alterações não publicadas', + 'device.draft.devices_showing_published': 'Os dispositivos ainda exibem a última versão publicada.', + 'device.draft.never_published': 'Esta playlist nunca foi publicada. Os dispositivos não exibirão nada até você publicar.', + 'device.draft.discard': 'Descartar', + 'device.draft.publish': 'Publicar', + 'device.draft.publishing': 'Publicando...', + 'device.layout.label': 'Layout da tela', + 'device.layout.fullscreen_default': 'Tela cheia (padrão)', + 'device.layout.zones_count': '{name} ({n} zonas)', + 'device.layout.template_zones_count': '[Modelo] {name} ({n} zonas)', + 'device.layout.apply': 'Aplicar', + 'device.playlist.label': 'Playlist', + 'device.playlist.no_playlist': 'Sem playlist', + 'device.playlist.copy_to_btn': 'Copiar para...', + 'device.playlist.add_content_btn': 'Adicionar conteúdo', + 'device.playlist.empty_title': 'Sem conteúdo atribuído', + 'device.playlist.empty_desc': 'Adicione conteúdo da sua biblioteca à playlist desta tela.', + 'device.playlist_picker.with_count': '{name} — {n} itens', + 'device.playlist_picker.with_auto': '{name} (auto) — {n} itens', + 'device.info.status': 'Status', + 'device.info.ip_address': 'Endereço IP', + 'device.info.battery': 'Bateria', + 'device.info.storage': 'Armazenamento', + 'device.info.size_free': '{size} livres', + 'device.info.player_type': 'Tipo de player', + 'device.info.web_player': 'Player web', + 'device.info.wifi': 'Wi-Fi', + 'device.info.uptime': 'Tempo ativo', + 'device.info.android_version': 'Versão do Android', + 'device.info.app_version': 'Versão do app', + 'device.info.screen_resolution': 'Resolução', + 'device.info.ram': 'RAM', + 'device.info.cpu_usage': 'Uso de CPU', + 'device.timeline.title': 'Linha do tempo (últimas 24 horas)', + 'device.timeline.h24_ago': 'há 24h', + 'device.timeline.now': 'Agora', + 'device.timeline.online': 'Online', + 'device.timeline.offline': 'Offline', + 'device.timeline.no_data': 'Sem dados', + 'device.timeline.uptime_pct_tracked': '{pct}% ativo ({n}min monitorados)', + 'device.timeline.uptime_pct_no_data': '{pct}% ativo (sem dados)', + 'device.form.orientation_label': 'Orientação / Rotação', + 'device.form.orientation.landscape': 'Paisagem (0°)', + 'device.form.orientation.portrait': 'Retrato (90° SH)', + 'device.form.orientation.landscape_flipped': 'Paisagem invertida (180°)', + 'device.form.orientation.portrait_flipped': 'Retrato invertido (270° SH)', + 'device.form.default_content_label': 'Conteúdo padrão', + 'device.form.default_content_none': 'Nenhum (mostrar "Aguardando...")', + 'device.form.notes_label': 'Notas', + 'device.form.notes_placeholder': 'Localização, detalhes de instalação, etc.', + 'device.form.save_settings': 'Salvar configurações', + 'device.ctl.reboot_device': 'Reiniciar dispositivo', + 'device.ctl.screen_off': 'Desligar tela', + 'device.ctl.screen_on': 'Ligar tela', + 'device.ctl.launch_player': 'Iniciar player', + 'device.ctl.force_update': 'Forçar atualização', + 'device.ctl.shutdown': 'Desligar', + 'device.remote.start_prompt': 'Clique em "Iniciar controle remoto" para começar', + 'device.remote.start': 'Iniciar controle remoto', + 'device.remote.stop': 'Parar controle remoto', + 'device.remote.vol_up': 'Vol +', + 'device.remote.vol_down': 'Vol -', + 'device.remote.home': 'Início', + 'device.remote.back': 'Voltar', + 'device.remote.recents': 'Recentes', + 'device.remote.power': 'Energia', + 'device.remote.ok': 'OK', + 'device.remote.settings': 'Configurações', + 'device.remote.scrn_off': 'Tela off', + 'device.remote.scrn_on': 'Tela on', + 'device.remote.enable_system_view': 'Ativar visão do sistema', + 'device.remote.system_view_tooltip': 'Solicita ao usuário do dispositivo permitir captura de tela cheia - habilita visualização remota da tela inicial, configurações e outros apps', + 'device.remote.system_view_hint': 'Aprovação única necessária no dispositivo', + 'device.remote.waiting_for_approval': 'Aguardando aprovação do dispositivo...', + 'device.remote.system_view_enabled': 'Visão do sistema ativada', + 'device.remote.unlocked_hint': 'Navegação e controles do sistema desbloqueados', + 'device.pl_item.widget_with_type': 'Widget ({type})', + 'device.pl_item.youtube': 'YouTube', + 'device.pl_item.video': 'Vídeo', + 'device.pl_item.image': 'Imagem', + 'device.pl_item.zone_label': 'Zona: {id}', + 'device.pl_item.no_zone': 'Sem zona', + 'device.pl_item.mute': 'Silenciar', + 'device.pl_item.unmute': 'Ativar áudio', + 'device.pl_item.remove': 'Remover', + 'device.copy.no_other_devices': 'Não há outros dispositivos para copiar', + 'device.copy.prompt': 'Copiar playlist para qual dispositivo?\n\n{list}\n\nDigite o número:', + 'device.copy.invalid_selection': 'Seleção inválida', + 'device.copy.toast': '{n} itens copiados para {device}', + 'device.assign.empty_all': 'Ainda não há conteúdo, widgets ou páginas de quiosque. Crie algo primeiro!', + 'device.assign.modal_title': 'Adicionar à playlist', + 'device.assign.zone_label': 'Zona', + 'device.assign.zone_default': 'Padrão (tela cheia)', + 'device.assign.duration_label': 'Duração (segundos, para imagens/widgets)', + 'device.assign.tab.media': 'Mídia ({n})', + 'device.assign.tab.widgets': 'Widgets ({n})', + 'device.assign.tab.kiosk': 'Quiosque ({n})', + 'device.assign.no_media': 'Nenhuma mídia enviada ainda', + 'device.assign.no_widgets': 'Nenhum widget criado ainda.', + 'device.assign.no_kiosk': 'Nenhuma página de quiosque ainda.', + 'device.assign.create_one': 'Crie um', + 'device.assign.add_selected': 'Adicionar selecionados', + 'device.assign.select_first': 'Selecione algo primeiro', + 'device.assign.kiosk_widget_name': 'Quiosque: {name}', + 'device.toast.screenshot_requested': 'Captura solicitada', + 'device.toast.renamed': 'Tela renomeada', + 'device.toast.removing': 'Removendo...', + 'device.toast.removed': 'Tela removida', + 'device.toast.settings_saved': 'Configurações salvas', + 'device.toast.published': 'Playlist publicada — dispositivos atualizados', + 'device.toast.draft_discarded': 'Alterações do rascunho descartadas', + 'device.toast.playlist_changed': 'Playlist alterada', + 'device.toast.layout_applied': 'Layout aplicado', + 'device.toast.switched_to_fullscreen': 'Alterado para tela cheia', + 'device.toast.added_to_playlist': 'Adicionado à playlist', + 'device.toast.unmuted': 'Áudio ativado', + 'device.toast.muted': 'Silenciado', + 'device.toast.zone_updated': 'Zona atualizada', + 'device.toast.removed_from_playlist': 'Conteúdo removido da playlist', + 'device.toast.playlist_reordered': 'Playlist reordenada', + 'device.toast.reboot_sent': 'Comando de reinício enviado', + 'device.toast.shutdown_sent': 'Comando de desligamento enviado', + 'device.toast.screen_off_sent': 'Comando para desligar tela enviado', + 'device.toast.screen_on_sent': 'Comando para ligar tela enviado', + 'device.toast.launch_sent': 'Comando de início enviado', + 'device.toast.update_triggered': 'Verificação de atualização disparada', + 'device.toast.remote_started': 'Sessão de controle remoto iniciada', + + // Settings + 'settings.title': 'Configurações', + 'settings.subtitle': 'Configuração do servidor e informações de instalação', + 'settings.account': 'Conta', + 'settings.save_profile': 'Salvar perfil', + 'settings.change_password': 'Alterar senha', + 'settings.password_min_8': 'Deve ter no mínimo 8 caracteres.', + 'settings.current_password': 'Senha atual', + 'settings.new_password': 'Nova senha', + 'settings.confirm_new_password': 'Confirmar nova senha', + 'settings.sso_note': 'Você entra via {provider}. Gerencie sua senha lá.', + 'settings.license': 'Licença', + 'settings.license_mit': 'Licença MIT - todos os recursos incluídos.', + 'settings.platform_admin_link': 'As ferramentas de admin da plataforma estão na página', + 'settings.platform_admin_page_suffix': '.', + 'settings.user_management': 'Gestão de usuários', + 'settings.loading_users': 'Carregando usuários...', + 'settings.white_label': 'Marca branca / Branding', + 'settings.white_label_desc': 'Personalize a aparência do painel e do player para seus clientes.', + 'settings.brand_name': 'Nome da marca', + 'settings.logo_url': 'URL do logotipo', + 'settings.primary_color': 'Cor primária', + 'settings.bg_color': 'Cor de fundo', + 'settings.custom_domain': 'Domínio personalizado', + 'settings.favicon_url': 'URL do favicon', + 'settings.custom_css': 'CSS personalizado (opcional)', + 'settings.hide_branding': 'Ocultar marca "ScreenTinker"', + 'settings.save_branding': 'Salvar branding', + 'settings.preview': 'Pré-visualizar', + 'settings.white_label_enterprise_only': 'Branding personalizado disponível no plano Enterprise', + 'settings.view_plans': 'Ver planos', + 'settings.server_info': 'Informações do servidor', + 'settings.server_url': 'URL do servidor', + 'settings.api_endpoint': 'Endpoint da API', + 'settings.server_url_hint': 'Use esta URL ao configurar o app Android', + 'settings.setup_guide': 'Guia de instalação', + 'settings.setup_step_1': 'Instale o APK do ScreenTinker na sua TV via sideloading', + 'settings.setup_step_2_prefix': 'Abra o app e digite esta URL do servidor:', + 'settings.setup_step_3': 'O app exibirá um código de pareamento de 6 dígitos', + 'settings.setup_step_4': 'Clique em "Adicionar tela" no painel e digite o código', + 'settings.setup_step_5': 'Envie conteúdo na Biblioteca de conteúdo', + 'settings.setup_step_6': 'Atribua conteúdo à playlist da tela', + 'settings.your_data': 'Seus dados', + 'settings.your_data_desc': 'Exporte ou importe seus dispositivos, conteúdo, layouts, agendas e todas as configurações. Use para migrar entre nuvem e auto-hospedado.', + 'settings.export_my_data': 'Exportar meus dados', + 'settings.include_media_zip': 'Incluir arquivos de mídia (ZIP)', + 'settings.import_data': 'Importar dados', + 'settings.language': 'Idioma', + 'settings.about': 'Sobre', + 'settings.about_tagline': 'Sistema de gerenciamento de sinalização digital.', + 'settings.third_party_licenses': 'Licenças de terceiros', + 'settings.import.reading_file': 'Lendo arquivo...', + 'settings.import.zip_detected': 'Exportação ZIP detectada: {name} ({size} MB)
Contém dados + arquivos de mídia.', + 'settings.import.confirm': 'Confirmar importação', + 'settings.import.invalid_file': 'Arquivo inválido. Deve ser JSON ou ZIP de exportação do ScreenTinker.', + 'settings.import.summary_devices': '{n} dispositivos', + 'settings.import.summary_content': '{n} itens de conteúdo', + 'settings.import.summary_widgets': '{n} widgets', + 'settings.import.summary_layouts': '{n} layouts', + 'settings.import.summary_schedules': '{n} agendas', + 'settings.import.summary_walls': '{n} paredes de vídeo', + 'settings.import.summary_kiosk': '{n} páginas de quiosque', + 'settings.import.found_summary': 'Encontrado: {summary}.
De: {email} (exportado {date})', + 'settings.import.empty_export': 'exportação vazia', + 'settings.import.uploading_zip': 'Enviando e importando... Pode demorar para arquivos grandes.', + 'settings.import.importing': 'Importando...', + 'settings.import.complete': 'Importação concluída: {imported}.', + 'settings.import.pairing_codes_title': 'Códigos de pareamento:', + 'settings.import.pairing_codes_hint': 'Digite estes códigos em cada dispositivo para revinculá-los. Atribuições e agendas serão preservadas.', + 'settings.import.failed': 'Falha na importação', + 'settings.import.failed_with_error': 'Falha na importação: {error}', + 'settings.import.read_failed': 'Falha ao ler o arquivo: {error}', + 'settings.toast.support_token_generated': 'Token de suporte gerado (válido {hours}h)', + 'settings.toast.import_success': 'Dados importados com sucesso', + 'settings.toast.name_required': 'O nome não pode ficar em branco', + 'settings.toast.profile_saved': 'Perfil salvo', + 'settings.toast.current_password_required': 'Digite sua senha atual', + 'settings.toast.new_password_min_8': 'A nova senha deve ter no mínimo 8 caracteres', + 'settings.toast.passwords_dont_match': 'As novas senhas não conferem', + 'settings.toast.password_changed': 'Senha alterada', + 'settings.toast.branding_saved': 'Branding salvo', + 'settings.toast.preview_applied': 'Pré-visualização aplicada (recarregue para redefinir)', + 'settings.toast.plan_updated': 'Plano atualizado', + 'settings.toast.user_removed': 'Usuário removido', + 'settings.user.col_user': 'Usuário', + 'settings.user.col_auth': 'Auth', + 'settings.user.col_role': 'Função', + 'settings.user.col_plan': 'Plano', + 'settings.user.col_actions': 'Ações', + 'settings.user.remove': 'Remover', + 'settings.user.you': 'Você', + 'settings.user.confirm': 'Confirmar?', + 'settings.user.count_one': '1 usuário registrado', + 'settings.user.count_other': '{n} usuários registrados', }; diff --git a/frontend/js/views/device-detail.js b/frontend/js/views/device-detail.js index cd0305c..dc9356e 100644 --- a/frontend/js/views/device-detail.js +++ b/frontend/js/views/device-detail.js @@ -2,6 +2,7 @@ import { api } from '../api.js'; import { on, off, requestScreenshot, startRemote, stopRemote, sendTouch, sendKey, sendCommand } from '../socket.js'; import { showToast } from '../components/toast.js'; import { esc } from '../utils.js'; +import { t, tn } from '../i18n.js'; let currentDevice = null; let statusHandler = null; @@ -33,10 +34,10 @@ export function render(container, deviceId) { - Back to Displays + ${t('device.back')}
-

Loading...

+

${t('common.loading')}

`; @@ -94,7 +95,7 @@ export function render(container, deviceId) { if (data.device_id !== deviceId) return; const el = document.getElementById('nowPlayingInfo'); if (el && data.current_content_id) { - el.textContent = `Playing: ${data.current_content_id}`; + el.textContent = t('device.now_playing_id', { id: data.current_content_id }); } }; @@ -115,26 +116,26 @@ async function loadDevice(deviceId, activeTab = null) {

${device.name}

${device.status} - ${device.owner_name || device.owner_email ? `Owner: ${device.owner_name || device.owner_email}` : ''} + ${device.owner_name || device.owner_email ? `${t('device.owner_label', { owner: device.owner_name || device.owner_email })}` : ''}
- + - +
-
Now Playing ?
-
Playlist ?
-
Device Info ?
-
Remote Control ?
+
${t('device.tab.now_playing')} ?
+
${t('device.tab.playlist')} ?
+
${t('device.tab.info')} ?
+
${t('device.tab.remote')} ?
@@ -148,12 +149,12 @@ async function loadDevice(deviceId, activeTab = null) { - No screenshot available. Click "Screenshot" to capture one. + ${t('device.no_screenshot')} ` }

- ${device.assignments?.length ? `${device.assignments.length} item(s) in playlist` : 'No content assigned'} + ${device.assignments?.length ? tn('device.playlist_count', device.assignments.length) : t('device.no_content_assigned')}

@@ -164,13 +165,13 @@ async function loadDevice(deviceId, activeTab = null) {
-
Unpublished changes
-
${device.playlist_has_published ? 'Devices are still showing the last published version.' : 'This playlist has never been published. Devices will show nothing until you publish.'}
+
${t('device.draft.banner_title')}
+
${device.playlist_has_published ? t('device.draft.devices_showing_published') : t('device.draft.never_published')}
- ${device.playlist_has_published ? '' : ''} - + ${device.playlist_has_published ? `` : ''} +
` : ''} @@ -180,28 +181,28 @@ async function loadDevice(deviceId, activeTab = null) {
-
Screen Layout
+
${t('device.layout.label')}
- +
-

Playlist

+

${t('device.playlist.label')}

- +
@@ -214,16 +215,16 @@ async function loadDevice(deviceId, activeTab = null) {
-
Status
+
${t('device.info.status')}
${device.status}
-
IP Address
+
${t('device.info.ip_address')}
${device.ip_address || '--'}
${device.android_version && !device.android_version.startsWith('Web/') ? `
-
Battery
+
${t('device.info.battery')}
${latestTelemetry.battery_level != null ? latestTelemetry.battery_level + '%' : '--'}
${latestTelemetry.battery_level != null ? `
@@ -232,8 +233,8 @@ async function loadDevice(deviceId, activeTab = null) {
` : ''}
-
Storage
-
${latestTelemetry.storage_free_mb ? formatBytes(latestTelemetry.storage_free_mb) + ' free' : '--'}
+
${t('device.info.storage')}
+
${latestTelemetry.storage_free_mb ? t('device.info.size_free', { size: formatBytes(latestTelemetry.storage_free_mb) }) : '--'}
${latestTelemetry.storage_total_mb ? `
` : `
-
Player Type
-
Web Player
+
${t('device.info.player_type')}
+
${t('device.info.web_player')}
`} ${device.android_version && !device.android_version.startsWith('Web/') ? `
-
WiFi
+
${t('device.info.wifi')}
${latestTelemetry.wifi_ssid || '--'}
${latestTelemetry.wifi_rssi ? latestTelemetry.wifi_rssi + ' dBm' : ''}
` : ''}
-
Uptime
+
${t('device.info.uptime')}
${formatUptime(latestTelemetry.uptime_seconds)}
${device.android_version && !device.android_version.startsWith('Web/') ? `
-
Android Version
+
${t('device.info.android_version')}
${device.android_version}
-
App Version
+
${t('device.info.app_version')}
${device.app_version || '--'}
` : ''}
-
Screen Resolution
+
${t('device.info.screen_resolution')}
${device.screen_width && device.screen_height ? device.screen_width + 'x' + device.screen_height : '--'}
${device.android_version && !device.android_version.startsWith('Web/') ? `
-
RAM
-
${latestTelemetry.ram_free_mb ? formatBytes(latestTelemetry.ram_free_mb) + ' free' : '--'}
+
${t('device.info.ram')}
+
${latestTelemetry.ram_free_mb ? t('device.info.size_free', { size: formatBytes(latestTelemetry.ram_free_mb) }) : '--'}
-
CPU Usage
+
${t('device.info.cpu_usage')}
${latestTelemetry.cpu_usage != null ? latestTelemetry.cpu_usage.toFixed(1) + '%' : '--'}
` : ''} @@ -285,16 +286,16 @@ async function loadDevice(deviceId, activeTab = null) {
-

Uptime Timeline (Last 24 Hours)

+

${t('device.timeline.title')}

- 24h ago - Now + ${t('device.timeline.h24_ago')} + ${t('device.timeline.now')}
- Online - Offline - No data + ${t('device.timeline.online')} + ${t('device.timeline.offline')} + ${t('device.timeline.no_data')}
@@ -302,63 +303,63 @@ async function loadDevice(deviceId, activeTab = null) {
- +
- +
- - + +
- +
@@ -375,24 +376,24 @@ async function loadDevice(deviceId, activeTab = null) { -

Click "Start Remote" to begin

+

${t('device.remote.start_prompt')}

- - + +
- - + +
- - - - + + + +
@@ -400,19 +401,19 @@ async function loadDevice(deviceId, activeTab = null) {
- +
- +
- - + +
- - Requires one-time approval on device + ${t('device.remote.system_view_hint')}
@@ -431,13 +432,13 @@ async function loadDevice(deviceId, activeTab = null) { // Unlock the system controls after a short delay (user needs to tap "Start now" on device) const btn = document.getElementById('enableSystemCaptureBtn'); const hint = document.getElementById('systemViewHint'); - if (btn) { btn.textContent = 'Waiting for device approval...'; btn.disabled = true; } + if (btn) { btn.textContent = t('device.remote.waiting_for_approval'); btn.disabled = true; } // Check periodically if the device granted it (we'll know because screenshots keep coming even after Home) setTimeout(() => { const controls = document.getElementById('systemViewControls'); if (controls) { controls.style.opacity = '1'; controls.style.pointerEvents = 'auto'; } - if (btn) { btn.textContent = 'System View Enabled'; btn.style.background = 'var(--success)'; } - if (hint) hint.textContent = 'Navigation and system controls unlocked'; + if (btn) { btn.textContent = t('device.remote.system_view_enabled'); btn.style.background = 'var(--success)'; } + if (hint) hint.textContent = t('device.remote.unlocked_hint'); }, 5000); }; @@ -465,13 +466,13 @@ async function loadDevice(deviceId, activeTab = null) { } } catch (err) { - contentEl.innerHTML = `

Failed to load device

${esc(err.message)}

`; + contentEl.innerHTML = `

${t('device.failed_load')}

${esc(err.message)}

`; } } function renderPlaylist(assignments) { if (!assignments.length) { - return `

No content assigned

Add content from your library to this display's playlist.

`; + return `

${t('device.playlist.empty_title')}

${t('device.playlist.empty_desc')}

`; } return assignments.map((a, i) => `
@@ -493,10 +494,10 @@ function renderPlaylist(assignments) {
` }
-
${esc(a.filename || a.widget_name || 'Unknown')}
+
${esc(a.filename || a.widget_name || t('common.unknown'))}
- ${a.widget_id && !a.content_id ? `Widget (${a.widget_type || 'custom'})` : a.mime_type === 'video/youtube' ? 'YouTube' : a.mime_type?.startsWith('video/') ? 'Video' : 'Image'} - ${a.zone_id ? ` · Zone: ${a.zone_id.slice(0,8)}` : ''} + ${a.widget_id && !a.content_id ? t('device.pl_item.widget_with_type', { type: a.widget_type || 'custom' }) : a.mime_type === 'video/youtube' ? t('device.pl_item.youtube') : a.mime_type?.startsWith('video/') ? t('device.pl_item.video') : t('device.pl_item.image')} + ${a.zone_id ? ` · ${t('device.pl_item.zone_label', { id: a.zone_id.slice(0,8) })}` : ''} ${a.content_duration ? ` · ${Math.floor(a.content_duration / 60)}:${String(Math.floor(a.content_duration % 60)).padStart(2, '0')}` : ''} ${!a.content_duration && !a.mime_type?.startsWith('video/') && a.duration_sec ? ` · ${a.duration_sec}s` : ''} ${a.schedule_start ? ` · ${a.schedule_start}-${a.schedule_end}` : ''} @@ -504,15 +505,15 @@ function renderPlaylist(assignments) {
- -
`; - }).join('') || '

No widgets created yet. Create one

'} + }).join('') || `

${t('device.assign.no_widgets')} ${t('device.assign.create_one')}

`}
- `).join('') || '

No kiosk pages yet. Create one

'} + `).join('') || `

${t('device.assign.no_kiosk')} ${t('device.assign.create_one')}

`} `; @@ -995,7 +998,7 @@ async function setupPlaylistActions(device) { modal.querySelector('#cancelAssign').onclick = () => modal.remove(); modal.querySelector('#confirmAssign').onclick = async () => { if (!selectedId) { - showToast('Select something first', 'error'); + showToast(t('device.assign.select_first'), 'error'); return; } const duration = parseInt(modal.querySelector('#assignDuration').value) || 10; @@ -1011,13 +1014,13 @@ async function setupPlaylistActions(device) { const wRes = await fetch('/api/widgets', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ widget_type: 'webpage', name: `Kiosk: ${kioskPages.find(k => k.id === selectedId)?.name || 'Page'}`, config: { url: `${serverUrl}/api/kiosk/${selectedId}/render` } }) + body: JSON.stringify({ widget_type: 'webpage', name: t('device.assign.kiosk_widget_name', { name: kioskPages.find(k => k.id === selectedId)?.name || 'Page' }), config: { url: `${serverUrl}/api/kiosk/${selectedId}/render` } }) }); const widget = await wRes.json(); await api.addAssignment(device.id, { widget_id: widget.id, duration_sec: 0 }); } modal.remove(); - showToast('Added to playlist', 'success'); + showToast(t('device.toast.added_to_playlist'), 'success'); loadDevice(device.id, 'playlist'); } catch (err) { showToast(err.message, 'error'); @@ -1060,7 +1063,7 @@ function attachRemoveHandlers(device) { select.onchange = async () => { try { await api.updateAssignment(assignmentId, { zone_id: select.value || null }); - showToast(`Zone updated`, 'success'); + showToast(t('device.toast.zone_updated'), 'success'); loadDevice(device.id, 'playlist'); } catch (err) { showToast(err.message, 'error'); } }; @@ -1076,7 +1079,7 @@ function attachRemoveHandlers(device) { const currentlyMuted = btn.dataset.muted === '1'; try { await api.updateAssignment(id, { muted: !currentlyMuted }); - showToast(currentlyMuted ? 'Unmuted' : 'Muted', 'success'); + showToast(currentlyMuted ? t('device.toast.unmuted') : t('device.toast.muted'), 'success'); loadDevice(device.id, 'playlist'); } catch (err) { showToast(err.message, 'error'); } }); @@ -1089,7 +1092,7 @@ function attachRemoveHandlers(device) { const id = btn.dataset.removeAssignment; try { await api.deleteAssignment(id); - showToast('Content removed from playlist', 'success'); + showToast(t('device.toast.removed_from_playlist'), 'success'); loadDevice(device.id, 'playlist'); } catch (err) { showToast(err.message, 'error'); @@ -1140,7 +1143,7 @@ function attachRemoveHandlers(device) { try { await api.reorderAssignments(device.id, newOrder); - showToast('Playlist reordered', 'success'); + showToast(t('device.toast.playlist_reordered'), 'success'); loadDevice(device.id, 'playlist'); } catch (err) { showToast(err.message, 'error'); @@ -1193,7 +1196,11 @@ function renderUptimeTimeline(uptimeData, statusLog = []) { const knownSlots = slotStatus.filter(s => s !== 'unknown').length; const onlineSlots = slotStatus.filter(s => s === 'online').length; const uptimePct = knownSlots > 0 ? Math.round((onlineSlots / knownSlots) * 100) : 0; - if (percentEl) percentEl.textContent = `${uptimePct}% uptime (${knownSlots > 0 ? knownSlots * 15 + 'min tracked' : 'no data'})`; + if (percentEl) { + percentEl.textContent = knownSlots > 0 + ? t('device.timeline.uptime_pct_tracked', { pct: uptimePct, n: knownSlots * 15 }) + : t('device.timeline.uptime_pct_no_data', { pct: uptimePct }); + } // Color map const colors = { @@ -1207,7 +1214,7 @@ function renderUptimeTimeline(uptimeData, statusLog = []) { timeline.innerHTML = slotStatus.map((status, i) => { const time = new Date((dayAgo + i * slotDuration) * 1000); const label = time.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - const statusLabel = status === 'unknown' ? 'No data' : status.charAt(0).toUpperCase() + status.slice(1); + const statusLabel = status === 'unknown' ? t('device.timeline.no_data') : status === 'online' ? t('device.timeline.online') : t('device.timeline.offline'); return `
`; }).join(''); } @@ -1218,11 +1225,11 @@ function updateTelemetryDisplay(telemetry) { if (el) el.textContent = val; }; if (telemetry.battery_level != null) update('telBattery', telemetry.battery_level + '%'); - if (telemetry.storage_free_mb) update('telStorage', formatBytes(telemetry.storage_free_mb) + ' free'); + if (telemetry.storage_free_mb) update('telStorage', t('device.info.size_free', { size: formatBytes(telemetry.storage_free_mb) })); if (telemetry.wifi_ssid) update('telWifi', telemetry.wifi_ssid); if (telemetry.wifi_rssi) update('telRssi', telemetry.wifi_rssi + ' dBm'); if (telemetry.uptime_seconds) update('telUptime', formatUptime(telemetry.uptime_seconds)); - if (telemetry.ram_free_mb) update('telRam', formatBytes(telemetry.ram_free_mb) + ' free'); + if (telemetry.ram_free_mb) update('telRam', t('device.info.size_free', { size: formatBytes(telemetry.ram_free_mb) })); if (telemetry.cpu_usage != null) update('telCpu', telemetry.cpu_usage.toFixed(1) + '%'); } diff --git a/frontend/js/views/settings.js b/frontend/js/views/settings.js index f237b04..1720dd1 100644 --- a/frontend/js/views/settings.js +++ b/frontend/js/views/settings.js @@ -1,6 +1,6 @@ import { api } from '../api.js'; import { showToast } from '../components/toast.js'; -import { getLanguage, setLanguage, getAvailableLanguages } from '../i18n.js'; +import { getLanguage, setLanguage, getAvailableLanguages, t, tn } from '../i18n.js'; import { esc } from '../utils.js'; import { resetBranding } from '../branding.js'; @@ -17,118 +17,115 @@ export async function render(container) { container.innerHTML = `
-

Account

+

${t('settings.account')}

-
-
+
+
- + ${user.auth_provider === 'local' ? `
-

Change Password

-

Must be at least 8 characters.

+

${t('settings.change_password')}

+

${t('settings.password_min_8')}

-
-
-
+
+
+
- +
` : ` -

You sign in via ${esc(user.auth_provider || 'SSO')}. Manage your password there.

+

${t('settings.sso_note', { provider: esc(user.auth_provider || 'SSO') })}

`}
${isAdmin ? `
-

License

-

MIT License - all features included.

+

${t('settings.license')}

+

${t('settings.license_mit')}

- ${isSuperAdmin ? '

Platform admin tools are in the Admin page.

' : ''} + ${isSuperAdmin ? `

${t('settings.platform_admin_link')} ${t('nav.admin')} ${t('settings.platform_admin_page_suffix')}

` : ''}
-

User Management

-

Loading users...

+

${t('settings.user_management')}

+

${t('settings.loading_users')}

-

White Label / Branding

+

${t('settings.white_label')}

-

Customize the look of your dashboard and player for your clients.

+

${t('settings.white_label_desc')}

-
-
-
-
-
-
+
+
+
+
+
+
-
-
- - +
+
+ +
` : ''}
-

Server Information

+

${t('settings.server_info')}

-
Server URL
+
${t('settings.server_url')}
${serverUrl}
-

Use this URL when setting up the Android app

+

${t('settings.server_url_hint')}

-
API Endpoint
+
${t('settings.api_endpoint')}
${serverUrl}/api
-

Setup Guide

+

${t('settings.setup_guide')}

    -
  1. Install the ScreenTinker APK on your Apolosign portable TV via sideloading
  2. -
  3. Open the app and enter this server URL: ${serverUrl}
  4. -
  5. The app will display a 6-digit pairing code
  6. -
  7. Click "Add Display" on the dashboard and enter the pairing code
  8. -
  9. Upload content in the Content Library
  10. -
  11. Assign content to the display's Playlist
  12. +
  13. ${t('settings.setup_step_1')}
  14. +
  15. ${t('settings.setup_step_2_prefix')} ${serverUrl}
  16. +
  17. ${t('settings.setup_step_3')}
  18. +
  19. ${t('settings.setup_step_4')}
  20. +
  21. ${t('settings.setup_step_5')}
  22. +
  23. ${t('settings.setup_step_6')}
- ${isAdmin ? ` - ` : ''} -
-

Your Data

-

Export or import your devices, content, layouts, schedules, and all settings. Use this to migrate between cloud and self-hosted instances.

+

${t('settings.your_data')}

+

${t('settings.your_data_desc')}

@@ -136,23 +133,23 @@ export async function render(container) {
-

Language

+

${t('settings.language')}

-

About

+

${t('settings.about')}

ScreenTinker v1.4.1

-

Digital signage management system.

+

${t('settings.about_tagline')}

- Terms of Service + ${t('auth.terms')}  ·  - Privacy Policy + ${t('auth.privacy')}  ·  - Third-Party Licenses + ${t('settings.third_party_licenses')}

@@ -177,7 +174,7 @@ export async function render(container) { if (res.ok) { document.getElementById('supportTokenOutput').value = data.token; document.getElementById('supportTokenResult').style.display = 'block'; - showToast(`Support token generated (valid ${hours}h)`, 'success'); + showToast(t('settings.toast.support_token_generated', { hours }), 'success'); } else showToast(data.error, 'error'); } catch (err) { showToast(err.message, 'error'); } }); @@ -204,35 +201,35 @@ export async function render(container) { statusEl.style.background = 'var(--bg-secondary)'; statusEl.style.border = '1px solid var(--border)'; statusEl.style.color = 'var(--text-secondary)'; - statusEl.textContent = 'Reading file...'; + statusEl.textContent = t('settings.import.reading_file'); try { let data; if (isZip) { // For ZIP, show basic info and skip preview parsing data = { format: 'screentinker-export-v1', _isZip: true }; - statusEl.innerHTML = `ZIP export detected: ${esc(file.name)} (${(file.size / 1048576).toFixed(1)} MB)
Contains data + media files.

`; + statusEl.innerHTML = `${t('settings.import.zip_detected', { name: esc(file.name), size: (file.size / 1048576).toFixed(1) })}

`; } else { const text = await file.text(); data = JSON.parse(text); if (!data.format || !data.format.startsWith('screentinker-export')) { statusEl.style.color = 'var(--danger)'; - statusEl.textContent = 'Invalid file. Must be a ScreenTinker export JSON or ZIP.'; + statusEl.textContent = t('settings.import.invalid_file'); return; } const summary = [ - data.devices?.length ? `${data.devices.length} devices` : null, - data.content?.length ? `${data.content.length} content items` : null, - data.widgets?.length ? `${data.widgets.length} widgets` : null, - data.layouts?.length ? `${data.layouts.length} layouts` : null, - data.schedules?.length ? `${data.schedules.length} schedules` : null, - data.video_walls?.length ? `${data.video_walls.length} video walls` : null, - data.kiosk_pages?.length ? `${data.kiosk_pages.length} kiosk pages` : null, + data.devices?.length ? t('settings.import.summary_devices', { n: data.devices.length }) : null, + data.content?.length ? t('settings.import.summary_content', { n: data.content.length }) : null, + data.widgets?.length ? t('settings.import.summary_widgets', { n: data.widgets.length }) : null, + data.layouts?.length ? t('settings.import.summary_layouts', { n: data.layouts.length }) : null, + data.schedules?.length ? t('settings.import.summary_schedules', { n: data.schedules.length }) : null, + data.video_walls?.length ? t('settings.import.summary_walls', { n: data.video_walls.length }) : null, + data.kiosk_pages?.length ? t('settings.import.summary_kiosk', { n: data.kiosk_pages.length }) : null, ].filter(Boolean).join(', '); - statusEl.innerHTML = `Found: ${esc(summary) || 'empty export'}.
From: ${esc(data.user?.email) || 'unknown'} (exported ${esc(data.exported_at?.split('T')[0]) || 'unknown'})

`; + statusEl.innerHTML = `${t('settings.import.found_summary', { summary: esc(summary) || t('settings.import.empty_export'), email: esc(data.user?.email) || t('common.unknown'), date: esc(data.exported_at?.split('T')[0]) || t('common.unknown') })}

`; } document.getElementById('cancelImportBtn').onclick = () => { statusEl.style.display = 'none'; e.target.value = ''; }; document.getElementById('confirmImportBtn').onclick = async () => { - statusEl.innerHTML = isZip ? 'Uploading and importing... This may take a moment for large files.' : 'Importing...'; + statusEl.innerHTML = isZip ? t('settings.import.uploading_zip') : t('settings.import.importing'); try { const token = localStorage.getItem('token'); let res; @@ -255,28 +252,28 @@ export async function render(container) { if (res.ok) { const imported = Object.entries(result.stats).filter(([k,v]) => v > 0 && k !== 'files_restored').map(([k,v]) => `${v} ${k}`).join(', '); statusEl.style.color = 'var(--success)'; - let html = `Import complete: ${imported}.`; + let html = t('settings.import.complete', { imported }); if (result.device_pairings?.length) { - html += `

Device Pairing Codes:
` + + html += `

${t('settings.import.pairing_codes_title')}
` + result.device_pairings.map(d => ``).join('') + - `
${d.name}${d.pairing_code}

Enter these codes on each device to re-link them. All assignments and schedules will be preserved.`; + `
${t('settings.import.pairing_codes_hint')}`; } html += `

${(result.notes || []).map(n => '• ' + n).join('
')}`; statusEl.innerHTML = html; - showToast('Data imported successfully', 'success'); + showToast(t('settings.toast.import_success'), 'success'); } else { statusEl.style.color = 'var(--danger)'; - statusEl.textContent = result.error || 'Import failed'; + statusEl.textContent = result.error || t('settings.import.failed'); } } catch (err) { statusEl.style.color = 'var(--danger)'; - statusEl.textContent = 'Import failed: ' + err.message; + statusEl.textContent = t('settings.import.failed_with_error', { error: err.message }); } e.target.value = ''; }; } catch (err) { statusEl.style.color = 'var(--danger)'; - statusEl.textContent = 'Failed to read file: ' + err.message; + statusEl.textContent = t('settings.import.read_failed', { error: err.message }); } }); @@ -288,14 +285,14 @@ export async function render(container) { document.getElementById('saveAcctBtn')?.addEventListener('click', async () => { const name = document.getElementById('acctName').value.trim(); - if (!name) return showToast('Name cannot be empty', 'error'); + if (!name) return showToast(t('settings.toast.name_required'), 'error'); const btn = document.getElementById('saveAcctBtn'); btn.disabled = true; try { const updated = await api.updateMe({ name }); const stored = JSON.parse(localStorage.getItem('user') || '{}'); localStorage.setItem('user', JSON.stringify({ ...stored, ...updated })); - showToast('Profile saved', 'success'); + showToast(t('settings.toast.profile_saved'), 'success'); } catch (err) { showToast(err.message, 'error'); } finally { @@ -307,9 +304,9 @@ export async function render(container) { const current = document.getElementById('acctCurrentPw').value; const next = document.getElementById('acctNewPw').value; const confirm = document.getElementById('acctConfirmPw').value; - if (!current) return showToast('Enter your current password', 'error'); - if (next.length < 8) return showToast('New password must be at least 8 characters', 'error'); - if (next !== confirm) return showToast('New passwords do not match', 'error'); + if (!current) return showToast(t('settings.toast.current_password_required'), 'error'); + if (next.length < 8) return showToast(t('settings.toast.new_password_min_8'), 'error'); + if (next !== confirm) return showToast(t('settings.toast.passwords_dont_match'), 'error'); const btn = document.getElementById('changePwBtn'); btn.disabled = true; try { @@ -317,7 +314,7 @@ export async function render(container) { document.getElementById('acctCurrentPw').value = ''; document.getElementById('acctNewPw').value = ''; document.getElementById('acctConfirmPw').value = ''; - showToast('Password changed', 'success'); + showToast(t('settings.toast.password_changed'), 'success'); } catch (err) { showToast(err.message, 'error'); } finally { @@ -336,10 +333,10 @@ async function loadWhiteLabel() { const section = document.getElementById('whiteLabelSection'); if (section && user.plan_id !== 'enterprise' && user.role !== 'superadmin') { section.innerHTML = ` -

White Label / Branding

+

${t('settings.white_label')}

-

Custom branding is available on the Enterprise plan

- View Plans +

${t('settings.white_label_enterprise_only')}

+ ${t('settings.view_plans')}
`; return; @@ -376,7 +373,7 @@ async function loadWhiteLabel() { }) }); await resetBranding(); - showToast('Branding saved', 'success'); + showToast(t('settings.toast.branding_saved'), 'success'); } catch (err) { showToast(err.message, 'error'); } @@ -387,7 +384,7 @@ async function loadWhiteLabel() { const bg = document.getElementById('wlBgColor').value; document.documentElement.style.setProperty('--accent', primary); document.documentElement.style.setProperty('--bg-primary', bg); - showToast('Preview applied (refresh to reset)', 'info'); + showToast(t('settings.toast.preview_applied'), 'info'); }); } @@ -408,11 +405,11 @@ async function loadUsers() { - - - - - + + + + + @@ -434,14 +431,14 @@ async function loadUsers() { `).join('')}
UserAuthRolePlanActions${t('settings.user.col_user')}${t('settings.user.col_auth')}${t('settings.user.col_role')}${t('settings.user.col_plan')}${t('settings.user.col_actions')}
- ${u.id !== currentUser.id ? `` : 'You'} + ${u.id !== currentUser.id ? `` : `${t('settings.user.you')}`}
-

${users.length} user(s) registered

+

${tn('settings.user.count', users.length)}

`; // Plan change handlers @@ -451,7 +448,7 @@ async function loadUsers() { const planId = select.value; try { await api.assignPlan(userId, planId); - showToast('Plan updated', 'success'); + showToast(t('settings.toast.plan_updated'), 'success'); } catch (err) { showToast(err.message, 'error'); loadUsers(); // Revert @@ -466,7 +463,7 @@ async function loadUsers() { if (confirming) { try { await api.deleteUser(btn.dataset.userId); - showToast('User removed', 'success'); + showToast(t('settings.toast.user_removed'), 'success'); loadUsers(); } catch (err) { showToast(err.message, 'error'); @@ -474,12 +471,12 @@ async function loadUsers() { return; } confirming = true; - btn.textContent = 'Confirm?'; + btn.textContent = t('settings.user.confirm'); btn.style.background = 'var(--danger)'; btn.style.color = 'white'; setTimeout(() => { confirming = false; - btn.textContent = 'Remove'; + btn.textContent = t('settings.user.remove'); btn.style.background = ''; btn.style.color = ''; }, 3000);