fix: translate leftover hardcoded ui text

This commit is contained in:
LukeGus committed 2026-09-30 14:39:28 -05:00
1 parent 1b342322dc
commit 20f19ff13b
26 files changed
+80 -41

No files matched your search

+5 -1
View File
@@ -379,7 +379,11 @@
"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}}"
"dragFailedDetail": "Drag failed: {{error}}",
"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."
},
"sftpTransfer": {
"title": "SFTP",
@@ -790,10 +790,7 @@ export function FileViewer({
<source src={videoUrl} type={mimeType} />
<div className="text-center text-muted-foreground p-4">
<AlertCircle className="w-8 h-8 mx-auto mb-2" />
<p>
Your browser does not support video playback for this
format.
</p>
<p>{t("fileManager.videoUnsupported")}</p>
{onDownload && (
<Button
variant="outline"
@@ -801,7 +798,7 @@ export function FileViewer({
className="mt-2 flex items-center gap-2 mx-auto"
>
<Download className="w-4 h-4" />
Download to play externally
{t("fileManager.downloadToPlay")}
</Button>
)}
</div>
@@ -918,11 +915,10 @@ export function FileViewer({
<div className="text-center text-muted-foreground">
<AlertCircle className="w-16 h-16 mx-auto mb-4 text-muted-foreground/50" />
<h3 className="text-lg font-medium mb-2">
Cannot preview this file type
{t("fileManager.cannotPreviewType")}
</h3>
<p className="text-sm mb-4">
This file type is not supported for preview. You can download
it to view in an external application.
{t("fileManager.cannotPreviewTypeHint")}
</p>
{onDownload && (
<Button
+9 -1
View File
@@ -161,7 +161,15 @@
"compactMode": "Compact Mode",
"selectHosts": "Select Hosts",
"listLayout": "List",
"gridLayout": "Grid"
"gridLayout": "Grid",
"configureUrl": "Set a URL in the widget settings",
"embeddedContent": "Embedded content",
"noBookmarks": "No bookmarks",
"noSections": "No sections set up",
"noServices": "No services set up",
"configureLocation": "Set a location in the widget settings",
"bannerTextPlaceholder": "Banner text",
"serviceNamePlaceholder": "My service"
},
"permissions": {
"use": {
@@ -39,7 +39,7 @@ export function ServiceLinkEditForm({
<Input
value={config.description ?? ""}
onChange={(e) => onChange({ ...config, description: e.target.value })}
placeholder="My service"
placeholder={t("homepage.serviceNamePlaceholder")}
className="h-8 text-sm"
/>
</div>
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Bookmark, ExternalLink } from "lucide-react";
import { registerWidget } from "./WidgetRegistry";
import type { BookmarkListConfig, WidgetComponentProps } from "../types.js";
@@ -8,6 +9,7 @@ function BookmarkListWidget({
widget,
isReadOnly,
}: WidgetComponentProps<BookmarkListConfig>) {
const { t } = useTranslation();
const { links } = config;
return (
@@ -23,7 +25,7 @@ function BookmarkListWidget({
<div className="flex-1 overflow-auto p-3 gap-1 flex flex-col">
{links.length === 0 && (
<span className="text-xs text-muted-foreground italic">
No bookmarks
{t("homepage.noBookmarks")}
</span>
)}
{links.map((link, i) =>
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Globe } from "lucide-react";
import { registerWidget } from "./WidgetRegistry";
import type { IframeConfig, WidgetComponentProps } from "../types.js";
@@ -9,13 +10,14 @@ function IframeWidget({
config,
isReadOnly,
}: WidgetComponentProps<IframeConfig>) {
const { t } = useTranslation();
const { url, scrolling } = config;
if (!url) {
return (
<div className="flex flex-col items-center justify-center w-full h-full gap-2 text-muted-foreground/50">
<Globe size={24} />
<span className="text-xs">Configure a URL in widget settings</span>
<span className="text-xs">{t("homepage.configureUrl")}</span>
</div>
);
}
@@ -29,7 +31,7 @@ function IframeWidget({
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
scrolling={scrolling ? "yes" : "no"}
className="w-full h-full border-0"
title="Embedded content"
title={t("homepage.embeddedContent")}
/>
{/* Block interaction in read-only mode so the canvas can still pan */}
{isReadOnly && <div className="absolute inset-0 pointer-events-none" />}
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { useRef } from "react";
import { List, ExternalLink } from "lucide-react";
import { registerWidget } from "./WidgetRegistry";
@@ -72,12 +73,13 @@ function LinkTreeWidget({
config,
isReadOnly,
}: WidgetComponentProps<LinkTreeConfig>) {
const { t } = useTranslation();
const { sections, compact } = config;
if (sections.length === 0) {
return (
<div className="flex items-center justify-center w-full h-full text-xs text-muted-foreground">
No sections configured
{t("homepage.noSections")}
</div>
);
}
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { useRef, useState, useEffect } from "react";
import { Grid3x3, ExternalLink } from "lucide-react";
import { registerWidget } from "./WidgetRegistry";
@@ -107,12 +108,13 @@ function ServiceGridWidget({
config,
isReadOnly,
}: WidgetComponentProps<ServiceGridConfig>) {
const { t } = useTranslation();
const { services, columns, showLabels, iconSize } = config;
if (services.length === 0) {
return (
<div className="flex items-center justify-center w-full h-full text-xs text-muted-foreground">
No services configured
{t("homepage.noServices")}
</div>
);
}
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Type } from "lucide-react";
import { registerWidget } from "./WidgetRegistry";
import type { TextBannerConfig, WidgetComponentProps } from "../types.js";
@@ -23,6 +24,7 @@ const TEXT_ALIGN_MAP = {
};
function TextBannerWidget({ config }: WidgetComponentProps<TextBannerConfig>) {
const { t } = useTranslation();
const { text, fontSize, textAlign, fontWeight, backgroundColor } = config;
return (
@@ -31,7 +33,7 @@ function TextBannerWidget({ config }: WidgetComponentProps<TextBannerConfig>) {
style={backgroundColor ? { background: backgroundColor } : undefined}
>
<span className="whitespace-pre-wrap break-words w-full text-foreground leading-tight">
{text || "Banner text"}
{text || t("homepage.bannerTextPlaceholder")}
</span>
</div>
);
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { useEffect, useState } from "react";
import {
Cloud,
@@ -54,6 +55,7 @@ function WeatherWidget({
widget,
config,
}: WidgetComponentProps<WeatherConfig>) {
const { t } = useTranslation();
const { location, unit, showForecast } = config;
const [data, setData] = useState<WttrResponse | null>(null);
const [error, setError] = useState(false);
@@ -90,7 +92,7 @@ function WeatherWidget({
if (!location) {
return (
<div className="flex items-center justify-center w-full h-full text-xs text-muted-foreground/60 p-3 text-center">
Configure a location in widget settings
{t("homepage.configureLocation")}
</div>
);
}
+2 -1
View File
@@ -177,7 +177,8 @@
"wgAllowedIPs": "Allowed IPs",
"wgLastHandshake": "Last handshake",
"wgHandshakeNever": "Never",
"wgTransfer": "Transfer"
"wgTransfer": "Transfer",
"wgNoPeers": "No peers"
}
},
"metricsHistory": {
@@ -162,7 +162,7 @@ export function WireGuardManagerCard({ hostId }: { hostId: number | null }) {
<div className="mb-1 ml-5 flex flex-col border-l border-border/50">
{iface.peers.length === 0 ? (
<span className="px-3 py-1 text-[10px] text-muted-foreground/50">
No peers
{t("hostMetrics.managers.wgNoPeers")}
</span>
) : (
iface.peers.map((peer) => (
+2 -1
View File
@@ -208,7 +208,8 @@
"colorScheme": "Color Scheme",
"backspaceKey": "Backspace Key",
"colorDepth": "Color Depth",
"resizeMethod": "Resize Method"
"resizeMethod": "Resize Method",
"driveNamePlaceholder": "Termix Drive"
},
"remoteDesktop": "Remote Desktop",
"tabRdp": "RDP",
@@ -523,7 +523,7 @@ export function HostEditorRdpTab({
{t("hosts.guac.driveName")}
</label>
<Input
placeholder="Termix Drive"
placeholder={t("hosts.guac.driveNamePlaceholder")}
value={form.guacamoleConfig["drive-name"] ?? ""}
onChange={(e) => setGuacField("drive-name", e.target.value)}
/>
@@ -45,5 +45,9 @@
"loadError": "Failed to load session logs",
"deleteError": "Failed to delete session log",
"filterByHost": "Filter by host..."
},
"player": {
"timeline": "Recording timeline",
"speed": "Playback speed"
}
}
@@ -1,3 +1,4 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { useCallback, useEffect, useRef, useState } from "react";
import { Pause, Play } from "lucide-react";
import { Terminal } from "@xterm/xterm";
@@ -36,6 +37,7 @@ function PlaybackControls({
onSeek: (position: number) => void;
onSpeed: (speed: number) => void;
}) {
const { t } = useTranslation();
return (
<div className="flex items-center gap-2 border-t border-border/60 bg-muted/20 px-3 py-2">
<button
@@ -61,7 +63,7 @@ function PlaybackControls({
value={Math.min(position, duration)}
onChange={(event) => onSeek(Number(event.target.value))}
className="min-w-0 flex-1 accent-primary"
aria-label="Recording timeline"
aria-label={t("player.timeline")}
/>
<span className="w-10 text-right text-[10px] tabular-nums text-muted-foreground">
{formatPosition(duration)}
@@ -70,7 +72,7 @@ function PlaybackControls({
value={speed}
onChange={(event) => onSpeed(Number(event.target.value))}
className="h-7 border border-border bg-background px-1 text-[10px]"
aria-label="Playback speed"
aria-label={t("player.speed")}
>
{SPEEDS.map((value) => (
<option key={value} value={value}>
+2 -1
View File
@@ -85,7 +85,8 @@
"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"
"imageClipboardEmpty": "No image found in the clipboard",
"localShell": "Local terminal shell"
},
"terminalToolbar": {
"detachTmux": "Detach tmux",
@@ -185,7 +185,7 @@ export function LocalTerminal({
{isWindows && (
<div className="flex justify-end border-b border-border px-2 py-1">
<select
aria-label="Local terminal shell"
aria-label={t("terminal.localShell")}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground"
value={shell}
onChange={(event) =>
+3 -3
View File
@@ -2874,7 +2874,7 @@ export function AppShell({
variant="ghost"
size="icon"
className="h-full w-12.5 border-y-0 border-border rounded-none text-muted-foreground hover:text-foreground"
title="Reset width"
title={t("nav.resetSidebarWidth")}
onClick={() => setSidebarWidth(291)}
>
<RotateCcw className="size-3.5" />
@@ -2913,7 +2913,7 @@ export function AppShell({
variant="ghost"
size="icon"
className="h-full w-12.5 rounded-none text-muted-foreground hover:text-foreground"
title="Collapse sidebar"
title={t("nav.collapseSidebar")}
onClick={() => {
setSettingsFullscreen(false);
setSidebarOpen(false);
@@ -3005,7 +3005,7 @@ export function AppShell({
{!isMobile && !sidebarOpen && (
<button
onClick={() => setSidebarOpen(true)}
title="Open Sidebar"
title={t("nav.openSidebar")}
className="absolute left-0 top-0 bottom-0 z-20 flex items-center justify-center w-6 bg-sidebar border-r border-border text-muted-foreground hover:text-accent-brand hover:bg-accent-brand/5 transition-colors"
>
<ChevronRight className="size-3.5" />
+1 -1
View File
@@ -299,7 +299,7 @@ export function ElectronLoginForm({
ref={iframeRef}
src={serverUrl}
className="w-full h-full border-0"
title="Server Authentication"
title={t("auth.serverAuthentication")}
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-storage-access-by-user-activation allow-top-navigation allow-top-navigation-by-user-activation allow-modals allow-downloads"
allow="clipboard-read; clipboard-write; publickey-credentials-get; publickey-credentials-create"
/>
+3 -1
View File
@@ -1,3 +1,4 @@
import { useTranslation } from "react-i18next";
import * as React from "react";
import { Check, ChevronDown, Search } from "lucide-react";
@@ -95,6 +96,7 @@ function Select2({
"aria-label": ariaLabel,
...props
}: Select2Props) {
const { t } = useTranslation();
const options = React.useMemo(() => getOptions(children), [children]);
const [open, setOpen] = React.useState(false);
const [query, setQuery] = React.useState("");
@@ -197,7 +199,7 @@ function Select2({
>
{filteredOptions.length === 0 ? (
<div className="px-2 py-6 text-center text-sm text-muted-foreground">
No results found
{t("common.noResults")}
</div>
) : (
filteredOptions.map((option, index) => {
+4 -2
View File
@@ -657,6 +657,7 @@ function CardItem({
statusLoading?: boolean;
isVisible?: boolean;
}) {
const { t } = useTranslation();
const cardRef = useRef<HTMLDivElement | null>(null);
const onResizeMouseDown = useCallback(
@@ -760,7 +761,7 @@ function CardItem({
<div
onMouseDown={onResizeMouseDown}
className="absolute bottom-0 left-0 right-0 h-2 z-20 flex items-center justify-center cursor-row-resize group/resize"
title="Drag to resize"
title={t("cardGrid.dragToResize")}
>
<div className="w-12 h-0.5 bg-border group-hover/resize:bg-accent-brand/60 transition-colors rounded-full" />
</div>
@@ -979,11 +980,12 @@ function ColumnDivider({
}: {
onMouseDown: (e: React.MouseEvent) => void;
}) {
const { t } = useTranslation();
return (
<div
onMouseDown={onMouseDown}
className="w-3 shrink-0 flex items-center justify-center cursor-col-resize group/divider self-stretch z-10"
title="Drag to resize columns"
title={t("dashboardTab.dragToResizeColumns")}
>
<div className="w-px h-full bg-border group-hover/divider:bg-accent-brand/50 transition-colors" />
<div className="absolute size-4 flex items-center justify-center opacity-0 group-hover/divider:opacity-100 transition-opacity">
+10 -4
View File
@@ -185,7 +185,8 @@
"required": "Required",
"remove": "Remove",
"add": "Add",
"update": "Update"
"update": "Update",
"noResults": "No results found"
},
"onboarding": {
"stepCounter": "Step {{current}} of {{total}}",
@@ -301,7 +302,10 @@
"reconnectTerminalsFailed_one": "Could not start reconnecting {{count}} terminal",
"reconnectTerminalsFailed_other": "Could not start reconnecting {{count}} terminals",
"fullscreenUnsupported": "Fullscreen is not supported by this browser",
"fullscreenFailed": "Could not toggle fullscreen"
"fullscreenFailed": "Could not toggle fullscreen",
"resetSidebarWidth": "Reset width",
"collapseSidebar": "Collapse sidebar",
"openSidebar": "Open sidebar"
},
"hosts": {
"hosts": "Hosts",
@@ -1048,7 +1052,8 @@
"confirmResetDataWipe": "This account has not logged in since the encryption upgrade, so its stored data cannot be recovered without the old password. Resetting will permanently delete its hosts, credentials and snippets. Continue?",
"password": "Password",
"secondFactorUnavailable": "Your account uses a second factor that is not available right now. Contact an admin to reset it.",
"secondFactorNoUI": "This second factor cannot be shown on this screen. Contact an admin."
"secondFactorNoUI": "This second factor cannot be shown on this screen. Contact an admin.",
"serverAuthentication": "Server sign-in"
},
"hostKey": {
"verifyNewHost": "Verify SSH Host Key",
@@ -1181,7 +1186,8 @@
"editModeInstructions": "Drag cards to reorder · Drag the column divider to resize columns · Drag the bottom edge of a card to resize its height · Trash to remove",
"empty": "Empty",
"clear": "Clear",
"viewServerDetails": "View server details"
"viewServerDetails": "View server details",
"dragToResizeColumns": "Drag to resize columns"
},
"admin": {
"sectionGeneral": "General",
+1 -1
View File
@@ -570,7 +570,7 @@ export function CommandPalette({
<>
<div className="w-px h-3.5 bg-border/60 mx-0.5 shrink-0" />
<button
title="Edit Host"
title={t("hosts.editHost")}
onClick={(e) => {
e.stopPropagation();
setIsOpen(false);
+1 -1
View File
@@ -94,7 +94,7 @@ export function AdminCreateUserDialog({
<div className="relative">
<Input
type={showNewPassword ? "text" : "password"}
placeholder="Enter password"
placeholder={t("placeholders.enterPassword")}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleCreateUser()}
+2 -2
View File
@@ -17,7 +17,7 @@ describe("ElectronLoginForm", () => {
);
const permissions = screen
.getByTitle("Server Authentication")
.getByTitle("auth.serverAuthentication")
.getAttribute("allow");
expect(permissions).toContain("publickey-credentials-get");
@@ -38,7 +38,7 @@ describe("ElectronLoginForm", () => {
/>,
);
const frame = screen.getByTitle(
"Server Authentication",
"auth.serverAuthentication",
) as HTMLIFrameElement;
window.dispatchEvent(
new MessageEvent("message", {