mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
Complete Chinese localization for remaining UI components
- Add comprehensive Chinese translations for Host Manager component - Translate all form labels, buttons, and descriptions - Add translations for SSH configuration warnings and instructions - Localize tunnel connection settings and port forwarding options - Localize SSH Tools panel - Translate key recording functionality - Add translations for settings and configuration options - Translate homepage welcome messages and navigation elements - Add Chinese translations for login success messages - Localize "Updates & Releases" section title - Translate sidebar "Host Manager" button - Fix translation key display issues - Remove duplicate translation keys in both language files - Ensure all components properly reference translation keys - Fix hosts.tunnelConnections key mapping This completes the full Chinese localization of the Termix application, achieving near 100% UI translation coverage while maintaining English as the default language.
This commit is contained in:
@@ -701,7 +701,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name="enableTerminal"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enable Terminal</FormLabel>
|
||||
<FormLabel>{t('hosts.enableTerminal')}</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@@ -709,7 +709,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Enable/disable host visibility in Terminal tab.
|
||||
{t('hosts.enableTerminalDesc')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -721,7 +721,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name="enableTunnel"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enable Tunnel</FormLabel>
|
||||
<FormLabel>{t('hosts.enableTunnel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@@ -729,7 +729,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Enable/disable host visibility in Tunnel tab.
|
||||
{t('hosts.enableTunnelDesc')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -739,44 +739,27 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
<>
|
||||
<Alert className="mt-4">
|
||||
<AlertDescription>
|
||||
<strong>Sshpass Required For Password Authentication</strong>
|
||||
<strong>{t('hosts.sshpassRequired')}</strong>
|
||||
<div>
|
||||
For password-based SSH authentication, sshpass must be installed on
|
||||
both the local and remote servers. Install with: <code
|
||||
className="bg-muted px-1 rounded inline">sudo apt install
|
||||
sshpass</code> (Debian/Ubuntu) or the equivalent for your OS.
|
||||
{t('hosts.sshpassInstallCommand')}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<strong>Other installation methods:</strong>
|
||||
<div>• CentOS/RHEL/Fedora: <code
|
||||
className="bg-muted px-1 rounded inline">sudo yum install
|
||||
sshpass</code> or <code
|
||||
className="bg-muted px-1 rounded inline">sudo dnf install
|
||||
sshpass</code></div>
|
||||
<div>• macOS: <code className="bg-muted px-1 rounded inline">brew
|
||||
install hudochenkov/sshpass/sshpass</code></div>
|
||||
<div>• Windows: Use WSL or consider SSH key authentication</div>
|
||||
<strong>{t('hosts.otherInstallMethods')}</strong>
|
||||
<div>• {t('hosts.sshpassOSInstructions.centos')}</div>
|
||||
<div>• {t('hosts.sshpassOSInstructions.macos')}</div>
|
||||
<div>• {t('hosts.sshpassOSInstructions.windows')}</div>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Alert className="mt-4">
|
||||
<AlertDescription>
|
||||
<strong>SSH Server Configuration Required</strong>
|
||||
<div>For reverse SSH tunnels, the endpoint SSH server must allow:</div>
|
||||
<div>• <code className="bg-muted px-1 rounded inline">GatewayPorts
|
||||
yes</code> (bind remote ports)
|
||||
</div>
|
||||
<div>• <code className="bg-muted px-1 rounded inline">AllowTcpForwarding
|
||||
yes</code> (port forwarding)
|
||||
</div>
|
||||
<div>• <code className="bg-muted px-1 rounded inline">PermitRootLogin
|
||||
yes</code> (if using root)
|
||||
</div>
|
||||
<div className="mt-2">Edit <code
|
||||
className="bg-muted px-1 rounded inline">/etc/ssh/sshd_config</code> and
|
||||
restart SSH: <code className="bg-muted px-1 rounded inline">sudo
|
||||
systemctl restart sshd</code></div>
|
||||
<strong>{t('hosts.sshServerConfig')}</strong>
|
||||
<div>{t('hosts.sshServerConfigReverse')}</div>
|
||||
<div>• <code className="bg-muted px-1 rounded inline">{t('hosts.gatewayPorts')}</code></div>
|
||||
<div>• <code className="bg-muted px-1 rounded inline">{t('hosts.allowTcpForwarding')}</code></div>
|
||||
<div>• <code className="bg-muted px-1 rounded inline">{t('hosts.permitRootLogin')}</code></div>
|
||||
<div className="mt-2">{t('hosts.editSshConfig')}</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -793,7 +776,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
className="p-4 border rounded-lg bg-muted/50">
|
||||
<div
|
||||
className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-bold">Connection {index + 1}</h4>
|
||||
<h4 className="text-sm font-bold">{t('hosts.connection')} {index + 1}</h4>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -803,7 +786,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
field.onChange(newConnections);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
{t('hosts.remove')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
@@ -812,7 +795,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name={`tunnelConnections.${index}.sourcePort`}
|
||||
render={({field: sourcePortField}) => (
|
||||
<FormItem className="col-span-4">
|
||||
<FormLabel>Source Port
|
||||
<FormLabel>{t('hosts.sourcePort')}
|
||||
(Source refers to the Current
|
||||
Connection Details in the
|
||||
General tab)</FormLabel>
|
||||
@@ -828,7 +811,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name={`tunnelConnections.${index}.endpointPort`}
|
||||
render={({field: endpointPortField}) => (
|
||||
<FormItem className="col-span-4">
|
||||
<FormLabel>Endpoint Port
|
||||
<FormLabel>{t('hosts.endpointPort')}
|
||||
(Remote)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -911,7 +894,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name={`tunnelConnections.${index}.maxRetries`}
|
||||
render={({field: maxRetriesField}) => (
|
||||
<FormItem className="col-span-4">
|
||||
<FormLabel>Max Retries</FormLabel>
|
||||
<FormLabel>{t('hosts.maxRetries', 'Max Retries')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="3" {...maxRetriesField} />
|
||||
@@ -928,8 +911,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name={`tunnelConnections.${index}.retryInterval`}
|
||||
render={({field: retryIntervalField}) => (
|
||||
<FormItem className="col-span-4">
|
||||
<FormLabel>Retry Interval
|
||||
(seconds)</FormLabel>
|
||||
<FormLabel>{t('hosts.retryInterval')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="10" {...retryIntervalField} />
|
||||
@@ -955,8 +937,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Automatically start this tunnel
|
||||
when the container launches.
|
||||
{t('hosts.autoStartDesc')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -978,7 +959,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
}]);
|
||||
}}
|
||||
>
|
||||
Add Tunnel Connection
|
||||
{t('hosts.addConnection')}
|
||||
</Button>
|
||||
</div>
|
||||
</FormControl>
|
||||
@@ -996,7 +977,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name="enableFileManager"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enable File Manager</FormLabel>
|
||||
<FormLabel>{t('hosts.enableFileManager')}</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@@ -1004,7 +985,7 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Enable/disable host visibility in File Manager tab.
|
||||
{t('hosts.enableFileManagerDesc')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -1017,12 +998,11 @@ export function HostManagerHostEditor({editingHost, onFormSubmit}: SSHManagerHos
|
||||
name="defaultPath"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Default Path</FormLabel>
|
||||
<FormLabel>{t('hosts.defaultPath')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('placeholders.homePath')} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Set default directory shown when connected via
|
||||
File Manager</FormDescription>
|
||||
<FormDescription>{t('hosts.defaultPathDesc')}</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {HomepageUpdateLog} from "@/ui/Homepage/HompageUpdateLog.tsx";
|
||||
import {HomepageAlertManager} from "@/ui/Homepage/HomepageAlertManager.tsx";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
import { getUserInfo, getDatabaseHealth } from "@/ui/main-axios.ts";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
interface HomepageProps {
|
||||
onSelectView: (view: string) => void;
|
||||
@@ -32,6 +33,7 @@ export function Homepage({
|
||||
onAuthSuccess,
|
||||
isTopbarOpen = true
|
||||
}: HomepageProps): React.ReactElement {
|
||||
const {t} = useTranslation();
|
||||
const [loggedIn, setLoggedIn] = useState(isAuthenticated);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [username, setUsername] = useState<string | null>(null);
|
||||
@@ -95,11 +97,9 @@ export function Homepage({
|
||||
<div className="flex flex-col items-center gap-6 w-[400px]">
|
||||
<div
|
||||
className="text-center bg-[#18181b] border-2 border-[#303032] rounded-lg p-6 w-full shadow-lg">
|
||||
<h3 className="text-xl font-bold mb-3 text-white">Logged in!</h3>
|
||||
<h3 className="text-xl font-bold mb-3 text-white">{t('homepage.loggedInTitle')}</h3>
|
||||
<p className="text-gray-300 leading-relaxed">
|
||||
You are logged in! Use the sidebar to access all available tools. To get started,
|
||||
create an SSH Host in the SSH Manager tab. Once created, you can connect to that
|
||||
host using the other apps in the sidebar.
|
||||
{t('homepage.loggedInMessage')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert.tsx";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
import {Separator} from "@/components/ui/separator.tsx";
|
||||
import { getReleasesRSS, getVersionInfo } from "@/ui/main-axios.ts";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
interface HomepageUpdateLogProps extends React.ComponentProps<"div"> {
|
||||
loggedIn: boolean;
|
||||
@@ -51,6 +52,7 @@ interface VersionResponse {
|
||||
}
|
||||
|
||||
export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
|
||||
const {t} = useTranslation();
|
||||
const [releases, setReleases] = useState<RSSResponse | null>(null);
|
||||
const [versionInfo, setVersionInfo] = useState<VersionResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -90,7 +92,7 @@ export function HomepageUpdateLog({loggedIn}: HomepageUpdateLogProps) {
|
||||
return (
|
||||
<div className="w-[400px] h-[600px] flex flex-col border-2 border-[#303032] rounded-lg bg-[#18181b] p-4 shadow-lg">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-3 text-white">Updates & Releases</h3>
|
||||
<h3 className="text-lg font-bold mb-3 text-white">{t('common.updatesAndReleases')}</h3>
|
||||
|
||||
<Separator className="p-0.25 mt-3 mb-3 bg-[#303032]"/>
|
||||
|
||||
|
||||
@@ -433,9 +433,9 @@ export function LeftSidebar({
|
||||
<SidebarGroup className="!m-0 !p-0 !-mb-2">
|
||||
<Button className="m-2 flex flex-row font-semibold border-2 !border-[#303032]" variant="outline"
|
||||
onClick={openSshManagerTab} disabled={!!sshManagerTab || isSplitScreenActive}
|
||||
title={sshManagerTab ? 'SSH Manager already open' : isSplitScreenActive ? 'Disabled during split screen' : undefined}>
|
||||
title={sshManagerTab ? t('interface.sshManagerAlreadyOpen') : isSplitScreenActive ? t('interface.disabledDuringSplitScreen') : undefined}>
|
||||
<HardDrive strokeWidth="2.5"/>
|
||||
Host Manager
|
||||
{t('nav.hostManager')}
|
||||
</Button>
|
||||
</SidebarGroup>
|
||||
<Separator className="p-0.25"/>
|
||||
|
||||
@@ -330,13 +330,13 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b border-[#303032]">
|
||||
<h2 className="text-lg font-semibold text-white">SSH Tools</h2>
|
||||
<h2 className="text-lg font-semibold text-white">{t('sshTools.title')}</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setToolsSheetOpen(false)}
|
||||
className="h-8 w-8 p-0 hover:bg-red-500 hover:text-white transition-colors flex items-center justify-center"
|
||||
title="Close SSH Tools"
|
||||
title={t('sshTools.closeTools')}
|
||||
>
|
||||
<span className="text-lg font-bold leading-none">×</span>
|
||||
</Button>
|
||||
@@ -345,7 +345,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<div className="space-y-4">
|
||||
<h1 className="font-semibold">
|
||||
Key Recording
|
||||
{t('sshTools.keyRecording')}
|
||||
</h1>
|
||||
|
||||
<div className="space-y-4">
|
||||
@@ -357,7 +357,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
>
|
||||
Start Key Recording
|
||||
{t('sshTools.startKeyRecording')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -365,7 +365,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
className="flex-1"
|
||||
variant="destructive"
|
||||
>
|
||||
Stop Key Recording
|
||||
{t('sshTools.stopKeyRecording')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -373,8 +373,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
{isRecording && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Select
|
||||
terminals:</label>
|
||||
<label className="text-sm font-medium text-white">{t('sshTools.selectTerminals')}</label>
|
||||
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto mt-2">
|
||||
{terminalTabs.map(tab => (
|
||||
<Button
|
||||
@@ -396,8 +395,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">Type commands (all
|
||||
keys supported):</label>
|
||||
<label className="text-sm font-medium text-white">{t('sshTools.typeCommands')}</label>
|
||||
<Input
|
||||
id="ssh-tools-input"
|
||||
placeholder={t('placeholders.typeHere')}
|
||||
@@ -408,8 +406,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
readOnly
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Commands will be sent to {selectedTabIds.length} selected
|
||||
terminal(s).
|
||||
{t('sshTools.commandsWillBeSent', { count: selectedTabIds.length })}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
@@ -420,7 +417,7 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
<Separator className="my-4"/>
|
||||
|
||||
<h1 className="font-semibold">
|
||||
Settings
|
||||
{t('sshTools.settings')}
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
@@ -433,14 +430,14 @@ export function TopNavbar({isTopbarOpen, setIsTopbarOpen}: TopNavbarProps): Reac
|
||||
htmlFor="enable-copy-paste"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white"
|
||||
>
|
||||
Enable right‑click copy/paste
|
||||
{t('sshTools.enableRightClickCopyPaste')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Separator className="my-4"/>
|
||||
|
||||
<p className="pt-2 pb-2 text-sm text-gray-500">
|
||||
Have ideas for what should come next for ssh tools? Share them on{" "}
|
||||
{t('sshTools.shareIdeas')}{" "}
|
||||
<a
|
||||
href="https://github.com/LukeGus/Termix/issues/new"
|
||||
target="_blank"
|
||||
|
||||
Reference in New Issue
Block a user