release-2.9.1 (#1557)

* fix(sso): send the PKCE verifier for GitHub login (#1534)

* fix(remote-desktop): clip cursor overflow without disabling zoom (#1535)

* test(remote-desktop): preserve sessions beyond one hour (#1536)

* fix(hosts): expose controls for overflowing editor tabs (#1537)

* fix(hosts): expose controls for overflowing editor tabs

* test(hosts): mock resize observation in admin panel tests

* fix(status): skip TCP probes while host sessions are active (#1538)

* fix(database): batch session activity persistence (#1539)

* docs(api): describe cookie and API key authentication (#1540)

* fix(snippets): repair missing note column on SQLite upgrades (#1541)

* fix(docker): retain stored SSH credential association (#1543)

* fix(file-manager): render transfer toasts without plugin hooks (#1546)

* feat(hosts): keep compact row actions inline (#1547)

* fix(auth): allow retrying unavailable second-factor interfaces (#1549)

* fix(auth): reject unresolved legacy identity provisioning (#1550)

* fix(desktop): ship the plugin SDK inside Mac App Store builds (#1551)

* fix(tmux): pass full session info to the terminal's session picker (#1552)

The tmux.sessions service reduced detected sessions to bare names, but the
picker reads session.name, so every entry rendered blank and selecting one
sent an empty name, which created a new session instead of attaching.

* fix(ssh): read force keyboard-interactive as a boolean when resolving hosts (#1553)

The column holds the text "true"/"false", and the resolver passed it
through as-is. The string "false" is truthy, so the password provider
treated every saved host as forced keyboard-interactive and left the
password out. Jump hops are built straight from the resolved host, so a
password hop whose server doesn't offer keyboard-interactive failed with
"All configured authentication methods failed".

* fix(database): batch database saves for bulk host writes (#1554)

* fix(database): yield to the event loop between database saves

Each SQLite save serializes and encrypts the whole database into new
buffers, which V8 only releases once the event loop turns. Boot
migrations and bulk host import write plugin settings per host and per
key in an awaited loop, so hundreds of full copies piled up and the
container ran out of memory.

* fix(database): save once per bulk host write instead of once per row

On SQLite every repository write force-saves the whole database. Boot
plugin data migrations, host defaults materialization and the bulk
host routes write one row per host per setting, so they serialized and
encrypted the full database hundreds of times in a row.

DatabaseSaveTrigger.batched wraps a function so force saves made while
it runs collapse into a single save when it finishes. Nested scopes fold
into the outer one, and work that outlives its scope saves normally.

* feat(hosts): add instance-wide predefined tag suggestions (#1548)

* feat(hosts): add shared predefined tag suggestions

* style(hosts): format tag catalog routes

* test(database): advance fake timers past the save yield (#1555)

* fix(i18n): complete Simplified Chinese core and plugin translations (#1542)

* fix(i18n): complete Chinese onboarding and navigation labels

* fix(i18n): translate remaining Chinese core and plugin interfaces

* fix(i18n): translate host editor tab overflow controls

* fix(i18n): use consistent Chinese fleet terminology

* fix(i18n): localize hardcoded controls and plugin views

* fix(i18n): translate built-in homepage widget catalog

* test(homepage): follow translated timezone placeholder

* fix(i18n): translate plugin-provided homepage widgets

* fix(i18n): localize feature settings section titles

* fix: 2.8 oidc accounts unable to sign in after upgrading (#1381)

* fix: plugins losing the saved ssh login when copying a host (#1391)

* fix: fleets, proxmox and automations not getting the host sudo password

* fix: host imports running a defaults pass and metrics restart per host (#1384)

* fix: high cpu from status probes and full database saves on every sample (#1300)

* fix: user data export freezing the server (#1393)

* fix: op:// secret references rejected as ssh keys on credentials (#1394)

* fix: slow file deletes from the trash lookups on every delete (#1390)

* fix: add openapi docs and error handling to host tag routes

* test: compare download stream buffers directly so it stops timing out

* chore: drop em dash from host row comment

* chore: update release notes for 2.9.1

* fix: restore space to add host tags and redesign predefined tags editor

* fix: host key silently accepted when the host is missing from the database (#1397)

* fix: clearer host login failed status label and fix its translations (#1396)

* chore: add host key and status label fixes to release notes

* fix: sso and ldap provider dialogs overflowing the screen and using mismatched toggles

* chore: increment ver

* fix(audit): store plugin entries with no acting user as a null user_id (#1556)

Plugin audit entries written outside a request used the literal "system"
as user_id. That column references users.id, so the insert was refused
(Postgres logs it as an FK violation) and the entry was silently dropped.
Write null instead and keep "system" / plugin:<id> in username.

* chore: add sso/ldap dialog and audit log fixes to release notes

* fix: tunnels and host settings missing from shared hosts on desktop

* fix: remote desktop logins missing from shared hosts on desktop

* fix: simplify host status to online/offline and keep it live without a refresh

* chore: sync Crowdin translations for 2.9.1

---------

Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
This commit is contained in:
Luke GustafsonandZacharyZcR authored and GitHub committed 2026-10-04 15:11:41 -05:00
1 parent 5786eaccbb
commit 6b708106e1
912 files changed
+14220 -2200

No files matched your search

+15 -3
View File
@@ -323,7 +323,7 @@
"sideBySide": "Side by Side",
"inline": "Inline",
"fileComparison": "File Comparison: {{file1}} vs {{file2}}",
"fileTooLarge": "File too large: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH connection failed. Please check your connection to {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Failed to load file: {{error}}",
"connecting": "Connecting...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Use comfortable layout",
"cannotLoadPdf": "Cannot load PDF",
"pdfLoadError": "There was an error loading this PDF file.",
"loadingPdf": "Loading PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Loading page...",
"cancel": "Cancel",
"copiedItems_one": "Copied {{count}} item",
@@ -383,7 +383,16 @@
"videoUnsupported": "Your browser cannot play this video format.",
"downloadToPlay": "Download to play it elsewhere",
"cannotPreviewType": "Cannot preview this file type",
"cannotPreviewTypeHint": "This file type has no preview. Download it to open it in another app."
"cannotPreviewTypeHint": "This file type has no preview. Download it to open it in another app.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Source → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → dest (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → dest (local): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Sy aan Sy",
"inline": "Inlyn",
"fileComparison": "Lêervergelyking: {{file1}} vs {{file2}}",
"fileTooLarge": "Lêer te groot: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-verbinding het misluk. Kontroleer asseblief jou verbinding met {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Kon nie lêer laai nie: {{error}}",
"connecting": "Verbind...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Gebruik gemaklike uitleg",
"cannotLoadPdf": "Kan nie PDF laai nie",
"pdfLoadError": "Daar was 'n fout tydens die laai van hierdie PDF-lêer.",
"loadingPdf": "Laai PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Laai bladsy...",
"cancel": "Kanselleer",
"copiedItems_one": "Gekopieerde {{count}} item",
@@ -383,7 +383,16 @@
"videoUnsupported": "Jou blaaier kan nie hierdie videoformaat speel nie.",
"downloadToPlay": "Laai dit af om dit elders te speel",
"cannotPreviewType": "Kan nie hierdie lêertipe voorbeskou nie",
"cannotPreviewTypeHint": "Hierdie lêertipe het geen voorskou nie. Laai dit af om dit in 'n ander toepassing oop te maak."
"cannotPreviewTypeHint": "Hierdie lêertipe het geen voorskou nie. Laai dit af om dit in 'n ander toepassing oop te maak.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Bron → bediener: {{throughput}}",
"metricsHopDestSftpWrite": "Bediener → bestemming (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Bediener → bestemming (plaaslik): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "جنباً إلى جنب",
"inline": "مضمن",
"fileComparison": "مقارنة الملفات: {{file1}} مقابل {{file2}}",
"fileTooLarge": "الملف كبير جداً: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "فشل اتصال SSH. يرجى التحقق من الاتصال بـ {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "فشل تحميل الملف: {{error}}",
"connecting": "جارٍ الاتصال...",
@@ -365,7 +365,7 @@
"comfortableLayout": "استخدم تصميمًا مريحًا",
"cannotLoadPdf": "لا يمكن تحميل PDF",
"pdfLoadError": "حدث خطأ أثناء تحميل ملف PDF هذا.",
"loadingPdf": "جارٍ تحميل PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "جارٍ تحميل الصفحة...",
"cancel": "إلغاء",
"copiedItems_one": "تم نسخ العنصر {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "لا يمكن لمتصفحك تشغيل هذا الفيديو.",
"downloadToPlay": "قم بتنزيله لتشغيله في مكان آخر",
"cannotPreviewType": "لا يمكن معاينة هذا النوع من الملفات",
"cannotPreviewTypeHint": "لا يتوفر معاينة لهذا النوع من الملفات. قم بتنزيله لفتحه في تطبيق آخر."
"cannotPreviewTypeHint": "لا يتوفر معاينة لهذا النوع من الملفات. قم بتنزيله لفتحه في تطبيق آخر.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "المصدر → الخادم: {{throughput}}",
"metricsHopDestSftpWrite": "الخادم → الوجهة (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "الخادم → الوجهة (محلي): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Рамо до рамо",
"inline": "Вграден",
"fileComparison": "Сравнение на файлове: {{file1}} срещу {{file2}}",
"fileTooLarge": "Файлът е твърде голям: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH връзката не бе успешна. Моля, проверете връзката си с {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Неуспешно зареждане на файл: {{error}}",
"connecting": "Свързване...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Използвайте удобно оформление",
"cannotLoadPdf": "Не може да се зареди PDF файл",
"pdfLoadError": "Възникна грешка при зареждането на този PDF файл.",
"loadingPdf": "Зареждане на PDF файл...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Зареждане на страницата...",
"cancel": "Отказ",
"copiedItems_one": "Копиран елемент {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Вашият браузър не може да възпроизведе този видео формат.",
"downloadToPlay": "Изтеглете, за да го играете другаде",
"cannotPreviewType": "Не може да се прегледа този тип файл",
"cannotPreviewTypeHint": "Този тип файл няма предварителен преглед. Изтеглете го, за да го отворите в друго приложение."
"cannotPreviewTypeHint": "Този тип файл няма предварителен преглед. Изтеглете го, за да го отворите в друго приложение.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Източник → сървър: {{throughput}}",
"metricsHopDestSftpWrite": "Сървър → дестинация (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Сървър → дестинация (локална): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "পাশাপাশি",
"inline": "ইনলাইন",
"fileComparison": "ফাইল তুলনা: {{file1}} বনাম {{file2}}",
"fileTooLarge": "ফাইলের আকার অনেক বড়: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH সংযোগ ব্যর্থ হয়েছে। অনুগ্রহ করে {{name}} ({{ip}}:{{port}} ) -এ আপনার সংযোগ পরীক্ষা করুন।",
"loadFileFailed": "ফাইল লোড করতে ব্যর্থ: {{error}}",
"connecting": "সংযোগ স্থাপন...",
@@ -365,7 +365,7 @@
"comfortableLayout": "আরামদায়ক বিন্যাস ব্যবহার করুন",
"cannotLoadPdf": "পিডিএফ লোড করা যাচ্ছে না",
"pdfLoadError": "এই পিডিএফ ফাইলটি লোড করতে ত্রুটি হয়েছে।",
"loadingPdf": "পিডিএফ লোড হচ্ছে...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "পৃষ্ঠা লোড হচ্ছে...",
"cancel": "বাতিল করুন",
"copiedItems_one": "{{count}} আইটেমটি অনুলিপি করা হয়েছে",
@@ -383,7 +383,16 @@
"videoUnsupported": "আপনার ব্রাউজার এই ভিডিও ফরম্যাটটি চালাতে পারছে না।",
"downloadToPlay": "অন্যত্র চালানোর জন্য ডাউনলোড করুন",
"cannotPreviewType": "এই ফাইল প্রকারের প্রিভিউ দেখা সম্ভব নয়",
"cannotPreviewTypeHint": "এই ফাইল টাইপের কোনো প্রিভিউ নেই। অন্য কোনো অ্যাপে খোলার জন্য এটি ডাউনলোড করুন।"
"cannotPreviewTypeHint": "এই ফাইল টাইপের কোনো প্রিভিউ নেই। অন্য কোনো অ্যাপে খোলার জন্য এটি ডাউনলোড করুন।",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "এসএফটিপি",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "উৎস → সার্ভার: {{throughput}}",
"metricsHopDestSftpWrite": "সার্ভার → গন্তব্য (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "সার্ভার → গন্তব্য (স্থানীয়): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Costat al costat",
"inline": "En línia",
"fileComparison": "Comparació de fitxers: {{file1}} vs {{file2}}",
"fileTooLarge": "Fitxer massa gran: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "La connexió SSH ha fallat. Si us plau, comproveu la connexió a {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "No s'ha pogut carregar el fitxer: {{error}}",
"connecting": "Connectant...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Utilitza un disseny còmode",
"cannotLoadPdf": "No es pot carregar el PDF",
"pdfLoadError": "S'ha produït un error en carregar aquest fitxer PDF.",
"loadingPdf": "S'està carregant el PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "S'està carregant la pàgina...",
"cancel": "Cancel·la",
"copiedItems_one": "S'ha copiat l'element {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "El vostre navegador no pot reproduir aquest format de vídeo.",
"downloadToPlay": "Descarrega-ho per reproduir-ho en un altre lloc",
"cannotPreviewType": "No es pot previsualitzar aquest tipus de fitxer",
"cannotPreviewTypeHint": "Aquest tipus de fitxer no té vista prèvia. Baixeu-lo per obrir-lo en una altra aplicació."
"cannotPreviewTypeHint": "Aquest tipus de fitxer no té vista prèvia. Baixeu-lo per obrir-lo en una altra aplicació.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Font → servidor: {{throughput}}",
"metricsHopDestSftpWrite": "Servidor → destí (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Servidor → destí (local): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Bok po boku",
"inline": "Vložený",
"fileComparison": "Porovnání souborů: {{file1}} vs. {{file2}}",
"fileTooLarge": "Soubor je příliš velký: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH připojení selhalo. Zkontrolujte prosím připojení k {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Nepodařilo se načíst soubor: {{error}}",
"connecting": "Spojovací...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Použijte pohodlné rozvržení",
"cannotLoadPdf": "Nelze načíst PDF",
"pdfLoadError": "Při načítání tohoto PDF souboru došlo k chybě.",
"loadingPdf": "Načítání PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Načítání stránky...",
"cancel": "Zrušit",
"copiedItems_one": "Zkopírována položka {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Váš prohlížeč tento formát videa nepřehrává.",
"downloadToPlay": "Stáhněte si pro přehrávání jinde",
"cannotPreviewType": "Nelze zobrazit náhled tohoto typu souboru",
"cannotPreviewTypeHint": "Tento typ souboru nemá náhled. Stáhněte si jej a otevřete jej v jiné aplikaci."
"cannotPreviewTypeHint": "Tento typ souboru nemá náhled. Stáhněte si jej a otevřete jej v jiné aplikaci.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Zdroj → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → cíl (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → cíl (lokální): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Side om side",
"inline": "Indlejret",
"fileComparison": "Filsammenligning: {{file1}} vs. {{file2}}",
"fileTooLarge": "Filen er for stor: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-forbindelse mislykkedes. Kontroller venligst din forbindelse til {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Kunne ikke indlæse filen: {{error}}",
"connecting": "Forbinder...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Brug et komfortabelt layout",
"cannotLoadPdf": "Kan ikke indlæse PDF'en",
"pdfLoadError": "Der opstod en fejl under indlæsning af denne PDF-fil.",
"loadingPdf": "Indlæser PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Indlæser side...",
"cancel": "Ophæve",
"copiedItems_one": "Kopieret {{count}} element",
@@ -383,7 +383,16 @@
"videoUnsupported": "Din browser kan ikke afspille dette videoformat.",
"downloadToPlay": "Download for at afspille det et andet sted",
"cannotPreviewType": "Kan ikke forhåndsvise denne filtype",
"cannotPreviewTypeHint": "Denne filtype har ingen forhåndsvisning. Download den for at åbne den i en anden app."
"cannotPreviewTypeHint": "Denne filtype har ingen forhåndsvisning. Download den for at åbne den i en anden app.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Kilde → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → destination (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → destination (lokal): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Nebeneinander",
"inline": "Inline",
"fileComparison": "Dateivergleich: {{file1}} vs. {{file2}}",
"fileTooLarge": "Datei zu groß: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-Verbindung fehlgeschlagen. Bitte überprüfen Sie Ihre Verbindung zu {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Fehler beim Laden der Datei: {{error}}",
"connecting": "Verbindung wird hergestellt...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Nutzen Sie ein komfortables Layout",
"cannotLoadPdf": "PDF kann nicht geladen werden",
"pdfLoadError": "Beim Laden dieser PDF-Datei ist ein Fehler aufgetreten.",
"loadingPdf": "PDF wird geladen...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Seite wird geladen...",
"cancel": "Abbrechen",
"copiedItems_one": "Kopierter Eintrag {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Ihr Browser kann dieses Videoformat nicht abspielen.",
"downloadToPlay": "Laden Sie es herunter, um es woanders abzuspielen.",
"cannotPreviewType": "Vorschau dieses Dateityps nicht möglich",
"cannotPreviewTypeHint": "Dieser Dateityp hat keine Vorschau. Laden Sie ihn herunter, um ihn in einer anderen App zu öffnen."
"cannotPreviewTypeHint": "Dieser Dateityp hat keine Vorschau. Laden Sie ihn herunter, um ihn in einer anderen App zu öffnen.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Quelle → Server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → Ziel (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → Ziel (lokal): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Πλάι πλάι",
"inline": "Ενσωματωμένο",
"fileComparison": "Σύγκριση αρχείων: {{file1}} έναντι {{file2}}",
"fileTooLarge": "Το αρχείο είναι πολύ μεγάλο: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Η σύνδεση SSH απέτυχε. Ελέγξτε τη σύνδεσή σας με {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Αποτυχία φόρτωσης αρχείου: {{error}}",
"connecting": "Συνδετικός...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Χρησιμοποιήστε άνετη διάταξη",
"cannotLoadPdf": "Δεν είναι δυνατή η φόρτωση του PDF",
"pdfLoadError": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση αυτού του αρχείου PDF.",
"loadingPdf": "Φόρτωση PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Φόρτωση σελίδας...",
"cancel": "Ματαίωση",
"copiedItems_one": "Αντιγράφηκε {{count}} στοιχείο",
@@ -383,7 +383,16 @@
"videoUnsupported": "Το πρόγραμμα περιήγησής σας δεν μπορεί να αναπαράγει αυτήν τη μορφή βίντεο.",
"downloadToPlay": "Κατεβάστε το για να το παίξετε αλλού",
"cannotPreviewType": "Δεν είναι δυνατή η προεπισκόπηση αυτού του τύπου αρχείου",
"cannotPreviewTypeHint": "Αυτός ο τύπος αρχείου δεν έχει προεπισκόπηση. Κατεβάστε το για να το ανοίξετε σε άλλη εφαρμογή."
"cannotPreviewTypeHint": "Αυτός ο τύπος αρχείου δεν έχει προεπισκόπηση. Κατεβάστε το για να το ανοίξετε σε άλλη εφαρμογή.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Πηγή → διακομιστής: {{throughput}}",
"metricsHopDestSftpWrite": "Διακομιστής → προορισμός (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Διακομιστής → προορισμός (τοπικός): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Lado a lado",
"inline": "En línea",
"fileComparison": "Comparación de archivos: {{file1}} vs {{file2}}",
"fileTooLarge": "Archivo demasiado grande: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Error de conexión SSH. Verifique su conexión a {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Error al cargar el archivo: {{error}}",
"connecting": "Conectando...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Utilice una disposición cómoda",
"cannotLoadPdf": "No se puede cargar el PDF",
"pdfLoadError": "Se produjo un error al cargar este archivo PDF.",
"loadingPdf": "Cargando PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Cargando página...",
"cancel": "Cancelar",
"copiedItems_one": "Copiado {{count}} elemento",
@@ -383,7 +383,16 @@
"videoUnsupported": "Tu navegador no puede reproducir este formato de vídeo.",
"downloadToPlay": "Descárgalo para jugarlo en otro lugar.",
"cannotPreviewType": "No se puede previsualizar este tipo de archivo.",
"cannotPreviewTypeHint": "Este tipo de archivo no tiene vista previa. Descárguelo para abrirlo en otra aplicación."
"cannotPreviewTypeHint": "Este tipo de archivo no tiene vista previa. Descárguelo para abrirlo en otra aplicación.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Origen → servidor: {{throughput}}",
"metricsHopDestSftpWrite": "Servidor → destino (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Servidor → destino (local): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Rinnakkain",
"inline": "Rivissä",
"fileComparison": "Tiedostovertailu: {{file1}} vs. {{file2}}",
"fileTooLarge": "Tiedosto liian suuri: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-yhteys epäonnistui. Tarkista yhteytesi kohteeseen {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Tiedoston lataus epäonnistui: {{error}}",
"connecting": "Yhdistetään...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Käytä mukavaa asettelua",
"cannotLoadPdf": "PDF-tiedoston lataaminen ei onnistu",
"pdfLoadError": "PDF-tiedoston lataamisessa tapahtui virhe.",
"loadingPdf": "Ladataan PDF-tiedostoa...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Sivua ladataan...",
"cancel": "Peruuttaa",
"copiedItems_one": "Kopioitu {{count}} kohde",
@@ -383,7 +383,16 @@
"videoUnsupported": "Selaimesi ei voi toistaa tätä videomuotoa.",
"downloadToPlay": "Lataa toistaaksesi sen muualla",
"cannotPreviewType": "Tätä tiedostotyyppiä ei voi esikatsella",
"cannotPreviewTypeHint": "Tällä tiedostotyypillä ei ole esikatselua. Lataa se avataksesi sen toisessa sovelluksessa."
"cannotPreviewTypeHint": "Tällä tiedostotyypillä ei ole esikatselua. Lataa se avataksesi sen toisessa sovelluksessa.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Lähde → palvelin: {{throughput}}",
"metricsHopDestSftpWrite": "Palvelin → kohde (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Palvelin → kohde (paikallinen): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Côte à côte",
"inline": "En ligne",
"fileComparison": "Comparaison de fichiers : {{file1}} vs {{file2}}",
"fileTooLarge": "Fichier trop volumineux : {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "La connexion SSH a échoué. Veuillez vérifier votre connexion à {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Impossible de charger le fichier : {{error}}",
"connecting": "Connexion en cours...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Utilisez une disposition confortable",
"cannotLoadPdf": "Impossible de charger le PDF",
"pdfLoadError": "Une erreur s'est produite lors du chargement de ce fichier PDF.",
"loadingPdf": "Chargement du PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Chargement de la page...",
"cancel": "Annuler",
"copiedItems_one": "Élément copié {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Votre navigateur ne peut pas lire ce format vidéo.",
"downloadToPlay": "Téléchargez-le pour y jouer ailleurs",
"cannotPreviewType": "Impossible de prévisualiser ce type de fichier",
"cannotPreviewTypeHint": "Ce type de fichier ne possède pas d'aperçu. Téléchargez-le pour l'ouvrir dans une autre application."
"cannotPreviewTypeHint": "Ce type de fichier ne possède pas d'aperçu. Téléchargez-le pour l'ouvrir dans une autre application.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Source → serveur : {{throughput}}",
"metricsHopDestSftpWrite": "Serveur → dest (SFTP) : {{throughput}}",
"metricsHopDestLocalWrite": "Serveur → dest (local) : {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "זֶה בְּצַד זֶה",
"inline": "מוטבע",
"fileComparison": "השוואת קבצים: {{file1}} לעומת {{file2}}",
"fileTooLarge": "קובץ גדול מדי: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "חיבור SSH נכשל. אנא בדוק את החיבור שלך אל {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "טעינת הקובץ נכשלה: {{error}}",
"connecting": "מְקַשֵׁר...",
@@ -365,7 +365,7 @@
"comfortableLayout": "השתמש בפריסה נוחה",
"cannotLoadPdf": "לא ניתן לטעון את ה-PDF",
"pdfLoadError": "אירעה שגיאה בטעינת קובץ PDF זה.",
"loadingPdf": "טוען PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "טוען דף...",
"cancel": "לְבַטֵל",
"copiedItems_one": "פריט {{count}} הועתק",
@@ -383,7 +383,16 @@
"videoUnsupported": "הדפדפן שלך לא יכול להפעיל פורמט וידאו זה.",
"downloadToPlay": "הורד כדי להפעיל אותו במקום אחר",
"cannotPreviewType": "לא ניתן להציג תצוגה מקדימה של סוג קובץ זה",
"cannotPreviewTypeHint": "לסוג קובץ זה אין תצוגה מקדימה. הורד אותו כדי לפתוח אותו באפליקציה אחרת."
"cannotPreviewTypeHint": "לסוג קובץ זה אין תצוגה מקדימה. הורד אותו כדי לפתוח אותו באפליקציה אחרת.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "מקור → שרת: {{throughput}}",
"metricsHopDestSftpWrite": "שרת → יעד (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "שרת → יעד (מקומי): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "साथ-साथ",
"inline": "इनलाइन",
"fileComparison": "फ़ाइल तुलना: {{file1}} बनाम {{file2}}",
"fileTooLarge": "फ़ाइल बहुत बड़ी है: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH कनेक्शन विफल। कृपया {{name}} ({{ip}}:{{port}}) से अपना कनेक्शन जाँचें।",
"loadFileFailed": "फ़ाइल लोड करने में विफल: {{error}}",
"connecting": "कनेक्ट हो रहा है...",
@@ -365,7 +365,7 @@
"comfortableLayout": "सुविधाजनक लेआउट का उपयोग करें",
"cannotLoadPdf": "PDF लोड नहीं कर सकता",
"pdfLoadError": "इस PDF फ़ाइल को लोड करने में त्रुटि हुई।",
"loadingPdf": "PDF लोड हो रहा है...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "पृष्ठ लोड हो रहा है...",
"cancel": "रद्द करें",
"copiedItems_one": "आइटम कॉपी किया गया {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "आपका ब्राउज़र इस वीडियो प्रारूप को नहीं चला सकता।",
"downloadToPlay": "इसे कहीं और खेलने के लिए डाउनलोड करें",
"cannotPreviewType": "इस फ़ाइल प्रकार का पूर्वावलोकन नहीं किया जा सकता है",
"cannotPreviewTypeHint": "इस फ़ाइल प्रकार का कोई पूर्वावलोकन नहीं है। इसे डाउनलोड करके किसी अन्य ऐप में खोलें।"
"cannotPreviewTypeHint": "इस फ़ाइल प्रकार का कोई पूर्वावलोकन नहीं है। इसे डाउनलोड करके किसी अन्य ऐप में खोलें।",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "एसएफटीपी",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "स्रोत → सर्वर: {{throughput}}",
"metricsHopDestSftpWrite": "सर्वर → गंतव्य (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "सर्वर → गंतव्य (स्थानीय): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Egymás mellett",
"inline": "Beágyazott",
"fileComparison": "Fájl-összehasonlítás: {{file1}} vs {{file2}}",
"fileTooLarge": "A fájl túl nagy: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH kapcsolat sikertelen. Kérjük, ellenőrizze a kapcsolatot a következővel: {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Nem sikerült betölteni a fájlt: {{error}}",
"connecting": "Kapcsolódás...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Használjon kényelmes elrendezést",
"cannotLoadPdf": "Nem tudom betölteni a PDF-et",
"pdfLoadError": "Hiba történt a PDF fájl betöltésekor.",
"loadingPdf": "PDF betöltése...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Oldal betöltése...",
"cancel": "Mégsem",
"copiedItems_one": "Másolt {{count}} elem",
@@ -383,7 +383,16 @@
"videoUnsupported": "A böngésződ nem tudja lejátszani ezt a videóformátumot.",
"downloadToPlay": "Töltsd le, hogy máshol is lejátszhasd",
"cannotPreviewType": "Ez a fájltípus nem tekinthető meg előnézetben",
"cannotPreviewTypeHint": "Ennek a fájltípusnak nincs előnézete. Töltse le, hogy egy másik alkalmazásban megnyissa."
"cannotPreviewTypeHint": "Ennek a fájltípusnak nincs előnézete. Töltse le, hogy egy másik alkalmazásban megnyissa.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Forrás → szerver: {{throughput}}",
"metricsHopDestSftpWrite": "Szerver → cél (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Szerver → cél (helyi): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Berdampingan",
"inline": "Sejajar",
"fileComparison": "Perbandingan File: {{file1}} vs {{file2}}",
"fileTooLarge": "Ukuran file terlalu besar: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Koneksi SSH gagal. Harap periksa koneksi Anda ke {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Gagal memuat file: {{error}}",
"connecting": "Menghubungkan...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Gunakan tata letak yang nyaman.",
"cannotLoadPdf": "Tidak dapat memuat PDF",
"pdfLoadError": "Terjadi kesalahan saat memuat file PDF ini.",
"loadingPdf": "Sedang memuat PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Sedang memuat halaman...",
"cancel": "Membatalkan",
"copiedItems_one": "Item {{count}} disalin",
@@ -383,7 +383,16 @@
"videoUnsupported": "Browser Anda tidak dapat memutar format video ini.",
"downloadToPlay": "Unduh untuk memainkannya di tempat lain",
"cannotPreviewType": "Tidak dapat melihat pratinjau tipe file ini.",
"cannotPreviewTypeHint": "Jenis file ini tidak memiliki pratinjau. Unduh untuk membukanya di aplikasi lain."
"cannotPreviewTypeHint": "Jenis file ini tidak memiliki pratinjau. Unduh untuk membukanya di aplikasi lain.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Sumber → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → tujuan (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → tujuan (lokal): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Fianco a fianco",
"inline": "In linea",
"fileComparison": "Confronto file: {{file1}} vs {{file2}}",
"fileTooLarge": "File troppo grande: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Connessione SSH non riuscita. Controlla la connessione a {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Caricamento del file non riuscito: {{error}}",
"connecting": "Connessione in corso...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Utilizzare una disposizione comoda",
"cannotLoadPdf": "Impossibile caricare il PDF",
"pdfLoadError": "Si è verificato un errore durante il caricamento del file PDF.",
"loadingPdf": "Caricamento PDF in corso...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Caricamento pagina in corso...",
"cancel": "Annulla",
"copiedItems_one": "Elemento copiato {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Il tuo browser non è in grado di riprodurre questo formato video.",
"downloadToPlay": "Scaricalo per riprodurlo altrove",
"cannotPreviewType": "Impossibile visualizzare l'anteprima di questo tipo di file",
"cannotPreviewTypeHint": "Questo tipo di file non ha un'anteprima. Scaricalo per aprirlo in un'altra applicazione."
"cannotPreviewTypeHint": "Questo tipo di file non ha un'anteprima. Scaricalo per aprirlo in un'altra applicazione.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Sorgente → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → dest (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → dest (locale): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "横並び表示",
"inline": "インライン表示",
"fileComparison": "ファイル比較: {{file1}} 対 {{file2}}",
"fileTooLarge": "ファイルが大きすぎます: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH接続に失敗しました。 {{name}} ({{ip}}:{{port}}) への接続を確認してください",
"loadFileFailed": "ファイルの読み込みに失敗しました: {{error}}",
"connecting": "接続中...",
@@ -365,7 +365,7 @@
"comfortableLayout": "快適なレイアウトを使用する",
"cannotLoadPdf": "PDFを読み込めません",
"pdfLoadError": "このPDFファイルの読み込み中にエラーが発生しました。",
"loadingPdf": "PDFを読み込み中...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "ページを読み込み中...",
"cancel": "キャンセル",
"copiedItems_one": "{{count}} アイテムをコピーしました",
@@ -383,7 +383,16 @@
"videoUnsupported": "お使いのブラウザはこの動画フォーマットを再生できません。",
"downloadToPlay": "他の場所でプレイするにはダウンロードしてください",
"cannotPreviewType": "このファイルタイプはプレビューできません",
"cannotPreviewTypeHint": "このファイル形式にはプレビュー機能がありません。ダウンロードして別のアプリで開いてください。"
"cannotPreviewTypeHint": "このファイル形式にはプレビュー機能がありません。ダウンロードして別のアプリで開いてください。",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "送信元 → サーバー: {{throughput}}",
"metricsHopDestSftpWrite": "サーバー → 送信先 (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "サーバー → 送信先 (ローカル): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "나란히 보기",
"inline": "인라인",
"fileComparison": "파일 비교: {{file1}} vs {{file2}}",
"fileTooLarge": "파일이 너무 큽니다: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH 연결 실패. {{name}}({{ip}}:{{port}})에 대한 연결을 확인하세요.",
"loadFileFailed": "파일 불러오기 실패: {{error}}",
"connecting": "연결 중...",
@@ -365,7 +365,7 @@
"comfortableLayout": "편안한 레이아웃을 사용하세요",
"cannotLoadPdf": "PDF를 로드할 수 없습니다",
"pdfLoadError": "이 PDF 파일을 로드하는 중 오류가 발생했습니다.",
"loadingPdf": "PDF 로드 중...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "페이지 로드 중...",
"cancel": "취소",
"copiedItems_one": "복사됨 {{count}} 항목",
@@ -383,7 +383,16 @@
"videoUnsupported": "현재 사용 중인 브라우저는 이 비디오 형식을 재생할 수 없습니다.",
"downloadToPlay": "다운로드하여 다른 곳에서 재생하세요.",
"cannotPreviewType": "이 파일 형식을 미리 볼 수 없습니다.",
"cannotPreviewTypeHint": "이 파일 형식은 미리보기를 지원하지 않습니다. 다운로드하여 다른 앱에서 열어보세요."
"cannotPreviewTypeHint": "이 파일 형식은 미리보기를 지원하지 않습니다. 다운로드하여 다른 앱에서 열어보세요.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "소스 → 서버: {{throughput}}",
"metricsHopDestSftpWrite": "서버 → 목적지 (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "서버 → 목적지 (로컬): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Naast elkaar",
"inline": "Inline",
"fileComparison": "Bestandsvergelijking: {{file1}} versus {{file2}}",
"fileTooLarge": "Bestand te groot: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-verbinding mislukt. Controleer uw verbinding met {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Bestand laden mislukt: {{error}}",
"connecting": "Verbinden...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Gebruik een comfortabele lay-out.",
"cannotLoadPdf": "Kan PDF niet laden",
"pdfLoadError": "Er is een fout opgetreden bij het laden van dit PDF-bestand.",
"loadingPdf": "PDF laden...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Pagina laden...",
"cancel": "Annuleren",
"copiedItems_one": "Gekopieerd {{count}} item",
@@ -383,7 +383,16 @@
"videoUnsupported": "Uw browser kan dit videoformaat niet afspelen.",
"downloadToPlay": "Download het om het elders te spelen.",
"cannotPreviewType": "Kan dit bestandstype niet bekijken.",
"cannotPreviewTypeHint": "Dit bestandstype heeft geen voorbeeldweergave. Download het om het in een andere app te openen."
"cannotPreviewTypeHint": "Dit bestandstype heeft geen voorbeeldweergave. Download het om het in een andere app te openen.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Bron → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → bestemming (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → bestemming (lokaal): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Side om side",
"inline": "Innebygd",
"fileComparison": "Filsammenligning: {{file1}} vs {{file2}}",
"fileTooLarge": "Fil for stor: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-tilkobling mislyktes. Kontroller tilkoblingen til {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Kunne ikke laste fil: {{error}}",
"connecting": "Kobler til...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Bruk komfortabel layout",
"cannotLoadPdf": "Kan ikke laste inn PDF-filen",
"pdfLoadError": "Det oppsto en feil under lasting av denne PDF-filen.",
"loadingPdf": "Laster inn PDF-filen...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Laster inn side...",
"cancel": "Avbryt",
"copiedItems_one": "Kopierte {{count}} element",
@@ -383,7 +383,16 @@
"videoUnsupported": "Nettleseren din kan ikke spille av dette videoformatet.",
"downloadToPlay": "Last ned for å spille det et annet sted",
"cannotPreviewType": "Kan ikke forhåndsvise denne filtypen",
"cannotPreviewTypeHint": "Denne filtypen har ingen forhåndsvisning. Last den ned for å åpne den i en annen app."
"cannotPreviewTypeHint": "Denne filtypen har ingen forhåndsvisning. Last den ned for å åpne den i en annen app.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Kilde → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → destinasjon (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → dest (lokal): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Obok siebie",
"inline": "W linii",
"fileComparison": "Porównanie plików: {{file1}} vs {{file2}}",
"fileTooLarge": "Plik jest za duży: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Połączenie SSH nie powiodło się. Sprawdź połączenie z {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Nie udało się załadować pliku: {{error}}",
"connecting": "Łączenie...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Użyj wygodnego układu",
"cannotLoadPdf": "Nie można załadować pliku PDF",
"pdfLoadError": "Wystąpił błąd podczas ładowania pliku PDF.",
"loadingPdf": "Ładowanie pliku PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Ładowanie strony...",
"cancel": "Anuluj",
"copiedItems_one": "Skopiowano element {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Twoja przeglądarka nie może odtworzyć tego formatu wideo.",
"downloadToPlay": "Pobierz, aby odtworzyć w innym miejscu",
"cannotPreviewType": "Nie można wyświetlić podglądu tego typu pliku",
"cannotPreviewTypeHint": "Ten typ pliku nie ma podglądu. Pobierz go, aby otworzyć w innej aplikacji."
"cannotPreviewTypeHint": "Ten typ pliku nie ma podglądu. Pobierz go, aby otworzyć w innej aplikacji.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Źródło → serwer: {{throughput}}",
"metricsHopDestSftpWrite": "Serwer → cel (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Serwer → cel (lokalny): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Lado a lado",
"inline": "Em linha",
"fileComparison": "Comparação de arquivos: {{file1}} vs {{file2}}",
"fileTooLarge": "Arquivo muito grande: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "A conexão SSH falhou. Verifique sua conexão com {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Falha ao carregar o arquivo: {{error}}",
"connecting": "Conectando...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Use um layout confortável",
"cannotLoadPdf": "Não foi possível carregar o PDF.",
"pdfLoadError": "Ocorreu um erro ao carregar este arquivo PDF.",
"loadingPdf": "Carregando PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Carregando página...",
"cancel": "Cancelar",
"copiedItems_one": "Item copiado {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Seu navegador não consegue reproduzir este formato de vídeo.",
"downloadToPlay": "Faça o download para jogar em outro lugar.",
"cannotPreviewType": "Não é possível visualizar este tipo de arquivo.",
"cannotPreviewTypeHint": "Este tipo de arquivo não possui pré-visualização. Faça o download para abri-lo em outro aplicativo."
"cannotPreviewTypeHint": "Este tipo de arquivo não possui pré-visualização. Faça o download para abri-lo em outro aplicativo.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Origem → servidor: {{throughput}}",
"metricsHopDestSftpWrite": "Servidor → destino (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Servidor → destino (local): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Lado a lado",
"inline": "Em linha",
"fileComparison": "Comparação de ficheiros: {{file1}} vs {{file2}}",
"fileTooLarge": "Ficheiro demasiado grande: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Falha na ligação SSH. Verifique a sua ligação a {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Falha ao carregar ficheiro: {{error}}",
"connecting": "A ligar...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Utilize um layout confortável",
"cannotLoadPdf": "Não é possível carregar o PDF",
"pdfLoadError": "Ocorreu um erro ao carregar este ficheiro PDF.",
"loadingPdf": "A carregar PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "A carregar página...",
"cancel": "Cancelar",
"copiedItems_one": "Item copiado {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "O seu navegador não consegue reproduzir este formato de vídeo.",
"downloadToPlay": "Faça o download para jogar noutro local.",
"cannotPreviewType": "Não é possível visualizar este tipo de ficheiro.",
"cannotPreviewTypeHint": "Este tipo de ficheiro não possui pré-visualização. Faça o download para abri-lo noutra aplicação."
"cannotPreviewTypeHint": "Este tipo de ficheiro não possui pré-visualização. Faça o download para abri-lo noutra aplicação.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Origem → servidor: {{throughput}}",
"metricsHopDestSftpWrite": "Servidor → destino (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Servidor → destino (local): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Alăturat",
"inline": "În linie",
"fileComparison": "Comparație fișiere: {{file1}} vs {{file2}}",
"fileTooLarge": "Fișier prea mare: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Conexiunea SSH a eșuat. Vă rugăm să verificați conexiunea la {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Nu s-a putut încărca fișierul: {{error}}",
"connecting": "Se conectează...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Folosește un aspect confortabil",
"cannotLoadPdf": "Nu se poate încărca PDF-ul",
"pdfLoadError": "A apărut o eroare la încărcarea acestui fișier PDF.",
"loadingPdf": "Se încarcă PDF-ul...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Se încarcă pagina...",
"cancel": "Anula",
"copiedItems_one": "Elementul {{count}} a fost copiat",
@@ -383,7 +383,16 @@
"videoUnsupported": "Browserul dvs. nu poate reda acest format video.",
"downloadToPlay": "Descarcă pentru a-l reda în altă parte",
"cannotPreviewType": "Nu se poate previzualiza acest tip de fișier",
"cannotPreviewTypeHint": "Acest tip de fișier nu are previzualizare. Descărcați-l pentru a-l deschide în altă aplicație."
"cannotPreviewTypeHint": "Acest tip de fișier nu are previzualizare. Descărcați-l pentru a-l deschide în altă aplicație.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Sursă → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → destinație (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → destinație (locală): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Бок о бок",
"inline": "Встроенный",
"fileComparison": "Сравнение файлов: {{file1}} и {{file2}}",
"fileTooLarge": "Файл слишком большой: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Ошибка SSH-подключения. Проверьте подключение к {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Не удалось загрузить файл: {{error}}",
"connecting": "Подключение...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Используйте удобную планировку",
"cannotLoadPdf": "Не удаётся загрузить PDF",
"pdfLoadError": "Ошибка при загрузке этого PDF-файла.",
"loadingPdf": "Загрузка PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Загрузка страницы...",
"cancel": "Отмена",
"copiedItems_one": "Скопировано {{count}} элемент",
@@ -383,7 +383,16 @@
"videoUnsupported": "Ваш браузер не может воспроизвести этот видеоформат.",
"downloadToPlay": "Скачайте, чтобы играть в другом месте.",
"cannotPreviewType": "Предварительный просмотр файла этого типа невозможен.",
"cannotPreviewTypeHint": "Для этого типа файла предварительный просмотр недоступен. Скачайте его, чтобы открыть в другом приложении."
"cannotPreviewTypeHint": "Для этого типа файла предварительный просмотр недоступен. Скачайте его, чтобы открыть в другом приложении.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Источник → сервер: {{throughput}}",
"metricsHopDestSftpWrite": "Сервер → назначение (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Сервер → назначение (локально): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Један поред другог",
"inline": "Уграђено",
"fileComparison": "Поређење датотека: {{file1}} наспрам {{file2}}",
"fileTooLarge": "Датотека је превелика: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH веза није успела. Молимо вас да проверите везу са {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Није успело учитавање датотеке: {{error}}",
"connecting": "Повезивање...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Користите удобан распоред",
"cannotLoadPdf": "Није могуће учитати PDF",
"pdfLoadError": "Дошло је до грешке при учитавању ове PDF датотеке.",
"loadingPdf": "Учитавање PDF-а...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Учитавање странице...",
"cancel": "Откажи",
"copiedItems_one": "Копирана ставка {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Ваш прегледач не може да репродукује овај видео формат.",
"downloadToPlay": "Преузмите да бисте је репродуковали негде другде",
"cannotPreviewType": "Не могу да прегледам овај тип датотеке",
"cannotPreviewTypeHint": "Овај тип датотеке нема преглед. Преузмите је да бисте је отворили у другој апликацији."
"cannotPreviewTypeHint": "Овај тип датотеке нема преглед. Преузмите је да бисте је отворили у другој апликацији.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "СФТП",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Извор → сервер: {{throughput}}",
"metricsHopDestSftpWrite": "Сервер → одредиште (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Сервер → одредиште (локално): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Sida vid sida",
"inline": "Inline",
"fileComparison": "Filjämförelse: {{file1}} vs {{file2}}",
"fileTooLarge": "Filen är för stor: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH-anslutningen misslyckades. Kontrollera din anslutning till {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Misslyckades med att ladda filen: {{error}}",
"connecting": "Ansluter...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Använd bekväm layout",
"cannotLoadPdf": "Kan inte ladda PDF-filen",
"pdfLoadError": "Det uppstod ett fel när PDF-filen laddades.",
"loadingPdf": "Laddar PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Laddar sida...",
"cancel": "Avboka",
"copiedItems_one": "Kopierade {{count}} objekt",
@@ -383,7 +383,16 @@
"videoUnsupported": "Din webbläsare kan inte spela upp det här videoformatet.",
"downloadToPlay": "Ladda ner för att spela det någon annanstans",
"cannotPreviewType": "Det går inte att förhandsgranska den här filtypen",
"cannotPreviewTypeHint": "Den här filtypen har ingen förhandsgranskning. Ladda ner den för att öppna den i en annan app."
"cannotPreviewTypeHint": "Den här filtypen har ingen förhandsgranskning. Ladda ner den för att öppna den i en annan app.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Källa → server: {{throughput}}",
"metricsHopDestSftpWrite": "Server → destination (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Server → destination (lokal): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "เคียงข้างกัน",
"inline": "อินไลน์",
"fileComparison": "การเปรียบเทียบไฟล์: {{file1}} กับ {{file2}}",
"fileTooLarge": "ไฟล์มีขนาดใหญ่เกินไป: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "การเชื่อมต่อ SSH ล้มเหลว โปรดตรวจสอบการเชื่อมต่อของคุณกับ {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "ไม่สามารถโหลดไฟล์ได้: {{error}}",
"connecting": "กำลังเชื่อมต่อ...",
@@ -365,7 +365,7 @@
"comfortableLayout": "ใช้รูปแบบการจัดวางที่สะดวกสบาย",
"cannotLoadPdf": "ไม่สามารถโหลดไฟล์ PDF ได้",
"pdfLoadError": "เกิดข้อผิดพลาดในการโหลดไฟล์ PDF นี้",
"loadingPdf": "กำลังโหลดไฟล์ PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "กำลังโหลดหน้าเว็บ...",
"cancel": "ยกเลิก",
"copiedItems_one": "คัดลอกรายการ {{count}} แล้ว",
@@ -383,7 +383,16 @@
"videoUnsupported": "เบราว์เซอร์ของคุณไม่สามารถเล่นวิดีโอรูปแบบนี้ได้",
"downloadToPlay": "ดาวน์โหลดเพื่อเล่นที่อื่น",
"cannotPreviewType": "ไม่สามารถแสดงตัวอย่างไฟล์ประเภทนี้ได้",
"cannotPreviewTypeHint": "ไฟล์ประเภทนี้ไม่มีตัวอย่างให้ดู ดาวน์โหลดเพื่อเปิดในแอปพลิเคชันอื่น"
"cannotPreviewTypeHint": "ไฟล์ประเภทนี้ไม่มีตัวอย่างให้ดู ดาวน์โหลดเพื่อเปิดในแอปพลิเคชันอื่น",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "เอสเอฟทีพี",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "แหล่งที่มา → เซิร์ฟเวอร์: {{throughput}}",
"metricsHopDestSftpWrite": "เซิร์ฟเวอร์ → ปลายทาง (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "เซิร์ฟเวอร์ → ปลายทาง (ภายในเครื่อง): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Yan Yana",
"inline": "Satır İçi",
"fileComparison": "Dosya Karşılaştırması: {{file1}} vs {{file2}}",
"fileTooLarge": "Dosya çok büyük: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH bağlantısı başarısız oldu. Lütfen {{name}} ({{ip}}:{{port}}) ile bağlantınızı kontrol edin",
"loadFileFailed": "Dosya yüklenemedi: {{error}}",
"connecting": "Bağlanıyor...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Rahat bir düzen kullanın.",
"cannotLoadPdf": "PDF yüklenemiyor",
"pdfLoadError": "Bu PDF dosyası yüklenirken bir hata oluştu.",
"loadingPdf": "PDF yükleniyor...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Sayfa yükleniyor...",
"cancel": "İptal",
"copiedItems_one": "Kopyalanan {{count}} öğe",
@@ -383,7 +383,16 @@
"videoUnsupported": "Tarayıcınız bu video formatını oynatamıyor.",
"downloadToPlay": "Başka yerde oynamak için indirin",
"cannotPreviewType": "Bu dosya türünün önizlemesi yapılamıyor.",
"cannotPreviewTypeHint": "Bu dosya türünün önizlemesi yok. Başka bir uygulamada açmak için indirin."
"cannotPreviewTypeHint": "Bu dosya türünün önizlemesi yok. Başka bir uygulamada açmak için indirin.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Kaynak → sunucu: {{throughput}}",
"metricsHopDestSftpWrite": "Sunucu → hedef (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Sunucu → hedef (yerel): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Пліч-о-пліч",
"inline": "Вбудований",
"fileComparison": "Порівняння файлів: {{file1}} проти {{file2}}",
"fileTooLarge": "Файл занадто великий: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Помилка SSH-з’єднання. Будь ласка, перевірте ваше з’єднання з {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Не вдалося завантажити файл: {{error}}",
"connecting": "Підключення...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Використовуйте зручний макет",
"cannotLoadPdf": "Не вдається завантажити PDF",
"pdfLoadError": "Під час завантаження цього PDF-файлу сталася помилка.",
"loadingPdf": "Завантаження PDF-файлу...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Завантаження сторінки...",
"cancel": "Скасувати",
"copiedItems_one": "Скопійовано елемент {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Ваш браузер не може відтворити цей відеоформат.",
"downloadToPlay": "Завантажте, щоб відтворити деінде",
"cannotPreviewType": "Неможливо переглянути цей тип файлу",
"cannotPreviewTypeHint": "Цей тип файлу не має попереднього перегляду. Завантажте його, щоб відкрити в іншій програмі."
"cannotPreviewTypeHint": "Цей тип файлу не має попереднього перегляду. Завантажте його, щоб відкрити в іншій програмі.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Джерело → сервер: {{throughput}}",
"metricsHopDestSftpWrite": "Сервер → призначення (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Сервер → призначення (локальний): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "Song song",
"inline": "Trong dòng",
"fileComparison": "So sánh tệp: {{file1}} vs {{file2}}",
"fileTooLarge": "Tệp quá lớn: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "Kết nối SSH thất bại. Vui lòng kiểm tra kết nối tới {{name}} ({{ip}}:{{port}})",
"loadFileFailed": "Không thể tải tệp: {{error}}",
"connecting": "Đang kết nối...",
@@ -365,7 +365,7 @@
"comfortableLayout": "Sử dụng bố cục thoải mái",
"cannotLoadPdf": "Không thể tải PDF",
"pdfLoadError": "Đã xảy ra lỗi khi tải tệp PDF này.",
"loadingPdf": "Đang tải PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "Đang tải trang...",
"cancel": "Hủy",
"copiedItems_one": "Đã sao chép mục {{count}}",
@@ -383,7 +383,16 @@
"videoUnsupported": "Trình duyệt của bạn không thể phát định dạng video này.",
"downloadToPlay": "Tải xuống để chơi ở nơi khác",
"cannotPreviewType": "Không thể xem trước loại tệp này",
"cannotPreviewTypeHint": "Loại tệp này không có bản xem trước. Hãy tải xuống để mở bằng ứng dụng khác."
"cannotPreviewTypeHint": "Loại tệp này không có bản xem trước. Hãy tải xuống để mở bằng ứng dụng khác.",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "Nguồn → máy chủ: {{throughput}}",
"metricsHopDestSftpWrite": "Máy chủ → đích (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "Máy chủ → đích (cục bộ): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "并排",
"inline": "内联",
"fileComparison": "文件比较: {{file1}} 对比 {{file2}}",
"fileTooLarge": "文件过大: {{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH 连接失败。请检查您到 {{name}} ({{ip}}:{{port}}) 的连接",
"loadFileFailed": "加载文件失败: {{error}}",
"connecting": "连接中...",
@@ -365,7 +365,7 @@
"comfortableLayout": "采用舒适的布局",
"cannotLoadPdf": "无法加载 PDF",
"pdfLoadError": "加载此 PDF 文件时出错。",
"loadingPdf": "正在加载 PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "正在加载页面...",
"cancel": "取消",
"copiedItems_one": "已复制 {{count}} 项目",
@@ -383,7 +383,16 @@
"videoUnsupported": "您的浏览器无法播放此视频格式。",
"downloadToPlay": "下载后即可在其他地方播放",
"cannotPreviewType": "无法预览此文件类型",
"cannotPreviewTypeHint": "此文件类型没有预览。请下载后在其他应用程序中打开。"
"cannotPreviewTypeHint": "此文件类型没有预览。请下载后在其他应用程序中打开。",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "源 → 服务器:{{throughput}}",
"metricsHopDestSftpWrite": "服务器 → 目标 (SFTP):{{throughput}}",
"metricsHopDestLocalWrite": "服务器 → 目标 (本地):{{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -323,7 +323,7 @@
"sideBySide": "並排",
"inline": "內嵌",
"fileComparison": "檔案比對:{{file1}} vs {{file2}}",
"fileTooLarge": "檔案過大:{{error}}",
"fileTooLarge": "File is too large (> 10MB) and cannot be opened as text for security reasons.",
"sshConnectionFailed": "SSH 連線失敗。請檢查與 {{name}} ({{ip}}:{{port}}) 的連線",
"loadFileFailed": "載入檔案失敗:{{error}}",
"connecting": "連線中...",
@@ -365,7 +365,7 @@
"comfortableLayout": "採用舒適的佈局",
"cannotLoadPdf": "無法載入 PDF",
"pdfLoadError": "載入此 PDF 檔案時發生錯誤。",
"loadingPdf": "正在載入 PDF...",
"loadingPdf": "Loading PDF viewer...",
"loadingPage": "正在載入頁面...",
"cancel": "取消",
"copiedItems_one": "已複製 {{count}} 項目",
@@ -383,7 +383,16 @@
"videoUnsupported": "您的瀏覽器無法播放此影片格式。",
"downloadToPlay": "下載後即可在其他地方播放",
"cannotPreviewType": "無法預覽此文件類型",
"cannotPreviewTypeHint": "此文件類型沒有預覽。請下載後在其他應用程式中開啟。"
"cannotPreviewTypeHint": "此文件類型沒有預覽。請下載後在其他應用程式中開啟。",
"loadingFile": "Loading file...",
"loadingImage": "Loading image...",
"loadingEditor": "Loading editor...",
"loadingPreview": "Loading preview...",
"loadingAudio": "Loading audio player...",
"emptyPreview": "Nothing to preview yet...",
"revert": "Revert",
"openAsText": "Open as Text",
"confirmOpenAsText": "Do you want to continue opening this file as text? This may slow down your browser."
},
"sftpTransfer": {
"title": "SFTP",
@@ -539,5 +548,8 @@
"metricsHopSourceRead": "來源主機 → 伺服器: {{throughput}}",
"metricsHopDestSftpWrite": "伺服器 → 目標主機 (SFTP): {{throughput}}",
"metricsHopDestLocalWrite": "伺服器 → 目標主機 (本機): {{throughput}}"
},
"plugin": {
"name": "File Manager"
}
}
@@ -1,5 +1,6 @@
import type { Express, Request, Response } from "express";
import type { PluginContext } from "@termix/plugin-sdk/backend";
import type { SFTPWrapper } from "ssh2";
import {
execChannel,
execWithSudo,
@@ -24,6 +25,8 @@ type FileOperationRoutesDeps = {
const TRASH_RETENTION_DAYS_KEY = "trashRetentionDays";
const TRASH_PURGE_INTERVAL_MS = 60 * 60 * 1000;
export function registerFileOperationRoutes(
app: Express,
{ ctx, sshSessions, verifySessionOwnership }: FileOperationRoutesDeps,
@@ -39,6 +42,22 @@ export function registerFileOperationRoutes(
}
};
// Deleting used to purge expired trash first, reading every trash entry
// before answering. Do it after the response, at most hourly per session.
const lastPurge = new WeakMap<SSHSession, number>();
const purgeExpiredTrash = (session: SSHSession, sftp: SFTPWrapper) => {
const now = Date.now();
if (now - (lastPurge.get(session) ?? 0) < TRASH_PURGE_INTERVAL_MS) return;
lastPurge.set(session, now);
void getTrashRetentionDays()
.then((days) => listTrash(sftp, days))
.catch((error) =>
ctx.log.warn(
`Could not purge expired trash: ${(error as Error).message}`,
),
);
};
async function ownedSession(
req: Request,
res: Response,
@@ -494,9 +513,8 @@ export function registerFileOperationRoutes(
if (!permanent) {
try {
const sftp = await getSessionSftp(sshConn);
const retentionDays = await getTrashRetentionDays();
await listTrash(sftp, retentionDays);
const item = await moveToTrash(sftp, itemPath);
purgeExpiredTrash(sshConn, sftp);
ctx.log.info(
`Item moved to trash: ${sessionId} (${itemPath}, trashId=${item.id})`,
);
@@ -70,6 +70,8 @@ async function exists(sftp: SFTPWrapper, target: string) {
}
async function ensureDirectory(sftp: SFTPWrapper, target: string) {
// Usually there already: one round trip instead of one per segment.
if (await exists(sftp, target)) return;
const normalized = target.replace(/\\/g, "/");
const root = normalized.startsWith("/") ? "/" : "";
const parts = normalized.split("/").filter(Boolean);
@@ -95,13 +97,32 @@ async function removeTree(sftp: SFTPWrapper, target: string): Promise<void> {
await rmdir(sftp, target);
}
async function trashPaths(sftp: SFTPWrapper) {
type TrashDirs = { root: string; files: string; info: string };
// The trash folders of a channel do not move while it is open, and finding
// them costs several round trips, which made every delete slow on a remote
// host (#1390).
const trashDirsByChannel = new WeakMap<SFTPWrapper, Promise<TrashDirs>>();
function trashPaths(sftp: SFTPWrapper): Promise<TrashDirs> {
let dirs = trashDirsByChannel.get(sftp);
if (!dirs) {
dirs = findTrashPaths(sftp);
trashDirsByChannel.set(sftp, dirs);
dirs.catch(() => trashDirsByChannel.delete(sftp));
}
return dirs;
}
async function findTrashPaths(sftp: SFTPWrapper): Promise<TrashDirs> {
const home = await call<string>((done) => sftp.realpath(".", done));
const root = path.posix.join(home.replace(/\\/g, "/"), TRASH_DIR);
const files = path.posix.join(root, "files");
const info = path.posix.join(root, "info");
await ensureDirectory(sftp, files);
await ensureDirectory(sftp, info);
await Promise.all([
ensureDirectory(sftp, files),
ensureDirectory(sftp, info),
]);
return { root, files, info };
}
@@ -124,7 +145,7 @@ function publicItem(item: StoredTrashItem): TrashItem {
async function readStoredItem(
sftp: SFTPWrapper,
dirs: { root: string; files: string; info: string },
dirs: TrashDirs,
id: string,
): Promise<StoredTrashItem> {
if (!ID_PATTERN.test(id)) throw new Error("Invalid trash item id");
@@ -1416,10 +1416,13 @@ function FileManagerContent({
const { downloadSSHFileStream } =
await import("./api/ssh-file-operations-api");
toast.loading(<DownloadProgressToast fileName={file.name} loaded={0} />, {
id: toastId,
duration: Infinity,
});
toast.loading(
<DownloadProgressToast t={t} fileName={file.name} loaded={0} />,
{
id: toastId,
duration: Infinity,
},
);
await downloadSSHFileStream(
sshSessionId,
@@ -1438,6 +1441,7 @@ function FileManagerContent({
toast.loading(
<DownloadProgressToast
t={t}
fileName={file.name}
loaded={loaded}
total={total}
@@ -134,7 +134,7 @@ export function DiffViewer({
const handleDownloadFile = async (file: FileItem) => {
const toastId = toast.loading(
<DownloadProgressToast fileName={file.name} loaded={0} />,
<DownloadProgressToast t={t} fileName={file.name} loaded={0} />,
{ duration: Infinity },
);
try {
@@ -145,6 +145,7 @@ export function DiffViewer({
({ loaded, total }) => {
toast.loading(
<DownloadProgressToast
t={t}
fileName={file.name}
loaded={loaded}
total={total}
@@ -1,9 +1,10 @@
import { formatTransferMbPerSec } from "../api/transfer-api";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import { ArrowDownToLine } from "lucide-react";
import { TransferProgressBar } from "./TransferProgressBar";
interface DownloadProgressToastProps {
t: TranslateFn;
fileName: string;
loaded: number;
total?: number;
@@ -25,12 +26,12 @@ function formatBytes(bytes: number): string {
}
export function DownloadProgressToast({
t,
fileName,
loaded,
total,
mbPerSec,
}: DownloadProgressToastProps) {
const { t } = useTranslation();
const percent =
total !== undefined && total > 0
? Math.min(100, Math.round((loaded / total) * 100))
@@ -350,7 +350,9 @@ export function FileViewer({
<div className="h-full flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-sm text-muted-foreground">Loading file...</p>
<p className="text-sm text-muted-foreground">
{t("fileManager.loadingFile")}
</p>
</div>
</div>
);
@@ -446,7 +448,7 @@ export function FileViewer({
className="flex items-center gap-2"
>
<RotateCcw className="w-4 h-4" />
Revert
{t("fileManager.revert")}
</Button>
<Button
variant="default"
@@ -456,7 +458,7 @@ export function FileViewer({
className="flex items-center gap-2"
>
<Save className="w-4 h-4" />
Save
{t("common.save")}
</Button>
</>
)}
@@ -647,14 +649,12 @@ export function FileViewer({
{isTooLarge ? (
<div className="bg-destructive/10 border border-destructive/30 rounded p-3 mb-4">
<p className="text-sm text-destructive font-medium">
File is too large (&gt; 10MB) and cannot be opened as
text for security reasons.
{t("fileManager.fileTooLarge")}
</p>
</div>
) : (
<p className="text-sm text-muted-foreground mb-4">
Do you want to continue opening this file as text? This
may slow down your browser.
{t("fileManager.confirmOpenAsText")}
</p>
)}
</div>
@@ -669,7 +669,7 @@ export function FileViewer({
className="flex items-center gap-2"
>
<FileText className="w-4 h-4" />
Open as Text
{t("fileManager.openAsText")}
</Button>
)}
<Button
@@ -686,7 +686,7 @@ export function FileViewer({
size="sm"
onClick={handleCancelOpenAsText}
>
Cancel
{t("common.cancel")}
</Button>
</div>
</div>
@@ -694,7 +694,9 @@ export function FileViewer({
)}
{fileTypeInfo.type === "image" && !showLargeFileWarning && (
<Suspense fallback={<PreviewFallback label="Loading image..." />}>
<Suspense
fallback={<PreviewFallback label={t("fileManager.loadingImage")} />}
>
<ImagePreview
content={content}
fileName={file.name}
@@ -708,7 +710,9 @@ export function FileViewer({
<div className="h-full flex flex-col">
{isEditable ? (
<Suspense
fallback={<PreviewFallback label="Loading editor..." />}
fallback={
<PreviewFallback label={t("fileManager.loadingEditor")} />
}
>
<CodeEditor
ref={editorRef}
@@ -857,12 +861,16 @@ export function FileViewer({
<div className="p-4">
<Suspense
fallback={
<PreviewFallback label="Loading preview..." />
<PreviewFallback
label={t("fileManager.loadingPreview")}
/>
}
>
<MarkdownRenderer
compact
content={editedContent || "Nothing to preview yet..."}
content={
editedContent || t("fileManager.emptyPreview")
}
/>
</Suspense>
</div>
@@ -872,7 +880,11 @@ export function FileViewer({
<div className="flex-1 overflow-auto thin-scrollbar p-6">
<div className="max-w-4xl mx-auto">
<Suspense
fallback={<PreviewFallback label="Loading preview..." />}
fallback={
<PreviewFallback
label={t("fileManager.loadingPreview")}
/>
}
>
<MarkdownRenderer content={editedContent} />
</Suspense>
@@ -885,7 +897,7 @@ export function FileViewer({
{fileTypeInfo.type === "pdf" && !showLargeFileWarning && (
<Suspense
fallback={<PreviewFallback label="Loading PDF viewer..." />}
fallback={<PreviewFallback label={t("fileManager.loadingPdf")} />}
>
<PdfPreview
content={content}
@@ -897,7 +909,7 @@ export function FileViewer({
{fileTypeInfo.type === "audio" && !showLargeFileWarning && (
<Suspense
fallback={<PreviewFallback label="Loading audio player..." />}
fallback={<PreviewFallback label={t("fileManager.loadingAudio")} />}
>
<AudioPreview
file={file}
@@ -940,7 +952,7 @@ export function FileViewer({
<span>{file.path}</span>
{hasChanges && (
<span className="text-orange-600 font-medium">
● Unsaved changes
● {t("common.unsavedChanges")}
</span>
)}
</div>
@@ -483,7 +483,7 @@ export function FileWindow({
const handleDownload = async () => {
const toastId = toast.loading(
<DownloadProgressToast fileName={file.name} loaded={0} />,
<DownloadProgressToast t={t} fileName={file.name} loaded={0} />,
{ duration: Infinity },
);
try {
@@ -495,6 +495,7 @@ export function FileWindow({
({ loaded, total }) => {
toast.loading(
<DownloadProgressToast
t={t}
fileName={file.name}
loaded={loaded}
total={total}
@@ -103,7 +103,9 @@ export function ImagePreview({
<div className="absolute inset-0 flex items-center justify-center bg-background/80">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">Loading image...</p>
<p className="text-sm text-muted-foreground">
{t("fileManager.loadingImage")}
</p>
</div>
</div>
)}
@@ -1,5 +1,5 @@
import { Button } from "@termix/plugin-sdk/ui";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import { formatFileSize } from "../file-manager-utils.ts";
export interface LocalTransferBatchStatus {
@@ -16,15 +16,16 @@ export interface LocalTransferBatchStatus {
}
interface LocalTransferProgressToastProps {
t: TranslateFn;
status: LocalTransferBatchStatus;
onCancel?: () => void;
}
export function LocalTransferProgressToast({
t,
status,
onCancel,
}: LocalTransferProgressToastProps) {
const { t } = useTranslation();
const percent =
status.totalBytes > 0
? Math.min(100, Math.round((status.bytesDone / status.totalBytes) * 100))
@@ -4,7 +4,7 @@ import {
getTransferProgressPercent,
type TransferProgressResponse,
} from "../api/transfer-api";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import {
Archive,
ArchiveRestore,
@@ -16,6 +16,7 @@ import {
import { TransferProgressBar } from "./TransferProgressBar";
interface TransferProgressToastProps {
t: TranslateFn;
status: TransferProgressResponse;
liveMbPerSec?: number;
stalled?: boolean;
@@ -25,6 +26,7 @@ interface TransferProgressToastProps {
}
export function TransferProgressToast({
t,
status,
liveMbPerSec,
stalled = false,
@@ -32,7 +34,6 @@ export function TransferProgressToast({
onCancel,
cancelling = false,
}: TransferProgressToastProps) {
const { t } = useTranslation();
const percent = getTransferProgressPercent(status);
let title = t("transfer.progressTransferring");
@@ -193,6 +193,7 @@ export function useLocalTransfers({
const render = () => {
toast.loading(
createElement(LocalTransferProgressToast, {
t,
status: { ...status, cancelling },
onCancel: () => {
cancelled = true;
@@ -38,6 +38,7 @@ function formatFileSize(bytes?: number): string {
}
function renderTransferProgressToast(
t: TFunction,
toastId: string | number,
status: TransferProgressResponse,
liveMbPerSec?: number,
@@ -47,6 +48,7 @@ function renderTransferProgressToast(
): void {
toast.loading(
<TransferProgressToast
t={t}
status={status}
liveMbPerSec={liveMbPerSec}
stalled={stalled}
@@ -265,6 +267,7 @@ export function beginTransferProgressMonitoring(
const progressToast = toast.loading(
<TransferProgressToast
t={t}
status={initialStatus}
formatSize={formatFileSize}
/>,
@@ -279,6 +282,7 @@ export function beginTransferProgressMonitoring(
if (cancelling) return;
cancelling = true;
renderTransferProgressToast(
t,
progressToast,
{ ...initialStatus, transferId },
undefined,
@@ -294,6 +298,7 @@ export function beginTransferProgressMonitoring(
options.onProgress?.(status);
const { rate, stalled } = progressTracker.update(status);
renderTransferProgressToast(
t,
progressToast,
status,
rate,
@@ -333,6 +338,7 @@ export function beginTransferProgressMonitoring(
});
renderTransferProgressToast(
t,
progressToast,
initialStatus,
undefined,
@@ -58,9 +58,13 @@ describe("SFTP download stream", () => {
const data = Buffer.alloc(1024 * 1024 + 123);
for (let i = 0; i < data.length; i++) data[i] = (i * 13 + (i >>> 8)) % 256;
const remote = source(data);
expect(
await contents(createDownloadStream(remote.sftp, "/file", data.length)),
).toEqual(data);
const received = await contents(
createDownloadStream(remote.sftp, "/file", data.length),
);
// toEqual walks a 1 MB buffer byte by byte, slow enough to time out
// when every plugin suite runs at once.
expect(received.length).toBe(data.length);
expect(received.equals(data)).toBe(true);
expect(remote.peak()).toBe(8);
expect(remote.closedWithPending).toEqual([0]);
});
@@ -0,0 +1,95 @@
import { describe, expect, it, vi } from "vitest";
import type { SFTPWrapper } from "ssh2";
import { listTrash, moveToTrash } from "../../src/backend/trash-service.js";
/** An in-memory SFTP channel that counts every request. */
function fakeSftp() {
const dirs = new Set(["/", "/home", "/home/me"]);
const files = new Map<string, Buffer>([["/home/me/a.txt", Buffer.from("a")]]);
const calls: string[] = [];
const stats = (target: string) =>
dirs.has(target) || files.has(target)
? {
isDirectory: () => dirs.has(target),
size: files.get(target)?.length ?? 0,
}
: null;
const sftp = {
realpath: vi.fn((_p: string, done: (e?: Error, v?: string) => void) => {
calls.push("realpath");
done(undefined, "/home/me");
}),
stat: vi.fn((target: string, done: (e?: Error, v?: unknown) => void) => {
calls.push("stat");
const found = stats(target);
done(found ? undefined : new Error("ENOENT"), found ?? undefined);
}),
lstat: vi.fn((target: string, done: (e?: Error, v?: unknown) => void) => {
calls.push("lstat");
const found = stats(target);
done(found ? undefined : new Error("ENOENT"), found ?? undefined);
}),
mkdir: vi.fn((target: string, done: (e?: Error) => void) => {
calls.push("mkdir");
dirs.add(target);
done();
}),
rename: vi.fn((from: string, to: string, done: (e?: Error) => void) => {
calls.push("rename");
files.set(to, files.get(from)!);
files.delete(from);
done();
}),
writeFile: vi.fn(
(target: string, data: string, done: (e?: Error) => void) => {
calls.push("writeFile");
files.set(target, Buffer.from(data));
done();
},
),
readdir: vi.fn((target: string, done: (e?: Error, v?: unknown) => void) => {
calls.push("readdir");
const prefix = `${target}/`;
done(
undefined,
[...files.keys()]
.filter((name) => name.startsWith(prefix))
.map((name) => ({ filename: name.slice(prefix.length) })),
);
}),
readFile: vi.fn((target: string, done: (e?: Error, v?: Buffer) => void) => {
calls.push("readFile");
done(undefined, files.get(target));
}),
unlink: vi.fn((target: string, done: (e?: Error) => void) => {
calls.push("unlink");
files.delete(target);
done();
}),
};
return { sftp: sftp as unknown as SFTPWrapper, calls, files };
}
describe("trash", () => {
it("finds the trash folders once per channel", async () => {
const { sftp, calls, files } = fakeSftp();
files.set("/home/me/b.txt", Buffer.from("b"));
await moveToTrash(sftp, "/home/me/a.txt");
const first = calls.length;
calls.length = 0;
await moveToTrash(sftp, "/home/me/b.txt");
expect(calls.filter((call) => call === "realpath")).toHaveLength(0);
expect(calls.filter((call) => call === "mkdir")).toHaveLength(0);
// lstat, rename and the metadata write.
expect(calls.length).toBeLessThan(first);
expect(calls).toEqual(["lstat", "rename", "writeFile"]);
});
it("lists what was moved", async () => {
const { sftp } = fakeSftp();
const item = await moveToTrash(sftp, "/home/me/a.txt");
expect(await listTrash(sftp, 7)).toEqual([item]);
});
});
@@ -0,0 +1,54 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, expect, it, vi } from "vitest";
vi.mock("@termix/plugin-sdk/frontend", () => ({
useTranslation: () => {
throw Error("No plugin scope in the global toaster");
},
}));
import { DownloadProgressToast } from "../../src/frontend/components/DownloadProgressToast";
import { TransferProgressToast } from "../../src/frontend/components/TransferProgressToast";
import { LocalTransferProgressToast } from "../../src/frontend/components/LocalTransferProgressToast";
afterEach(cleanup);
const t = (key: string) => key;
it("renders download progress outside plugin scope", () => {
render(
<DownloadProgressToast t={t} fileName="test.txt" loaded={50} total={100} />,
);
expect(screen.getByRole("progressbar").getAttribute("aria-valuenow")).toBe(
"50",
);
});
it("renders host transfer progress outside plugin scope", () => {
render(
<TransferProgressToast
t={t}
status={{
transferId: "test",
status: "running",
phase: "transferring",
bytesTransferred: 50,
totalBytes: 100,
}}
formatSize={String}
/>,
);
expect(screen.getByRole("progressbar")).toBeTruthy();
});
it("preserves cancellation on local transfers outside plugin scope", () => {
const cancel = vi.fn();
render(
<LocalTransferProgressToast
t={t}
status={{
direction: "upload",
totalFiles: 2,
completedFiles: 1,
bytesDone: 50,
totalBytes: 100,
}}
onCancel={cancel}
/>,
);
fireEvent.click(screen.getByRole("button"));
expect(cancel).toHaveBeenCalledOnce();
});