diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 369be86..23737ee 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -141,6 +141,7 @@ _KX_UI_ASSETS: dict[str, str] = { "style.css": "text/css", "app.js": "application/javascript", } +_KX_UI_TRANSLATION_RE = re.compile(r"^translations/([a-z]{2}(?:-[a-z]{2})?)\.json$") # Ring-Buffer für Browser-Log-Stream (letzte 200 Einträge) import collections as _collections @@ -2152,11 +2153,21 @@ class KobraXBridge: }) async def handle_kx_ui_asset(self, request): - name = request.match_info.get("name", "") + name = request.match_info.get("name", "").lstrip("/") ctype = _KX_UI_ASSETS.get(name) - if ctype is None: - raise web.HTTPNotFound() - path = os.path.join(_WEB_BASE, "web", "themes", self._ui_theme, name) + cache_control = "public, max-age=86400" + + if ctype is not None: + path = os.path.join(_WEB_BASE, "web", "themes", self._ui_theme, name) + else: + m = _KX_UI_TRANSLATION_RE.match(name) + if not m: + raise web.HTTPNotFound() + lang = m.group(1) + ctype = "application/json" + cache_control = "no-store" + path = os.path.join(_WEB_BASE, "web", "translations", f"{lang}.json") + try: raw = pathlib.Path(path).read_text(encoding="utf-8") except OSError: @@ -2166,7 +2177,7 @@ class KobraXBridge: return web.Response( text=raw, content_type=ctype, - headers={"Cache-Control": "public, max-age=86400"}, + headers={"Cache-Control": cache_control}, ) async def handle_index(self, request): @@ -3505,7 +3516,7 @@ def build_app(bridge: KobraXBridge) -> web.Application: r.add_post("/kx/files/{file_id}/verify", bridge.handle_kx_file_verify) r.add_get("/kx/filament/slots", bridge.handle_kx_filament_slots) r.add_get("/kx/history", bridge.handle_kx_history) - r.add_get("/kx/ui/{name}", bridge.handle_kx_ui_asset) + r.add_get("/kx/ui/{name:.*}", bridge.handle_kx_ui_asset) r.add_get("/kx/files/{id}/objects", bridge.handle_kx_file_objects) r.add_post("/kx/skip", bridge.handle_kx_skip) r.add_post("/kx/skip/query", bridge.handle_kx_skip_query) diff --git a/web/themes/default/app.js b/web/themes/default/app.js index 611e22c..e6a34d0 100644 --- a/web/themes/default/app.js +++ b/web/themes/default/app.js @@ -89,144 +89,90 @@ function toggleTheme(){ (function(){var t=localStorage.getItem('theme');if(t)document.documentElement.setAttribute('data-theme',t)})(); // ── i18n ── -var LANG_DE={ - header_status_standby:'Bereit',header_status_printing:'Druckt',header_status_complete:'Fertig',header_status_error:'Fehler', - kobra_free:'Bereit',kobra_busy:'Beschäftigt',kobra_printing:'Druckt',kobra_preheating:'Aufheizen',kobra_auto_leveling:'Nivellierung',kobra_checking:'Prüfung',kobra_updated:'Aktualisierung',kobra_init:'Initialisierung',kobra_pausing:'Pausiert...',kobra_paused:'Pausiert',kobra_resuming:'Fortsetzen...',kobra_resumed:'Fortgesetzt',kobra_stopping:'Stoppt...',kobra_stoped:'Gestoppt',kobra_finished:'Abgeschlossen',kobra_failed:'Fehler',kobra_canceled:'Abgebrochen',kobra_offline:'Offline', - nav_dashboard:'Dashboard',nav_print:'Druck',nav_temps:'Temperaturen',nav_motion:'Achsen',nav_ams:'AMS',nav_extras:'Licht / Lüfter',nav_console:'Konsole', - card_progress:'Fortschritt',card_temps:'Temperaturen',card_light_fan:'Lüfter',card_speed:'Druckgeschwindigkeit',card_cam:'Kamera',lbl_elapsed:'Verstrichen:',lbl_remaining:'Restzeit:',lbl_slicer_time:'Slicer-Schätzung:',lbl_layers:'Layer', - speed_silent:'🐢 Leise',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport', - lbl_light:'💡 Licht',lbl_feed:'Einziehen',lbl_unload:'Ausziehen', - card_ace_dry:'ACE Trocknung',ace_dry_dryer:'Trockner',ace_dry_status_off:'Status: Aus',ace_dry_status_on:'Status: Aktiv',ace_dry_status_remaining:'Rest',ace_dry_humidity:'Luftfeuchte',ace_dry_current_temp:'Temperatur',ace_dry_chart:'Verlauf (Temp/Feuchte)',ace_dry_temp:'Temperatur (°C)',ace_dry_duration:'Dauer (Min)',ace_dry_start:'▶ Start',ace_dry_stop:'■ Stop',ace_dry_auto_refill:'Auto-Nachschub',ace_dry_enable:'Trocknung aktivieren',ace_dry_temp_line:'Trocknungstemperatur',ace_dry_time_line:'Trocknungszeit',ace_dry_ui_pending:'(nur UI, Backend folgt)',ace_dry_dialog_title:'Trockner Temp/Zeit-Einstellungen',ace_dry_dialog_temp:'Temperatur (30-80°C)',ace_dry_dialog_time:'Restzeit (h:m:s)',ace_dry_dialog_confirm:'Bestätigen',ace_dry_dialog_cancel:'Abbrechen',ace_dry_dialog_save_restart:'Speichern & Neustart',ace_dry_dialog_custom_name:'Eigener Name',ace_dry_dialog_reset_default:'Auf Standard zurücksetzen', - cam_placeholder:'📷 Kamera nicht gestartet',btn_cam_start:'▶ Kamera',btn_cam_stop:'◼ Kamera', - btn_pause:'⏸ Pause',btn_resume:'▶ Weiter',btn_cancel:'✕ Stopp', - label_nozzle:'Nozzle',label_bed:'Bett',label_fan:'🌀 Lüfter',label_light:'💡 Licht',label_on_off:'Ein / Aus',label_speed:'Geschwindigkeit', - panel_print_title:'Drucksteuerung',panel_print_btn_pause:'⏸ Pause',panel_print_btn_resume:'▶ Fortsetzen',panel_print_btn_cancel:'✕ Abbrechen',panel_print_temps_live:'Temperaturen (Live)', - label_set:'Setzen',label_off:'Aus', - panel_temps_nozzle:'Nozzle',panel_temps_bed:'Heizbett',panel_temps_chart:'Verlauf (letzte 60 Messungen)',label_target_c:'Ziel:', - panel_motion_xy:'XY-Achsen',panel_motion_z:'Z-Achse',label_step:'Schrittweite:',btn_home_z:'Home Z',btn_home_xy:'Home XY',btn_home_all:'Home All',btn_disable_motors:'Motoren aus', - panel_ams_title:'Filament',card_ams:'Filament',ams_no_data:'Keine AMS-Daten empfangen',label_slot:'Slot',ams_empty:'Leer', - panel_extras_light:'Licht',panel_extras_fan:'Lüfter',panel_extras_camera:'Kamera',btn_cam_start2:'▶ Start',btn_cam_stop2:'◼ Stop', - panel_console_title:'Ereignis-Log', - log_light_on:'Licht an',log_light_off:'Licht aus',log_fan:'Lüfter →',log_nozzle:'Nozzle →',log_bed:'Bett →',log_axis:'Achse',log_home:'Home',log_home_all:'Home All',log_cam_start:'Kamera gestartet:',log_cam_stop:'Kamera gestoppt',log_poll_error:'Poll-Fehler:',log_error:'Fehler:', - confirm_cancel:'Druck wirklich abbrechen?', - settings_title:'Einstellungen',settings_connection:'Verbindung',settings_print:'Druckeinstellungen',settings_poll:'Poll-Intervall',settings_version:'Version', - settings_save:'Speichern & Neustart',settings_printer_name:'Drucker-Name',settings_printer_ip:'Drucker-IP',settings_mqtt_port:'MQTT-Port', - settings_username:'MQTT-Benutzername',settings_password:'MQTT-Passwort',settings_device_id:'Device-ID',settings_mode_id:'Mode-ID',hint_ip_no_port:'Nur IP-Adresse, kein Port (z.B. 192.168.1.102)', - settings_default_slot:'Standard-Slot (Einfarbdruck)',settings_slot_auto:'Auto (alle belegten Slots)',settings_auto_leveling:'Auto-Leveling vor Druck',settings_camera_on_print:'Kamera bei Druckstart einschalten', - settings_web_upload_warning:'Warnung bei Web-Upload-Druck anzeigen', - update_check:'Auf Updates prüfen',update_checking:'Prüfe...',update_available:'verfügbar',update_none:'Bereits aktuell', - update_apply:'Jetzt installieren',update_applying:'Lade herunter...',update_restarting:'Starte neu...',update_error:'Fehler', - btn_connect:'⚡ Verbinden',btn_disconnect:'✕ Trennen', - lbl_conn_error:'Verbindungsfehler:', - slot_edit_title:'Slot bearbeiten',slot_edit_color:'Farbe',slot_edit_material:'Material', - slot_edit_load:'⬇ Einziehen',slot_edit_unload:'⬆ Ausziehen', - slot_edit_save:'💾 Speichern',slot_edit_custom:'z.B. PLA, PETG, ABS…', - slot_edit_ok:'AMS Slot', - log_dir_all:'Alle',log_lvl_label:'Level:', - file_ready_btn:'▶ Druck starten', - file_slots_btn:'🎨 Slots wählen', - file_cancel_btn:'✕ Abbrechen', - nav_printers:'Drucker', - skip_title:'✂ Objekte überspringen',skip_hint:'Objekte abwählen, die nicht weiter gedruckt werden sollen:', - skip_btn_label:'Objekte',skip_no_objects:'Keine Objekte in diesem Druck.', - skip_already:'übersprungen',skip_select_at_least_one:'Bitte mindestens ein Objekt wählen.', - skip_sending:'Sende …',skip_success:'Objekte werden übersprungen.', - fd_objects_hint:'Objekte überspringen (optional):', - add_printer:'Drucker hinzufügen',apd_lbl_ip:'Drucker-IP',apd_lbl_name:'Name (optional)', - apd_fetching:'Hole Daten vom Drucker…',apd_success:'Drucker hinzugefügt, Bridge startet neu…',apd_err_ip:'Bitte IP-Adresse eingeben', - printers_remove:'Drucker entfernen',printers_remove_confirm:'Drucker "{name}" entfernen? Die Bridge startet neu.', - printers_active:'● aktiv', - printers_switch:'Wechseln →', - printers_current:'Aktueller Drucker', - printers_loading:'Lade…', - printers_none:'Keine Drucker konfiguriert.', - printers_empty_hint:'Noch kein Drucker eingerichtet.', - nav_browser:'Browser', - panel_browser_title:'Datei-Browser', - store_empty:'Noch keine Dateien hochgeladen.', - store_refresh:'↻ Aktualisieren', - store_print:'▶ Drucken', - store_download:'⬇ Download', - store_delete_confirm:'Datei löschen?', - store_print_confirm:'Datei drucken?', - store_web_verify_title:'Datei verifizieren', - store_web_verify_msg:'Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.', - store_web_verify_confirm:'Bestätigen', - store_web_verify_abort:'Abbrechen', - store_no_results:'Keine Dateien gefunden.', - store_never:'noch nicht gedruckt', - sf_all:'Alle',sf_ok:'✓ Erfolgreich',sf_err:'✗ Fehler',sf_new:'Neu', - ss_date:'↓ Datum',ss_name:'A–Z Name',ss_dur:'⏱ Druckzeit' -}; -var LANG_EN={ - header_status_standby:'Ready',header_status_printing:'Printing',header_status_complete:'Complete',header_status_error:'Error', - kobra_free:'Ready',kobra_busy:'Busy',kobra_printing:'Printing',kobra_preheating:'Preheating',kobra_auto_leveling:'Auto Leveling',kobra_checking:'Checking',kobra_updated:'Updating',kobra_init:'Initializing',kobra_pausing:'Pausing...',kobra_paused:'Paused',kobra_resuming:'Resuming...',kobra_resumed:'Resumed',kobra_stopping:'Stopping...',kobra_stoped:'Stopped',kobra_finished:'Finished',kobra_failed:'Error',kobra_canceled:'Cancelled',kobra_offline:'Offline', - nav_dashboard:'Dashboard',nav_print:'Print',nav_temps:'Temperatures',nav_motion:'Motion',nav_ams:'AMS',nav_extras:'Light / Fan',nav_console:'Console', - card_progress:'Progress',card_temps:'Temperatures',card_light_fan:'Fan',card_speed:'Print Speed',card_cam:'Camera',lbl_elapsed:'Elapsed:',lbl_remaining:'Remaining:',lbl_slicer_time:'Slicer estimate:',lbl_layers:'Layer', - speed_silent:'🐢 Silent',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport', - lbl_light:'💡 Light',lbl_feed:'Load',lbl_unload:'Unload', - card_ace_dry:'ACE Drying',ace_dry_dryer:'Dryer',ace_dry_status_off:'Status: Off',ace_dry_status_on:'Status: Active',ace_dry_status_remaining:'Remaining',ace_dry_humidity:'Humidity',ace_dry_current_temp:'Temperature',ace_dry_chart:'History (Temp/Humidity)',ace_dry_temp:'Temperature (°C)',ace_dry_duration:'Duration (min)',ace_dry_start:'▶ Start',ace_dry_stop:'■ Stop',ace_dry_auto_refill:'Auto Refill',ace_dry_enable:'Enable Drying',ace_dry_temp_line:'Drying Temperature',ace_dry_time_line:'Drying Time',ace_dry_ui_pending:'(UI only, backend next)',ace_dry_dialog_title:'Dryer Temp/Time Settings',ace_dry_dialog_temp:'Temperature (30-80°C)',ace_dry_dialog_time:'Rem. Time (h:m:s)',ace_dry_dialog_confirm:'Confirm',ace_dry_dialog_cancel:'Cancel',ace_dry_dialog_save_restart:'Save & Restart',ace_dry_dialog_custom_name:'Custom Name',ace_dry_dialog_reset_default:'Reset to Default', - cam_placeholder:'📷 Camera not started',btn_cam_start:'▶ Camera',btn_cam_stop:'◼ Camera', - btn_pause:'⏸ Pause',btn_resume:'▶ Resume',btn_cancel:'✕ Stop', - label_nozzle:'Nozzle',label_bed:'Bed',label_fan:'🌀 Fan',label_light:'💡 Light',label_on_off:'On / Off',label_speed:'Speed', - panel_print_title:'Print Control',panel_print_btn_pause:'⏸ Pause',panel_print_btn_resume:'▶ Resume',panel_print_btn_cancel:'✕ Cancel',panel_print_temps_live:'Temperatures (Live)', - label_set:'Set',label_off:'Off', - panel_temps_nozzle:'Nozzle',panel_temps_bed:'Heated Bed',panel_temps_chart:'History (last 60 readings)',label_target_c:'Target:', - panel_motion_xy:'XY Axes',panel_motion_z:'Z Axis',label_step:'Step size:',btn_home_z:'Home Z',btn_home_xy:'Home XY',btn_home_all:'Home All',btn_disable_motors:'Motors Off', - panel_ams_title:'Filament',card_ams:'Filament',ams_no_data:'No AMS data received',label_slot:'Slot',ams_empty:'Empty', - panel_extras_light:'Light',panel_extras_fan:'Fan',panel_extras_camera:'Camera',btn_cam_start2:'▶ Start',btn_cam_stop2:'◼ Stop', - panel_console_title:'Event Log', - log_light_on:'Light on',log_light_off:'Light off',log_fan:'Fan →',log_nozzle:'Nozzle →',log_bed:'Bed →',log_axis:'Axis',log_home:'Home',log_home_all:'Home All',log_cam_start:'Camera started:',log_cam_stop:'Camera stopped',log_poll_error:'Poll error:',log_error:'Error:', - confirm_cancel:'Really cancel the print?', - settings_title:'Settings',settings_connection:'Connection',settings_print:'Print Settings',settings_poll:'Poll Interval',settings_version:'Version', - settings_save:'Save & Restart',settings_printer_name:'Printer Name',settings_printer_ip:'Printer IP',settings_mqtt_port:'MQTT Port', - settings_username:'MQTT Username',settings_password:'MQTT Password',settings_device_id:'Device ID',settings_mode_id:'Mode ID',hint_ip_no_port:'IP address only, no port (e.g. 192.168.1.102)', - settings_default_slot:'Default Slot (single color)',settings_slot_auto:'Auto (all loaded slots)',settings_auto_leveling:'Auto-Leveling before print',settings_camera_on_print:'Turn camera on at print start', - settings_web_upload_warning:'Show warning when printing web uploads', - update_check:'Check for Updates',update_checking:'Checking...',update_available:'available',update_none:'Already up to date', - update_apply:'Install Now',update_applying:'Downloading...',update_restarting:'Restarting...',update_error:'Error', - btn_connect:'⚡ Connect',btn_disconnect:'✕ Disconnect', - lbl_conn_error:'Connection error:', - slot_edit_title:'Edit Slot',slot_edit_color:'Color',slot_edit_material:'Material', - slot_edit_load:'⬇ Load',slot_edit_unload:'⬆ Unload', - slot_edit_save:'💾 Save',slot_edit_custom:'e.g. PLA, PETG, ABS…', - slot_edit_ok:'AMS Slot', - log_dir_all:'All',log_lvl_label:'Level:', - file_ready_btn:'▶ Start Print', - file_slots_btn:'🎨 Select Slots', - file_cancel_btn:'✕ Cancel', - nav_printers:'Printers', - skip_title:'✂ Skip objects',skip_hint:'Uncheck objects you no longer want to print:', - skip_btn_label:'Objects',skip_no_objects:'No objects in this print.', - skip_already:'skipped',skip_select_at_least_one:'Please pick at least one object.', - skip_sending:'Sending …',skip_success:'Objects will be skipped.', - fd_objects_hint:'Skip objects (optional):', - add_printer:'Add printer',apd_lbl_ip:'Printer IP',apd_lbl_name:'Name (optional)', - apd_fetching:'Fetching data from printer…',apd_success:'Printer added, bridge restarting…',apd_err_ip:'Please enter an IP address', - printers_remove:'Remove printer',printers_remove_confirm:'Remove printer "{name}"? The bridge will restart.', - printers_active:'● active', - printers_switch:'Switch →', - printers_current:'Current printer', - printers_loading:'Loading…', - printers_none:'No printers configured.', - printers_empty_hint:'No printer set up yet.', - nav_browser:'Browser', - panel_browser_title:'File Browser', - store_empty:'No files uploaded yet.', - store_refresh:'↻ Refresh', - store_print:'▶ Print', - store_download:'⬇ Download', - store_delete_confirm:'Delete file?', - store_print_confirm:'Print file?', - store_web_verify_title:'Verify file', - store_web_verify_msg:'Please verify this file was made for Anycubic Kobra X.', - store_web_verify_confirm:'Confirm', - store_web_verify_abort:'Abort', - store_no_results:'No files found.', - store_never:'never printed', - sf_all:'All',sf_ok:'✓ Completed',sf_err:'✗ Failed',sf_new:'New', - ss_date:'↓ Date',ss_name:'A–Z Name',ss_dur:'⏱ Print time' -}; +var currentLang='de'; +var T={}; +var _langCache={}; + +function tr(key,fallback){ + var v=T&&T[key]; + return (typeof v==='string'&&v.length)?v:(fallback!==undefined?fallback:''); +} + +function _langToggleLabel(lang){ + if(lang==='de')return 'Deutsch'; + if(lang==='en')return 'English'; + if(lang==='zh-cn')return '简体中文'; + return 'Espanol'; +} + +function _mapSupportedLang(lang){ + if(!lang)return ''; + var l=String(lang).toLowerCase().replace(/_/g,'-').trim(); + if(l==='de'||l==='en'||l==='es'||l==='zh-cn')return l; + + var base=l.split('-')[0]; + if(base==='de'||base==='en'||base==='es')return base; + + if(base==='zh'){ + if(l.indexOf('cn')>=0||l.indexOf('hans')>=0||l==='zh')return 'zh-cn'; + } + return ''; +} + +function _normalizeLang(lang){ + return _mapSupportedLang(lang)||'de'; +} + +function _detectBrowserLanguage(){ + var prefs=[]; + if(Array.isArray(navigator.languages)&&navigator.languages.length)prefs=navigator.languages; + else if(navigator.language)prefs=[navigator.language]; + for(var i=0;ie.textContent=T.lbl_feed); document.querySelectorAll('.lbl-unload').forEach(e=>e.textContent=T.lbl_unload); for(var i=0;i<4;i++){ - setText('d-card-ace-dry-'+i,'ACE '+(i+1)+' - '+(T.ace_dry_dryer||'Dryer')); - setText('d-ace-auto-refill-label-'+i,T.ace_dry_auto_refill||'Auto Refill'); - setText('d-ace-drying-enable-label-'+i,T.ace_dry_enable||'Enable Drying'); - setText('d-ace-dry-humidity-label-'+i,(T.ace_dry_humidity||'Humidity')+':'); - setText('d-ace-dry-current-temp-label-'+i,(T.ace_dry_current_temp||'Current Temp')+':'); - setText('d-ace-dry-target-label-'+i,(T.ace_dry_temp_line||'Drying Temperature')+':'); - setText('d-ace-dry-time-label-'+i,(T.ace_dry_time_line||'Drying Time')+':'); - setText('d-ace-dry-chart-label-'+i,T.ace_dry_chart||'History (Temp/Humidity)'); + setText('d-card-ace-dry-'+i,'ACE '+(i+1)+' - '+tr('ace_dry_dryer')); + setText('d-ace-auto-refill-label-'+i,tr('ace_dry_auto_refill')); + setText('d-ace-drying-enable-label-'+i,tr('ace_dry_enable')); + setText('d-ace-dry-humidity-label-'+i,tr('ace_dry_humidity')+':'); + setText('d-ace-dry-current-temp-label-'+i,tr('ace_dry_current_temp')+':'); + setText('d-ace-dry-target-label-'+i,tr('ace_dry_temp_line')+':'); + setText('d-ace-dry-time-label-'+i,tr('ace_dry_time_line')+':'); + setText('d-ace-dry-chart-label-'+i,tr('ace_dry_chart')); var adTemp=document.getElementById('ace-dry-temp-'+i);if(adTemp)adTemp.setAttribute('placeholder',T.ace_dry_temp); var adDur=document.getElementById('ace-dry-duration-'+i);if(adDur)adDur.setAttribute('placeholder',T.ace_dry_duration); } - setText('ace-dry-dialog-title',T.ace_dry_dialog_title||'Dryer Temp/Time Settings'); - setText('ace-dry-dialog-temp-label',T.ace_dry_dialog_temp||'Temperature (30-80°C)'); - setText('ace-dry-dialog-time-label',T.ace_dry_dialog_time||'Rem. Time (h:m:s)'); - setText('ace-dry-dialog-custom-name-label',T.ace_dry_dialog_custom_name||'Custom Name'); - setText('ace-dry-dialog-cancel',T.ace_dry_dialog_cancel||'Cancel'); - setText('ace-dry-dialog-confirm',T.ace_dry_dialog_confirm||'Confirm'); - setText('ace-dry-dialog-reset-default',T.ace_dry_dialog_reset_default||'Reset to Default'); - setText('ace-dry-dialog-save-preset',T.ace_dry_dialog_save_restart||'Save & Restart'); + setText('ace-dry-dialog-title',tr('ace_dry_dialog_title')); + setText('ace-dry-dialog-temp-label',tr('ace_dry_dialog_temp')); + setText('ace-dry-dialog-time-label',tr('ace_dry_dialog_time')); + setText('ace-dry-dialog-custom-name-label',tr('ace_dry_dialog_custom_name')); + setText('ace-dry-dialog-cancel',tr('ace_dry_dialog_cancel')); + setText('ace-dry-dialog-confirm',tr('ace_dry_dialog_confirm')); + setText('ace-dry-dialog-reset-default',tr('ace_dry_dialog_reset_default')); + setText('ace-dry-dialog-save-preset',tr('ace_dry_dialog_save_restart')); aceDryDialogSyncCustomButtonNames(); // conn-btn text (nur wenn nicht im Übergangszustand) updateConnBtn(); @@ -471,13 +416,14 @@ function ensureAceDryCards(){ grid.setAttribute('data-init','1'); } (function(){ - var l=localStorage.getItem('lang')||'de'; - currentLang=l;T=l==='de'?LANG_DE:LANG_EN; - document.getElementById('lang-btn').textContent=l==='de'?'EN':'DE'; - document.documentElement.setAttribute('lang',l); + var l=_resolveInitialLanguage(); + currentLang=_normalizeLang(l); + var langSel=document.getElementById('lang-select'); + if(langSel)langSel.value=currentLang; + document.documentElement.setAttribute('lang',currentLang); // defer until DOM ready window.addEventListener('DOMContentLoaded',function(){ - applyLang(); + setLanguage(currentLang).catch(function(){}); // Kein Drucker konfiguriert? → direkt in den Drucker-Tab (zeigt "+ Drucker hinzufügen") fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){ if(!d.result||!d.result.length){showPanel('printers');loadPrinterTab();} @@ -649,7 +595,7 @@ function applyState(){ _syncAceDryPresetsFromServer(s.ace_dry_presets); // connection error banner – nur wenn überhaupt ein Drucker konfiguriert ist var banner=document.getElementById('conn-error-banner'); - if(banner){if(s.connection_error&&_printers.length>0){banner.textContent='⚠ '+(T.lbl_conn_error||'Connection error:')+' '+s.connection_error;banner.style.display='block';}else{banner.style.display='none';}} + if(banner){if(s.connection_error&&_printers.length>0){banner.textContent='⚠ '+tr('lbl_conn_error')+' '+s.connection_error;banner.style.display='block';}else{banner.style.display='none';}} var frb=document.getElementById('file-ready-banner'); if(frb){ if(s.file_ready&&s.print_state==='standby'){ @@ -734,7 +680,7 @@ function applyState(){ var amsTitle=document.getElementById('d-card-ams'); if(amsTitle){ - var baseTitle=T.card_ams||'Filament'; + var baseTitle=tr('card_ams'); var modeMap={toolhead:'Toolhead',ace_direct:'ACE Direct',ace_hub:'ACE Hub'}; var modeTxt=modeMap[s.filament_mode]||''; amsTitle.textContent=modeTxt?(baseTitle+' - '+modeTxt):baseTitle; @@ -823,7 +769,7 @@ function applyState(){ var loaded=(s.ams_loaded_slot!=null&&s.ams_loaded_slot>=0&&globalIdx===s.ams_loaded_slot); var activity=(slot.activity||''); var pct=empty?T.ams_empty:(slot.consumables_percent!=null?slot.consumables_percent+'%':'–'); - var slotLabel='Slot '+(globalIdx+1); + var slotLabel=T.label_slot+' '+(globalIdx+1); html+='
' +'
' @@ -834,7 +780,7 @@ function applyState(){ +'
'; }); if(bid===-1&&acePresent){ - html+='
' + html+='
' +'
ACE
' +'
'; } @@ -861,10 +807,10 @@ function updateConnBtn(){ var offline=S.kobra_state==='offline'; if(offline){ btn.className='conn-btn disconnected'; - btn.textContent=T.btn_connect||'⚡ Verbinden'; + btn.textContent=tr('btn_connect'); } else { btn.className='conn-btn connected'; - btn.textContent=T.btn_disconnect||'✕ Trennen'; + btn.textContent=tr('btn_disconnect'); } } @@ -956,7 +902,7 @@ function updateSlotEditFeedButton(){ return; } btn.style.display=''; - btn.textContent=_slotEditLoaded?(T.slot_edit_unload||'⬆ Unload'):(T.slot_edit_load||'⬇ Load'); + btn.textContent=_slotEditLoaded?tr('slot_edit_unload'):tr('slot_edit_load'); } function openSlotEdit(i){ var slot=(window._amsSlots||[])[i]||{}; @@ -1010,7 +956,7 @@ function startReadyFile(){ if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);} }) .catch(function(e){ - clog((T.log_error||'Error:')+' '+e,'msg-err'); + clog(tr('log_error')+' '+e,'msg-err'); if(btn){btn.disabled=false;setText('file-ready-btn',T.file_ready_btn);} }); } @@ -1041,7 +987,7 @@ function saveSlotEdit(){ .then(function(r){return r.json();}) .then(function(r){ closeSlotEdit(); - clog((T.slot_edit_ok||'AMS Slot')+' '+(_slotEditIndex+1)+': '+mat+' '+hex,'msg-ok'); + clog(tr('slot_edit_ok')+' '+(_slotEditIndex+1)+': '+mat+' '+hex,'msg-ok'); }) .catch(function(e){clog('Fehler: '+e,'msg-err');}); } @@ -1266,13 +1212,13 @@ function camStart(){ img.style.display='none'; ph.style.display='flex'; camOn=false; - document.getElementById('cam-toggle-btn').textContent=T.btn_cam_start||'▶ Kamera'; - clog((T.log_error||'Fehler:')+' Stream nicht verfügbar','msg-err'); + document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start'); + clog(tr('log_error')+' '+tr('cam_stream_unavailable'),'msg-err'); }; img.src='/api/camera/stream?t='+Date.now(); camOn=true; - document.getElementById('cam-toggle-btn').textContent=T.btn_cam_stop||'◼ Kamera'; - clog((T.log_cam_start||'Kamera gestartet'),'msg-ok'); + document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_stop'); + clog(tr('log_cam_start'),'msg-ok'); // MJPEG liefert kein onload – Spinner nach kurzem Timeout ausblenden setTimeout(function(){ sp.style.display='none'; @@ -1281,7 +1227,7 @@ function camStart(){ }).catch(function(e){ sp.style.display='none'; ph.style.display='flex'; - clog((T.log_error||'Fehler:')+' '+e,'msg-err'); + clog(tr('log_error')+' '+e,'msg-err'); }); } @@ -1292,8 +1238,8 @@ function camStop(){ img.style.display='none'; document.getElementById('cam-placeholder').style.display='flex'; camOn=false; - document.getElementById('cam-toggle-btn').textContent=T.btn_cam_start||'▶ Kamera'; - clog((T.log_cam_stop||'Kamera gestoppt'),'msg-ok'); + document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start'); + clog(tr('log_cam_stop'),'msg-ok'); } function aceDryStart(aceId){ @@ -1307,7 +1253,7 @@ function aceDryStart(aceId){ .then(function(r){return r.json();}) .then(function(r){ if(r.error){throw new Error(r.error);} - clog('ACE '+(aceId+1)+' - '+(T.ace_dry_dryer||'Dryer')+': '+(T.ace_dry_start||'start')+' ('+t+'°C, '+d+' min)','msg-ok'); + clog('ACE '+(aceId+1)+' - '+tr('ace_dry_dryer')+': '+tr('ace_dry_start')+' ('+t+'°C, '+d+' min)','msg-ok'); poll(); }) .catch(function(e){clog('ACE-Fehler: '+e,'msg-err');}); @@ -1323,7 +1269,7 @@ function aceAutoRefillToggle(aceId){ .then(function(d){ delete _aceAutoFeedPending[aceId]; if(d.error){clog('Auto Refill error: '+d.error,'msg-err');var t=document.getElementById('ace-auto-refill-toggle-'+aceId);if(t)t.checked=!on;return;} - clog('ACE '+(aceId+1)+' - '+(T.ace_dry_auto_refill||'Auto Refill')+': '+(on?'ON':'OFF'),'msg-ok'); + clog('ACE '+(aceId+1)+' - '+tr('ace_dry_auto_refill')+': '+(on?'ON':'OFF'),'msg-ok'); }) .catch(function(e){delete _aceAutoFeedPending[aceId];clog('Auto Refill error: '+e,'msg-err');var t=document.getElementById('ace-auto-refill-toggle-'+aceId);if(t)t.checked=!on;}); } @@ -1351,7 +1297,7 @@ function openAceDryDialog(aceId){ aceDryDialogUpdateSaveButton(); aceDryDialogUpdateResetButton(); var sb=document.getElementById('ace-dry-dialog-save-preset'); - if(sb){sb.disabled=false;sb.textContent=T.ace_dry_dialog_save_restart||'Save & Restart';} + if(sb){sb.disabled=false;sb.textContent=tr('ace_dry_dialog_save_restart');} document.getElementById('ace-dry-dialog').classList.add('open'); } @@ -1509,11 +1455,11 @@ function saveAceDryPresetAndRestart(){ }; return post('/api/settings',d); }).then(function(){ - clog('ACE preset '+key+' '+(T.settings_save||'Save & Restart'),'msg-ok'); + clog('ACE preset '+key+' '+tr('settings_save'),'msg-ok'); closeAceDryDialog(); }).catch(function(e){ btn.disabled=false; - btn.textContent=T.ace_dry_dialog_save_restart||'Save & Restart'; + btn.textContent=tr('ace_dry_dialog_save_restart'); clog('ACE-Preset Fehler: '+e,'msg-err'); }); } @@ -1549,7 +1495,7 @@ function aceDryStop(aceId){ .then(function(r){return r.json();}) .then(function(r){ if(r.error){throw new Error(r.error);} - clog('ACE '+(aceId+1)+' - '+(T.ace_dry_dryer||'Dryer')+': '+(T.ace_dry_stop||'stop'),'msg-ok'); + clog('ACE '+(aceId+1)+' - '+tr('ace_dry_dryer')+': '+tr('ace_dry_stop'),'msg-ok'); poll(); }) .catch(function(e){clog('ACE-Fehler: '+e,'msg-err');}); @@ -1567,7 +1513,7 @@ function uploadGcode(file){ var zone=document.getElementById('store-upload-zone'); var status=document.getElementById('store-upload-status'); var label=document.getElementById('store-upload-label'); - if(status) { status.textContent='⏳ Hochladen…'; status.style.display=''; status.className='upload-status-busy'; } + if(status) { status.textContent=T.store_upload_busy; status.style.display=''; status.className='upload-status-busy'; } if(label) label.style.display='none'; if(zone) zone.style.pointerEvents='none'; var fd=new FormData(); @@ -1579,7 +1525,7 @@ function uploadGcode(file){ return r.json(); }) .then(function(){ - if(status){ status.textContent='✓ '+file.name; status.className='upload-status-ok'; } + if(status){ status.textContent=T.store_upload_success.replace('{file}',file.name); status.className='upload-status-ok'; } loadStore(); setTimeout(function(){ if(status){status.style.display='none'; status.className='';} @@ -1588,7 +1534,7 @@ function uploadGcode(file){ }, 3000); }) .catch(function(e){ - if(status){ status.textContent='✗ '+e.message; status.className='upload-status-err'; } + if(status){ status.textContent=T.store_upload_error.replace('{error}',e.message); status.className='upload-status-err'; } if(label) label.style.display=''; if(zone) zone.style.pointerEvents=''; clog('Upload-Fehler: '+e,'msg-err'); @@ -1660,7 +1606,7 @@ function renderStore(){ thumb+ '
'+name+statusBadge+'
'+ lastInfo+ - '
⏱ Schätzung: '+est+'
'+ + '
⏱ '+T.store_estimate+': '+est+'
'+ '
📅 '+date+'
'+ '
'+ ' - +
+ + +
@@ -239,7 +247,7 @@
Temperaturen
-
Nozzle
+
Nozzle
°C
@@ -411,7 +419,7 @@ - GCode hierher ziehen oder durchsuchen + GCode hierher ziehen oder durchsuchen
diff --git a/web/translations/de.json b/web/translations/de.json new file mode 100644 index 0000000..76cd8b9 --- /dev/null +++ b/web/translations/de.json @@ -0,0 +1,231 @@ +{ + "header_status_standby": "Bereit", + "header_status_printing": "Druckt", + "header_status_complete": "Fertig", + "header_status_error": "Fehler", + "kobra_free": "Bereit", + "kobra_busy": "Beschäftigt", + "kobra_printing": "Druckt", + "kobra_preheating": "Aufheizen", + "kobra_auto_leveling": "Nivellierung", + "kobra_checking": "Prüfung", + "kobra_updated": "Aktualisierung", + "kobra_init": "Initialisierung", + "kobra_pausing": "Pausiert...", + "kobra_paused": "Pausiert", + "kobra_resuming": "Fortsetzen...", + "kobra_resumed": "Fortgesetzt", + "kobra_stopping": "Stoppt...", + "kobra_stoped": "Gestoppt", + "kobra_finished": "Abgeschlossen", + "kobra_failed": "Fehler", + "kobra_canceled": "Abgebrochen", + "kobra_offline": "Offline", + "nav_dashboard": "Dashboard", + "nav_print": "Druck", + "nav_temps": "Temperaturen", + "nav_motion": "Achsen", + "nav_ams": "AMS", + "nav_extras": "Licht / Lüfter", + "nav_console": "Konsole", + "card_progress": "Fortschritt", + "card_temps": "Temperaturen", + "card_light_fan": "Lüfter", + "card_speed": "Druckgeschwindigkeit", + "card_cam": "Kamera", + "lbl_elapsed": "Verstrichen:", + "lbl_remaining": "Restzeit:", + "lbl_slicer_time": "Slicer-Schätzung:", + "lbl_layers": "Layer", + "speed_silent": "🐢 Leise", + "speed_normal": "⚡ Normal", + "speed_sport": "🚀 Sport", + "lbl_light": "💡 Licht", + "lbl_feed": "Einziehen", + "lbl_unload": "Ausziehen", + "card_ace_dry": "ACE Trocknung", + "ace_dry_dryer": "Trockner", + "ace_dry_status_off": "Status: Aus", + "ace_dry_status_on": "Status: Aktiv", + "ace_dry_status_remaining": "Rest", + "ace_dry_humidity": "Luftfeuchte", + "ace_dry_current_temp": "Temperatur", + "ace_dry_chart": "Verlauf (Temp/Feuchte)", + "ace_dry_temp": "Temperatur (°C)", + "ace_dry_duration": "Dauer (Min)", + "ace_dry_start": "▶ Start", + "ace_dry_stop": "■ Stop", + "ace_dry_auto_refill": "Auto-Nachschub", + "ace_dry_enable": "Trocknung aktivieren", + "ace_dry_temp_line": "Trocknungstemperatur", + "ace_dry_time_line": "Trocknungszeit", + "ace_dry_ui_pending": "(nur UI, Backend folgt)", + "ace_dry_dialog_title": "Trockner Temp/Zeit-Einstellungen", + "ace_dry_dialog_temp": "Temperatur (30-80°C)", + "ace_dry_dialog_time": "Restzeit (h:m:s)", + "ace_dry_dialog_confirm": "Bestätigen", + "ace_dry_dialog_cancel": "Abbrechen", + "ace_dry_dialog_save_restart": "Speichern & Neustart", + "ace_dry_dialog_custom_name": "Eigener Name", + "ace_dry_dialog_reset_default": "Auf Standard zurücksetzen", + "cam_placeholder": "📷 Kamera nicht gestartet", + "cam_stream_unavailable": "Stream nicht verfügbar", + "btn_cam_start": "▶ Kamera", + "btn_cam_stop": "◼ Kamera", + "btn_pause": "⏸ Pause", + "btn_resume": "▶ Weiter", + "btn_cancel": "✕ Stopp", + "label_nozzle": "Düse", + "label_bed": "Bett", + "label_fan": "🌀 Lüfter", + "label_light": "💡 Licht", + "label_on_off": "Ein / Aus", + "label_speed": "Geschwindigkeit", + "panel_print_title": "Drucksteuerung", + "panel_print_btn_pause": "⏸ Pause", + "panel_print_btn_resume": "▶ Fortsetzen", + "panel_print_btn_cancel": "✕ Abbrechen", + "panel_print_temps_live": "Temperaturen (Live)", + "label_set": "Setzen", + "label_off": "Aus", + "panel_temps_nozzle": "Düse", + "panel_temps_bed": "Heizbett", + "panel_temps_chart": "Verlauf (letzte 60 Messungen)", + "label_target_c": "Ziel:", + "panel_motion_xy": "XY-Achsen", + "panel_motion_z": "Z-Achse", + "label_step": "Schrittweite:", + "btn_home_z": "Home Z", + "btn_home_xy": "Home XY", + "btn_home_all": "Home All", + "btn_disable_motors": "Motoren aus", + "panel_ams_title": "Filament", + "card_ams": "Filament", + "ams_no_data": "Keine AMS-Daten empfangen", + "label_slot": "Slot", + "ams_empty": "Leer", + "panel_extras_light": "Licht", + "panel_extras_fan": "Lüfter", + "panel_extras_camera": "Kamera", + "btn_cam_start2": "▶ Start", + "btn_cam_stop2": "◼ Stop", + "panel_console_title": "Ereignis-Log", + "log_light_on": "Licht an", + "log_light_off": "Licht aus", + "log_fan": "Lüfter →", + "log_nozzle": "Düse →", + "log_bed": "Bett →", + "log_axis": "Achse", + "log_home": "Home", + "log_home_all": "Home All", + "log_cam_start": "Kamera gestartet:", + "log_cam_stop": "Kamera gestoppt", + "log_poll_error": "Poll-Fehler:", + "log_error": "Fehler:", + "confirm_cancel": "Druck wirklich abbrechen?", + "settings_title": "Einstellungen", + "settings_connection": "Verbindung", + "settings_print": "Druckeinstellungen", + "settings_poll": "Poll-Intervall", + "settings_version": "Version", + "settings_save": "Speichern & Neustart", + "settings_printer_name": "Drucker-Name", + "settings_printer_ip": "Drucker-IP", + "settings_mqtt_port": "MQTT-Port", + "settings_username": "MQTT-Benutzername", + "settings_password": "MQTT-Passwort", + "settings_device_id": "Device-ID", + "settings_mode_id": "Mode-ID", + "hint_ip_no_port": "Nur IP-Adresse, kein Port (z.B. 192.168.1.102)", + "settings_default_slot": "Standard-Slot (Einfarbdruck)", + "settings_slot_auto": "Auto (alle belegten Slots)", + "settings_auto_leveling": "Auto-Leveling vor Druck", + "settings_camera_on_print": "Kamera bei Druckstart einschalten", + "settings_web_upload_warning": "Warnung bei Web-Upload-Druck anzeigen", + "update_check": "Auf Updates prüfen", + "update_checking": "Prüfe...", + "update_available": "verfügbar", + "update_none": "Bereits aktuell", + "update_apply": "Jetzt installieren", + "update_applying": "Lade herunter...", + "update_restarting": "Starte neu...", + "update_error": "Fehler", + "btn_connect": "⚡ Verbinden", + "btn_disconnect": "✕ Trennen", + "lbl_conn_error": "Verbindungsfehler:", + "slot_edit_title": "Slot bearbeiten", + "slot_edit_color": "Farbe", + "slot_edit_material": "Material", + "slot_edit_load": "⬇ Einziehen", + "slot_edit_unload": "⬆ Ausziehen", + "slot_edit_save": "💾 Speichern", + "slot_edit_custom": "z.B. PLA, PETG, ABS…", + "slot_edit_ok": "AMS Slot", + "log_dir_all": "Alle", + "log_lvl_label": "Level:", + "file_ready_btn": "▶ Druck starten", + "file_slots_btn": "🎨 Slots wählen", + "file_cancel_btn": "✕ Abbrechen", + "nav_printers": "Drucker", + "skip_title": "✂ Objekte überspringen", + "skip_hint": "Objekte abwählen, die nicht weiter gedruckt werden sollen:", + "skip_btn_label": "Objekte", + "skip_no_objects": "Keine Objekte in diesem Druck.", + "skip_already": "übersprungen", + "skip_select_at_least_one": "Bitte mindestens ein Objekt wählen.", + "skip_sending": "Sende …", + "skip_success": "Objekte werden übersprungen.", + "fd_objects_hint": "Objekte überspringen (optional):", + "fd_slots_hint": "GCode-Kanal → AMS-Slot zuweisen:", + "fd_cancel": "Abbrechen", + "fd_print": "▶ Drucken", + "fd_no_slots_msg": "Keine belegten AMS-Slots.{br}Druck trotzdem starten?", + "fd_slot": "Slot", + "fd_no_matching_material": "Kein passendes Material", + "fd_used": "BELEGT", + "add_printer": "Drucker hinzufügen", + "apd_lbl_ip": "Drucker-IP", + "apd_lbl_name": "Name (optional)", + "apd_placeholder_name": "z.B. Kobra X Wohnzimmer", + "apd_cancel": "Abbrechen", + "apd_confirm": "Hinzufügen", + "apd_fetching": "Hole Daten vom Drucker…", + "apd_success": "Drucker hinzugefügt, Bridge startet neu…", + "apd_err_ip": "Bitte IP-Adresse eingeben", + "printers_remove": "Drucker entfernen", + "printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.", + "printers_active": "● aktiv", + "printers_switch": "Wechseln →", + "printers_current": "Aktueller Drucker", + "printers_loading": "Lade…", + "printers_none": "Keine Drucker konfiguriert.", + "printers_empty_hint": "Noch kein Drucker eingerichtet.", + "nav_browser": "Browser", + "panel_browser_title": "Datei-Browser", + "store_search_placeholder": "🔍 Suche…", + "store_empty": "Noch keine Dateien hochgeladen.", + "store_refresh": "↻ Aktualisieren", + "store_print": "▶ Drucken", + "store_download": "⬇ Download", + "store_delete_confirm": "Datei löschen?", + "store_print_confirm": "Datei drucken?", + "store_web_verify_title": "Datei verifizieren", + "store_web_verify_msg": "Bitte bestätige, dass diese Datei für den Anycubic Kobra X erstellt wurde.", + "store_web_verify_confirm": "Bestätigen", + "store_web_verify_abort": "Abbrechen", + "store_no_results": "Keine Dateien gefunden.", + "store_never": "noch nicht gedruckt", + "store_estimate": "Schätzung", + "store_upload_label_prefix": "GCode hierher ziehen oder ", + "store_upload_label_browse": "durchsuchen", + "store_upload_busy": "⏳ Hochladen…", + "store_upload_success": "✓ {file}", + "store_upload_error": "✗ {error}", + "sf_all": "Alle", + "sf_ok": "✓ Erfolgreich", + "sf_err": "✗ Fehler", + "sf_new": "Neu", + "ss_date": "↓ Datum", + "ss_name": "A–Z Name", + "ss_dur": "⏱ Druckzeit" +} diff --git a/web/translations/en.json b/web/translations/en.json new file mode 100644 index 0000000..ac9c887 --- /dev/null +++ b/web/translations/en.json @@ -0,0 +1,231 @@ +{ + "header_status_standby": "Ready", + "header_status_printing": "Printing", + "header_status_complete": "Complete", + "header_status_error": "Error", + "kobra_free": "Ready", + "kobra_busy": "Busy", + "kobra_printing": "Printing", + "kobra_preheating": "Preheating", + "kobra_auto_leveling": "Auto Leveling", + "kobra_checking": "Checking", + "kobra_updated": "Updating", + "kobra_init": "Initializing", + "kobra_pausing": "Pausing...", + "kobra_paused": "Paused", + "kobra_resuming": "Resuming...", + "kobra_resumed": "Resumed", + "kobra_stopping": "Stopping...", + "kobra_stoped": "Stopped", + "kobra_finished": "Finished", + "kobra_failed": "Error", + "kobra_canceled": "Cancelled", + "kobra_offline": "Offline", + "nav_dashboard": "Dashboard", + "nav_print": "Print", + "nav_temps": "Temperatures", + "nav_motion": "Motion", + "nav_ams": "AMS", + "nav_extras": "Light / Fan", + "nav_console": "Console", + "card_progress": "Progress", + "card_temps": "Temperatures", + "card_light_fan": "Fan", + "card_speed": "Print Speed", + "card_cam": "Camera", + "lbl_elapsed": "Elapsed:", + "lbl_remaining": "Remaining:", + "lbl_slicer_time": "Slicer estimate:", + "lbl_layers": "Layer", + "speed_silent": "🐢 Silent", + "speed_normal": "⚡ Normal", + "speed_sport": "🚀 Sport", + "lbl_light": "💡 Light", + "lbl_feed": "Load", + "lbl_unload": "Unload", + "card_ace_dry": "ACE Drying", + "ace_dry_dryer": "Dryer", + "ace_dry_status_off": "Status: Off", + "ace_dry_status_on": "Status: Active", + "ace_dry_status_remaining": "Remaining", + "ace_dry_humidity": "Humidity", + "ace_dry_current_temp": "Temperature", + "ace_dry_chart": "History (Temp/Humidity)", + "ace_dry_temp": "Temperature (°C)", + "ace_dry_duration": "Duration (min)", + "ace_dry_start": "▶ Start", + "ace_dry_stop": "■ Stop", + "ace_dry_auto_refill": "Auto Refill", + "ace_dry_enable": "Enable Drying", + "ace_dry_temp_line": "Drying Temperature", + "ace_dry_time_line": "Drying Time", + "ace_dry_ui_pending": "(UI only, backend next)", + "ace_dry_dialog_title": "Dryer Temp/Time Settings", + "ace_dry_dialog_temp": "Temperature (30-80°C)", + "ace_dry_dialog_time": "Rem. Time (h:m:s)", + "ace_dry_dialog_confirm": "Confirm", + "ace_dry_dialog_cancel": "Cancel", + "ace_dry_dialog_save_restart": "Save & Restart", + "ace_dry_dialog_custom_name": "Custom Name", + "ace_dry_dialog_reset_default": "Reset to Default", + "cam_placeholder": "📷 Camera not started", + "cam_stream_unavailable": "Stream unavailable", + "btn_cam_start": "▶ Camera", + "btn_cam_stop": "◼ Camera", + "btn_pause": "⏸ Pause", + "btn_resume": "▶ Resume", + "btn_cancel": "✕ Stop", + "label_nozzle": "Nozzle", + "label_bed": "Bed", + "label_fan": "🌀 Fan", + "label_light": "💡 Light", + "label_on_off": "On / Off", + "label_speed": "Speed", + "panel_print_title": "Print Control", + "panel_print_btn_pause": "⏸ Pause", + "panel_print_btn_resume": "▶ Resume", + "panel_print_btn_cancel": "✕ Cancel", + "panel_print_temps_live": "Temperatures (Live)", + "label_set": "Set", + "label_off": "Off", + "panel_temps_nozzle": "Nozzle", + "panel_temps_bed": "Heated Bed", + "panel_temps_chart": "History (last 60 readings)", + "label_target_c": "Target:", + "panel_motion_xy": "XY Axes", + "panel_motion_z": "Z Axis", + "label_step": "Step size:", + "btn_home_z": "Home Z", + "btn_home_xy": "Home XY", + "btn_home_all": "Home All", + "btn_disable_motors": "Motors Off", + "panel_ams_title": "Filament", + "card_ams": "Filament", + "ams_no_data": "No AMS data received", + "label_slot": "Slot", + "ams_empty": "Empty", + "panel_extras_light": "Light", + "panel_extras_fan": "Fan", + "panel_extras_camera": "Camera", + "btn_cam_start2": "▶ Start", + "btn_cam_stop2": "◼ Stop", + "panel_console_title": "Event Log", + "log_light_on": "Light on", + "log_light_off": "Light off", + "log_fan": "Fan →", + "log_nozzle": "Nozzle →", + "log_bed": "Bed →", + "log_axis": "Axis", + "log_home": "Home", + "log_home_all": "Home All", + "log_cam_start": "Camera started:", + "log_cam_stop": "Camera stopped", + "log_poll_error": "Poll error:", + "log_error": "Error:", + "confirm_cancel": "Really cancel the print?", + "settings_title": "Settings", + "settings_connection": "Connection", + "settings_print": "Print Settings", + "settings_poll": "Poll Interval", + "settings_version": "Version", + "settings_save": "Save & Restart", + "settings_printer_name": "Printer Name", + "settings_printer_ip": "Printer IP", + "settings_mqtt_port": "MQTT Port", + "settings_username": "MQTT Username", + "settings_password": "MQTT Password", + "settings_device_id": "Device ID", + "settings_mode_id": "Mode ID", + "hint_ip_no_port": "IP address only, no port (e.g. 192.168.1.102)", + "settings_default_slot": "Default Slot (single color)", + "settings_slot_auto": "Auto (all loaded slots)", + "settings_auto_leveling": "Auto-Leveling before print", + "settings_camera_on_print": "Turn camera on at print start", + "settings_web_upload_warning": "Show warning when printing web uploads", + "update_check": "Check for Updates", + "update_checking": "Checking...", + "update_available": "available", + "update_none": "Already up to date", + "update_apply": "Install Now", + "update_applying": "Downloading...", + "update_restarting": "Restarting...", + "update_error": "Error", + "btn_connect": "⚡ Connect", + "btn_disconnect": "✕ Disconnect", + "lbl_conn_error": "Connection error:", + "slot_edit_title": "Edit Slot", + "slot_edit_color": "Color", + "slot_edit_material": "Material", + "slot_edit_load": "⬇ Load", + "slot_edit_unload": "⬆ Unload", + "slot_edit_save": "💾 Save", + "slot_edit_custom": "e.g. PLA, PETG, ABS…", + "slot_edit_ok": "AMS Slot", + "log_dir_all": "All", + "log_lvl_label": "Level:", + "file_ready_btn": "▶ Start Print", + "file_slots_btn": "🎨 Select Slots", + "file_cancel_btn": "✕ Cancel", + "nav_printers": "Printers", + "skip_title": "✂ Skip objects", + "skip_hint": "Uncheck objects you no longer want to print:", + "skip_btn_label": "Objects", + "skip_no_objects": "No objects in this print.", + "skip_already": "skipped", + "skip_select_at_least_one": "Please pick at least one object.", + "skip_sending": "Sending …", + "skip_success": "Objects will be skipped.", + "fd_objects_hint": "Skip objects (optional):", + "fd_slots_hint": "Assign GCode channel to AMS slot:", + "fd_cancel": "Cancel", + "fd_print": "▶ Print", + "fd_no_slots_msg": "No loaded AMS slots.{br}Start print anyway?", + "fd_slot": "Slot", + "fd_no_matching_material": "No matching material", + "fd_used": "USED", + "add_printer": "Add printer", + "apd_lbl_ip": "Printer IP", + "apd_lbl_name": "Name (optional)", + "apd_placeholder_name": "e.g. Kobra X Living Room", + "apd_cancel": "Cancel", + "apd_confirm": "Add", + "apd_fetching": "Fetching data from printer…", + "apd_success": "Printer added, bridge restarting…", + "apd_err_ip": "Please enter an IP address", + "printers_remove": "Remove printer", + "printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.", + "printers_active": "● active", + "printers_switch": "Switch →", + "printers_current": "Current printer", + "printers_loading": "Loading…", + "printers_none": "No printers configured.", + "printers_empty_hint": "No printer set up yet.", + "nav_browser": "Browser", + "panel_browser_title": "File Browser", + "store_search_placeholder": "🔍 Search…", + "store_empty": "No files uploaded yet.", + "store_refresh": "↻ Refresh", + "store_print": "▶ Print", + "store_download": "⬇ Download", + "store_delete_confirm": "Delete file?", + "store_print_confirm": "Print file?", + "store_web_verify_title": "Verify file", + "store_web_verify_msg": "Please verify this file was made for Anycubic Kobra X.", + "store_web_verify_confirm": "Confirm", + "store_web_verify_abort": "Abort", + "store_no_results": "No files found.", + "store_never": "never printed", + "store_estimate": "Estimate", + "store_upload_label_prefix": "Drag GCode here or ", + "store_upload_label_browse": "browse", + "store_upload_busy": "⏳ Uploading…", + "store_upload_success": "✓ {file}", + "store_upload_error": "✗ {error}", + "sf_all": "All", + "sf_ok": "✓ Completed", + "sf_err": "✗ Failed", + "sf_new": "New", + "ss_date": "↓ Date", + "ss_name": "A–Z Name", + "ss_dur": "⏱ Print time" +} diff --git a/web/translations/es.json b/web/translations/es.json new file mode 100644 index 0000000..3ae5d0f --- /dev/null +++ b/web/translations/es.json @@ -0,0 +1,231 @@ +{ + "header_status_standby": "Listo", + "header_status_printing": "Imprimiendo", + "header_status_complete": "Completado", + "header_status_error": "Error", + "kobra_free": "Listo", + "kobra_busy": "Ocupado", + "kobra_printing": "Imprimiendo", + "kobra_preheating": "Precalentando", + "kobra_auto_leveling": "Autonivelado", + "kobra_checking": "Comprobando", + "kobra_updated": "Actualizando", + "kobra_init": "Inicializando", + "kobra_pausing": "Pausando...", + "kobra_paused": "Pausado", + "kobra_resuming": "Reanudando...", + "kobra_resumed": "Reanudado", + "kobra_stopping": "Deteniendo...", + "kobra_stoped": "Detenido", + "kobra_finished": "Finalizado", + "kobra_failed": "Error", + "kobra_canceled": "Cancelado", + "kobra_offline": "Offline", + "nav_dashboard": "Panel", + "nav_print": "Impresion", + "nav_temps": "Temperaturas", + "nav_motion": "Movimiento", + "nav_ams": "AMS", + "nav_extras": "Luz / Ventilador", + "nav_console": "Consola", + "card_progress": "Progreso", + "card_temps": "Temperaturas", + "card_light_fan": "Ventilador", + "card_speed": "Velocidad de impresion", + "card_cam": "Camara", + "lbl_elapsed": "Transcurrido:", + "lbl_remaining": "Restante:", + "lbl_slicer_time": "Estimacion del slicer:", + "lbl_layers": "Layer", + "speed_silent": "🐢 Silencioso", + "speed_normal": "⚡ Normal", + "speed_sport": "🚀 Sport", + "lbl_light": "💡 Luz", + "lbl_feed": "Cargar", + "lbl_unload": "Descargar", + "card_ace_dry": "Secado ACE", + "ace_dry_dryer": "Secador", + "ace_dry_status_off": "Estado: Apagado", + "ace_dry_status_on": "Estado: Activo", + "ace_dry_status_remaining": "Restante", + "ace_dry_humidity": "Humedad", + "ace_dry_current_temp": "Temperatura", + "ace_dry_chart": "Historial (Temp/Humedad)", + "ace_dry_temp": "Temperatura (°C)", + "ace_dry_duration": "Duracion (min)", + "ace_dry_start": "▶ Start", + "ace_dry_stop": "■ Stop", + "ace_dry_auto_refill": "Relleno automatico", + "ace_dry_enable": "Activar secado", + "ace_dry_temp_line": "Temperatura de secado", + "ace_dry_time_line": "Tiempo de secado", + "ace_dry_ui_pending": "(solo UI, backend despues)", + "ace_dry_dialog_title": "Ajustes de temp/tiempo del secador", + "ace_dry_dialog_temp": "Temperatura (30-80°C)", + "ace_dry_dialog_time": "Tiempo restante (h:m:s)", + "ace_dry_dialog_confirm": "Confirmar", + "ace_dry_dialog_cancel": "Cancelar", + "ace_dry_dialog_save_restart": "Guardar y reiniciar", + "ace_dry_dialog_custom_name": "Nombre personalizado", + "ace_dry_dialog_reset_default": "Restablecer por defecto", + "cam_placeholder": "📷 Camara no iniciada", + "cam_stream_unavailable": "Stream no disponible", + "btn_cam_start": "▶ Camara", + "btn_cam_stop": "◼ Camara", + "btn_pause": "⏸ Pause", + "btn_resume": "▶ Reanudar", + "btn_cancel": "✕ Detener", + "label_nozzle": "Boquilla", + "label_bed": "Cama", + "label_fan": "🌀 Ventilador", + "label_light": "💡 Luz", + "label_on_off": "Encendido / Apagado", + "label_speed": "Velocidad", + "panel_print_title": "Control de impresion", + "panel_print_btn_pause": "⏸ Pause", + "panel_print_btn_resume": "▶ Reanudar", + "panel_print_btn_cancel": "✕ Cancelar", + "panel_print_temps_live": "Temperaturas (en vivo)", + "label_set": "Set", + "label_off": "Off", + "panel_temps_nozzle": "Boquilla", + "panel_temps_bed": "Cama caliente", + "panel_temps_chart": "Historial (ultimas 60 lecturas)", + "label_target_c": "Objetivo:", + "panel_motion_xy": "Ejes XY", + "panel_motion_z": "Eje Z", + "label_step": "Tamano del paso:", + "btn_home_z": "Home Z", + "btn_home_xy": "Home XY", + "btn_home_all": "Home All", + "btn_disable_motors": "Motores apagados", + "panel_ams_title": "Filamento", + "card_ams": "Filamento", + "ams_no_data": "No se recibieron datos de AMS", + "label_slot": "Ranura", + "ams_empty": "Vacio", + "panel_extras_light": "Luz", + "panel_extras_fan": "Ventilador", + "panel_extras_camera": "Camara", + "btn_cam_start2": "▶ Start", + "btn_cam_stop2": "◼ Detener", + "panel_console_title": "Registro de eventos", + "log_light_on": "Luz encendida", + "log_light_off": "Luz apagada", + "log_fan": "Ventilador →", + "log_nozzle": "Boquilla →", + "log_bed": "Cama →", + "log_axis": "Eje", + "log_home": "Home", + "log_home_all": "Home All", + "log_cam_start": "Camara iniciada:", + "log_cam_stop": "Camara detenida", + "log_poll_error": "Error de sondeo:", + "log_error": "Error:", + "confirm_cancel": "Realmente cancelar la impresion?", + "settings_title": "Configuracion", + "settings_connection": "Conexion", + "settings_print": "Ajustes de impresion", + "settings_poll": "Intervalo de sondeo", + "settings_version": "Version", + "settings_save": "Guardar y reiniciar", + "settings_printer_name": "Nombre de impresora", + "settings_printer_ip": "IP de impresora", + "settings_mqtt_port": "MQTT Port", + "settings_username": "Usuario MQTT", + "settings_password": "Contrasena MQTT", + "settings_device_id": "ID del dispositivo", + "settings_mode_id": "Mode ID", + "hint_ip_no_port": "Solo direccion IP, sin puerto (p. ej. 192.168.1.102)", + "settings_default_slot": "Ranura predeterminada (un color)", + "settings_slot_auto": "Auto (todos los slots cargados)", + "settings_auto_leveling": "Autonivelado antes de imprimir", + "settings_camera_on_print": "Encender camara al iniciar impresion", + "settings_web_upload_warning": "Mostrar advertencia al imprimir subidas web", + "update_check": "Buscar actualizaciones", + "update_checking": "Comprobando...", + "update_available": "disponible", + "update_none": "Ya actualizado", + "update_apply": "Instalar ahora", + "update_applying": "Descargando...", + "update_restarting": "Reiniciando...", + "update_error": "Error", + "btn_connect": "⚡ Conectar", + "btn_disconnect": "✕ Desconectar", + "lbl_conn_error": "Error de conexion:", + "slot_edit_title": "Editar slot", + "slot_edit_color": "Color", + "slot_edit_material": "Material", + "slot_edit_load": "⬇ Cargar", + "slot_edit_unload": "⬆ Descargar", + "slot_edit_save": "💾 Guardar", + "slot_edit_custom": "p. ej. PLA, PETG, ABS…", + "slot_edit_ok": "Ranura AMS", + "log_dir_all": "Todos", + "log_lvl_label": "Level:", + "file_ready_btn": "▶ Iniciar impresion", + "file_slots_btn": "🎨 Seleccionar ranuras", + "file_cancel_btn": "✕ Cancelar", + "nav_printers": "Impresoras", + "skip_title": "✂ Omitir objetos", + "skip_hint": "Desmarca objetos que ya no quieras imprimir:", + "skip_btn_label": "Objetos", + "skip_no_objects": "No hay objetos en esta impresion.", + "skip_already": "omitido", + "skip_select_at_least_one": "Elige al menos un objeto.", + "skip_sending": "Enviando …", + "skip_success": "Se omitiran los objetos.", + "fd_objects_hint": "Omitir objetos (opcional):", + "fd_slots_hint": "Asignar canal GCode a la ranura AMS:", + "fd_cancel": "Cancelar", + "fd_print": "▶ Imprimir", + "fd_no_slots_msg": "No hay slots AMS cargados.{br}Iniciar impresion de todos modos?", + "fd_slot": "Ranura", + "fd_no_matching_material": "No hay material compatible", + "fd_used": "USADO", + "add_printer": "Agregar impresora", + "apd_lbl_ip": "IP de impresora", + "apd_lbl_name": "Nombre (opcional)", + "apd_placeholder_name": "p. ej. Kobra X Sala", + "apd_cancel": "Cancelar", + "apd_confirm": "Agregar", + "apd_fetching": "Obteniendo datos de la impresora…", + "apd_success": "Impresora agregada, reiniciando bridge…", + "apd_err_ip": "Introduce una direccion IP", + "printers_remove": "Eliminar impresora", + "printers_remove_confirm": "Eliminar impresora \"{name}\"? El bridge se reiniciara.", + "printers_active": "● activa", + "printers_switch": "Cambiar →", + "printers_current": "Impresora actual", + "printers_loading": "Cargando…", + "printers_none": "No hay impresoras configuradas.", + "printers_empty_hint": "Aun no hay impresora configurada.", + "nav_browser": "Explorador", + "panel_browser_title": "Explorador de archivos", + "store_search_placeholder": "🔍 Buscar…", + "store_empty": "Aun no hay archivos subidos.", + "store_refresh": "↻ Actualizar", + "store_print": "▶ Imprimir", + "store_download": "⬇ Descargar", + "store_delete_confirm": "Eliminar archivo?", + "store_print_confirm": "Imprimir archivo?", + "store_web_verify_title": "Verificar archivo", + "store_web_verify_msg": "Verifica que este archivo fue creado para Anycubic Kobra X.", + "store_web_verify_confirm": "Confirmar", + "store_web_verify_abort": "Abortar", + "store_no_results": "No se encontraron archivos.", + "store_never": "nunca impreso", + "store_estimate": "Estimacion", + "store_upload_label_prefix": "Arrastra GCode aqui o ", + "store_upload_label_browse": "buscar", + "store_upload_busy": "⏳ Subiendo…", + "store_upload_success": "✓ {file}", + "store_upload_error": "✗ {error}", + "sf_all": "Todos", + "sf_ok": "✓ Completado", + "sf_err": "✗ Fallido", + "sf_new": "Nuevo", + "ss_date": "↓ Fecha", + "ss_name": "A–Z Nombre", + "ss_dur": "⏱ Tiempo de impresion" +} diff --git a/web/translations/zh-cn.json b/web/translations/zh-cn.json new file mode 100644 index 0000000..28521b5 --- /dev/null +++ b/web/translations/zh-cn.json @@ -0,0 +1,231 @@ +{ + "header_status_standby": "就绪", + "header_status_printing": "打印中", + "header_status_complete": "完成", + "header_status_error": "错误", + "kobra_free": "就绪", + "kobra_busy": "忙碌", + "kobra_printing": "打印中", + "kobra_preheating": "预热中", + "kobra_auto_leveling": "自动调平", + "kobra_checking": "检查中", + "kobra_updated": "更新中", + "kobra_init": "初始化中", + "kobra_pausing": "暂停中...", + "kobra_paused": "已暂停", + "kobra_resuming": "恢复中...", + "kobra_resumed": "已恢复", + "kobra_stopping": "停止中...", + "kobra_stoped": "已停止", + "kobra_finished": "已完成", + "kobra_failed": "错误", + "kobra_canceled": "已取消", + "kobra_offline": "离线", + "nav_dashboard": "仪表盘", + "nav_print": "打印", + "nav_temps": "温度", + "nav_motion": "运动", + "nav_ams": "AMS", + "nav_extras": "灯光 / 风扇", + "nav_console": "控制台", + "card_progress": "进度", + "card_temps": "温度", + "card_light_fan": "风扇", + "card_speed": "打印速度", + "card_cam": "相机", + "lbl_elapsed": "已用时间:", + "lbl_remaining": "剩余时间:", + "lbl_slicer_time": "切片预估:", + "lbl_layers": "层", + "speed_silent": "🐢 静音", + "speed_normal": "⚡ 标准", + "speed_sport": "🚀 运动", + "lbl_light": "💡 灯光", + "lbl_feed": "进料", + "lbl_unload": "退料", + "card_ace_dry": "ACE 烘干", + "ace_dry_dryer": "烘干机", + "ace_dry_status_off": "状态: 关闭", + "ace_dry_status_on": "状态: 运行中", + "ace_dry_status_remaining": "剩余", + "ace_dry_humidity": "湿度", + "ace_dry_current_temp": "温度", + "ace_dry_chart": "历史 (温度/湿度)", + "ace_dry_temp": "温度 (°C)", + "ace_dry_duration": "时长 (分钟)", + "ace_dry_start": "▶ 启动", + "ace_dry_stop": "■ 停止", + "ace_dry_auto_refill": "自动补料", + "ace_dry_enable": "启用烘干", + "ace_dry_temp_line": "烘干温度", + "ace_dry_time_line": "烘干时间", + "ace_dry_ui_pending": "(仅 UI,后端稍后支持)", + "ace_dry_dialog_title": "烘干温度/时间设置", + "ace_dry_dialog_temp": "温度 (30-80°C)", + "ace_dry_dialog_time": "剩余时间 (h:m:s)", + "ace_dry_dialog_confirm": "确认", + "ace_dry_dialog_cancel": "取消", + "ace_dry_dialog_save_restart": "保存并重启", + "ace_dry_dialog_custom_name": "自定义名称", + "ace_dry_dialog_reset_default": "恢复默认", + "cam_placeholder": "📷 相机未启动", + "cam_stream_unavailable": "视频流不可用", + "btn_cam_start": "▶ 相机", + "btn_cam_stop": "◼ 相机", + "btn_pause": "⏸ 暂停", + "btn_resume": "▶ 继续", + "btn_cancel": "✕ 停止", + "label_nozzle": "喷嘴", + "label_bed": "热床", + "label_fan": "🌀 风扇", + "label_light": "💡 灯光", + "label_on_off": "开 / 关", + "label_speed": "速度", + "panel_print_title": "打印控制", + "panel_print_btn_pause": "⏸ 暂停", + "panel_print_btn_resume": "▶ 继续", + "panel_print_btn_cancel": "✕ 取消", + "panel_print_temps_live": "温度 (实时)", + "label_set": "设置", + "label_off": "关闭", + "panel_temps_nozzle": "喷嘴", + "panel_temps_bed": "热床", + "panel_temps_chart": "历史 (最近 60 次读数)", + "label_target_c": "目标:", + "panel_motion_xy": "XY 轴", + "panel_motion_z": "Z 轴", + "label_step": "步进:", + "btn_home_z": "回零 Z", + "btn_home_xy": "回零 XY", + "btn_home_all": "全部回零", + "btn_disable_motors": "关闭电机", + "panel_ams_title": "耗材", + "card_ams": "耗材", + "ams_no_data": "未收到 AMS 数据", + "label_slot": "槽位", + "ams_empty": "空", + "panel_extras_light": "灯光", + "panel_extras_fan": "风扇", + "panel_extras_camera": "相机", + "btn_cam_start2": "▶ 启动", + "btn_cam_stop2": "◼ 停止", + "panel_console_title": "事件日志", + "log_light_on": "灯光已开", + "log_light_off": "灯光已关", + "log_fan": "风扇 →", + "log_nozzle": "喷嘴 →", + "log_bed": "热床 →", + "log_axis": "轴", + "log_home": "回零", + "log_home_all": "全部回零", + "log_cam_start": "相机已启动:", + "log_cam_stop": "相机已停止", + "log_poll_error": "轮询错误:", + "log_error": "错误:", + "confirm_cancel": "确定要取消打印吗?", + "settings_title": "设置", + "settings_connection": "连接", + "settings_print": "打印设置", + "settings_poll": "轮询间隔", + "settings_version": "版本", + "settings_save": "保存并重启", + "settings_printer_name": "打印机名称", + "settings_printer_ip": "打印机 IP", + "settings_mqtt_port": "MQTT 端口", + "settings_username": "MQTT 用户名", + "settings_password": "MQTT 密码", + "settings_device_id": "设备 ID", + "settings_mode_id": "模式 ID", + "hint_ip_no_port": "仅填写 IP,不要端口 (例如 192.168.1.102)", + "settings_default_slot": "默认槽位 (单色)", + "settings_slot_auto": "自动 (所有已装载槽位)", + "settings_auto_leveling": "打印前自动调平", + "settings_camera_on_print": "打印开始时开启相机", + "settings_web_upload_warning": "打印网页上传文件时显示警告", + "update_check": "检查更新", + "update_checking": "检查中...", + "update_available": "可用", + "update_none": "已是最新版本", + "update_apply": "立即安装", + "update_applying": "下载中...", + "update_restarting": "重启中...", + "update_error": "错误", + "btn_connect": "⚡ 连接", + "btn_disconnect": "✕ 断开", + "lbl_conn_error": "连接错误:", + "slot_edit_title": "编辑槽位", + "slot_edit_color": "颜色", + "slot_edit_material": "材料", + "slot_edit_load": "⬇ 进料", + "slot_edit_unload": "⬆ 退料", + "slot_edit_save": "💾 保存", + "slot_edit_custom": "例如 PLA, PETG, ABS…", + "slot_edit_ok": "AMS 槽位", + "log_dir_all": "全部", + "log_lvl_label": "级别:", + "file_ready_btn": "▶ 开始打印", + "file_slots_btn": "🎨 选择槽位", + "file_cancel_btn": "✕ 取消", + "nav_printers": "打印机", + "skip_title": "✂ 跳过对象", + "skip_hint": "取消勾选不想继续打印的对象:", + "skip_btn_label": "对象", + "skip_no_objects": "此打印任务没有对象。", + "skip_already": "已跳过", + "skip_select_at_least_one": "请至少选择一个对象。", + "skip_sending": "发送中 …", + "skip_success": "对象将被跳过。", + "fd_objects_hint": "跳过对象 (可选):", + "fd_slots_hint": "将 GCode 通道分配到 AMS 槽位:", + "fd_cancel": "取消", + "fd_print": "▶ 打印", + "fd_no_slots_msg": "没有已装载的 AMS 槽位。{br}仍要开始打印吗?", + "fd_slot": "槽位", + "fd_no_matching_material": "无匹配材料", + "fd_used": "已用", + "add_printer": "添加打印机", + "apd_lbl_ip": "打印机 IP", + "apd_lbl_name": "名称 (可选)", + "apd_placeholder_name": "例如 Kobra X 客厅", + "apd_cancel": "取消", + "apd_confirm": "添加", + "apd_fetching": "正在从打印机获取数据…", + "apd_success": "打印机已添加,Bridge 正在重启…", + "apd_err_ip": "请输入 IP 地址", + "printers_remove": "移除打印机", + "printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。", + "printers_active": "● 活动", + "printers_switch": "切换 →", + "printers_current": "当前打印机", + "printers_loading": "加载中…", + "printers_none": "未配置打印机。", + "printers_empty_hint": "尚未设置打印机。", + "nav_browser": "浏览器", + "panel_browser_title": "文件浏览器", + "store_search_placeholder": "🔍 搜索…", + "store_empty": "尚未上传文件。", + "store_refresh": "↻ 刷新", + "store_print": "▶ 打印", + "store_download": "⬇ 下载", + "store_delete_confirm": "删除文件?", + "store_print_confirm": "打印文件?", + "store_web_verify_title": "验证文件", + "store_web_verify_msg": "请确认此文件是为 Anycubic Kobra X 创建的。", + "store_web_verify_confirm": "确认", + "store_web_verify_abort": "取消", + "store_no_results": "未找到文件。", + "store_never": "从未打印", + "store_estimate": "估算", + "store_upload_label_prefix": "将 GCode 拖到这里或 ", + "store_upload_label_browse": "浏览", + "store_upload_busy": "⏳ 上传中…", + "store_upload_success": "✓ {file}", + "store_upload_error": "✗ {error}", + "sf_all": "全部", + "sf_ok": "✓ 已完成", + "sf_err": "✗ 失败", + "sf_new": "新", + "ss_date": "↓ 日期", + "ss_name": "A–Z 名称", + "ss_dur": "⏱ 打印时间" +}