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:
ZacharyZcR
2025-09-02 21:57:51 +08:00
parent 511e4e7db3
commit ba9fac55ea
7 changed files with 164 additions and 79 deletions
@@ -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 -4
View File
@@ -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 -1
View File
@@ -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]"/>
+2 -2
View File
@@ -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"/>
+11 -14
View File
@@ -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 rightclick 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"