fix: file manager and sftp text showing raw i18n keys

This commit is contained in:
LukeGus committed 2026-10-01 13:13:45 -05:00
1 parent 9015d5dccb
commit 95a5e75495
38 files changed
+61 -45

No files matched your search

+19
View File
@@ -214,4 +214,23 @@ export default tseslint.config([
"termix-plugins/stay-inside": "error", "termix-plugins/stay-inside": "error",
}, },
}, },
{
// react-i18next's hook reads core's namespace, so plugin strings show as raw keys.
files: ["plugins/*/src/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "react-i18next",
importNames: ["useTranslation", "Trans", "withTranslation"],
message:
"Use useTranslation from @termix/plugin-sdk/frontend so keys resolve in this plugin's namespace.",
},
],
},
],
},
},
]); ]);
@@ -26,7 +26,7 @@ import { useDragToDesktop } from "./hooks/useDragToDesktop";
import { useDragToSystemDesktop } from "./hooks/useDragToSystemDesktop"; import { useDragToSystemDesktop } from "./hooks/useDragToSystemDesktop";
import { useConfirmation } from "@termix/plugin-sdk/ui"; import { useConfirmation } from "@termix/plugin-sdk/ui";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { FileManagerDialogs } from "./FileManagerDialogs.tsx"; import { FileManagerDialogs } from "./FileManagerDialogs.tsx";
import { PassphraseDialog } from "@termix/plugin-sdk/ui"; import { PassphraseDialog } from "@termix/plugin-sdk/ui";
import { FileManagerToolbar } from "./FileManagerToolbar.tsx"; import { FileManagerToolbar } from "./FileManagerToolbar.tsx";
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { FileManager } from "./FileManager.tsx"; import { FileManager } from "./FileManager.tsx";
import { FullScreenAppWrapper, ConnectionScreen } from "@termix/plugin-sdk/ui"; import { FullScreenAppWrapper, ConnectionScreen } from "@termix/plugin-sdk/ui";
@@ -21,7 +21,7 @@ import {
ArrowRightLeft, ArrowRightLeft,
Link, Link,
} from "lucide-react"; } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Kbd, KbdKey, KbdSeparator } from "@termix/plugin-sdk/ui"; import { Kbd, KbdKey, KbdSeparator } from "@termix/plugin-sdk/ui";
const VIEWPORT_PADDING = 16; const VIEWPORT_PADDING = 16;
@@ -29,7 +29,7 @@ import {
Move, Move,
GitCompare, GitCompare,
} from "lucide-react"; } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { FileItem } from "./host-types"; import type { FileItem } from "./host-types";
import type { CreateIntent } from "./file-manager-types.ts"; import type { CreateIntent } from "./file-manager-types.ts";
import { formatFileSize } from "./file-manager-utils.ts"; import { formatFileSize } from "./file-manager-utils.ts";
@@ -16,7 +16,7 @@ import {
ChevronDown, ChevronDown,
Check, Check,
} from "lucide-react"; } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { SSHHost } from "./host-types"; import type { SSHHost } from "./host-types";
import { import {
getRecentFiles, getRecentFiles,
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { RotateCcw, Trash2 } from "lucide-react"; import { RotateCcw, Trash2 } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
Button, Button,
@@ -13,7 +13,7 @@ import {
Trash2, Trash2,
Upload, Upload,
} from "lucide-react"; } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Kbd, KbdKey, KbdSeparator } from "@termix/plugin-sdk/ui"; import { Kbd, KbdKey, KbdSeparator } from "@termix/plugin-sdk/ui";
import type { LocalFileEntry } from "@termix/plugin-sdk/frontend"; import type { LocalFileEntry } from "@termix/plugin-sdk/frontend";
@@ -6,7 +6,7 @@ import React, {
useState, useState,
} from "react"; } from "react";
import { useVirtualizer } from "@tanstack/react-virtual"; import { useVirtualizer } from "@tanstack/react-virtual";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
ArrowDown, ArrowDown,
@@ -9,8 +9,8 @@ import {
type MouseEvent, type MouseEvent,
} from "react"; } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { TFunction } from "i18next"; import type { TranslateFn as TFunction } from "@termix/plugin-sdk/frontend";
import { import {
ArrowLeftRight, ArrowLeftRight,
File as FileIcon, File as FileIcon,
@@ -10,7 +10,7 @@ import {
PasswordInput, PasswordInput,
} from "@termix/plugin-sdk/ui"; } from "@termix/plugin-sdk/ui";
import { Shield } from "lucide-react"; import { Shield } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
interface SudoPasswordDialogProps { interface SudoPasswordDialogProps {
open: boolean; open: boolean;
@@ -1,5 +1,5 @@
import type { TranslateFn } from "@termix/plugin-sdk/frontend"; import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import type { TFunction } from "i18next"; import type { TranslateFn as TFunction } from "@termix/plugin-sdk/frontend";
import { getTransferStatus, listActiveTransfers } from "./api/transfer-api"; import { getTransferStatus, listActiveTransfers } from "./api/transfer-api";
import { createFormatTransferMetrics } from "./transferMetricsFormat.ts"; import { createFormatTransferMetrics } from "./transferMetricsFormat.ts";
import { import {
@@ -3,7 +3,7 @@ import AudioPlayer from "react-h5-audio-player";
import "react-h5-audio-player/lib/styles.css"; import "react-h5-audio-player/lib/styles.css";
import { Music } from "lucide-react"; import { Music } from "lucide-react";
import { cn } from "@termix/plugin-sdk/ui"; import { cn } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
interface FileItem { interface FileItem {
name: string; name: string;
@@ -1,6 +1,6 @@
import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Check } from "lucide-react"; import { Check } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { cn } from "@termix/plugin-sdk/ui"; import { cn } from "@termix/plugin-sdk/ui";
import type { ResizableColumns } from "../hooks/useResizableColumns.ts"; import type { ResizableColumns } from "../hooks/useResizableColumns.ts";
@@ -11,7 +11,7 @@ import {
Label, Label,
} from "@termix/plugin-sdk/ui"; } from "@termix/plugin-sdk/ui";
import { Package } from "lucide-react"; import { Package } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
interface CompressDialogProps { interface CompressDialogProps {
open: boolean; open: boolean;
@@ -4,7 +4,7 @@ import { DiffEditor } from "@monaco-editor/react";
import { Button } from "@termix/plugin-sdk/ui"; import { Button } from "@termix/plugin-sdk/ui";
import { toast } from "sonner"; import { toast } from "sonner";
import { DownloadProgressToast } from "./DownloadProgressToast"; import { DownloadProgressToast } from "./DownloadProgressToast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { import {
Download, Download,
RefreshCw, RefreshCw,
@@ -2,7 +2,7 @@ import React from "react";
import { DraggableWindow } from "./DraggableWindow.tsx"; import { DraggableWindow } from "./DraggableWindow.tsx";
import { DiffViewer } from "./DiffViewer.tsx"; import { DiffViewer } from "./DiffViewer.tsx";
import { useWindowManager } from "./WindowManager.tsx"; import { useWindowManager } from "./WindowManager.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { FileItem, SSHHost } from "../host-types"; import type { FileItem, SSHHost } from "../host-types";
interface DiffWindowProps { interface DiffWindowProps {
@@ -1,5 +1,5 @@
import { formatTransferMbPerSec } from "../api/transfer-api"; import { formatTransferMbPerSec } from "../api/transfer-api";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { ArrowDownToLine } from "lucide-react"; import { ArrowDownToLine } from "lucide-react";
import { TransferProgressBar } from "./TransferProgressBar"; import { TransferProgressBar } from "./TransferProgressBar";
@@ -1,7 +1,7 @@
import React, { useState, useRef, useCallback, useEffect } from "react"; import React, { useState, useRef, useCallback, useEffect } from "react";
import { cn } from "@termix/plugin-sdk/ui"; import { cn } from "@termix/plugin-sdk/ui";
import { Minus, X, Maximize2, Minimize2 } from "lucide-react"; import { Minus, X, Maximize2, Minimize2 } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
interface DraggableWindowProps { interface DraggableWindowProps {
title: string; title: string;
@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import React, { Suspense, lazy, useState, useEffect, useRef } from "react"; import React, { Suspense, lazy, useState, useEffect, useRef } from "react";
import { cn } from "@termix/plugin-sdk/ui"; import { cn } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { import {
FileText, FileText,
Image as ImageIcon, Image as ImageIcon,
@@ -15,7 +15,7 @@ import { SudoPasswordDialog } from "../SudoPasswordDialog.tsx";
import { toast } from "sonner"; import { toast } from "sonner";
import { DownloadProgressToast } from "./DownloadProgressToast"; import { DownloadProgressToast } from "./DownloadProgressToast";
import type { SSHHost } from "../host-types"; import type { SSHHost } from "../host-types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
interface FileItem { interface FileItem {
name: string; name: string;
@@ -3,7 +3,7 @@ import { PhotoProvider, PhotoView } from "react-photo-view";
import "react-photo-view/dist/react-photo-view.css"; import "react-photo-view/dist/react-photo-view.css";
import { AlertCircle, Download } from "lucide-react"; import { AlertCircle, Download } from "lucide-react";
import { Button } from "@termix/plugin-sdk/ui"; import { Button } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
interface ImagePreviewProps { interface ImagePreviewProps {
content: string; content: string;
@@ -1,5 +1,5 @@
import { Button } from "@termix/plugin-sdk/ui"; import { Button } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { formatFileSize } from "../file-manager-utils.ts"; import { formatFileSize } from "../file-manager-utils.ts";
export interface LocalTransferBatchStatus { export interface LocalTransferBatchStatus {
@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf"; import { Document, Page, pdfjs } from "react-pdf";
import { AlertCircle, Download } from "lucide-react"; import { AlertCircle, Download } from "lucide-react";
import { Button } from "@termix/plugin-sdk/ui"; import { Button } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import pdfjsWorkerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url"; import pdfjsWorkerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorkerUrl; pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorkerUrl;
@@ -9,7 +9,7 @@ import {
Button, Button,
Input, Input,
} from "@termix/plugin-sdk/ui"; } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Lock } from "lucide-react"; import { Lock } from "lucide-react";
interface FileItem { interface FileItem {
@@ -3,7 +3,7 @@ import { DraggableWindow } from "./DraggableWindow.tsx";
import type { TabHandle as TerminalHandle } from "@termix/plugin-sdk/frontend"; import type { TabHandle as TerminalHandle } from "@termix/plugin-sdk/frontend";
import { PluginComponent } from "@termix/plugin-sdk/ui"; import { PluginComponent } from "@termix/plugin-sdk/ui";
import { useWindowManager } from "./WindowManager.tsx"; import { useWindowManager } from "./WindowManager.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { SSHHost } from "../host-types"; import type { SSHHost } from "../host-types";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
@@ -4,7 +4,7 @@ import {
getTransferProgressPercent, getTransferProgressPercent,
type TransferProgressResponse, type TransferProgressResponse,
} from "../api/transfer-api"; } from "../api/transfer-api";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { import {
Archive, Archive,
ArchiveRestore, ArchiveRestore,
@@ -18,7 +18,7 @@ import {
Label, Label,
Select2, Select2,
} from "@termix/plugin-sdk/ui"; } from "@termix/plugin-sdk/ui";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
ArrowRightLeft, ArrowRightLeft,
@@ -1,5 +1,5 @@
import { FolderSearch } from "lucide-react"; import { FolderSearch } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import { useHost } from "@termix/plugin-sdk/frontend"; import { useHost } from "@termix/plugin-sdk/frontend";
import { WidgetTitle } from "@termix/plugin-sdk/ui"; import { WidgetTitle } from "@termix/plugin-sdk/ui";
import type { import type {
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { createElement } from "react"; import { createElement } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useTranslation } from "react-i18next"; import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { FileItem } from "../host-types"; import type { FileItem } from "../host-types";
import type { LocalWalkResult } from "@termix/plugin-sdk/frontend"; import type { LocalWalkResult } from "@termix/plugin-sdk/frontend";
import { useSettings } from "@termix/plugin-sdk/frontend"; import { useSettings } from "@termix/plugin-sdk/frontend";
@@ -1,4 +1,4 @@
import type { TFunction } from "i18next"; import type { TranslateFn as TFunction } from "@termix/plugin-sdk/frontend";
import { import {
formatDurationMs, formatDurationMs,
formatTransferMbPerSec, formatTransferMbPerSec,
@@ -1,6 +1,6 @@
import { getErrorMessage } from "./lib/error-message"; import { getErrorMessage } from "./lib/error-message";
import { toast } from "sonner"; import { toast } from "sonner";
import type { TFunction } from "i18next"; import type { TranslateFn as TFunction } from "@termix/plugin-sdk/frontend";
import { import {
pollTransferUntilComplete, pollTransferUntilComplete,
cancelTransferToHost, cancelTransferToHost,
@@ -4,9 +4,9 @@ import "@testing-library/jest-dom/vitest";
import { FileManagerGrid } from "../../src/frontend/FileManagerGrid"; import { FileManagerGrid } from "../../src/frontend/FileManagerGrid";
import type { FileItem } from "../../src/frontend/host-types"; import type { FileItem } from "../../src/frontend/host-types";
vi.mock("react-i18next", () => ({ vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({
...(await importOriginal<typeof import("@termix/plugin-sdk/frontend")>()),
useTranslation: () => ({ t: (key: string) => key }), useTranslation: () => ({ t: (key: string) => key }),
initReactI18next: { type: "3rdParty", init: () => {} },
})); }));
vi.mock("@tanstack/react-virtual", () => ({ vi.mock("@tanstack/react-virtual", () => ({
@@ -35,12 +35,12 @@ const sonnerToast = vi.hoisted(() => {
}); });
vi.mock("sonner", () => ({ toast: sonnerToast })); vi.mock("sonner", () => ({ toast: sonnerToast }));
vi.mock("react-i18next", () => ({ vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({
...(await importOriginal<typeof import("@termix/plugin-sdk/frontend")>()),
useTranslation: () => ({ useTranslation: () => ({
t: (key: string, opts?: Record<string, unknown>) => t: (key: string, opts?: Record<string, unknown>) =>
opts?.count !== undefined ? `${key}:${opts.count}` : key, opts?.count !== undefined ? `${key}:${opts.count}` : key,
}), }),
initReactI18next: { type: "3rdParty", init: () => {} },
})); }));
const HOME = "/Users/max"; const HOME = "/Users/max";
@@ -47,13 +47,9 @@ vi.mock("../../src/frontend/transferMetricsFormat", () => ({
createFormatTransferMetrics: () => () => "", createFormatTransferMetrics: () => () => "",
})); }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (key: string) => key }),
initReactI18next: { type: "3rdParty", init: () => {} },
}));
vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({ vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({
...(await importOriginal<typeof import("@termix/plugin-sdk/frontend")>()), ...(await importOriginal<typeof import("@termix/plugin-sdk/frontend")>()),
useTranslation: () => ({ t: (key: string) => key, language: "en" }),
useSettings: () => ({ useSettings: () => ({
values: { confirmBeforeTrash: api.confirmBeforeTrash }, values: { confirmBeforeTrash: api.confirmBeforeTrash },
}), }),
@@ -26,12 +26,12 @@ vi.mock("../../../src/frontend/api/ssh-file-operations-api", () => api);
vi.mock("sonner", () => ({ vi.mock("sonner", () => ({
toast: { loading: vi.fn(), success: vi.fn(), error: vi.fn() }, toast: { loading: vi.fn(), success: vi.fn(), error: vi.fn() },
})); }));
vi.mock("react-i18next", () => ({ vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({
...(await importOriginal<typeof import("@termix/plugin-sdk/frontend")>()),
useTranslation: () => ({ useTranslation: () => ({
t: (key: string, params?: { name?: string }) => t: (key: string, params?: { name?: string }) =>
params?.name ? `${key}: ${params.name}` : key, params?.name ? `${key}: ${params.name}` : key,
}), }),
initReactI18next: { type: "3rdParty", init: () => {} },
})); }));
vi.mock("../../../src/frontend/components/WindowManager", () => ({ vi.mock("../../../src/frontend/components/WindowManager", () => ({
useWindowManager: () => ({ useWindowManager: () => ({
+1
View File
@@ -121,6 +121,7 @@
"linkInvalid": "This guest link is invalid or the meeting has ended" "linkInvalid": "This guest link is invalid or the meeting has ended"
}, },
"reopenFromPanel": "This meeting tab expired. Reopen the room from the Meetings panel.", "reopenFromPanel": "This meeting tab expired. Reopen the room from the Meetings panel.",
"retry": "Retry",
"requestControl": "Request control", "requestControl": "Request control",
"cancelControlRequest": "Cancel request", "cancelControlRequest": "Cancel request",
"controlQueue": "Control requests", "controlQueue": "Control requests",
@@ -533,7 +533,7 @@ export function CollabRoomTab({
<AlertCircle className="size-8" /> <AlertCircle className="size-8" />
<p className="max-w-sm text-center text-sm">{loadError}</p> <p className="max-w-sm text-center text-sm">{loadError}</p>
<Button variant="outline" onClick={() => void refresh()}> <Button variant="outline" onClick={() => void refresh()}>
{t("common.retry")} {t("collab.retry")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -562,7 +562,7 @@ export function CollabRoomTab({
<AlertCircle className="size-4 shrink-0 text-destructive" /> <AlertCircle className="size-4 shrink-0 text-destructive" />
<span className="flex-1 truncate">{loadError}</span> <span className="flex-1 truncate">{loadError}</span>
<Button size="sm" variant="outline" onClick={() => void refresh()}> <Button size="sm" variant="outline" onClick={() => void refresh()}>
{t("common.retry")} {t("collab.retry")}
</Button> </Button>
</div> </div>
)} )}