From 1b342322dc4fa5f9f65f54e215de21501eb794fa Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 30 Sep 2026 14:25:51 -0500 Subject: [PATCH] fix: translate hardcoded toasts and drop em dashes from ui text --- plugins/file-manager/locales/en.json | 24 ++++++++++++------- .../src/backend/transfer-errors.ts | 2 +- .../file-manager/src/frontend/FileManager.tsx | 7 +++++- .../src/frontend/components/FileWindow.tsx | 9 ++++--- .../components/TransferToHostDialog.tsx | 4 ++-- .../src/frontend/hooks/useDragToDesktop.ts | 18 ++++++++++---- .../frontend/hooks/useDragToSystemDesktop.ts | 6 ++++- plugins/proxmox/locales/en.json | 4 ++-- plugins/ssh-terminal/locales/en.json | 6 ++++- .../src/frontend/terminal/Terminal.tsx | 8 +++---- plugins/tmux-monitor/locales/en.json | 6 ++--- src/ui/AppShell.tsx | 4 ++-- src/ui/locales/en.json | 15 ++++++++---- src/ui/main-axios.ts | 3 ++- src/ui/sidebar/CredentialEditorView.tsx | 2 +- src/ui/sidebar/HostEditor.tsx | 2 +- src/ui/sidebar/HostsPanel.tsx | 21 ++++++++-------- 17 files changed, 89 insertions(+), 52 deletions(-) diff --git a/plugins/file-manager/locales/en.json b/plugins/file-manager/locales/en.json index b1971d5a6..04d10bddb 100644 --- a/plugins/file-manager/locales/en.json +++ b/plugins/file-manager/locales/en.json @@ -72,6 +72,7 @@ "rename": "Rename", "uploading": "Uploading...", "uploadingFile": "Uploading {{name}}...", + "uploadingChunk": "Uploading {{name}}: {{percent}}% (part {{chunk}} of {{total}})", "fileName": "File Name", "folderName": "Folder Name", "fileUploadedSuccessfully": "File \"{{name}}\" uploaded successfully", @@ -373,7 +374,12 @@ "dragMovedItems_one": "Moved {{count}} item to {{target}}", "dragMovedItems_other": "Moved {{count}} items to {{target}}", "moveBackFileFailed": "Could not move {{name}} back: {{error}}", - "terminal": "Terminal: {{host}} ({{path}})" + "terminal": "Terminal: {{host}} ({{path}})", + "dragToDesktop": "Dragging {{name}} to the desktop", + "dragFilesToDesktop": "Dragging {{count}} files to the desktop", + "folderDragUnsupported": "Dragging folders to the desktop is not supported yet", + "saveToDesktopFailed": "Save failed: {{error}}", + "dragFailedDetail": "Drag failed: {{error}}" }, "sftpTransfer": { "title": "SFTP", @@ -454,7 +460,7 @@ "destinationPath": "Destination path", "destMustBeDirectory": "Destination must be a directory for multi-item transfers", "goUp": "Go up", - "hostAuthRequired": "Authentication required — open File Manager on this host first", + "hostAuthRequired": "Authentication required. Open File Manager on this host first", "hostConnecting": "Connecting…", "hostConnectionFailed": "Connection failed", "hostDisconnected": "Not connected", @@ -475,12 +481,12 @@ "methodReason": { "user_item_sftp": "You chose per-file SFTP.", "user_tar": "You chose tar archive.", - "tar_unavailable": "Tar is not available on one or both hosts — per-file SFTP will be used instead.", - "windows_host": "A Windows host is involved — tar is not used.", - "auto_multi_large": "Auto: multiple files including a large file ({{largestSize}}) with compressible data — tar bundles into one transfer.", - "auto_single_large_in_archive": "Auto: one large file ({{largestSize}}) in this set — per-file SFTP.", - "auto_many_incompressible": "Auto: mostly incompressible data — per-file SFTP.", - "auto_many_files": "Auto: many files ({{fileCount}}) — tar reduces per-file overhead.", + "tar_unavailable": "Tar is not available on one or both hosts, so per-file SFTP will be used instead.", + "windows_host": "A Windows host is involved, so tar is not used.", + "auto_multi_large": "Auto: multiple files including a large file ({{largestSize}}) with compressible data. Tar bundles them into one transfer.", + "auto_single_large_in_archive": "Auto: one large file ({{largestSize}}) in this set, so per-file SFTP.", + "auto_many_incompressible": "Auto: mostly incompressible data, so per-file SFTP.", + "auto_many_files": "Auto: many files ({{fileCount}}), tar cuts the per-file overhead.", "auto_default": "Auto: per-file SFTP for this set." }, "methodTar": "Tar archive", @@ -488,7 +494,7 @@ "metricsCompress": "Compress on source: {{duration}}", "metricsExtract": "Extract on destination: {{duration}}", "metricsPrepare": "Prepare destination: {{duration}}", - "metricsRouteBenchmark": "Route probe — direct: {{direct}}, relay: {{relay}}", + "metricsRouteBenchmark": "Route probe: direct {{direct}}, relay {{relay}}", "metricsSourceDelete": "Remove from source: {{duration}}", "metricsTotal": "Total: {{duration}}", "metricsTransfer": "End-to-end: {{throughput}} ({{duration}})", diff --git a/plugins/file-manager/src/backend/transfer-errors.ts b/plugins/file-manager/src/backend/transfer-errors.ts index 2180e1ff7..5f8de3405 100644 --- a/plugins/file-manager/src/backend/transfer-errors.ts +++ b/plugins/file-manager/src/backend/transfer-errors.ts @@ -12,7 +12,7 @@ export class TransferStalledError extends Error { constructor(byteOffset?: number, segmentIndex?: number) { const pos = byteOffset !== undefined ? ` at byte offset ${byteOffset}` : ""; const seg = segmentIndex !== undefined ? ` (segment ${segmentIndex})` : ""; - super(`Transfer stalled — no data moved for 45 seconds${pos}${seg}`); + super(`Transfer stalled: no data moved for 45 seconds${pos}${seg}`); this.name = "TransferStalledError"; this.byteOffset = byteOffset; this.segmentIndex = segmentIndex; diff --git a/plugins/file-manager/src/frontend/FileManager.tsx b/plugins/file-manager/src/frontend/FileManager.tsx index cdce9016e..353afe854 100644 --- a/plugins/file-manager/src/frontend/FileManager.tsx +++ b/plugins/file-manager/src/frontend/FileManager.tsx @@ -1345,7 +1345,12 @@ function FileManagerContent({ Math.round((p.bytesSent / p.totalBytes) * 100), ); toast.loading( - `Uploading ${file.name} — ${percent}% (chunk ${p.chunkIndex + 1}/${p.totalChunks})`, + t("fileManager.uploadingChunk", { + name: file.name, + percent, + chunk: p.chunkIndex + 1, + total: p.totalChunks, + }), { id: progressToast, duration: Infinity, cancel }, ); }; diff --git a/plugins/file-manager/src/frontend/components/FileWindow.tsx b/plugins/file-manager/src/frontend/components/FileWindow.tsx index 2761e509b..0c3ff5bd5 100644 --- a/plugins/file-manager/src/frontend/components/FileWindow.tsx +++ b/plugins/file-manager/src/frontend/components/FileWindow.tsx @@ -216,9 +216,12 @@ export function FileWindow({ }; const errorData = err?.response?.data; if (errorData?.tooLarge) { - toast.error(`File too large: ${errorData.error}`, { - duration: 10000, - }); + toast.error( + t("fileManager.fileTooLarge", { error: errorData.error }), + { + duration: 10000, + }, + ); } else if ( err.message?.includes("connection") || err.message?.includes("established") diff --git a/plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx b/plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx index 77e3218cd..f810391da 100644 --- a/plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx +++ b/plugins/file-manager/src/frontend/components/TransferToHostDialog.tsx @@ -102,7 +102,7 @@ function formatByteSize(bytes: number): string { function formatTruncatedDestination(hostLabel: string, path: string): string { const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "") || "/"; if (normalized === "/") { - return `${hostLabel} — /`; + return `${hostLabel}: /`; } const segments = normalized.split("/").filter(Boolean); @@ -111,7 +111,7 @@ function formatTruncatedDestination(hostLabel: string, path: string): string { ? `/${segments.join("/")}` : `.../${segments.slice(-2).join("/")}`; - return `${hostLabel} — ${pathPart}`; + return `${hostLabel}: ${pathPart}`; } function longestCommonPrefix(strings: string[]): string { diff --git a/plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts b/plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts index 83a4d9522..dc9a38092 100644 --- a/plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts +++ b/plugins/file-manager/src/frontend/hooks/useDragToDesktop.ts @@ -1,5 +1,6 @@ import { useState, useCallback } from "react"; import { toast } from "sonner"; +import { useTranslation } from "@termix/plugin-sdk/frontend"; import { downloadSSHFile } from "../api/ssh-file-operations-api"; import type { FileItem, SSHHost } from "../host-types"; @@ -22,6 +23,7 @@ interface DragToDesktopOptions { } export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) { + const { t } = useTranslation(); const [state, setState] = useState({ isDragging: false, isDownloading: false, @@ -98,7 +100,7 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) { setState((prev) => ({ ...prev, progress: 100 })); if (enableToast) { - toast.success(`Dragging ${file.name} to desktop`); + toast.success(t("fileManager.dragToDesktop", { name: file.name })); } onSuccess?.(); @@ -128,7 +130,9 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) { })); if (enableToast) { - toast.error(`Drag failed: ${errorMessage}`); + toast.error( + t("fileManager.dragFailedDetail", { error: errorMessage }), + ); } onError?.(errorMessage); @@ -209,7 +213,9 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) { setState((prev) => ({ ...prev, progress: 100 })); if (enableToast) { - toast.success(`Dragging ${fileList.length} files to desktop`); + toast.success( + t("fileManager.dragFilesToDesktop", { count: fileList.length }), + ); } onSuccess?.(); @@ -238,7 +244,9 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) { })); if (enableToast) { - toast.error(`Batch drag failed: ${errorMessage}`); + toast.error( + t("fileManager.dragFailedDetail", { error: errorMessage }), + ); } onError?.(errorMessage); @@ -268,7 +276,7 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) { } if (enableToast) { - toast.info("Folder drag functionality is under development..."); + toast.info(t("fileManager.folderDragUnsupported")); } return false; diff --git a/plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts b/plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts index 3b3a9b351..cc66c3f04 100644 --- a/plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts +++ b/plugins/file-manager/src/frontend/hooks/useDragToSystemDesktop.ts @@ -1,6 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useState, useCallback, useRef } from "react"; import { toast } from "sonner"; +import { useTranslation } from "@termix/plugin-sdk/frontend"; import { downloadSSHFile } from "../api/ssh-file-operations-api"; import type { FileItem, SSHHost } from "../host-types"; @@ -23,6 +24,7 @@ interface DragToSystemOptions { } export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) { + const { t } = useTranslation(); const [state, setState] = useState({ isDragging: false, isDownloading: false, @@ -250,7 +252,9 @@ export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) { })); if (enableToast) { - toast.error(`Save failed: ${errorMessage}`); + toast.error( + t("fileManager.saveToDesktopFailed", { error: errorMessage }), + ); } onError?.(errorMessage); diff --git a/plugins/proxmox/locales/en.json b/plugins/proxmox/locales/en.json index 4827300f6..b4f5d48be 100644 --- a/plugins/proxmox/locales/en.json +++ b/plugins/proxmox/locales/en.json @@ -110,8 +110,8 @@ "hostNotFound": "Host not found", "loadingHost": "Loading host...", "none": "None", - "proxmoxGuestsSelected_one": "{{count}} guest — {{selected}} selected", - "proxmoxGuestsSelected_other": "{{count}} guests — {{selected}} selected", + "proxmoxGuestsSelected_one": "{{count}} guest, {{selected}} selected", + "proxmoxGuestsSelected_other": "{{count}} guests, {{selected}} selected", "proxmoxImportButton_one": "Import {{count}} host", "proxmoxImportButton_other": "Import {{count}} hosts" }, diff --git a/plugins/ssh-terminal/locales/en.json b/plugins/ssh-terminal/locales/en.json index 40abb5e3a..62b44316e 100644 --- a/plugins/ssh-terminal/locales/en.json +++ b/plugins/ssh-terminal/locales/en.json @@ -81,7 +81,11 @@ "clipboardWriteFailed": "Failed to copy to clipboard. Make sure the page is served over HTTPS or localhost.", "connected": "Connected", "totpCodeLabel": "Verification Code", - "duplicateTab": "Duplicate tab" + "duplicateTab": "Duplicate tab", + "imageChooseFile": "Choose an image file", + "imageUploaded": "Image uploaded: {{path}}", + "imageClipboardUnavailable": "This browser cannot read images from the clipboard", + "imageClipboardEmpty": "No image found in the clipboard" }, "terminalToolbar": { "detachTmux": "Detach tmux", diff --git a/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx b/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx index b9f0b124d..53cc181d4 100644 --- a/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx +++ b/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx @@ -3523,7 +3523,7 @@ const TerminalInner = forwardRef( source: TerminalImageUploadSource, ) { if (file.type && !file.type.startsWith("image/")) { - toast.error("Choose an image file"); + toast.error(t("terminal.imageChooseFile")); return; } setIsImageUploading(true); @@ -3548,7 +3548,7 @@ const TerminalInner = forwardRef( data: quoteTerminalImagePath(shellPath), }), ); - toast.success(`Image uploaded: ${shellPath}`); + toast.success(t("terminal.imageUploaded", { path: shellPath })); } else { toast.warning( `Image uploaded, but the terminal was not available to paste it: ${shellPath}`, @@ -3567,7 +3567,7 @@ const TerminalInner = forwardRef( async function handleClipboardImage() { if (!navigator.clipboard?.read) { - toast.error("Clipboard image access is not available in this browser"); + toast.error(t("terminal.imageClipboardUnavailable")); return; } setIsImageUploading(true); @@ -3619,7 +3619,7 @@ const TerminalInner = forwardRef( await handleImageUpload(clipboardFile, "clipboard"); return; } - toast.error("No image found in the clipboard"); + toast.error(t("terminal.imageClipboardEmpty")); } catch (error) { toast.error(getErrorMessage(error, "Clipboard read failed")); } finally { diff --git a/plugins/tmux-monitor/locales/en.json b/plugins/tmux-monitor/locales/en.json index 6c9a87e69..2f7f1b2dc 100644 --- a/plugins/tmux-monitor/locales/en.json +++ b/plugins/tmux-monitor/locales/en.json @@ -73,7 +73,7 @@ "sessionRenameFailed": "Failed to rename session", "editTagsTitle": "Edit tags for \"{{name}}\"", "killPane": "Kill pane", - "resizeTree": "Drag to resize — double-click to reset", + "resizeTree": "Drag to resize, double-click to reset", "reattach": "Re-attach (fixes a garbled view)", "killWindow": "Kill window", "killWindowTitle": "Kill window {{index}} of \"{{session}}\"?", @@ -93,14 +93,14 @@ "sessionKillFailed": "Failed to kill session", "hostUnreachable": "Could not connect to the host. Check that it is online and reachable.", "noServer": "No tmux server is running on this host.", - "searchTruncated": "Partial results — search covers the last {{lines}} lines of each pane and at most {{panes}} panes.", + "searchTruncated": "Partial results. Search covers the last {{lines}} lines of each pane and at most {{panes}} panes.", "closeSearchResults": "Close search results", "retry": "Retry", "noHosts": "No SSH hosts available", "noHostsHint": "Enable the Tmux Monitor option on an SSH host (Host Manager, Tmux Monitor tab) to monitor its tmux sessions.", "tmuxInstallHint": "Install it on the host with:", "attachTooltip": "Open a terminal to {{host}}", - "attachTooltipPane": "Open a terminal to {{host}} — tmux session {{session}}", + "attachTooltipPane": "Open a terminal to {{host}}, tmux session {{session}}", "timeJustNow": "just now", "timeMinutes": "{{count}}m ago", "timeHours": "{{count}}h ago", diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index e8ba72e7a..eda0202b6 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -461,13 +461,13 @@ export function AppShell({ } if (!document.fullscreenEnabled) { - toast.error("Fullscreen is not supported by this browser"); + toast.error(t("nav.fullscreenUnsupported")); return; } await document.documentElement.requestFullscreen(); } catch { - toast.error("Unable to toggle fullscreen mode"); + toast.error(t("nav.fullscreenFailed")); } }, []); diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 89112aada..a92f546ab 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -299,7 +299,9 @@ "reconnectingTerminals_other": "Reconnecting {{count}} terminals", "noDisconnectedTerminals": "No disconnected terminals are ready to reconnect", "reconnectTerminalsFailed_one": "Could not start reconnecting {{count}} terminal", - "reconnectTerminalsFailed_other": "Could not start reconnecting {{count}} terminals" + "reconnectTerminalsFailed_other": "Could not start reconnecting {{count}} terminals", + "fullscreenUnsupported": "Fullscreen is not supported by this browser", + "fullscreenFailed": "Could not toggle fullscreen" }, "hosts": { "hosts": "Hosts", @@ -859,7 +861,11 @@ "editorUpdate": "Update Host", "editorAdd": "Add Host", "speedSearch": "Search hosts. Arrow keys to select, Enter to connect, Esc to close", - "speedSearchList": "Host list. Type or press / to search." + "speedSearchList": "Host list. Type or press / to search.", + "importNoHosts": "No hosts found in the file", + "importTooMany": "You can import up to {{max}} hosts at once", + "importSummary": "Import done: {{imported}} imported, {{updated}} updated, {{failed}} failed", + "importHostsFailed": "Failed to import hosts" }, "commandPalette": { "searchPlaceholder": "Search hosts, commands, or settings...", @@ -1083,7 +1089,8 @@ "resetCodeRateLimitedWithTime": "Rate limited: Too many verification attempts. Please wait {{time}} seconds before trying again.", "authTokenSaveFailed": "Failed to save authentication token", "authTokenMissing": "The server did not return a new authentication token. Please sign in again.", - "failedToLoadServer": "Failed to load server" + "failedToLoadServer": "Failed to load server", + "sessionExpiredLogin": "Session expired. Please log in again." }, "messages": { "registrationDisabled": "New account registration is currently disabled by an admin. Please log in or contact an administrator.", @@ -1434,7 +1441,7 @@ "noSessionsForUser": "No active sessions", "deleteSuccessorLabel": "Hand over hosts and credentials to", "deleteSuccessorMe": "Me (the deleting admin)", - "deleteSuccessorNone": "Nobody — delete them", + "deleteSuccessorNone": "Nobody, delete them", "deleteSuccessorDesc": "Everything this user shared keeps working under the new owner; choosing nobody removes their hosts, credentials and shares.", "deleteUserDangerDesc": "Permanently delete {{username}} and all of their data (hosts, credentials, snippets, history). This cannot be undone.", "deleteUserConfirm": "Permanently delete {{username}} and all of their data?", diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index c6f983bee..18607fa87 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -6,6 +6,7 @@ import axios, { type InternalAxiosRequestConfig, } from "axios"; import { toast } from "sonner"; +import i18n from "i18next"; import { getBasePath } from "@/lib/base-path"; import { isElectron } from "@/lib/electron"; import { clearTermixSessionStorage } from "@/shell/TabContext"; @@ -534,7 +535,7 @@ function createApiInstance( if (typeof window !== "undefined") { console.warn("Session expired - please log in again"); - toast.warning("Session expired. Please log in again."); + toast.warning(i18n.t("errors.sessionExpiredLogin")); window.dispatchEvent(new Event("termix:logout")); } diff --git a/src/ui/sidebar/CredentialEditorView.tsx b/src/ui/sidebar/CredentialEditorView.tsx index 7a91a9e14..f112bbed9 100644 --- a/src/ui/sidebar/CredentialEditorView.tsx +++ b/src/ui/sidebar/CredentialEditorView.tsx @@ -393,7 +393,7 @@ export function CredentialEditorView({ /> {credForm.value === "existing_key" && (
- {t("hosts.keySaved")} — {t("hosts.keyReplaceNotice")} + {t("hosts.keySaved")}. {t("hosts.keyReplaceNotice")}
)}