mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
@@ -79,19 +79,27 @@
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
.hg-button.hg-functionBtn.hg-button-space {
|
||||
.hg-layout-default .hg-button.hg-functionBtn.hg-button-space {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.hg-layout-mac .hg-button.hg-functionBtn.hg-button-space {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-layout-default .hg-button.hg-highlight {
|
||||
.simple-keyboard.hg-layout-default .simple-keyboard.hg-layout-mac .hg-button.hg-highlight {
|
||||
background: rgb(37 99 235);
|
||||
border-bottom: 1px solid #2563eb;
|
||||
box-shadow: 0 1px 3px 0 rgb(37 99 235 / 0.1), 0 1px 2px -1px rgb(37 99 235 / 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.simple-keyboard.hg-layout-default .hg-button.hg-double{
|
||||
.simple-keyboard.hg-layout-default .simple-keyboard.hg-layout-mac .hg-button.hg-double{
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.keyboard-header {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import { getLanguage } from '@/lib/localstorage.ts';
|
||||
import { en } from './en';
|
||||
import { fr } from './fr';
|
||||
import { zh } from './zh';
|
||||
import { ru } from './ru';
|
||||
|
||||
const resources = { en, fr, zh };
|
||||
const resources = { en, fr, ru, zh };
|
||||
|
||||
function getCurrentLanguage(): string {
|
||||
const languages = Object.keys(resources);
|
||||
|
||||
164
web/src/i18n/ru.ts
Normal file
164
web/src/i18n/ru.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
export const ru = {
|
||||
translation: {
|
||||
language: 'Язык',
|
||||
changePassword: 'Изменить пароль',
|
||||
logout: 'Выйти',
|
||||
keyboard: 'Клавиатура',
|
||||
images: 'Образы',
|
||||
loading: 'Загрузка',
|
||||
empty: 'Пусто',
|
||||
settings: 'Настройки',
|
||||
showMouse: 'Показать мышь',
|
||||
hideMouse: 'Скрыть мышь',
|
||||
power: 'Питание',
|
||||
reset: 'Экстренная перезагрузка',
|
||||
powerShort: 'Питание (короткое нажатие)',
|
||||
powerLong: 'Питание (длинное нажатие)',
|
||||
hddLed: 'Индикатор активности хранилища',
|
||||
checkLibFailed: 'Не удалось проверить библиотеку среды выполнения, попробуйте еще раз',
|
||||
updateLibFailed: 'Не удалось обновить библиотеку среды выполнения, попробуйте еще раз',
|
||||
updatingLib: 'Обновление библиотеки среды выполнения. Пожалуйста, обновите страницу после обновления.',
|
||||
checkForUpdate: 'Проверить обновления',
|
||||
head: {
|
||||
desktop: 'Удаленный рабочий стол',
|
||||
login: 'Войти',
|
||||
changePassword: 'Изменить пароль',
|
||||
terminal: 'Терминал'
|
||||
},
|
||||
auth: {
|
||||
login: 'Войти',
|
||||
placeholderUsername: 'Введите имя пользователя',
|
||||
placeholderPassword: 'Введите пароль',
|
||||
placeholderPassword2: 'Введите пароль снова',
|
||||
noEmptyUsername: 'Имя пользователя не может быть пустым',
|
||||
noEmptyPassword: 'Пароль не может быть пустым',
|
||||
noAccount: 'Не удалось получить информацию о пользователе, пожалуйста, обновите веб-страницу или сбросьте пароль',
|
||||
invalidUser: 'Неверное имя пользователя или пароль',
|
||||
error: 'Непредвиденная ошибка',
|
||||
changePassword: 'Изменить пароль',
|
||||
differentPassword: 'Пароли не совпадают',
|
||||
illegalUsername: 'Имя пользователя содержит недопустимые символы',
|
||||
illegalPassword: 'Пароль содержит недопустимые символы',
|
||||
forgetPassword: 'Забыли пароль?',
|
||||
resetPassword: 'Сбросить пароль',
|
||||
reset1: 'Если вы забыли пароль, выполните следующие действия для его восстановления:',
|
||||
reset2: '1. Войдите в устройство NanoKVM по протоколу SSH;',
|
||||
reset3: '2. Удалите файл на устройстве: ',
|
||||
reset4: '3. Используйте учетную запись по умолчанию для входа в систему: ',
|
||||
ok: 'ОК',
|
||||
cancel: 'Отмена'
|
||||
},
|
||||
screen: {
|
||||
resolution: 'Разрешение',
|
||||
fps: 'Частота кадров',
|
||||
customizeFps: 'Настроить',
|
||||
quality: 'Качество',
|
||||
frameDetect: 'Экономия трафика',
|
||||
frameDetectTip:
|
||||
"Вычисляет разницу между кадрами и прекращает передачу видеопотока, если на экране удаленного узла не обнаружено никаких изменений."
|
||||
},
|
||||
cursor: {
|
||||
default: 'Курсор по умолчанию',
|
||||
pointer: 'Курсор: указатель',
|
||||
cell: 'Курсор: крест',
|
||||
text: 'Курсор: выбор текста',
|
||||
grab: 'Курсор: захват',
|
||||
hide: 'Скрыть курсор',
|
||||
mode: 'Режим мыши',
|
||||
absolute: 'Абсолютный режим',
|
||||
relative: 'Относительный режим',
|
||||
requestPointer: 'Используется относительный режим. Нажмите на рабочий стол для захвата мыши.',
|
||||
resetHid: 'Перезагрузить HID-подсистему'
|
||||
},
|
||||
image: {
|
||||
title: 'Образы',
|
||||
loading: 'Загрузка...',
|
||||
empty: 'Пусто',
|
||||
tips: {
|
||||
title: 'Как загрузить',
|
||||
usb1: 'Подключите NanoKVM к вашему компьютеру через USB.',
|
||||
usb2: 'Убедитесь что Виртуальный USB-накопитель смонтирован (Настройки - Виртуальный USB-накопитель).',
|
||||
usb3: 'Откройте виртуальный USB-накопитель на вашем компьютере и скопируйте файл образа в корневую директорию USB-накопителя.',
|
||||
scp1: 'Убедитесь что NanoKVM и ваш компьютер находятся в одной и той же сети.',
|
||||
scp2: 'Откройте терминал на вашем компьютере и используйте команду scp чтобы загрузить файл образа в директорию /data, находящуюся на NanoKVM.',
|
||||
scp3: 'Например: scp путь-к-образу root@ip-вашего-nanokvm:/data',
|
||||
tfCard: 'Карта памяти',
|
||||
tf1: 'Этот метод поддерживается на Linux-системах',
|
||||
tf2: 'Извлеките карту памяти из NanoKVM (если у вас "полная" (Full) версия, сначала нужно разобрать корпус).',
|
||||
tf3: 'Вставьте карту памяти в картридер и подключите его в свой компьютер.',
|
||||
tf4: 'Скопируйте файл образа в директорию /data на карте памяти.',
|
||||
tf5: 'Вставьте карту памяти в NanoKVM.'
|
||||
},
|
||||
},
|
||||
script: {
|
||||
title: 'Скрипты',
|
||||
upload: 'Загрузить',
|
||||
run: 'Выполнить',
|
||||
runBackground: 'Выполнить в фоне',
|
||||
runFailed: 'Не удалось выполнить',
|
||||
attention: 'Внимание',
|
||||
delDesc: 'Вы уверены, что хотите удалить этот файл?',
|
||||
confirm: 'Да',
|
||||
cancel: 'Нет',
|
||||
delete: 'Удалить',
|
||||
close: 'Закрыть'
|
||||
},
|
||||
terminal: {
|
||||
title: 'Терминал',
|
||||
nanokvm: 'Терминал NanoKVM',
|
||||
serial: 'Терминал COM-порта',
|
||||
serialPort: 'COM-порт',
|
||||
serialPortPlaceholder: 'Введите COM-порт',
|
||||
baudrate: 'Скорость передачи',
|
||||
confirm: 'ОК'
|
||||
},
|
||||
wol: {
|
||||
sending: 'Отправка команды...',
|
||||
sent: 'Команда отправлена',
|
||||
input: 'Введите MAC-адрес',
|
||||
ok: 'ОК'
|
||||
},
|
||||
about: {
|
||||
title: 'О системе NanoKVM',
|
||||
information: 'Информация',
|
||||
ip: 'IP-адрес',
|
||||
mdns: 'Доменное имя mDNS',
|
||||
firmware: 'Версия ПО',
|
||||
image: 'Версия прошивки',
|
||||
deviceKey: 'Ключ устройства',
|
||||
queryFailed: 'Запрос не удался',
|
||||
community: 'Сообщество'
|
||||
},
|
||||
update: {
|
||||
title: 'Проверить обновления',
|
||||
queryFailed: 'Получить версию не удалось',
|
||||
updateFailed: 'Обновление не удалось. Пожалуйста, попробуйте еще раз.',
|
||||
isLatest: 'У вас уже есть последняя версия.',
|
||||
available: 'Доступно обновление. Вы уверены, что хотите обновить?',
|
||||
updating: 'Начато обновление. Пожалуйста, подождите...',
|
||||
confirm: 'Подтвердить',
|
||||
cancel: 'Отмена'
|
||||
},
|
||||
virtualDevice: {
|
||||
network: 'Виртуальная сеть',
|
||||
usb: 'Виртуальный USB-накопитель'
|
||||
},
|
||||
tailscale: {
|
||||
loading: 'Загрузка...',
|
||||
notInstalled: 'Tailscale не найден! Пожалуйста, установите.',
|
||||
install: 'Установить',
|
||||
installing: 'Установка...',
|
||||
urlPeriod: 'Этот адрес действителен в течение 10 минут',
|
||||
installed: 'Tailscale уже установлен. Пожалуйста, войдите в систему.',
|
||||
login: 'Войти',
|
||||
failed: 'Не удалось установить',
|
||||
retry: 'Пожалуйста, обновите и попробуйте снова, или попробуйте установить вручную',
|
||||
download: 'Скачайте',
|
||||
package: 'установочный пакет',
|
||||
unzip: 'и разархивируйте его',
|
||||
upTailscale: 'Переместите tailscale в каталог /usr/bin/ на NanoKVM',
|
||||
upTailscaled: 'Переместите tailscaled в каталог /usr/sbin/ на NanoKVM',
|
||||
refresh: 'Обновите текущую страницу'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -106,6 +106,8 @@ export const KeyboardCodes: Map<string, number> = new Map([
|
||||
['NumpadDecimal', 99],
|
||||
['KeyKpDot', 99],
|
||||
|
||||
['Menu', 118],
|
||||
|
||||
['ControlLeft', 224],
|
||||
['ShiftLeft', 225],
|
||||
['AltLeft', 226],
|
||||
|
||||
@@ -4,6 +4,9 @@ import { useAtom } from 'jotai';
|
||||
import { XIcon } from 'lucide-react';
|
||||
import Keyboard, { KeyboardButtonTheme } from 'react-simple-keyboard';
|
||||
import { Drawer } from 'vaul';
|
||||
import { Radio } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import 'react-simple-keyboard/build/css/index.css';
|
||||
import '@/assets/styles/keyboard.css';
|
||||
@@ -25,11 +28,19 @@ type KeyboardProps = {
|
||||
isBigScreen: boolean;
|
||||
};
|
||||
|
||||
const keyboardLayoutOptions = [
|
||||
{ label: 'Win', value: 'default' },
|
||||
{ label: 'Mac', value: 'mac' }
|
||||
];
|
||||
|
||||
export const VirtualKeyboard = ({ isBigScreen }: KeyboardProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isKeyboardOpen, setIsKeyboardOpen] = useAtom(isKeyboardOpenAtom);
|
||||
|
||||
const [isCapsLock, setIsCapsLock] = useState(false);
|
||||
const [activeModifierKeys, setActiveModifierKeys] = useState<string[]>([]);
|
||||
const [layout, setLayout] = useState('default');
|
||||
|
||||
const keyboardRef = useRef<any>(null);
|
||||
|
||||
@@ -126,6 +137,10 @@ export const VirtualKeyboard = ({ isBigScreen }: KeyboardProps) => {
|
||||
return theme;
|
||||
}
|
||||
|
||||
function selectLayout({ target: { value } }: RadioChangeEvent) {
|
||||
setLayout(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer.Root open={isKeyboardOpen} onOpenChange={setIsKeyboardOpen} modal={false}>
|
||||
<Drawer.Portal>
|
||||
@@ -137,7 +152,18 @@ export const VirtualKeyboard = ({ isBigScreen }: KeyboardProps) => {
|
||||
>
|
||||
{/* header */}
|
||||
<div className="flex items-center justify-between px-3 py-1">
|
||||
<div className="w-[100px] text-sm font-bold text-neutral-500">Keyboard</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="keyboard-header w-fit my-auto mr-2 text-sm font-bold text-neutral-500">
|
||||
{t('keyboard')}
|
||||
</div>
|
||||
<Radio.Group
|
||||
options={keyboardLayoutOptions}
|
||||
onChange={selectLayout}
|
||||
value={layout}
|
||||
optionType='button'
|
||||
buttonStyle='solid'
|
||||
/>
|
||||
</div>
|
||||
<div className="h-1.5 w-12 flex-shrink-0 rounded-full bg-zinc-300" />
|
||||
<div className="flex w-[100px] items-center justify-end">
|
||||
<div
|
||||
@@ -157,6 +183,7 @@ export const VirtualKeyboard = ({ isBigScreen }: KeyboardProps) => {
|
||||
keyboardRef={(r) => (keyboardRef.current = r)}
|
||||
onKeyPress={onKeyPress}
|
||||
onKeyReleased={onKeyReleased}
|
||||
layoutName={layout}
|
||||
{...keyboardOptions}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
// main keys
|
||||
export const keyboardOptions = {
|
||||
theme: 'simple-keyboard hg-theme-default hg-layout-default',
|
||||
theme: 'simple-keyboard hg-theme-default',
|
||||
baseClass: 'simple-keyboard-main',
|
||||
layout: {
|
||||
default: [
|
||||
'{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12',
|
||||
'Backquote Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0 Minus Equal {backspace}',
|
||||
'{tab} KeyQ KeyW KeyE KeyR KeyT KeyY KeyU KeyI KeyO KeyP BracketLeft BracketRight Backslash',
|
||||
'{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon Quote {enter}',
|
||||
'{shiftleft} KeyZ KeyX KeyC KeyV KeyB KeyN KeyM Comma Period Slash {shiftright}',
|
||||
'{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}'
|
||||
],
|
||||
mac: [
|
||||
'{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12',
|
||||
'Backquote Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0 Minus Equal {backspace}',
|
||||
'{tab} KeyQ KeyW KeyE KeyR KeyT KeyY KeyU KeyI KeyO KeyP BracketLeft BracketRight Backslash',
|
||||
@@ -74,15 +82,19 @@ export const keyboardOptions = {
|
||||
'{controlleft}': 'Ctrl',
|
||||
'{altleft}': 'Alt',
|
||||
'{metaleft}': 'Cmd',
|
||||
'{winleft}': 'Win',
|
||||
'{space}': 'Space',
|
||||
'{metaright}': 'Cmd',
|
||||
'{altright}': 'Alt'
|
||||
'{winright}': 'Win',
|
||||
'{altright}': 'Alt',
|
||||
'{menu}': "Menu",
|
||||
'{controlright}': 'Ctrl',
|
||||
}
|
||||
};
|
||||
|
||||
// control keys
|
||||
export const keyboardControlPadOptions = {
|
||||
theme: 'simple-keyboard hg-theme-default hg-layout-default',
|
||||
theme: 'simple-keyboard hg-theme-default',
|
||||
baseClass: 'simple-keyboard-control',
|
||||
layout: {
|
||||
default: [
|
||||
@@ -107,7 +119,7 @@ export const keyboardControlPadOptions = {
|
||||
|
||||
// arrow keys
|
||||
export const keyboardArrowsOptions = {
|
||||
theme: 'simple-keyboard hg-theme-default hg-layout-default',
|
||||
theme: 'simple-keyboard hg-theme-default',
|
||||
baseClass: 'simple-keyboard-arrows',
|
||||
layout: {
|
||||
default: ['{arrowup}', '{arrowleft} {arrowdown} {arrowright}']
|
||||
@@ -126,8 +138,10 @@ export const specialKeyMap = new Map([
|
||||
['{controlleft}', 'ControlLeft'],
|
||||
['{altleft}', 'AltLeft'],
|
||||
['{metaleft}', 'MetaLeft'],
|
||||
['{winleft}', 'WinLeft'],
|
||||
['{space}', 'Space'],
|
||||
['{metaright}', 'MetaRight'],
|
||||
['{winright}', 'WinRight'],
|
||||
['{altright}', 'AltRight'],
|
||||
['{prtscr}', 'PrintScreen'],
|
||||
['{scrolllock}', 'ScrollLock'],
|
||||
@@ -150,10 +164,12 @@ export const modifierKeys = [
|
||||
'{controlleft}',
|
||||
'{altleft}',
|
||||
'{metaleft}',
|
||||
'{winleft}',
|
||||
'{shiftright}',
|
||||
'{controlright}',
|
||||
'{altright}',
|
||||
'{metaright}'
|
||||
'{metaright}',
|
||||
'{winright}'
|
||||
];
|
||||
|
||||
// double line display buttons
|
||||
|
||||
@@ -18,6 +18,7 @@ export const Language = () => {
|
||||
const languageItems = [
|
||||
{ lng: 'en', name: 'English' },
|
||||
{ lng: 'fr', name: 'Français' },
|
||||
{ lng: 'ru', name: 'Русский' },
|
||||
{ lng: 'zh', name: '中文' }
|
||||
];
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export const Language = () => {
|
||||
const languageItems = [
|
||||
{ lng: 'en', name: 'English' },
|
||||
{ lng: 'fr', name: 'Français' },
|
||||
{ lng: 'ru', name: 'Русский' },
|
||||
{ lng: 'zh', name: '中文' }
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user