feat(i18n): add multi-language support (ja/ko/vi/ru/zh-TW) with system auto-detection

- Extend AppLanguage to 7 locales: zh-CN, zh-TW, en, ja, ko, vi, ru
- Auto-detect system language via navigator.languages; fallback to English
- Add BCP47 locale mapping for date formatting and sorting
- Create 50 translation JSON files (5 languages × 10 namespaces, 1137 keys each)
- Update language selector with native language names
- Extend locale key parity test to cover all 7 languages (70 assertions)
- Add VNC/RDP desktop, serial terminal, AI agent, port forwarding, and SFTP features
- Add credential encryption, host metrics, session backup crypto, and zmodem bridge
- Update Rust backend: desktop services, host metrics, SSH/SFTP enhancements
This commit is contained in:
Logan.Li committed 2026-08-12 00:07:48 +08:00
1 parent fea6d8db04
commit b0d0e3a5f3
301 files changed
+28002 -2705

No files matched your search

@@ -25,13 +25,13 @@ describe('Session workflow acceptance', () => {
const onClose = vi.fn()
render(<SessionDialog isOpen onClose={onClose} editSession={null} />)
await user.type(screen.getByPlaceholderText('自动用 user@host'), 'OpsBase 验收')
await user.clear(screen.getByPlaceholderText('root@192.168.1.10'))
await user.type(screen.getByPlaceholderText('root@192.168.1.10'), 'root@opsbase.cn:55555')
await user.type(screen.getByPlaceholderText('自动用 user@host 或 vnc:host'), 'OpsBase 验收')
await user.clear(screen.getByPlaceholderText('192.168.1.10'))
await user.type(screen.getByPlaceholderText('192.168.1.10'), 'root@opsbase.cn:55555')
expect(screen.getByLabelText('端口')).toHaveValue(55555)
await user.click(screen.getByRole('button', { name: /高级选项/ }))
await user.clear(screen.getByPlaceholderText('default'))
await user.type(screen.getByPlaceholderText('default'), '验收环境')
await user.selectOptions(screen.getByLabelText('分组'), '__new__')
await user.type(screen.getByPlaceholderText('如:生产环境'), '验收环境')
await user.click(screen.getByRole('button', { name: '创建' }))
await user.click(screen.getByRole('button', { name: '创建并保存' }))
await waitFor(() => {
@@ -64,8 +64,8 @@ describe('Session workflow acceptance', () => {
expect(screen.getByRole('button', { name: '密钥' })).toBeVisible()
expect(screen.queryByRole('button', { name: 'Agent' })).not.toBeInTheDocument()
await user.clear(screen.getByLabelText('目标'))
await user.type(screen.getByLabelText('目标'), 'admin@example.com')
await user.clear(screen.getByLabelText('主机'))
await user.type(screen.getByLabelText('主机'), 'admin@example.com')
await user.type(screen.getByLabelText('密码'), 'session-secret')
await user.click(screen.getByRole('button', { name: '创建并保存' }))
@@ -88,9 +88,11 @@ describe('Session workflow acceptance', () => {
const user = userEvent.setup()
render(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
await user.clear(screen.getByLabelText('目标'))
await user.type(screen.getByLabelText('目标'), 'root@opsbase.cn')
await user.clear(screen.getByLabelText('主机'))
await user.type(screen.getByLabelText('主机'), 'root@opsbase.cn')
await user.click(screen.getByRole('button', { name: '密钥' }))
expect(screen.getByRole('button', { name: '文件' })).toBeVisible()
expect(screen.getByRole('button', { name: '文本' })).toBeVisible()
await user.clear(screen.getByLabelText('私钥路径'))
await user.type(screen.getByLabelText('私钥路径'), '~/.ssh/id_ed25519')
await user.type(screen.getByLabelText('密钥口令(可选)'), 'key-secret')
@@ -109,12 +111,39 @@ describe('Session workflow acceptance', () => {
})
})
it('saves pasted PEM private key as text content', async () => {
const user = userEvent.setup()
const pem = [
'-----BEGIN OPENSSH PRIVATE KEY-----',
'b3BlbnNzaC1rZXktdjEAAAAABG5vbmU=',
'-----END OPENSSH PRIVATE KEY-----',
].join('\n')
render(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
await user.clear(screen.getByLabelText('主机'))
await user.type(screen.getByLabelText('主机'), 'root@opsbase.cn')
await user.click(screen.getByRole('button', { name: '密钥' }))
await user.click(screen.getByRole('button', { name: '文本' }))
await user.click(screen.getByLabelText('私钥内容'))
await user.paste(pem)
await user.click(screen.getByRole('button', { name: '创建并保存' }))
await waitFor(() => {
expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith(
expect.objectContaining({
authType: 'key',
privateKey: pem,
password: undefined,
})
)
})
})
it('saves a SOCKS5 connection proxy with optional credentials', async () => {
const user = userEvent.setup()
render(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
await user.clear(screen.getByLabelText('目标'))
await user.type(screen.getByLabelText('目标'), 'admin@internal.example.com')
await user.clear(screen.getByLabelText('主机'))
await user.type(screen.getByLabelText('主机'), 'admin@internal.example.com')
await user.click(screen.getByRole('button', { name: /高级选项/ }))
await user.click(screen.getByRole('button', { name: 'SOCKS5' }))
await user.type(screen.getByPlaceholderText('代理地址'), '127.0.0.1')
@@ -153,8 +182,8 @@ describe('Session workflow acceptance', () => {
})
render(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
await user.clear(screen.getByLabelText('目标'))
await user.type(screen.getByLabelText('目标'), 'root@internal.example.com')
await user.clear(screen.getByLabelText('主机'))
await user.type(screen.getByLabelText('主机'), 'root@internal.example.com')
await user.click(screen.getByRole('button', { name: /高级选项/ }))
await user.selectOptions(screen.getByLabelText('跳板机(可选)'), 'jump-1')
await user.click(screen.getByRole('button', { name: '创建并保存' }))
@@ -201,7 +230,7 @@ describe('Session workflow acceptance', () => {
quick.unmount()
render(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
const sessionTarget = screen.getByLabelText('目标')
const sessionTarget = screen.getByLabelText('主机')
await user.clear(sessionTarget)
await user.paste('admin@example.com:2200')
expect(sessionTarget).toHaveValue('admin@example.com:2200')