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