From f16c9c86ebeeb488fea3d85959739ae4438f2b3f Mon Sep 17 00:00:00 2001 From: Tobias Leuschner Date: Sat, 9 May 2026 15:12:33 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Mehrsprachige=20Unterst=C3=BCtzung=20du?= =?UTF-8?q?rch=20Datenattribute=20f=C3=BCr=20=C3=9Cbersetzungen=20verbesse?= =?UTF-8?q?rt;=20Versionsnummer=20auf=201.0.55=20erh=C3=B6ht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 109 ++++++++++++++++++++++++++++++++++++---------------- version.txt | 2 +- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/app.py b/app.py index eaaf4b6..32a84b5 100644 --- a/app.py +++ b/app.py @@ -2155,7 +2155,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
PiCopy -
+
verfügbar
@@ -2182,7 +2182,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
Kopierstatus - +
Bereit
@@ -2311,7 +2311,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -2461,8 +2461,8 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
-
Heimnetz
-
Hotspot (AP)
+
Heimnetz
+
Hotspot (AP)
Heimnetz für die Router-Verbindung. Ohne Verbindung startet PiCopy automatisch einen eigenen Hotspot.
@@ -2470,7 +2470,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -2525,7 +2525,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
Installiere WireGuard...
-
apt-get läuft - bitte warten (bis 60 s)
+
apt-get läuft – bitte warten (bis 60 s)
@@ -2556,7 +2556,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -2649,7 +2649,28 @@ const I18N = { 'wg.connecting':'Verbinde VPN...', 'wg.disconnecting':'Trenne VPN...', 'wg.disc_failed':'Trennen fehlgeschlagen', 'wg.cfg_empty':'Konfiguration ist leer', 'wg.saving':'Speichere...', 'wg.saved':'✓ Konfiguration gespeichert', + 'wg.invalid_cfg':'[Interface] fehlt', + 'wg.action_install':'Installiere', 'wg.action_remove':'Deinstalliere', 'copy.cfg_saved':'Gespeichert!', + 'copy.error_prefix':'Fehler: ', + 'status.active':'Aktiv', 'status.inactive':'Inaktiv', + 'status.configured':'Konfiguriert', 'status.not_cfg':'Nicht konfiguriert', + 'size.free':' frei', + 'confirm.samba':'Samba installieren und /opt/picopy/internal als Netzwerkfreigabe PiCopy bereitstellen?\n\nDie Freigabe ist im Netzwerk lesbar erreichbar.', + 'share.activating':'Aktiviere Freigabe...', 'share.deactivating':'Deaktiviere Freigabe...', + 'share.active_ok':'✓ Freigabe aktiv', 'share.deactive_ok':'✓ Freigabe deaktiviert', + 'update.available':'Update v${v} verfügbar – über das Badge oben installieren.', + 'update.error_prefix':'Fehler: ', 'update.current':'PiCopy ist aktuell.', + 'update.installing':'↓ Installiere...', + 'btn.connect_load':'🔗 Verbinden & Freigaben laden', + 'ut.toggle_close':'✕ Abbrechen', 'ut.toggle_open':'+ NAS-Ziel hinzufügen', + 'check_update':'Nach Update suchen', + 'title.install':'Klicken zum Installieren', 'title.dismiss':'Meldung schließen', + 'title.reload':'Neu laden', 'title.scan_nets':'Netzwerke suchen', + 'title.wg_rm':'wireguard-Paket entfernen', + 'wg.apt_running':'apt-get läuft – bitte warten (bis 60 s)', + 'wg.not_installed':'Nicht installiert', + 'Heimnetz':'Heimnetz', }, en: { 'wifi.connecting':'Connecting...', 'wifi.none':'No Wi-Fi', 'wifi.connected':'Connected', @@ -2697,7 +2718,28 @@ const I18N = { 'wg.connecting':'Connecting VPN...', 'wg.disconnecting':'Disconnecting VPN...', 'wg.disc_failed':'Disconnect failed', 'wg.cfg_empty':'Configuration is empty', 'wg.saving':'Saving...', 'wg.saved':'✓ Configuration saved', + 'wg.invalid_cfg':'[Interface] missing', + 'wg.action_install':'Installing', 'wg.action_remove':'Uninstalling', 'copy.cfg_saved':'Saved!', + 'copy.error_prefix':'Error: ', + 'status.active':'Active', 'status.inactive':'Inactive', + 'status.configured':'Configured', 'status.not_cfg':'Not configured', + 'size.free':' free', + 'confirm.samba':'Install Samba and share /opt/picopy/internal as network share PiCopy?\n\nThe share will be accessible read-only on the network.', + 'share.activating':'Activating share...', 'share.deactivating':'Deactivating share...', + 'share.active_ok':'✓ Share active', 'share.deactive_ok':'✓ Share deactivated', + 'update.available':'Update v${v} available – install via the badge above.', + 'update.error_prefix':'Error: ', 'update.current':'PiCopy is up to date.', + 'update.installing':'↓ Installing...', + 'btn.connect_load':'🔗 Connect & Load Shares', + 'ut.toggle_close':'✕ Cancel', 'ut.toggle_open':'+ Add NAS Target', + 'check_update':'Check For Update', + 'title.install':'Click to install', 'title.dismiss':'Close message', + 'title.reload':'Reload', 'title.scan_nets':'Scan networks', + 'title.wg_rm':'Remove WireGuard package', + 'wg.apt_running':'apt-get running – please wait (up to 60 s)', + 'wg.not_installed':'Not installed', + 'Heimnetz':'Home Network', } }; const STATIC_EN = { @@ -2789,6 +2831,7 @@ function applyLang(){ 'ap-pw':['PiCopy,','PiCopy,'], }; Object.entries(ph).forEach(([id,v])=>{const el=$(id); if(el) el.placeholder=lang==='en'?v[1]:v[0];}); + document.querySelectorAll('[data-title-i18n]').forEach(el=>{el.title=t(el.dataset.titleI18n);}); } async function setLang(lang){ cfg.ui_lang=lang; @@ -2877,8 +2920,8 @@ function qrFormatBits(ecl,mask){ // -- Tabs --------------------------------------------------------------------- function swTab(show,hide){ $(show).classList.add('on'); $(hide).classList.remove('on'); - document.querySelectorAll('.tab').forEach(t=> - t.classList.toggle('on',t.textContent.trim().startsWith(show==='tc'?'Heim':'Hot')) + document.querySelectorAll('.tab').forEach(tab=> + tab.classList.toggle('on', tab.dataset.tab===show) ); } @@ -3158,7 +3201,7 @@ function renderUTs(){
- +
@@ -3167,7 +3210,7 @@ function renderUTs(){ function utToggleForm(){ const f=$('ut-form'),b=$('ut-add-btn'),show=f.style.display==='none'; f.style.display=show?'block':'none'; - b.innerHTML=show?'✕ Abbrechen':'+ NAS-Ziel hinzufügen'; + b.innerHTML=show?t('ut.toggle_close'):t('ut.toggle_open'); if(show){ $('ut-step1').style.display=''; $('ut-step2').style.display='none'; ['ut-host','ut-user','ut-pass','ut-name'].forEach(id=>{$(id).value='';}); @@ -3180,12 +3223,12 @@ async function utConnect(){ const host=$('ut-host').value.trim(); if(!host){flash('ut-form-flash','err',t('flash.no_server'));return;} const btn=$('ut-connect-btn'); - btn.disabled=true; btn.textContent='Verbinde...'; + btn.disabled=true; btn.textContent=t('flash.connect_btn'); $('ut-form-flash').style.display='none'; const r=await api('/upload/browse','POST',{ host, user:$('ut-user').value.trim(), pass:$('ut-pass').value }); - btn.disabled=false; btn.innerHTML='🔗 Verbinden & Freigaben laden'; + btn.disabled=false; btn.innerHTML=t('btn.connect_load'); if(r.error){flash('ut-form-flash','err','✗ '+r.error);return;} if(!r.shares||!r.shares.length){flash('ut-form-flash','warn',t('flash.no_shares'));return;} _utConn={host, user:$('ut-user').value.trim(), pass:$('ut-pass').value}; @@ -3239,7 +3282,7 @@ async function updateInternalShareBox(state=null){ if(!$('internal-share-box'))return; const s=state||await api('/internal-share/status'); const btn=$('internal-share-btn'), detail=$('internal-share-detail'); - const free=s.free!=null?fmtBytes(s.free)+' frei':''; + const free=s.free!=null?fmtBytes(s.free)+t('size.free'):''; if(s.pkg_running){ btn.disabled=true; btn.textContent=t('share.install'); detail.textContent=t('share.installing')+free; @@ -3248,7 +3291,7 @@ async function updateInternalShareBox(state=null){ btn.disabled=false; btn.textContent=s.enabled?t('share.on'):t('share.off'); const status=s.enabled - ? ((s.active?'Aktiv':'Konfiguriert')+' | \\\\'+(location.hostname||'picopy')+'\\PiCopy') + ? ((s.active?t('status.active'):t('status.configured'))+' | \\\\'+(location.hostname||'picopy')+'\\PiCopy') : t('share.inactive'); detail.textContent=status+(free?' | '+free:''); } @@ -3257,12 +3300,12 @@ async function toggleInternalShare(){ const current=await api('/internal-share/status'); const enable=!current.enabled; if(enable && !current.installed){ - if(!confirm('Samba installieren und /opt/picopy/internal als Netzwerkfreigabe PiCopy bereitstellen?\n\nDie Freigabe ist lesbar im Netzwerk erreichbar.'))return; + if(!confirm(t('confirm.samba')))return; } - flash('internal-share-flash','ok',enable?'Aktiviere Freigabe...':'Deaktiviere Freigabe...'); + flash('internal-share-flash','ok',enable?t('share.activating'):t('share.deactivating')); const r=await api('/internal-share','POST',{enabled:enable}); if(r.error){flash('internal-share-flash','err',r.error);return;} - flash('internal-share-flash','ok',enable?'✓ Freigabe aktiv':'✓ Freigabe deaktiviert'); + flash('internal-share-flash','ok',enable?t('share.active_ok'):t('share.deactive_ok')); updateInternalShareBox(r.status); } @@ -3377,14 +3420,14 @@ async function poll(){ const ni=$('wg-not-installed'),pp=$('wg-pkg-progress'),ui=$('wg-installed-ui'); if(v.pkg_running){ ni.style.display='none'; pp.style.display='block'; ui.style.display='none'; - const act=v.pkg_action==='remove'?'Deinstalliere':'Installiere'; + const act=t(v.pkg_action==='remove'?'wg.action_remove':'wg.action_install'); $('wg-pkg-title').textContent=act+' WireGuard...'; $('wg-pkg-icon').textContent='⏳'; $('wg-status-sub').textContent=act+'...'; vp.style.display='none'; } else if(!v.installed){ ni.style.display='block'; pp.style.display='none'; ui.style.display='none'; - $('wg-status-sub').textContent='Nicht installiert'; + $('wg-status-sub').textContent=t('wg.not_installed')||'Nicht installiert'; vp.style.display='none'; if(v.pkg_error) flash('wg-flash','err',v.pkg_error); } else { @@ -3404,7 +3447,7 @@ async function poll(){ wgd.className='wdot d'; wgl.textContent=t('vpn.disconnected'); wgdet.textContent=v.error||''; bc.style.display=v.has_config?'':'none'; bc.disabled=false; bd.style.display='none'; - $('wg-status-sub').textContent=v.has_config?'Konfiguriert':'Nicht konfiguriert'; + $('wg-status-sub').textContent=v.has_config?t('status.configured'):t('status.not_cfg'); } } } @@ -3449,13 +3492,13 @@ async function poll(){ bS.style.display=''; bC.style.display='none'; cf.textContent=''; eta.style.display='none'; spd.style.display='none'; pfiles.style.display='none'; pbytes.style.display='none'; pp.style.display='none'; if(c.error){ - tx.className='st-headline st-err'; tx.textContent='Fehler: '+c.error; + tx.className='st-headline st-err'; tx.textContent=t('copy.error_prefix')+c.error; pf.className='prog-fill err'; pw.style.display='block'; pf.style.width='100%'; sum.textContent=''; time.textContent=''; }else if(c.last_copy && !_dismissed){ tx.className='st-headline st-ok'; tx.textContent=t('copy.done'); pf.className='prog-fill done'; pw.style.display='block'; pf.style.width='100%'; - sum.textContent=c.total+' Dateien | '+fmtBytes(c.bytes_total); + sum.textContent=c.total+t('copy.files')+' | '+fmtBytes(c.bytes_total); time.textContent=new Date(c.last_copy).toLocaleString('de-DE'); $('st-dismiss').style.display=''; // Auto-dismiss nach 5 Minuten @@ -3503,7 +3546,7 @@ let _dismissed = false, _autoDismissTimer = null; function dismissStatus(){ _dismissed = true; if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; } - $('st-text').className='st-headline st-idle'; $('st-text').textContent='Bereit'; + $('st-text').className='st-headline st-idle'; $('st-text').textContent=t('copy.ready'); $('prog-wrap').style.display='none'; $('st-summary').textContent=''; $('st-time').textContent=''; $('st-dismiss').style.display='none'; @@ -3528,7 +3571,7 @@ async function installUpdate() { const latest = (u.latest || '?'); if (!confirm(t('confirm.update').replace('${v}',latest))) return; - $('upd-badge').innerHTML = '↓ Installiere...'; + $('upd-badge').innerHTML = t('update.installing'); $('upd-badge').style.pointerEvents = 'none'; try { @@ -3548,7 +3591,7 @@ async function installUpdate() { async function checkUpdate() { const btn = event.currentTarget; - btn.disabled = true; btn.innerHTML = '🔍 Prüfe...'; + btn.disabled = true; btn.innerHTML = '🔍 '+t('btn.checking'); try { await api('/update/check', 'POST'); // Warten bis der Server-Check abgeschlossen ist (max 15 s, alle 500 ms) @@ -3561,19 +3604,19 @@ async function checkUpdate() { await pollUpdate(); // Badge sofort aktualisieren const fl = $('sys-update-flash'); if (u.available && u.latest) { - fl.className = 'flash warn'; fl.textContent = 'Update v' + u.latest + ' verfügbar - über das Badge oben installieren.'; + fl.className = 'flash warn'; fl.textContent = t('update.available').replace('${v}',u.latest); } else if (u.error) { - fl.className = 'flash err'; fl.textContent = 'Fehler: ' + u.error; + fl.className = 'flash err'; fl.textContent = t('update.error_prefix') + u.error; } else { - fl.className = 'flash ok'; fl.textContent = 'PiCopy ist aktuell.'; + fl.className = 'flash ok'; fl.textContent = t('update.current'); } fl.style.display = 'block'; if (fl.className.includes('ok')) setTimeout(() => fl.style.display = 'none', 3500); } catch(e) { const fl = $('sys-update-flash'); - fl.className = 'flash err'; fl.textContent = 'Verbindung fehlgeschlagen.'; fl.style.display = 'block'; + fl.className = 'flash err'; fl.textContent = t('expl.conn_err'); fl.style.display = 'block'; } finally { - btn.disabled = false; btn.innerHTML = '🔍 Nach Update suchen'; + btn.disabled = false; btn.innerHTML = '🔍 '+t('check_update'); } } @@ -3614,7 +3657,7 @@ async function wgDisconnect(){ async function wgSaveConfig(){ const content=$('wg-config').value.trim(); if(!content){flash('wg-flash','err',t('wg.cfg_empty'));return;} - if(!content.includes('[Interface]')){flash('wg-flash','err','[Interface] fehlt');return;} + if(!content.includes('[Interface]')){flash('wg-flash','err',t('wg.invalid_cfg'));return;} const auto=$('wg-auto').checked; flash('wg-flash','ok',t('wg.saving')); const r=await api('/wireguard/config','POST',{content,auto}); diff --git a/version.txt b/version.txt index 970b756..61098d2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.54 +1.0.55 \ No newline at end of file