Extend Chinese localization coverage to Host Manager components
- Added comprehensive translations for HostManagerHostViewer component - Localized all host management UI text including import/export features - Translated error messages and confirmation dialogs for host operations - Added translations for HostManagerHostEditor validation messages - Localized connection details, organization settings, and form labels - Fixed syntax error in FileManagerOperations component - Achieved near-complete localization of SSH host management interface - Updated placeholders and tooltips for better user guidance Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import {Input} from "@/components/ui/input";
|
||||
import {Accordion, AccordionContent, AccordionItem, AccordionTrigger} from "@/components/ui/accordion";
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
import {getSSHHosts, deleteSSHHost, bulkImportSSHHosts} from "@/ui/main-axios.ts";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {
|
||||
Edit,
|
||||
Trash2,
|
||||
@@ -47,6 +48,7 @@ interface SSHManagerHostViewerProps {
|
||||
}
|
||||
|
||||
export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
const {t} = useTranslation();
|
||||
const [hosts, setHosts] = useState<SSHHost[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -64,20 +66,20 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
setHosts(data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError('Failed to load hosts');
|
||||
setError(t('hosts.failedToLoadHosts'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (hostId: number, hostName: string) => {
|
||||
if (window.confirm(`Are you sure you want to delete "${hostName}"?`)) {
|
||||
if (window.confirm(t('hosts.confirmDelete', { name: hostName }))) {
|
||||
try {
|
||||
await deleteSSHHost(hostId);
|
||||
await fetchHosts();
|
||||
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
|
||||
} catch (err) {
|
||||
alert('Failed to delete host');
|
||||
alert(t('hosts.failedToDeleteHost'));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -98,32 +100,32 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
const data = JSON.parse(text);
|
||||
|
||||
if (!Array.isArray(data.hosts) && !Array.isArray(data)) {
|
||||
throw new Error('JSON must contain a "hosts" array or be an array of hosts');
|
||||
throw new Error(t('hosts.jsonMustContainHosts'));
|
||||
}
|
||||
|
||||
const hostsArray = Array.isArray(data.hosts) ? data.hosts : data;
|
||||
|
||||
if (hostsArray.length === 0) {
|
||||
throw new Error('No hosts found in JSON file');
|
||||
throw new Error(t('hosts.noHostsInJson'));
|
||||
}
|
||||
|
||||
if (hostsArray.length > 100) {
|
||||
throw new Error('Maximum 100 hosts allowed per import');
|
||||
throw new Error(t('hosts.maxHostsAllowed'));
|
||||
}
|
||||
|
||||
const result = await bulkImportSSHHosts(hostsArray);
|
||||
|
||||
if (result.success > 0) {
|
||||
alert(`Import completed: ${result.success} successful, ${result.failed} failed${result.errors.length > 0 ? '\n\nErrors:\n' + result.errors.join('\n') : ''}`);
|
||||
alert(t('hosts.importCompleted', { success: result.success, failed: result.failed }) + (result.errors.length > 0 ? '\n\nErrors:\n' + result.errors.join('\n') : ''));
|
||||
await fetchHosts();
|
||||
window.dispatchEvent(new CustomEvent('ssh-hosts:changed'));
|
||||
} else {
|
||||
alert(`Import failed: ${result.errors.join('\n')}`);
|
||||
alert(`${t('hosts.importFailed')}: ${result.errors.join('\n')}`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to import JSON file';
|
||||
alert(`Import error: ${errorMessage}`);
|
||||
const errorMessage = err instanceof Error ? err.message : t('hosts.failedToImportJson');
|
||||
alert(`${t('hosts.importError')}: ${errorMessage}`);
|
||||
} finally {
|
||||
setImporting(false);
|
||||
event.target.value = '';
|
||||
@@ -163,7 +165,7 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
const grouped: { [key: string]: SSHHost[] } = {};
|
||||
|
||||
filteredAndSortedHosts.forEach(host => {
|
||||
const folder = host.folder || 'Uncategorized';
|
||||
const folder = host.folder || t('hosts.uncategorized');
|
||||
if (!grouped[folder]) {
|
||||
grouped[folder] = [];
|
||||
}
|
||||
@@ -171,8 +173,9 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
});
|
||||
|
||||
const sortedFolders = Object.keys(grouped).sort((a, b) => {
|
||||
if (a === 'Uncategorized') return -1;
|
||||
if (b === 'Uncategorized') return 1;
|
||||
const uncategorized = t('hosts.uncategorized');
|
||||
if (a === uncategorized) return -1;
|
||||
if (b === uncategorized) return 1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
@@ -189,7 +192,7 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white mx-auto mb-2"></div>
|
||||
<p className="text-muted-foreground">Loading hosts...</p>
|
||||
<p className="text-muted-foreground">{t('hosts.loadingHosts')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -201,7 +204,7 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
<div className="text-center">
|
||||
<p className="text-red-500 mb-4">{error}</p>
|
||||
<Button onClick={fetchHosts} variant="outline">
|
||||
Retry
|
||||
{t('hosts.retry')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -213,9 +216,9 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Server className="h-12 w-12 text-muted-foreground mx-auto mb-4"/>
|
||||
<h3 className="text-lg font-semibold mb-2">No SSH Hosts</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">{t('hosts.noHosts')}</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
You haven't added any SSH hosts yet. Click "Add Host" to get started.
|
||||
{t('hosts.noHostsMessage')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -226,9 +229,9 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
<div className="flex flex-col h-full min-h-0">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">SSH Hosts</h2>
|
||||
<h2 className="text-xl font-semibold">{t('hosts.sshHosts')}</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{filteredAndSortedHosts.length} hosts
|
||||
{t('hosts.hostsCount', { count: filteredAndSortedHosts.length })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -242,15 +245,15 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
onClick={() => document.getElementById('json-import-input')?.click()}
|
||||
disabled={importing}
|
||||
>
|
||||
{importing ? 'Importing...' : 'Import JSON'}
|
||||
{importing ? t('hosts.importing') : t('hosts.importJson')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom"
|
||||
className="max-w-sm bg-popover text-popover-foreground border border-border shadow-lg">
|
||||
<div className="space-y-2">
|
||||
<p className="font-semibold text-sm">Import SSH Hosts from JSON</p>
|
||||
<p className="font-semibold text-sm">{t('hosts.importJsonTitle')}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Upload a JSON file to bulk import multiple SSH hosts (max 100).
|
||||
{t('hosts.importJsonDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
@@ -318,7 +321,7 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
URL.revokeObjectURL(url);
|
||||
}}
|
||||
>
|
||||
Download Sample
|
||||
{t('hosts.downloadSample')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -328,13 +331,13 @@ export function HostManagerHostViewer({onEditHost}: SSHManagerHostViewerProps) {
|
||||
window.open('https://docs.termix.site/json-import', '_blank');
|
||||
}}
|
||||
>
|
||||
Format Guide
|
||||
{t('hosts.formatGuide')}
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-6 bg-border mx-2"/>
|
||||
|
||||
<Button onClick={fetchHosts} variant="outline" size="sm">
|
||||
Refresh
|
||||
{t('hosts.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user