mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 07:35:24 +00:00
fix: translate hardcoded toasts and drop em dashes from ui text
This commit is contained in:
1 parent
3c66931245
commit
1b342322dc
17 files changed
+89
-52
No files matched your search
@@ -72,6 +72,7 @@
|
|||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"uploading": "Uploading...",
|
"uploading": "Uploading...",
|
||||||
"uploadingFile": "Uploading {{name}}...",
|
"uploadingFile": "Uploading {{name}}...",
|
||||||
|
"uploadingChunk": "Uploading {{name}}: {{percent}}% (part {{chunk}} of {{total}})",
|
||||||
"fileName": "File Name",
|
"fileName": "File Name",
|
||||||
"folderName": "Folder Name",
|
"folderName": "Folder Name",
|
||||||
"fileUploadedSuccessfully": "File \"{{name}}\" uploaded successfully",
|
"fileUploadedSuccessfully": "File \"{{name}}\" uploaded successfully",
|
||||||
@@ -373,7 +374,12 @@
|
|||||||
"dragMovedItems_one": "Moved {{count}} item to {{target}}",
|
"dragMovedItems_one": "Moved {{count}} item to {{target}}",
|
||||||
"dragMovedItems_other": "Moved {{count}} items to {{target}}",
|
"dragMovedItems_other": "Moved {{count}} items to {{target}}",
|
||||||
"moveBackFileFailed": "Could not move {{name}} back: {{error}}",
|
"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": {
|
"sftpTransfer": {
|
||||||
"title": "SFTP",
|
"title": "SFTP",
|
||||||
@@ -454,7 +460,7 @@
|
|||||||
"destinationPath": "Destination path",
|
"destinationPath": "Destination path",
|
||||||
"destMustBeDirectory": "Destination must be a directory for multi-item transfers",
|
"destMustBeDirectory": "Destination must be a directory for multi-item transfers",
|
||||||
"goUp": "Go up",
|
"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…",
|
"hostConnecting": "Connecting…",
|
||||||
"hostConnectionFailed": "Connection failed",
|
"hostConnectionFailed": "Connection failed",
|
||||||
"hostDisconnected": "Not connected",
|
"hostDisconnected": "Not connected",
|
||||||
@@ -475,12 +481,12 @@
|
|||||||
"methodReason": {
|
"methodReason": {
|
||||||
"user_item_sftp": "You chose per-file SFTP.",
|
"user_item_sftp": "You chose per-file SFTP.",
|
||||||
"user_tar": "You chose tar archive.",
|
"user_tar": "You chose tar archive.",
|
||||||
"tar_unavailable": "Tar is not available on one or both hosts — per-file SFTP will be used instead.",
|
"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 — tar is not used.",
|
"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 into one transfer.",
|
"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 — per-file SFTP.",
|
"auto_single_large_in_archive": "Auto: one large file ({{largestSize}}) in this set, so per-file SFTP.",
|
||||||
"auto_many_incompressible": "Auto: mostly incompressible data — per-file SFTP.",
|
"auto_many_incompressible": "Auto: mostly incompressible data, so per-file SFTP.",
|
||||||
"auto_many_files": "Auto: many files ({{fileCount}}) — tar reduces per-file overhead.",
|
"auto_many_files": "Auto: many files ({{fileCount}}), tar cuts the per-file overhead.",
|
||||||
"auto_default": "Auto: per-file SFTP for this set."
|
"auto_default": "Auto: per-file SFTP for this set."
|
||||||
},
|
},
|
||||||
"methodTar": "Tar archive",
|
"methodTar": "Tar archive",
|
||||||
@@ -488,7 +494,7 @@
|
|||||||
"metricsCompress": "Compress on source: {{duration}}",
|
"metricsCompress": "Compress on source: {{duration}}",
|
||||||
"metricsExtract": "Extract on destination: {{duration}}",
|
"metricsExtract": "Extract on destination: {{duration}}",
|
||||||
"metricsPrepare": "Prepare 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}}",
|
"metricsSourceDelete": "Remove from source: {{duration}}",
|
||||||
"metricsTotal": "Total: {{duration}}",
|
"metricsTotal": "Total: {{duration}}",
|
||||||
"metricsTransfer": "End-to-end: {{throughput}} ({{duration}})",
|
"metricsTransfer": "End-to-end: {{throughput}} ({{duration}})",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class TransferStalledError extends Error {
|
|||||||
constructor(byteOffset?: number, segmentIndex?: number) {
|
constructor(byteOffset?: number, segmentIndex?: number) {
|
||||||
const pos = byteOffset !== undefined ? ` at byte offset ${byteOffset}` : "";
|
const pos = byteOffset !== undefined ? ` at byte offset ${byteOffset}` : "";
|
||||||
const seg = segmentIndex !== undefined ? ` (segment ${segmentIndex})` : "";
|
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.name = "TransferStalledError";
|
||||||
this.byteOffset = byteOffset;
|
this.byteOffset = byteOffset;
|
||||||
this.segmentIndex = segmentIndex;
|
this.segmentIndex = segmentIndex;
|
||||||
|
|||||||
@@ -1345,7 +1345,12 @@ function FileManagerContent({
|
|||||||
Math.round((p.bytesSent / p.totalBytes) * 100),
|
Math.round((p.bytesSent / p.totalBytes) * 100),
|
||||||
);
|
);
|
||||||
toast.loading(
|
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 },
|
{ id: progressToast, duration: Infinity, cancel },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -216,9 +216,12 @@ export function FileWindow({
|
|||||||
};
|
};
|
||||||
const errorData = err?.response?.data;
|
const errorData = err?.response?.data;
|
||||||
if (errorData?.tooLarge) {
|
if (errorData?.tooLarge) {
|
||||||
toast.error(`File too large: ${errorData.error}`, {
|
toast.error(
|
||||||
duration: 10000,
|
t("fileManager.fileTooLarge", { error: errorData.error }),
|
||||||
});
|
{
|
||||||
|
duration: 10000,
|
||||||
|
},
|
||||||
|
);
|
||||||
} else if (
|
} else if (
|
||||||
err.message?.includes("connection") ||
|
err.message?.includes("connection") ||
|
||||||
err.message?.includes("established")
|
err.message?.includes("established")
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function formatByteSize(bytes: number): string {
|
|||||||
function formatTruncatedDestination(hostLabel: string, path: string): string {
|
function formatTruncatedDestination(hostLabel: string, path: string): string {
|
||||||
const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "") || "/";
|
const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "") || "/";
|
||||||
if (normalized === "/") {
|
if (normalized === "/") {
|
||||||
return `${hostLabel} — /`;
|
return `${hostLabel}: /`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const segments = normalized.split("/").filter(Boolean);
|
const segments = normalized.split("/").filter(Boolean);
|
||||||
@@ -111,7 +111,7 @@ function formatTruncatedDestination(hostLabel: string, path: string): string {
|
|||||||
? `/${segments.join("/")}`
|
? `/${segments.join("/")}`
|
||||||
: `.../${segments.slice(-2).join("/")}`;
|
: `.../${segments.slice(-2).join("/")}`;
|
||||||
|
|
||||||
return `${hostLabel} — ${pathPart}`;
|
return `${hostLabel}: ${pathPart}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function longestCommonPrefix(strings: string[]): string {
|
function longestCommonPrefix(strings: string[]): string {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useTranslation } from "@termix/plugin-sdk/frontend";
|
||||||
import { downloadSSHFile } from "../api/ssh-file-operations-api";
|
import { downloadSSHFile } from "../api/ssh-file-operations-api";
|
||||||
import type { FileItem, SSHHost } from "../host-types";
|
import type { FileItem, SSHHost } from "../host-types";
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ interface DragToDesktopOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState<DragToDesktopState>({
|
const [state, setState] = useState<DragToDesktopState>({
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
isDownloading: false,
|
isDownloading: false,
|
||||||
@@ -98,7 +100,7 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
|||||||
setState((prev) => ({ ...prev, progress: 100 }));
|
setState((prev) => ({ ...prev, progress: 100 }));
|
||||||
|
|
||||||
if (enableToast) {
|
if (enableToast) {
|
||||||
toast.success(`Dragging ${file.name} to desktop`);
|
toast.success(t("fileManager.dragToDesktop", { name: file.name }));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
@@ -128,7 +130,9 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (enableToast) {
|
if (enableToast) {
|
||||||
toast.error(`Drag failed: ${errorMessage}`);
|
toast.error(
|
||||||
|
t("fileManager.dragFailedDetail", { error: errorMessage }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onError?.(errorMessage);
|
onError?.(errorMessage);
|
||||||
@@ -209,7 +213,9 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
|||||||
setState((prev) => ({ ...prev, progress: 100 }));
|
setState((prev) => ({ ...prev, progress: 100 }));
|
||||||
|
|
||||||
if (enableToast) {
|
if (enableToast) {
|
||||||
toast.success(`Dragging ${fileList.length} files to desktop`);
|
toast.success(
|
||||||
|
t("fileManager.dragFilesToDesktop", { count: fileList.length }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
@@ -238,7 +244,9 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (enableToast) {
|
if (enableToast) {
|
||||||
toast.error(`Batch drag failed: ${errorMessage}`);
|
toast.error(
|
||||||
|
t("fileManager.dragFailedDetail", { error: errorMessage }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onError?.(errorMessage);
|
onError?.(errorMessage);
|
||||||
@@ -268,7 +276,7 @@ export function useDragToDesktop({ sshSessionId }: UseDragToDesktopProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (enableToast) {
|
if (enableToast) {
|
||||||
toast.info("Folder drag functionality is under development...");
|
toast.info(t("fileManager.folderDragUnsupported"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useState, useCallback, useRef } from "react";
|
import { useState, useCallback, useRef } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useTranslation } from "@termix/plugin-sdk/frontend";
|
||||||
import { downloadSSHFile } from "../api/ssh-file-operations-api";
|
import { downloadSSHFile } from "../api/ssh-file-operations-api";
|
||||||
import type { FileItem, SSHHost } from "../host-types";
|
import type { FileItem, SSHHost } from "../host-types";
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ interface DragToSystemOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
|
export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState<DragToSystemState>({
|
const [state, setState] = useState<DragToSystemState>({
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
isDownloading: false,
|
isDownloading: false,
|
||||||
@@ -250,7 +252,9 @@ export function useDragToSystemDesktop({ sshSessionId }: UseDragToSystemProps) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (enableToast) {
|
if (enableToast) {
|
||||||
toast.error(`Save failed: ${errorMessage}`);
|
toast.error(
|
||||||
|
t("fileManager.saveToDesktopFailed", { error: errorMessage }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onError?.(errorMessage);
|
onError?.(errorMessage);
|
||||||
|
|||||||
@@ -110,8 +110,8 @@
|
|||||||
"hostNotFound": "Host not found",
|
"hostNotFound": "Host not found",
|
||||||
"loadingHost": "Loading host...",
|
"loadingHost": "Loading host...",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"proxmoxGuestsSelected_one": "{{count}} guest — {{selected}} selected",
|
"proxmoxGuestsSelected_one": "{{count}} guest, {{selected}} selected",
|
||||||
"proxmoxGuestsSelected_other": "{{count}} guests — {{selected}} selected",
|
"proxmoxGuestsSelected_other": "{{count}} guests, {{selected}} selected",
|
||||||
"proxmoxImportButton_one": "Import {{count}} host",
|
"proxmoxImportButton_one": "Import {{count}} host",
|
||||||
"proxmoxImportButton_other": "Import {{count}} hosts"
|
"proxmoxImportButton_other": "Import {{count}} hosts"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,7 +81,11 @@
|
|||||||
"clipboardWriteFailed": "Failed to copy to clipboard. Make sure the page is served over HTTPS or localhost.",
|
"clipboardWriteFailed": "Failed to copy to clipboard. Make sure the page is served over HTTPS or localhost.",
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"totpCodeLabel": "Verification Code",
|
"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": {
|
"terminalToolbar": {
|
||||||
"detachTmux": "Detach tmux",
|
"detachTmux": "Detach tmux",
|
||||||
|
|||||||
@@ -3523,7 +3523,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
source: TerminalImageUploadSource,
|
source: TerminalImageUploadSource,
|
||||||
) {
|
) {
|
||||||
if (file.type && !file.type.startsWith("image/")) {
|
if (file.type && !file.type.startsWith("image/")) {
|
||||||
toast.error("Choose an image file");
|
toast.error(t("terminal.imageChooseFile"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsImageUploading(true);
|
setIsImageUploading(true);
|
||||||
@@ -3548,7 +3548,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
data: quoteTerminalImagePath(shellPath),
|
data: quoteTerminalImagePath(shellPath),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
toast.success(`Image uploaded: ${shellPath}`);
|
toast.success(t("terminal.imageUploaded", { path: shellPath }));
|
||||||
} else {
|
} else {
|
||||||
toast.warning(
|
toast.warning(
|
||||||
`Image uploaded, but the terminal was not available to paste it: ${shellPath}`,
|
`Image uploaded, but the terminal was not available to paste it: ${shellPath}`,
|
||||||
@@ -3567,7 +3567,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
|
|
||||||
async function handleClipboardImage() {
|
async function handleClipboardImage() {
|
||||||
if (!navigator.clipboard?.read) {
|
if (!navigator.clipboard?.read) {
|
||||||
toast.error("Clipboard image access is not available in this browser");
|
toast.error(t("terminal.imageClipboardUnavailable"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsImageUploading(true);
|
setIsImageUploading(true);
|
||||||
@@ -3619,7 +3619,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
|||||||
await handleImageUpload(clipboardFile, "clipboard");
|
await handleImageUpload(clipboardFile, "clipboard");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.error("No image found in the clipboard");
|
toast.error(t("terminal.imageClipboardEmpty"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(getErrorMessage(error, "Clipboard read failed"));
|
toast.error(getErrorMessage(error, "Clipboard read failed"));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
"sessionRenameFailed": "Failed to rename session",
|
"sessionRenameFailed": "Failed to rename session",
|
||||||
"editTagsTitle": "Edit tags for \"{{name}}\"",
|
"editTagsTitle": "Edit tags for \"{{name}}\"",
|
||||||
"killPane": "Kill pane",
|
"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)",
|
"reattach": "Re-attach (fixes a garbled view)",
|
||||||
"killWindow": "Kill window",
|
"killWindow": "Kill window",
|
||||||
"killWindowTitle": "Kill window {{index}} of \"{{session}}\"?",
|
"killWindowTitle": "Kill window {{index}} of \"{{session}}\"?",
|
||||||
@@ -93,14 +93,14 @@
|
|||||||
"sessionKillFailed": "Failed to kill session",
|
"sessionKillFailed": "Failed to kill session",
|
||||||
"hostUnreachable": "Could not connect to the host. Check that it is online and reachable.",
|
"hostUnreachable": "Could not connect to the host. Check that it is online and reachable.",
|
||||||
"noServer": "No tmux server is running on this host.",
|
"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",
|
"closeSearchResults": "Close search results",
|
||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
"noHosts": "No SSH hosts available",
|
"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.",
|
"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:",
|
"tmuxInstallHint": "Install it on the host with:",
|
||||||
"attachTooltip": "Open a terminal to {{host}}",
|
"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",
|
"timeJustNow": "just now",
|
||||||
"timeMinutes": "{{count}}m ago",
|
"timeMinutes": "{{count}}m ago",
|
||||||
"timeHours": "{{count}}h ago",
|
"timeHours": "{{count}}h ago",
|
||||||
|
|||||||
+2
-2
@@ -461,13 +461,13 @@ export function AppShell({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!document.fullscreenEnabled) {
|
if (!document.fullscreenEnabled) {
|
||||||
toast.error("Fullscreen is not supported by this browser");
|
toast.error(t("nav.fullscreenUnsupported"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await document.documentElement.requestFullscreen();
|
await document.documentElement.requestFullscreen();
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("Unable to toggle fullscreen mode");
|
toast.error(t("nav.fullscreenFailed"));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -299,7 +299,9 @@
|
|||||||
"reconnectingTerminals_other": "Reconnecting {{count}} terminals",
|
"reconnectingTerminals_other": "Reconnecting {{count}} terminals",
|
||||||
"noDisconnectedTerminals": "No disconnected terminals are ready to reconnect",
|
"noDisconnectedTerminals": "No disconnected terminals are ready to reconnect",
|
||||||
"reconnectTerminalsFailed_one": "Could not start reconnecting {{count}} terminal",
|
"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": "Hosts",
|
"hosts": "Hosts",
|
||||||
@@ -859,7 +861,11 @@
|
|||||||
"editorUpdate": "Update Host",
|
"editorUpdate": "Update Host",
|
||||||
"editorAdd": "Add Host",
|
"editorAdd": "Add Host",
|
||||||
"speedSearch": "Search hosts. Arrow keys to select, Enter to connect, Esc to close",
|
"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": {
|
"commandPalette": {
|
||||||
"searchPlaceholder": "Search hosts, commands, or settings...",
|
"searchPlaceholder": "Search hosts, commands, or settings...",
|
||||||
@@ -1083,7 +1089,8 @@
|
|||||||
"resetCodeRateLimitedWithTime": "Rate limited: Too many verification attempts. Please wait {{time}} seconds before trying again.",
|
"resetCodeRateLimitedWithTime": "Rate limited: Too many verification attempts. Please wait {{time}} seconds before trying again.",
|
||||||
"authTokenSaveFailed": "Failed to save authentication token",
|
"authTokenSaveFailed": "Failed to save authentication token",
|
||||||
"authTokenMissing": "The server did not return a new authentication token. Please sign in again.",
|
"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": {
|
"messages": {
|
||||||
"registrationDisabled": "New account registration is currently disabled by an admin. Please log in or contact an administrator.",
|
"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",
|
"noSessionsForUser": "No active sessions",
|
||||||
"deleteSuccessorLabel": "Hand over hosts and credentials to",
|
"deleteSuccessorLabel": "Hand over hosts and credentials to",
|
||||||
"deleteSuccessorMe": "Me (the deleting admin)",
|
"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.",
|
"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.",
|
"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?",
|
"deleteUserConfirm": "Permanently delete {{username}} and all of their data?",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import axios, {
|
|||||||
type InternalAxiosRequestConfig,
|
type InternalAxiosRequestConfig,
|
||||||
} from "axios";
|
} from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import i18n from "i18next";
|
||||||
import { getBasePath } from "@/lib/base-path";
|
import { getBasePath } from "@/lib/base-path";
|
||||||
import { isElectron } from "@/lib/electron";
|
import { isElectron } from "@/lib/electron";
|
||||||
import { clearTermixSessionStorage } from "@/shell/TabContext";
|
import { clearTermixSessionStorage } from "@/shell/TabContext";
|
||||||
@@ -534,7 +535,7 @@ function createApiInstance(
|
|||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
console.warn("Session expired - please log in again");
|
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"));
|
window.dispatchEvent(new Event("termix:logout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ export function CredentialEditorView({
|
|||||||
/>
|
/>
|
||||||
{credForm.value === "existing_key" && (
|
{credForm.value === "existing_key" && (
|
||||||
<div className="px-3 py-2 text-[10px] border border-accent-brand/30 bg-accent-brand/5 text-accent-brand">
|
<div className="px-3 py-2 text-[10px] border border-accent-brand/30 bg-accent-brand/5 text-accent-brand">
|
||||||
{t("hosts.keySaved")} — {t("hosts.keyReplaceNotice")}
|
{t("hosts.keySaved")}. {t("hosts.keyReplaceNotice")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@@ -983,7 +983,7 @@ export function HostEditor({
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
{form.key === "existing_key" && (
|
{form.key === "existing_key" && (
|
||||||
<div className="px-3 py-2 text-[10px] border border-accent-brand/30 bg-accent-brand/5 text-accent-brand">
|
<div className="px-3 py-2 text-[10px] border border-accent-brand/30 bg-accent-brand/5 text-accent-brand">
|
||||||
{t("hosts.keySaved")} —{" "}
|
{t("hosts.keySaved")}.{" "}
|
||||||
{t("hosts.keyReplaceNotice")}
|
{t("hosts.keyReplaceNotice")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -455,11 +455,11 @@ export function HostsPanel({
|
|||||||
? parsed.credentials
|
? parsed.credentials
|
||||||
: undefined;
|
: undefined;
|
||||||
if (!Array.isArray(hostsArray) || hostsArray.length === 0) {
|
if (!Array.isArray(hostsArray) || hostsArray.length === 0) {
|
||||||
toast.error("No hosts found in file");
|
toast.error(t("hosts.importNoHosts"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hostsArray.length > 100) {
|
if (hostsArray.length > 100) {
|
||||||
toast.error("Cannot import more than 100 hosts at once");
|
toast.error(t("hosts.importTooMany", { max: 100 }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const normalized = hostsArray.map(
|
const normalized = hostsArray.map(
|
||||||
@@ -477,16 +477,15 @@ export function HostsPanel({
|
|||||||
const hosts = await getSSHHosts();
|
const hosts = await getSSHHosts();
|
||||||
setRawHosts(hosts);
|
setRawHosts(hosts);
|
||||||
window.dispatchEvent(new CustomEvent("termix:hosts-changed"));
|
window.dispatchEvent(new CustomEvent("termix:hosts-changed"));
|
||||||
const msg = [
|
toast.success(
|
||||||
result.success ? `${result.success} imported` : null,
|
t("hosts.importSummary", {
|
||||||
result.updated ? `${result.updated} updated` : null,
|
imported: result.success ?? 0,
|
||||||
result.failed ? `${result.failed} failed` : null,
|
updated: result.updated ?? 0,
|
||||||
]
|
failed: result.failed ?? 0,
|
||||||
.filter(Boolean)
|
}),
|
||||||
.join(", ");
|
);
|
||||||
toast.success(`Import complete: ${msg}`);
|
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
toast.error(getErrorMessage(err, "Failed to import hosts"));
|
toast.error(getErrorMessage(err, t("hosts.importHostsFailed")));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in new issue
Block a user