Merge branch 'main' into claude/add-clipboard-copy-paste-011CUogNYVLkp3YrnDiwZabG

This commit is contained in:
wenjie
2025-11-21 11:44:15 +08:00
committed by GitHub
26 changed files with 1673 additions and 46 deletions

View File

@@ -13,7 +13,7 @@ NanoKVM
<a href="https://github.com/sipeed/NanoKVM/releases/latest">💾 Firmware Releases</a>
</h3>
English | [中文](./README_ZH.md) | [日本語](./README_JA.md)
English | [中文](./README_ZH.md) | [日本語](./README_JA.md) | [한국어](./README_KO.md)
</div>

View File

@@ -11,7 +11,7 @@ NanoKVM
<a href="https://cn.dl.sipeed.com/shareURL/KVM/nanoKVM"> ハードウェア </a>
</h3>
[English](./README.md) | [中文](./README_ZH.md) | 日本語
[English](./README.md) | [中文](./README_ZH.md) | 日本語 | [한국어](./README_KO.md)
</div>

139
README_KO.md Normal file
View File

@@ -0,0 +1,139 @@
NanoKVM
======
<div align="center">
![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png)
<h3>
<a href="https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/introduction.html">🚀 빠른 시작</a>
|
<a href="https://cn.dl.sipeed.com/shareURL/KVM/nanoKVM">🛠️ 하드웨어 정보</a>
|
<a href="https://github.com/sipeed/NanoKVM/releases/latest">💾 펌웨어 릴리즈</a>
</h3>
[English](./README.md) | [中文](./README_ZH.md) | [日本語](./README_JA.md) | 한국어
</div>
## 🌟 NanoKVM이 무엇인가요?
NanoKVM은 소형 오픈 소스 IP-KVM 장치 시리즈입니다. LicheeRV Nano(RISC-V)를 기반으로 구축된 NanoKVM을 사용하면 마치 컴퓨터 앞에 바로 앉아있는 것처럼 원격으로 컴퓨터에 접근하고 제어할 수 있어 서버, 임베디드 시스템 또는 모든 헤드리스 머신을 관리하는 데 완벽합니다.
## 📦 제품군
다양한 요구를 충족할 수 있도록 여러 NanoKVM 버전을 제공합니다:
* **NanoKVM-Cube Lite:** DIY 사용자 및 대량 배포가 필요한 기업용을 위한 최소 구성 키트입니다.
* **NanoKVM-Cube Full:** 세련된 케이스, 액세서리, 사전 플래시된 시스템 SD 카드가 포함된 완전 구성 패키지입니다. 개봉 후 즉시 사용할 수 있어 개인 사용자에게 적합합니다.
* **NanoKVM-PCIe:** 독특한 폼팩터로, PCIe 브래킷을 통해 섀시 내부에 장착하며, 전원인 PCIe 슬롯에서 직접 공급받습니다. 선택적으로 WiFi와 PoE 기능을 추가할 수 있습니다.
### 🚀 NanoKVM-Pro: 4K를 위한 파워 머신
대중적인 수요를 기반으로, NanoKVM을 **NanoKVM-Pro**로 업그레이드했습니다:
* **해상도 업그레이드:** 1080P에서 놀라운 **4K@30fps / 2K@60fps**까지.
* **네트워크 업그레이드:** 초고속 **GbE + PoE + WiFi 6** (100M 이더넷 대비 향상).
* **지연 시간 업그레이드:** 인코더 가속 지연시간이 100-150ms에서 **50-100ms**로 감소.
> NanoKVM-Pro가 예약 판매 중입니다! [제품 세부 정보는 여기를 클릭하세요](https://sipeed.com/nanokvm/pro).
<div align="center">
![NanoKVM Product Family](https://cdn.sipeed.com/public/nanokvm-products.jpg)
</div>
## 🛠️ 기술 사양
| 제품 | NanoKVM-Pro | NanoKVM (Cube/PCIe) | GxxKVM | JxxKVM |
|------------------- |-------------------------------------- |---------------------------------- |----------------------------------- |------------------------------------ |
| 코어 | AX631 2xA53 1.5G | SG2002 1xC906 1.0G | RV1126 4xA7 1.5G | RV1106 1xA7 1.2G |
| 메모리 & 저장 공간 | 1G LPDDR4X + 32G eMMC | 256M DDR3 + 32G microSD | 1G DDR3 + 8G eMMc | 256M DDR3 + 16G eMMC |
| 시스템 | NanoKVM / PiKVM | NanoKVM | GxxKVM | JxxKVM |
| 해상도 | 4K@30fps / 2K@60fps | 1080P@60fps | 4K@30fps / 2K@60fps | 1080P@60fps |
| HDMI 루프아웃 | 4K 루프아웃 | x | x | x |
| 영상 인코딩 | MJPEG / H.264 / H.265 | MJPEG / H264 | MJPEG / H264 | MJPEG / H264 |
| 오디오 전송 | ✓ | x | ✓ | x |
| UEFI / BIOS | ✓ | ✓ | ✓ | ✓ |
| USB 키보드 & 마우스 에뮬레이션 | ✓ | ✓ | ✓ | ✓ |
| USB ISO 에뮬레이션 | ✓ | ✓ | ✓ | ✓ |
| IPMI | ✓ | ✓ | ✓ | x |
| Wake-on-LAN | ✓ | ✓ | ✓ | ✓ |
| 웹 터미널 | ✓ | ✓ | ✓ | ✓ |
| 시리얼 터미널 | 3 채널 | 2 채널 | x | 1 채널 |
| 사용자 정의 스크립트 | ✓ | ✓ | x | x |
| 저장 공간 | 32G eMMC 300MB/s | 32G MicroSD 12MB/s | 8G eMMC 120MB/s | 8G eMMC 60MB/s |
| 이더넷 | 1000M | 100M | 1000M | 100M |
| PoE | 옵션 | 옵션 | x | x |
| WiFi | WiFi6 옵션 | WiFi6 옵션 | x | x |
| ATX 전원 조작 | ✓ | ✓ | $15 추가 | $10 추가 |
| 디스플레이 | 1.47" 320x172 LCD / 0.96" 128x64 OLED | 0.96" 128x64 OLED | - | 1.68" 280x240 |
| 기타 기능 | LED 줄 동기화 / 스마트 어시스턴트 | - | - | - |
| 전력 소비량 | 0.4A@5V | 0.2A@5V | 0.4A@5V | 0.2A@5V |
| 전원 입력 | USB-C 또는 PoE | USB-C | USB-C | USB-C |
| 크기 | 65x65x26mm | 40x36x36mm | 80x60x17.5mm | 60x43x(24~31)mm |
| 가격 | ~~$79~~ $69 ATX / ~~$89~~ $79 Desk | $25 Lite / $50 Full(with ATX) | ATX 미사용 시 $89 / ATX 사용 시 $102 | ATX 미사용 시 $69 / ATX 사용 시 $79 |
## 📂 프로젝트 구조
``` shell
├── kvmapp # APP 업데이트 패키지
│ ├── jpg_stream # 매우 오래된 버전에서도 직접 업데이트 가능하도록 호환됨
│ ├── kvm_new_app # kvm_system 업데이트에 필요한 구성 요소를 트리거함
│ ├── kvm_system # kvm_system 애플리케이션
│ ├── server # NanoKVM 프론트 엔드와 백엔드 애플리케이션
│ └── system # 필요한 시스템 구성 요소
├── web # NanoKVM 프론트엔드
├── server # NanoKVM 백엔드
├── support # 보조 기능 (이미지 하위 시스템, 시스템 상태, 시스템 업데이트, 화면, 키, 기타.)
│ ├── sg2002 # NanoKVM-Lite/Full/PCIe
│ └── h618 # NanoKVM-Pro
├── ...
```
## 🔩 하드웨어 플랫폼 (NanoKVM Cube/PCIe)
NanoKVM은 Sipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)를 기반으로 하며,[여기](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)에서 사양, 도식, 치수, 도면은 여기에서 찾을 수 있습니다.
NanoKVM Lite는 LicheeRV Nano와 HDMItoCSI 보드로 구성되어 있으며, NanoKVM FULL은 NanoKVM Lite에 NasnoKVM-A/B 보드와 케이스를 추가한 구성입니다. HDMItoCSI 보드는 HDMI 신호를 변환하는 역할을 하고 NanoKVM-A는 OLED, ATX 전원 출력 (USB Type-C 인터페이스), 보조 전원 공급 장치, ATX 전원 켜기/끄기 및 리셋 버튼을 포함합니다. NanoKVM-B는 한쪽 끝이 컴퓨터와 연결되고 다른 한쪽 끝은 컴퓨터의 ATX 핀에 연결되어 컴퓨터의 전원을 원격으로 제어하는 데 사용됩니다.
NanoKVM 이미지는 LicheeRV Nano SDK와 MaixCDK로 구축되었으며, LicheeRV Nano를 사용하는 자재와 호환됩니다. 반면에 KVM 소프트웨어는 LicheeRV Nano나 다른 SG2002 제품과 함께 사용할 수 없습니다. 만약 LicheeRV Nano나 MaixCam에서 HDMI 입력 애플리케이션을 구축하고 싶으시다면, 기술 지원에 문의해 주세요.
참고: SG2002의 256MB 메모리 중에 158MB는 멀티미디어 하위 폴더에 할당되어 있으며, NanoKVM은 비디오 이미지 획득과 처리에 사용됩니다.
* [NanoKVM-A 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf)
* [NanoKVM-B 도식](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_B_30131.pdf)
* [NanoKVM 이미지](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM)
![NanoKVM PCB Pinout](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg)
## 🤝 기여
여러분의 기여를 환영합니다! 도움을 줄 수 있는 방법은 다음과 같습니다:
1. 레포지토리를 포크하세요
2. 기능 브랜치를 만드세요
3. 변경한 것을 커밋하세요
4. 브랜치로 푸시하세요
5. 풀 리퀘스트를 생성하세요
Please keep your pull requests small and focused to facilitate easier review and merging.
> 🎁 **고품질 풀 리퀘스트를 제출한 기여자는 감사의 표시로 NanoKVM Cube, PCIe, 또는 Pro를 받을 수 있습니다!**
## 🛒 어디서 구매하나요?
* [Aliexpress(미국&러시아를 제외한 전 세계)](https://www.aliexpress.com/item/1005007369816019.html)
* [淘宝](https://item.taobao.com/item.htm?id=811206560480)
* [사전 주문 (Aliexpress나 Taobao를 지원하지 않는 국가)](https://sipeed.com/nanokvm)
## 💬 커뮤니티 & 지원
* [MaixHub Discussion](https://maixhub.com/discussion/nanokvm)
* QQ group: 703230713
## 📜 라이센스
이 프로젝트는 GPL-3.0 라이센스에 따라 라이센스가 부여됩니다. 자세한 사항은 LICENSE 파일을 참조하세요.

View File

@@ -13,7 +13,7 @@ NanoKVM
<a href="https://github.com/sipeed/NanoKVM/releases/latest">💾 固件发布</a>
</h3>
[English](./README.md) | 中文 | [日本語](./README_JA.md)
[English](./README.md) | 中文 | [日本語](./README_JA.md) | [한국어](./README_KO.md)
</div>

View File

@@ -104,6 +104,7 @@ start_usb_dev(){
echo 1 > functions/mass_storage.disk0/lun.0/ro
echo 0 > functions/mass_storage.disk0/lun.0/cdrom
fi
echo "NanoKVM USB Mass Storage0520" > functions/mass_storage.disk0/lun.0/inquiry_string
disk=$(cat /boot/usb.disk0)
if [ -z "${disk}" ]
then

View File

@@ -36,7 +36,6 @@ EOF
case "$1" in
start)
tailscale set --accept-dns=false
if [ -f /etc/kvm/GOMEMLIMIT ]; then
value=$(cat /etc/kvm/GOMEMLIMIT)
export GOMEMLIMIT="${value}MiB"
@@ -51,7 +50,14 @@ case "$1" in
--socket=/var/run/tailscale/tailscaled.sock \
--port=${PORT} \
$FLAGS
[ $? = 0 ] && echo "OK" || echo "FAIL"
if [ $? = 0 ]; then
echo "OK"
tailscale set --accept-dns=false
# example to make your nanoKVM an exit node
# tailscale set --advertise-exit-node --advertise-routes=192.168.0.0/16
else
echo "FAIL"
fi
;;
stop)
printf "Stopping $DAEMON: "
@@ -111,4 +117,4 @@ EOF
exit 1
esac
exit 0
exit 0

View File

@@ -16,6 +16,7 @@ import (
"NanoKVM-Server/middleware"
"NanoKVM-Server/router"
"NanoKVM-Server/service/vm/jiggler"
"NanoKVM-Server/utils"
"github.com/gin-gonic/gin"
cors "github.com/rs/cors/wrapper/gin"
@@ -38,7 +39,9 @@ func initialize() {
vision := common.GetKvmVision()
vision.SetHDMI(false)
time.Sleep(10 * time.Millisecond)
vision.SetHDMI(true)
if !utils.IsHdmiDisabled() {
vision.SetHDMI(true)
}
// run mouse jiggler
jiggler.GetJiggler().Run()

View File

@@ -16,6 +16,75 @@ type Char struct {
type PasteReq struct {
Content string `form:"content" validate:"required"`
Langue string `form:"langue"`
}
func LangueSwitch(base map[rune]Char, lang string) map[rune]Char {
// wenn kein lang angegeben → Standardmap zurück
if lang == "" {
return base
}
// immer Kopie erstellen
m := copyMap(base)
switch lang {
case "de":
// Y tauschen
m['y'] = Char{0, 29}
m['Y'] = Char{2, 29}
// Z tauschen
m['z'] = Char{0, 28}
m['Z'] = Char{2, 28}
// deutsche Sonderzeichen hinzufügen oder remappen
m['\u00E4'] = Char{0, 52} // ä
m['\u00C4'] = Char{2, 52} // Ä
m['\u00F6'] = Char{0, 51} // ö
m['\u00D6'] = Char{2, 51} // Ö
m['\u00FC'] = Char{0, 47} // ü
m['\u00DC'] = Char{2, 47} // Ü
m['\u00DF'] = Char{0, 45} // ß
//Tauschen
m['^'] = Char{0, 53} // muss doppelt sein
m['/'] = Char{2, 36} // Shift + 7
m['('] = Char{2, 37} // Shift + 8
m['&'] = Char{2, 35} // Shift + 6
m[')'] = Char{2, 38} // Shift + 9
m['`'] = Char{2, 46} // Grave Accent / Backtick
m['"'] = Char{2, 31} // Shift + 2
m['?'] = Char{2, 45} // Shift + ß
m['{'] = Char{0x40, 36} // ALt Gr + 7
m['['] = Char{0x40, 37} // ALt Gr + 8
m[']'] = Char{0x40, 38} // ALt Gr + 6
m['}'] = Char{0x40, 39} // ALt Gr + 0
m['\\'] = Char{0x40, 45} // ALt Gr + ß
m['@'] = Char{0x40, 20} // ALt Gr + q
m['+'] = Char{0, 48} // Shift + +
m['*'] = Char{2, 48} // Shift + +
m['~'] = Char{0x40, 48} // Shift + +
m['#'] = Char{0, 49} // Shift + #
m['\''] = Char{2, 49} // Shift + #
m['<'] = Char{0, 100} // Shift + <
m['>'] = Char{2, 100} // Shift + <
m['|'] = Char{0x40, 100} // ALt Gr + <
m[';'] = Char{2, 54} // Shift + ,
m[':'] = Char{2, 55} // Shift + .
m['-'] = Char{0, 56} // Shift + -
m['_'] = Char{2, 56} // Shift + -
//neu
m['\u00B4'] = Char{0, 46} // ´
m['\u00B0'] = Char{2, 53} // °
m['\u00A7'] = Char{2, 32} // §
m['\u20AC'] = Char{0x40, 8} // €
m['\u00B2'] = Char{0x40, 31} // ²
m['\u00B3'] = Char{0x40, 32} // ³
}
return m
}
func (s *Service) Paste(c *gin.Context) {
@@ -25,6 +94,7 @@ func (s *Service) Paste(c *gin.Context) {
rsp.ErrRsp(c, -1, "invalid arguments")
return
}
charMapLocal := LangueSwitch(charMap, req.Langue)
if len(req.Content) > 1024 {
rsp.ErrRsp(c, -2, "content too long")
return
@@ -34,7 +104,7 @@ func (s *Service) Paste(c *gin.Context) {
keyUp := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
for _, char := range req.Content {
key, ok := charMap[char]
key, ok := charMapLocal[char]
if !ok {
log.Debugf("unknown key '%c' (rune: %d)", char, char)
continue
@@ -48,6 +118,15 @@ func (s *Service) Paste(c *gin.Context) {
rsp.OkRsp(c)
log.Debugf("hid paste success, total %d characters processed", len(req.Content))
}
func copyMap(src map[rune]Char) map[rune]Char {
dst := make(map[rune]Char, len(src))
for k, v := range src {
dst[k] = v
}
return dst
}
var charMap = map[rune]Char{
// Lowercase letters
'a': {0, 4}, 'b': {0, 5}, 'c': {0, 6}, 'd': {0, 7}, 'e': {0, 8},

View File

@@ -12,6 +12,14 @@ import (
func (s *Service) Reset(c *gin.Context) {
var rsp proto.Response
h := GetHid()
h.Lock()
h.CloseNoLock()
defer func() {
h.OpenNoLock()
h.Unlock()
}()
// reset USB
f, err := os.OpenFile("/sys/kernel/config/usb_gadget/g0/UDC", os.O_WRONLY, 0644)
if err != nil {

View File

@@ -1,6 +1,7 @@
package storage
import (
"fmt"
"os"
"os/exec"
"path/filepath"
@@ -12,6 +13,7 @@ import (
log "github.com/sirupsen/logrus"
"NanoKVM-Server/proto"
"NanoKVM-Server/service/hid"
)
const (
@@ -19,6 +21,7 @@ const (
imageNone = "/dev/mmcblk0p3"
cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom"
mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file"
inquiryString = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/inquiry_string"
roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro"
)
@@ -91,6 +94,20 @@ func (s *Service) MountImage(c *gin.Context) {
}
}
inquiryVen := "NanoKVM"
inquiryPrd := "USB Mass Storage"
inquiryVer := 0x0520
if req.Cdrom {
inquiryPrd = "USB CD/DVD-ROM"
}
inquiryData := fmt.Sprintf("%-8s%-16s%04x", inquiryVen, inquiryPrd, inquiryVer)
if err := os.WriteFile(inquiryString, []byte(inquiryData), 0o666); err != nil {
log.Errorf("set inquiry %s failed: %s", inquiryData, err)
rsp.ErrRsp(c, -2, "set inquiry failed")
return
}
// mount
image := req.File
if image == "" {
@@ -103,6 +120,14 @@ func (s *Service) MountImage(c *gin.Context) {
return
}
h := hid.GetHid()
h.Lock()
h.CloseNoLock()
defer func() {
h.OpenNoLock()
h.Unlock()
}()
// reset usb
commands := []string{
"echo > /sys/kernel/config/usb_gadget/g0/UDC",

View File

@@ -5,13 +5,12 @@ import (
"NanoKVM-Server/common"
"NanoKVM-Server/proto"
"NanoKVM-Server/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
var hdmiEnabled = true
func (s *Service) ResetHdmi(c *gin.Context) {
var rsp proto.Response
@@ -20,7 +19,7 @@ func (s *Service) ResetHdmi(c *gin.Context) {
vision.SetHDMI(false)
time.Sleep(1 * time.Second)
vision.SetHDMI(true)
hdmiEnabled = true
utils.PersistHDMIEnabled()
rsp.OkRsp(c)
log.Debug("reset hdmi")
@@ -32,7 +31,7 @@ func (s *Service) EnableHdmi(c *gin.Context) {
vision := common.GetKvmVision()
vision.SetHDMI(true)
hdmiEnabled = true
utils.PersistHDMIEnabled()
rsp.OkRsp(c)
log.Debug("enable hdmi")
@@ -44,7 +43,7 @@ func (s *Service) DisableHdmi(c *gin.Context) {
vision := common.GetKvmVision()
vision.SetHDMI(false)
hdmiEnabled = false
utils.PersistHDMIDisabled()
rsp.OkRsp(c)
log.Debug("disable hdmi")
@@ -54,7 +53,7 @@ func (s *Service) GetHdmiState(c *gin.Context) {
var rsp proto.Response
rsp.OkRspWithData(c, &proto.GetGetHdmiStateRsp{
Enabled: hdmiEnabled,
Enabled: !utils.IsHdmiDisabled(),
})
log.Debug("get hdmi state")

View File

@@ -3,6 +3,7 @@ package vm
import (
"fmt"
"os"
"os/exec"
"strings"
"NanoKVM-Server/proto"
@@ -14,6 +15,7 @@ import (
const (
BootHostnameFile = "/boot/hostname"
EtcHostname = "/etc/hostname"
EtcHosts = "/etc/hosts"
)
func (s *Service) SetHostname(c *gin.Context) {
@@ -25,6 +27,29 @@ func (s *Service) SetHostname(c *gin.Context) {
return
}
dataRead, err := os.ReadFile(EtcHostname)
if err != nil {
rsp.ErrRsp(c, -1, "read Hostname failed")
return
}
oldHostname := strings.Replace(string(dataRead), "\n", "", -1)
if (oldHostname != req.Hostname) {
dataRead, err = os.ReadFile(EtcHosts)
if err != nil {
rsp.ErrRsp(c, -1, "read Hosts failed")
return
}
data := []byte(strings.Replace(string(dataRead), oldHostname, req.Hostname, -1))
if err := os.WriteFile(EtcHosts, data, 0o644); err != nil {
rsp.ErrRsp(c, -2, "failed to write data")
return
}
}
data := []byte(fmt.Sprintf("%s", req.Hostname))
if err := os.WriteFile(BootHostnameFile, data, 0o644); err != nil {
@@ -39,6 +64,8 @@ func (s *Service) SetHostname(c *gin.Context) {
rsp.OkRsp(c)
log.Debugf("set Hostname: %s", req.Hostname)
_ = exec.Command("hostname", "-F", EtcHostname).Run()
}
func (s *Service) GetHostname(c *gin.Context) {

38
server/utils/hdmi.go Normal file
View File

@@ -0,0 +1,38 @@
package utils
import (
"os"
log "github.com/sirupsen/logrus"
)
const (
HDMIDisableFile = "/etc/kvm/hdmi_disable"
)
func PersistHDMIDisabled() {
f, err := os.OpenFile(HDMIDisableFile, os.O_CREATE|os.O_RDONLY, 0644)
if err != nil {
log.Error("failed to create hdmi disable file:", err)
return
}
f.Close()
}
func PersistHDMIEnabled() {
if err := os.Remove(HDMIDisableFile); err != nil {
log.Error("failed to remove hdmi disable file:", err)
return
}
}
func IsHdmiDisabled() bool {
if _, err := os.Stat(HDMIDisableFile); err != nil {
if os.IsNotExist(err) {
return false // HDMI is enabled
}
log.Error("failed to check hdmi disable file:", err)
return false // Assume HDMI is enabled on error
}
return true // HDMI is disabled
}

View File

@@ -1,8 +1,8 @@
import { http } from '@/lib/http.ts';
// paste
export function paste(content: string) {
return http.post('/api/hid/paste', { content });
export function paste(content: string, langue: string) {
return http.post('/api/hid/paste', { content, langue });
}
// reset hid

View File

@@ -1,4 +1,5 @@
const languages = [
{ key: 'ca', name: 'Català' },
{ key: 'nl', name: 'Nederlands' },
{ key: 'da', name: 'Danish' },
{ key: 'de', name: 'Deutsch' },
@@ -8,7 +9,9 @@ const languages = [
{ key: 'id', name: 'Indonesia' },
{ key: 'it', name: 'Italian' },
{ key: 'pl', name: 'Polski' },
{ key: 'pt_br', name: 'Português (Brasil)' },
{ key: 'ru', name: 'Русский' },
{ key: 'tr', name: 'Türkçe'},
{ key: 'ko', name: '한국어' },
{ key: 'zh', name: '简体中文' },
{ key: 'zh_tw', name: '繁體中文' },
@@ -18,7 +21,8 @@ const languages = [
{ key: 'cz', name: 'Česky' },
{ key: 'uk', name: 'Українська' },
{ key: 'nb', name: 'Norsk, bokmål' },
{ key: 'th', name: 'ภาษาไทย' }
{ key: 'th', name: 'ภาษาไทย' },
{ key: 'se', name: "Svenska"}
];
languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }));

347
web/src/i18n/locales/ca.ts Normal file
View File

@@ -0,0 +1,347 @@
const ca = {
translation: {
head: {
desktop: "Escriptori remot",
login: "Inici de sessió",
changePassword: "Canviar contrasenya",
terminal: "Terminal",
wifi: "Xarxa Sense fils"
},
auth: {
login: "Inici de sessió",
placeholderUsername: "Nom d'usuari",
placeholderPassword: "Contrasenya",
placeholderPassword2: "Torna a introduir la contrasenya",
noEmptyUsername: "Cal introduir el nom d'usuari",
noEmptyPassword: "Cal introduir la contrasenya",
noAccount: "No s'ha pogut obtenir la informació de l'usuari, actualitza la pàgina web o restableix la contrasenya",
invalidUser: "Nom d'usuari o contrasenya invàlids",
error: "Error inesperat",
changePassword: "Canviar la contrasenya",
changePasswordDesc: "Per a la seguretat del dispositiu, canvia la contrasenya!",
differentPassword: "Les contrasenyes no coincideixen",
illegalUsername: "El nom d'usuari conté caràcters no permesos",
illegalPassword: "La contrasenya conté caràcters no permesos",
forgetPassword: "Has oblidat la contrasenya",
ok: "D'acord",
cancel: "Cancel·la",
loginButtonText: "Inicia sessió",
tips: {
reset1: "Per restablir les contrasenyes, mantingues premut el botó BOOT del NanoKVM durant 10 segons.",
reset2: "Per veure els passos detallats, consulta aquest document:",
reset3: "Compte web per defecte:",
reset4: "Compte SSH per defecte:",
change1: "Aquesta acció canviarà les següents contrasenyes:",
change2: "Contrasenya d'inici de sessió web",
change3: "Contrasenya root del sistema (inici de sessió SSH)",
change4: "Per restablir les contrasenyes, mantingues premut el botó BOOT del NanoKVM."
}
},
wifi: {
title: "Xarxa Sense fils",
description: "Configura la xarxa sense fils per al NanoKVM",
success: "Comprova l'estat de la xarxa del NanoKVM i visita la nova adreça IP.",
failed: "L'operació ha fallat, torna-ho a intentar.",
confirmBtn: "D'acord",
finishBtn: "Fet"
},
screen: {
title: "Pantalla",
video: "Mode de vídeo",
videoDirectTips: "Activa HTTPS a 'Configuració > Dispositiu' per utilitzar aquest mode",
resolution: "Resolució",
auto: "Automàtic",
autoTips: "Poden aparèixer talls o desajustos del ratolí en certes resolucions. Prova a canviar la resolució de l'amfitrió remot o desactiva el mode automàtic.",
fps: "FPS",
customizeFps: "Personalitzat",
quality: "Qualitat",
qualityLossless: "Sense pèrdua",
qualityHigh: "Alta",
qualityMedium: "Mitjana",
qualityLow: "Baixa",
frameDetect: "Detecció de fotogrames",
frameDetectTip: "Calcula la diferència entre fotogrames. S'atura la transmissió si no hi ha canvis a la pantalla de l'amfitrió remot.",
resetHdmi: "Restablir HDMI"
},
keyboard: {
title: "Teclat",
paste: "Enganxa",
tips: "Només es permeten lletres i símbols estàndard",
placeholder: "Escriu aquí",
submit: "Envia",
virtual: "Teclat",
ctrlaltdel: "Ctrl+Alt+Del"
},
mouse: {
title: "Ratolí",
cursor: "Estil del cursor",
default: "Cursor per defecte",
pointer: "Cursor punter",
cell: "Cursor de cel·la",
text: "Cursor de text",
grab: "Cursor de mà",
hide: "Amaga el cursor",
mode: "Mode de ratolí",
absolute: "Mode absolut",
relative: "Mode relatiu",
speed: "Velocitat de la roda",
fast: "Ràpida",
slow: "Lenta",
requestPointer: "Estàs usant el mode relatiu. Fes clic a l'escriptori per obtenir el punter.",
resetHid: "Restablir HID",
hidOnly: {
title: "Mode només HID",
desc: "Si el teclat o ratolí deixen de funcionar i restablir HID no ajuda, pot ser un problema de compatibilitat. Prova d'activar el mode només HID.",
tip1: "Activar aquest mode desmuntarà el disc virtual i la xarxa virtual",
tip2: "En mode només HID, no es pot muntar imatges",
tip3: "El NanoKVM es reiniciarà automàticament en canviar de mode",
enable: "Activa mode només HID",
disable: "Desactiva mode només HID"
}
},
image: {
title: "Imatges",
loading: "Carregant...",
empty: "No s'ha trobat res",
cdrom: "Munta la imatge en mode CD-ROM",
mountFailed: "Error en muntar",
mountDesc: "En alguns sistemes cal expulsar el disc virtual abans de muntar la imatge.",
refresh: "Actualitza la llista",
tips: {
title: "Com pujar imatges",
usb1: "Connecta el NanoKVM al teu ordinador via USB.",
usb2: "Assegura't que el disc virtual està muntat (Configuració - Disc Virtual).",
usb3: "Obre el disc virtual i copia la imatge a l'arrel.",
scp1: "Comprova que el NanoKVM i el teu ordinador estan a la mateixa xarxa.",
scp2: "Obre un terminal i usa SCP per pujar la imatge al directori /data del NanoKVM.",
scp3: "Exemple: scp ruta-de-la-imatge root@ip-del-nanokvm:/data",
tfCard: "Targeta TF",
tf1: "Mètode disponible a sistemes GNU/Linux",
tf2: "Extreu la targeta TF del NanoKVM (versió FULL, cal obrir la carcassa).",
tf3: "Introdueix la targeta en un lector i connecta'l a l'ordinador.",
tf4: "Copia la imatge al directori /data de la targeta.",
tf5: "Reintrodueix la targeta al NanoKVM."
}
},
script: {
title: "Scripts",
upload: "Puja",
run: "Executa",
runBackground: "Executa en segon pla",
runFailed: "Error en l'execució",
attention: "Atenció",
delDesc: "Estàs segur que vols eliminar aquest fitxer?",
confirm: "Sí",
cancel: "No",
delete: "Esborra",
close: "Tanca"
},
terminal: {
title: "Terminal",
nanokvm: "Terminal NanoKVM",
serial: "Terminal de port sèrie",
serialPort: "Port sèrie",
serialPortPlaceholder: "Introdueix el port sèrie",
baudrate: "Velocitat (baudrate)",
parity: "Paritat",
parityNone: "Cap",
parityEven: "Parell",
parityOdd: "Senar",
flowControl: "Control de flux",
flowControlNone: "Cap",
flowControlSoft: "Suau",
flowControlHard: "Dur",
dataBits: "Bits de dades",
stopBits: "Bits de parada",
confirm: "D'acord"
},
wol: {
title: "Wake-on-LAN",
sending: "Enviant comanda...",
sent: "Comanda enviada",
input: "Introdueix la MAC",
ok: "D'acord"
},
download: {
title: "Descarregador d'imatges",
input: "Introdueix la URL de la imatge",
ok: "D'acord",
disabled: "La partició /data és només lectura. No es pot descarregar la imatge."
},
power: {
title: "Alimentació",
showConfirm: "Confirmació",
showConfirmTip: "Les operacions d'alimentació requereixen confirmació",
reset: "Reinicia",
power: "Encén",
powerShort: "Clic curt",
powerLong: "Clic llarg",
resetConfirm: "Vols realment reiniciar?",
powerConfirm: "Vols realment encendre/apagar?",
okBtn: "Sí",
cancelBtn: "No"
},
settings: {
title: "Configuració",
about: {
title: "Sobre NanoKVM",
information: "Informació",
ip: "IP",
mdns: "mDNS",
application: "Versió aplicació",
applicationTip: "Versió de la interfície web de NanoKVM",
image: "Versió de la imatge",
imageTip: "Versió del sistema NanoKVM",
deviceKey: "Clau del dispositiu",
community: "Comunitat",
hostname: "Nom del dispositiu",
hostnameUpdated: "Nom actualitzat. Reinicia per aplicar.",
ipType: {
Wired: "Cablejada",
Wireless: "Sense fil",
Other: "Altra"
}
},
appearance: {
title: "Aparença",
display: "Pantalla",
language: "Idioma",
menuBar: "Barra de menú",
menuBarDesc: "Mostra icones a la barra de menú",
webTitle: "Títol web",
webTitleDesc: "Personalitza el títol de la pàgina"
},
device: {
title: "Dispositiu",
oled: {
title: "OLED",
description: "Apagar pantalla OLED després de",
0: "Mai",
15: "15 s",
30: "30 s",
60: "1 min",
180: "3 min",
300: "5 min",
600: "10 min",
1800: "30 min",
3600: "1 h"
},
wifi: {
title: "Xarxa sense fils",
description: "Configura la xarxa sense fils",
setBtn: "Configura"
},
ssh: {
description: "Activa accés remot per SSH",
tip: "Configura una contrasenya segura abans (Compte - Canvia contrasenya)"
},
tls: {
description: "Activa el protocol HTTPS",
tip: "Atenció: Usar HTTPS pot augmentar la latència, sobretot amb vídeo MJPEG."
},
advanced: "Configuració avançada",
swap: {
title: "Swap",
disable: "Desactiva",
description: "Defineix la mida del fitxer swap",
tip: "Pot reduir la vida útil de la targeta SD!"
},
mouseJiggler: {
title: "Mou-ratolí automàtic",
description: "Evita que el dispositiu remot entri en repòs",
disable: "Desactiva",
absolute: "Mode absolut",
relative: "Mode relatiu"
},
mdns: {
description: "Activa descobriment mDNS",
tip: "Desactiva-ho si no és necessari"
},
hdmi: {
description: "Activa la sortida HDMI"
},
hidOnly: "Mode només HID",
disk: "Disc virtual",
diskDesc: "Munta un disc U virtual al dispositiu remot",
network: "Xarxa virtual",
networkDesc: "Munta una targeta de xarxa virtual al dispositiu remot",
reboot: "Reinicia",
rebootDesc: "Segur que vols reiniciar el NanoKVM?",
okBtn: "Sí",
cancelBtn: "No"
},
tailscale: {
title: "Tailscale",
memory: {
title: "Optimització de memòria",
tip: "Quan es supera el límit de memòria, es fa una neteja més agressiva. Recomanat: 75MB si uses Tailscale. Requereix reiniciar Tailscale.",
disable: "Desactiva"
},
restart: "Reiniciar Tailscale?",
stop: "Aturar Tailscale?",
stopDesc: "Tanca la sessió i desactiva l'inici automàtic",
loading: "Carregant...",
notInstall: "Tailscale no instal·lat! Instal·la-ho.",
install: "Instal·la",
installing: "Instal·lant",
failed: "Error en la instal·lació",
retry: "Actualitza i torna-ho a provar. O instal·la manualment",
download: "Descarrega el",
package: "paquet d'instal·lació",
unzip: "i descomprimeix-lo",
upTailscale: "Puja tailscale a /usr/bin/",
upTailscaled: "Puja tailscaled a /usr/sbin/",
refresh: "Actualitza la pàgina",
notLogin: "El dispositiu no està vinculat. Inicia sessió per vincular-lo.",
urlPeriod: "Aquesta URL és vàlida durant 10 minuts",
login: "Inicia sessió",
loginSuccess: "Sessió iniciada correctament",
enable: "Activa Tailscale",
deviceName: "Nom del dispositiu",
deviceIP: "IP del dispositiu",
account: "Compte",
logout: "Tanca sessió",
logoutDesc: "Segur que vols tancar sessió?",
uninstall: "Desinstal·la Tailscale",
okBtn: "Sí",
cancelBtn: "No"
},
update: {
title: "Comprova actualitzacions",
queryFailed: "Error en obtenir la versió",
updateFailed: "Error en actualitzar. Torna-ho a intentar.",
isLatest: "Ja tens la darrera versió.",
available: "Hi ha una actualització disponible. Vols actualitzar ara?",
updating: "Actualitzant... espera",
confirm: "Confirma",
cancel: "Cancel·la",
preview: "Versió de prova",
previewDesc: "Prova noves funcions abans que ningú",
previewTip: "Compte: aquestes versions poden tenir errors o funcions inacabades!"
},
account: {
title: "Compte",
webAccount: "Nom del compte web",
password: "Contrasenya",
updateBtn: "Canvia",
logoutBtn: "Tanca sessió",
logoutDesc: "Segur que vols tancar sessió?",
okBtn: "Sí",
cancelBtn: "No"
}
},
error: {
title: "Hi ha hagut un error",
refresh: "Actualitza"
},
fullscreen: {
toggle: "Pantalla completa"
},
menu: {
collapse: "Amaga menú",
expand: "Mostra menú"
}
}
};
export default ca;

View File

@@ -68,11 +68,13 @@ const de = {
keyboard: {
title: 'Tastatur',
paste: 'Einfügen',
tips: 'Nur Standard-Tastaturbuchstaben und Symbole werden unterstützt',
tips: 'Server Tastaturbelegung',
placeholder: 'Bitte eingeben',
submit: 'Senden',
virtual: 'Tastatur',
ctrlaltdel: 'Ctrl+Alt+Del'
ctrlaltdel: 'Ctrl+Alt+Del',
dropdownEnglish: 'Englisch',
dropdownGerman: 'Deutsch'
},
mouse: {
title: 'Maus',

View File

@@ -69,7 +69,7 @@ const en = {
keyboard: {
title: 'Keyboard',
paste: 'Paste',
tips: 'Only standard keyboard letters and symbols are supported',
tips: 'Server keyboard layout',
placeholder: 'Please input',
submit: 'Submit',
virtual: 'Keyboard',
@@ -84,6 +84,8 @@ const en = {
clipboardReadError: 'Failed to read clipboard',
clipboardTooLong: 'Clipboard content exceeds 1024 characters',
nonAsciiError: 'Only ASCII characters are supported'
dropdownEnglish: 'English',
dropdownGerman: 'German'
},
mouse: {
title: 'Mouse',

View File

@@ -47,13 +47,15 @@ const ko = {
finishBtn: '완료'
},
screen: {
title: '화면',
video: '비디오 모드',
videoDirectTips: '이 모드를 사용하려면 "설정 > 장치"에서 HTTPS를 활성화하세요',
resolution: '해상도',
auto: '자동 설정',
autoTips:
'일부 해상도에서는 화면이 왜곡되거나 마우스 동작이 비정상적으로 나타날 수 있습니다. 원격 컴퓨터의 해상도를 변경하거나 자동 설정 대신 수동 설정을 사용해 보세요.',
fps: 'FPS',
customizeFps: 'FPS 설정',
customizeFps: '사용자 지정',
quality: '품질',
qualityLossless: '무손실',
qualityHigh: '높음',
@@ -65,6 +67,7 @@ const ko = {
resetHdmi: 'HDMI 초기화'
},
keyboard: {
title: '키보드',
paste: '붙여넣기',
tips: '표준 키보드 문자 및 기호만 지원됩니다',
placeholder: '입력하세요',
@@ -73,6 +76,8 @@ const ko = {
ctrlaltdel: 'Ctrl+Alt+Del'
},
mouse: {
title: '마우스',
cursor: '커서 스타일',
default: '기본 커서',
pointer: '포인터 커서',
cell: '셀 커서',
@@ -82,8 +87,20 @@ const ko = {
mode: '마우스 모드',
absolute: '절대값 모드',
relative: '상대값 모드',
speed: '휠 속도',
fast: '빠름',
slow: '느림',
requestPointer: '상대값 모드를 사용 중입니다. 커서를 찾으려면 데스크톱을 클릭하세요.',
resetHid: 'HID 초기화'
resetHid: 'HID 초기화',
hidOnly: {
title: 'HID 전용 모드',
desc: '마우스와 키보드가 응답하지 않고 HID 초기화도 도움이 되지 않는다면, NanoKVM과 장치 간의 호환성 문제일 수 있습니다. 더 나은 호환성을 위해 HID 전용 모드를 활성화해 보세요.',
tip1: 'HID 전용 모드를 활성화하면 가상 USB와 가상 네트워크가 언마운트됩니다',
tip2: 'HID 전용 모드에서는 이미지 마운트가 비활성화됩니다',
tip3: '모드 전환 후 NanoKVM이 자동으로 재부팅됩니다',
enable: 'HID 전용 모드 활성화',
disable: 'HID 전용 모드 비활성화'
}
},
image: {
title: '이미지',
@@ -129,7 +146,17 @@ const ko = {
serial: '시리얼 포트 터미널',
serialPort: '시리얼 포트',
serialPortPlaceholder: '시리얼 포트를 입력하세요',
baudrate: 'Baud rate',
baudrate: '전송 속도',
parity: '패리티',
parityNone: '없음',
parityEven: '짝수',
parityOdd: '홀수',
flowControl: '흐름 제어',
flowControlNone: '없음',
flowControlSoft: '소프트웨어',
flowControlHard: '하드웨어',
dataBits: '데이터 비트',
stopBits: '정지 비트',
confirm: '확인'
},
wol: {
@@ -147,10 +174,16 @@ const ko = {
},
power: {
title: '전원',
showConfirm: '확인',
showConfirmTip: '전원 작업에는 추가 확인이 필요합니다',
reset: '리셋',
power: '전원',
powerShort: '전원 (짧게 누르기)',
powerLong: '전원 (길게 누르기)'
powerLong: '전원 (길게 누르기)',
resetConfirm: '리셋 작업을 진행하시겠습니까?',
powerConfirm: '전원 작업을 진행하시겠습니까?',
okBtn: '네',
cancelBtn: '아니오'
},
settings: {
title: '설정',
@@ -164,14 +197,23 @@ const ko = {
image: '이미지 버전',
imageTip: 'NanoKVM 시스템 이미지 버전',
deviceKey: '장치 키',
community: '커뮤니티'
community: '커뮤니티',
hostname: '호스트 이름',
hostnameUpdated: '호스트 이름이 업데이트되었습니다. 적용하려면 재부팅하세요.',
ipType: {
Wired: '유선',
Wireless: '무선',
Other: '기타'
}
},
appearance: {
title: '디자인',
display: '표시',
language: '언어',
menuBar: '메뉴 바',
menuBarDesc: '메뉴 바에 아이콘을 표시'
menuBarDesc: '메뉴 바에 아이콘을 표시',
webTitle: '웹 제목',
webTitleDesc: '웹 페이지 제목 사용자 지정'
},
device: {
title: '장치',
@@ -197,10 +239,40 @@ const ko = {
description: 'SSH 원격 접속 활성화',
tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)'
},
tls: {
description: 'HTTPS 프로토콜 활성화',
tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.'
},
advanced: '고급 설정',
swap: {
title: '스왑',
disable: '비활성화',
description: '스왑 파일 크기 설정',
tip: '이 기능을 활성화하면 SD 카드의 수명이 단축될 수 있습니다!'
},
mouseJiggler: {
title: '마우스 흔들기',
description: '원격 호스트가 절전 모드로 진입하는 것을 방지',
disable: '비활성화',
absolute: '절대값 모드',
relative: '상대값 모드'
},
mdns: {
description: 'mDNS 검색 서비스 활성화',
tip: '사용하지 않는 경우 끄는 것이 좋습니다'
},
hdmi: {
description: 'HDMI/모니터 출력 활성화'
},
hidOnly: 'HID 전용 모드',
disk: '가상 디스크',
diskDesc: '원격 호스트에서 가상 U-디스크를 마운트합니다.',
diskDesc: '원격 호스트에서 가상 USB를 마운트합니다.',
network: '가상 네트워크',
networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.'
networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.',
reboot: '재부팅',
rebootDesc: 'NanoKVM을 재부팅하시겠습니까?',
okBtn: '네',
cancelBtn: '아니오'
},
tailscale: {
title: 'Tailscale',
@@ -234,6 +306,7 @@ const ko = {
deviceIP: '장치 IP',
account: '계정',
logout: '로그아웃',
logoutDesc: '정말로 로그아웃 하시겠습니까?',
logout2: '정말로 로그아웃 합니까?',
uninstall: 'Tailscale 제거',
okBtn: '네',
@@ -247,19 +320,32 @@ const ko = {
available: '업데이트가 가능합니다. 정말로 업데이트 할까요?',
updating: '업데이트 시작. 잠시 기다려주세요...',
confirm: '확인',
cancel: '취소'
cancel: '취소',
preview: '미리보기 업데이트',
previewDesc: '새로운 기능과 개선 사항에 미리 접근하세요',
previewTip: '미리보기 버전에는 버그나 완성되지 않은 기능이 포함될 수 있으니 주의하세요!'
},
account: {
title: '계정',
webAccount: 'Web 계정',
webAccount: ' 계정',
password: '비밀번호',
updateBtn: '업데이트',
logoutBtn: '로그아웃'
logoutBtn: '로그아웃',
logoutDesc: '정말로 로그아웃 하시겠습니까?',
okBtn: '네',
cancelBtn: '아니오'
}
},
error: {
title: '문제가 발생했습니다.',
refresh: '새로고침'
},
fullscreen: {
toggle: '전체 화면 전환'
},
menu: {
collapse: '메뉴 접기',
expand: '메뉴 펼치기'
}
}
};

353
web/src/i18n/locales/se.ts Normal file
View File

@@ -0,0 +1,353 @@
const se = {
translation: {
head: {
desktop: 'Fjärrskrivbord',
login: 'Logga in',
changePassword: 'Byt lösenord',
terminal: 'Terminal',
wifi: 'Wi-Fi'
},
auth: {
login: 'Logga in',
placeholderUsername: 'Användarnamn',
placeholderPassword: 'Lösenord',
placeholderPassword2: 'Vänligen ange lösenordet igen',
noEmptyUsername: 'Användarnamn krävs',
noEmptyPassword: 'Lösenord krävs',
noAccount: 'Kunde inte hämta användarinformation, uppdatera sidan eller återställ lösenordet',
invalidUser: 'Ogiltigt användarnamn eller lösenord',
error: 'Oväntat fel',
changePassword: 'Byt lösenord',
changePasswordDesc: 'För din enhets säkerhet, byt lösenord!',
differentPassword: 'Lösenorden matchar inte',
illegalUsername: 'Användarnamnet innehåller ogiltiga tecken',
illegalPassword: 'Lösenordet innehåller ogiltiga tecken',
forgetPassword: 'Glömt lösenord',
ok: 'Ok',
cancel: 'Avbryt',
loginButtonText: 'Logga in',
tips: {
reset1:
'För att återställa lösenordet, håll in BOOT-knappen på NanoKVM i 10 sekunder.',
reset2: 'För detaljerade steg, se detta dokument:',
reset3: 'Standardkonto för webben:',
reset4: 'Standardkonto för SSH:',
change1: 'Observera att denna åtgärd ändrar följande lösenord:',
change2: 'Webbinloggningslösenord',
change3: 'Systemets root-lösenord (SSH-lösenord)',
change4: 'För att återställa lösenordet, håll in BOOT-knappen på NanoKVM.'
}
},
wifi: {
title: 'Wi-Fi',
description: 'Konfigurera Wi-Fi för NanoKVM',
success: 'Kontrollera nätverksstatusen för NanoKVM och besök den nya IP-adressen.',
failed: 'Åtgärden misslyckades, försök igen.',
confirmBtn: 'Ok',
finishBtn: 'Färdig'
},
screen: {
title: 'Skärm',
video: 'Videoläge',
videoDirectTips: 'Aktivera HTTPS i "Inställningar > Enhet" för att använda detta läge',
resolution: 'Upplösning',
auto: 'Automatisk',
autoTips:
'Skärmtear eller musförskjutning kan förekomma vid vissa upplösningar. Överväg att justera fjärrvärdens upplösning eller inaktivera automatiskt läge.',
fps: 'FPS',
customizeFps: 'Anpassa',
quality: 'Kvalitet',
qualityLossless: 'Förlustfri',
qualityHigh: 'Hög',
qualityMedium: 'Medel',
qualityLow: 'Låg',
frameDetect: 'Ramdetection',
frameDetectTip:
"Beräkna skillnaden mellan ramar. Sluta skicka videoström när inga förändringar upptäcks på fjärrvärdens skärm.",
resetHdmi: 'Återställ HDMI'
},
keyboard: {
title: 'Tangentbord',
paste: 'Klistra in',
tips: 'Endast standardbokstäver och symboler på tangentbordet stöds',
placeholder: 'Ange text',
submit: 'Skicka',
virtual: 'Tangentbord',
ctrlaltdel: 'Ctrl+Alt+Del'
},
mouse: {
title: 'Mus',
cursor: 'Markörstil',
default: 'Standardmarkör',
pointer: 'Pekmarkör',
cell: 'Cellmarkör',
text: 'Textmarkör',
grab: 'Greppmarkör',
hide: 'Dölj markör',
mode: 'Musläge',
absolute: 'Absolut läge',
relative: 'Relativt läge',
speed: 'Scrollhastighet',
fast: 'Snabb',
slow: 'Långsam',
requestPointer: 'Använder relativt läge. Klicka på skrivbordet för att få muspekaren.',
resetHid: 'Återställ HID',
hidOnly: {
title: 'Endast-HID-läge',
desc: 'Om din mus och ditt tangentbord slutar svara och återställning av HID inte hjälper, kan det bero på kompatibilitetsproblem mellan NanoKVM och enheten. Prova att aktivera Endast-HID-läge för bättre kompatibilitet.',
tip1: 'Aktivering av Endast-HID-läge avmonterar den virtuella U-disken och nätverket',
tip2: 'I Endast-HID-läge är avbildningsmontering inaktiverat',
tip3: 'NanoKVM kommer automatiskt att starta om efter lägesbyte',
enable: 'Aktivera Endast-HID-läge',
disable: 'Inaktivera Endast-HID-läge'
}
},
image: {
title: 'Avbildningar',
loading: 'Laddar...',
empty: 'Inget hittades',
cdrom: 'Montera avbildning i CD-ROM-läge',
mountFailed: 'Montering misslyckades',
mountDesc:
"I vissa system måste den virtuella disken avmonteras på fjärrvärden innan avbildningen monteras.",
refresh: 'Uppdatera avbildningslistan',
tips: {
title: 'Hur man laddar upp',
usb1: 'Anslut NanoKVM till din dator via USB.',
usb2: 'Säkerställ att den virtuella disken är monterad (Inställningar - Virtuell Disk).',
usb3: 'Öppna den virtuella disken på din dator och kopiera avbildningsfilen till rotkatalogen.',
scp1: 'Säkerställ att NanoKVM och din dator är på samma lokala nätverk.',
scp2: 'Öppna en terminal på din dator och använd SCP-kommandot för att ladda upp avbildningen till /data på NanoKVM.',
scp3: 'Exempel: scp din-avbildningssökväg root@din-nanokvm-ip:/data',
tfCard: 'TF-kort',
tf1: 'Denna metod stöds på Linux-system',
tf2: 'Ta ut TF-kortet från NanoKVM (för FULL-versionen, öppna chassit först).',
tf3: 'Sätt in TF-kortet i en kortläsare och anslut till din dator.',
tf4: 'Kopiera avbildningsfilen till /data på TF-kortet.',
tf5: 'Sätt in TF-kortet i NanoKVM.'
}
},
script: {
title: 'Skript',
upload: 'Ladda upp',
run: 'Kör',
runBackground: 'Kör i bakgrunden',
runFailed: 'Körning misslyckades',
attention: 'Observera',
delDesc: 'Är du säker på att du vill ta bort denna fil?',
confirm: 'Ja',
cancel: 'Nej',
delete: 'Ta bort',
close: 'Stäng'
},
terminal: {
title: 'Terminal',
nanokvm: 'NanoKVM Terminal',
serial: 'Serieport-terminal',
serialPort: 'Serieport',
serialPortPlaceholder: 'Ange serieport',
baudrate: 'Baudhastighet',
parity: 'Paritet',
parityNone: 'Ingen',
parityEven: 'Jämn',
parityOdd: 'Udda',
flowControl: 'Flödeskontroll',
flowControlNone: 'Ingen',
flowControlSoft: 'Mjuk',
flowControlHard: 'Hård',
dataBits: 'Databitar',
stopBits: 'Stoppbitar',
confirm: 'Ok'
},
wol: {
title: 'Wake-on-LAN',
sending: 'Skickar kommando...',
sent: 'Kommando skickat',
input: 'Ange MAC-adress',
ok: 'Ok'
},
download: {
title: 'Avbildningshämtare',
input: 'Ange en fjärravbildnings-URL',
ok: 'Ok',
disabled: '/data partitionen är skrivskyddad, kan inte hämta avbildning'
},
power: {
title: 'Ström',
showConfirm: 'Bekräftelse',
showConfirmTip: 'Strömätgärder kräver extra bekräftelse',
reset: 'Starta om',
power: 'Ström',
powerShort: 'Ström (kort tryck)',
powerLong: 'Ström (långt tryck)',
resetConfirm: 'Utföra omstart?',
powerConfirm: 'Utföra strömåtgärd?',
okBtn: 'Ja',
cancelBtn: 'Nej'
},
settings: {
title: 'Inställningar',
about: {
title: 'Om NanoKVM',
information: 'Information',
ip: 'IP',
mdns: 'mDNS',
application: 'Applikationsversion',
applicationTip: 'NanoKVM webbapplikationsversion',
image: 'Systemversion',
imageTip: 'NanoKVM systemavbildningsversion',
deviceKey: 'Enhetsnyckel',
community: 'Community',
hostname: 'Värdnamn',
hostnameUpdated: 'Värdnamn uppdaterat. Starta om för att tillämpa.',
ipType: {
Wired: 'Trådbundet',
Wireless: 'Trådlöst',
Other: 'Annat'
}
},
appearance: {
title: 'Utseende',
display: 'Display',
language: 'Språk',
menuBar: 'Menyfält',
menuBarDesc: 'Visa ikoner i menyfältet',
webTitle: 'Webbtitel',
webTitleDesc: 'Anpassa webbsidans titel'
},
device: {
title: 'Enhet',
oled: {
title: 'OLED',
description: 'Stäng av OLED-skärmen efter',
0: 'Aldrig',
15: '15 sek',
30: '30 sek',
60: '1 min',
180: '3 min',
300: '5 min',
600: '10 min',
1800: '30 min',
3600: '1 timme'
},
wifi: {
title: 'Wi-Fi',
description: 'Konfigurera Wi-Fi',
setBtn: 'Konfigurera'
},
ssh: {
description: 'Aktivera SSH-fjärråtkomst',
tip: 'Ställ in ett starkt lösenord innan du aktiverar (Konto - Byt lösenord)'
},
tls: {
description: 'Aktivera HTTPS-protokoll',
tip: 'Observera: Användning av HTTPS kan öka fördröjningen, särskilt med MJPEG-läge.'
},
advanced: 'Avancerade inställningar',
swap: {
title: 'Swap',
disable: 'Inaktivera',
description: 'Ange swap-filens storlek',
tip: 'Aktivering av denna funktion kan förkorta livslängden på ditt SD-kort!'
},
mouseJiggler: {
title: 'Musvickare',
description: 'Förhindra att fjärrvärden går i viloläge',
disable: 'Inaktivera',
absolute: 'Absolut läge',
relative: 'Relativt läge'
},
mdns: {
description: 'Aktivera mDNS-upptäckningstjänst',
tip: 'Stäng av om det inte behövs'
},
hdmi: {
description: 'Aktivera HDMI/monitorutgång'
},
hidOnly: 'Endast-HID-läge',
disk: 'Virtuell disk',
diskDesc: 'Montera virtuell U-disk på fjärrvärden',
network: 'Virtuellt nätverk',
networkDesc: 'Montera virtuell nätverkskort på fjärrvärden',
reboot: 'Starta om',
rebootDesc: 'Är du säker på att du vill starta om NanoKVM?',
okBtn: 'Ja',
cancelBtn: 'Nej'
},
tailscale: {
title: 'Tailscale',
memory: {
title: 'Minnesoptimering',
tip: 'När minnesanvändningen överskrider gränsen utförs aggressivare skräpsamling för att frigöra minne. Rekommenderas att sättas till 75 MB om du använder Tailscale. Omstart krävs för att det ska gälla.',
disable: 'Inaktivera'
},
restart: 'Starta om Tailscale?',
stop: 'Stoppa Tailscale?',
stopDesc: 'Logga ut från Tailscale och inaktivera autostart vid uppstart.',
loading: 'Laddar...',
notInstall: 'Tailscale hittades inte! Installera först.',
install: 'Installera',
installing: 'Installerar',
failed: 'Installationen misslyckades',
retry: 'Uppdatera sidan och försök igen. Eller installera manuellt',
download: 'Ladda ner',
package: 'installationspaketet',
unzip: 'och packa upp det',
upTailscale: 'Ladda upp tailscale till NanoKVM-katalogen /usr/bin/',
upTailscaled: 'Ladda upp tailscaled till katalogen /usr/sbin/',
refresh: 'Uppdatera sidan',
notLogin:
'Enheten är ännu inte bunden. Logga in och bind enheten till ditt konto.',
urlPeriod: 'Denna URL är giltig i 10 minuter',
login: 'Logga in',
loginSuccess: 'Inloggning lyckades',
enable: 'Aktivera Tailscale',
deviceName: 'Enhetsnamn',
deviceIP: 'Enhets-IP',
account: 'Konto',
logout: 'Logga ut',
logoutDesc: 'Är du säker på att du vill logga ut?',
uninstall: 'Avinstallera Tailscale',
okBtn: 'Ja',
cancelBtn: 'Nej'
},
update: {
title: 'Sök efter uppdateringar',
queryFailed: 'Kunde inte hämta version',
updateFailed: 'Uppdatering misslyckades. Försök igen.',
isLatest: 'Du har redan den senaste versionen.',
available: 'En uppdatering finns tillgänglig. Vill du uppdatera nu?',
updating: 'Uppdatering påbörjad. Vänta...',
confirm: 'Bekräfta',
cancel: 'Avbryt',
preview: 'Förhandsvisning av uppdateringar',
previewDesc: 'Få tidig tillgång till nya funktioner och förbättringar',
previewTip:
'Observera att förhandsversioner kan innehålla buggar eller ofullständig funktionalitet!'
},
account: {
title: 'Konto',
webAccount: 'Webbkonto-namn',
password: 'Lösenord',
updateBtn: 'Byt',
logoutBtn: 'Logga ut',
logoutDesc: 'Är du säker på att du vill logga ut?',
okBtn: 'Ja',
cancelBtn: 'Nej'
}
},
error: {
title: 'Vi stötte på ett problem',
refresh: 'Uppdatera'
},
fullscreen: {
toggle: 'Växla fullskärm'
},
menu: {
collapse: 'Fäll ihop menyn',
expand: 'Expandera menyn'
}
}
};
export default se;

353
web/src/i18n/locales/tr.ts Normal file
View File

@@ -0,0 +1,353 @@
const tr = {
translation: {
head: {
desktop: 'Uzak masaüstü',
login: 'Giriş',
changePassword: 'Şifreyi değiştir',
terminal: 'Uçbirim',
wifi: 'Wi-Fi'
},
auth: {
login: 'Giriş',
placeholderUsername: 'Kullanıcı Adı',
placeholderPassword: 'Şifre',
placeholderPassword2: 'Şifrenizi tekrar deneyiniz',
noEmptyUsername: 'Kullanıcı adı gereklidir',
noEmptyPassword: 'Şifre gereklidir',
noAccount: 'Kullanıcı verileri alınırken hata yaşandı, lütfen sayfayı yenileyiniz ya da şifrenizi sıfırlayınız',
invalidUser: 'Yanlış kullanıcı adı ya da şifre',
error: 'Beklenmedik bir hata',
changePassword: 'Şifrenizi değiştiriniz',
changePasswordDesc: 'Güvenlik sebebiyle lütfen şifrenizi değiştiriniz!',
differentPassword: 'Şifreler eşleşmemektedir',
illegalUsername: 'Kullanıcı adı istenmeyen karakterler içermektedir',
illegalPassword: 'Şifre istenmeyen karakterler içermektedir',
forgetPassword: 'Şifremi unuttum',
ok: 'Tamam',
cancel: 'İptal',
loginButtonText: 'Giriş',
tips: {
reset1:
'Şifreleri sıfırlamak için NanoKVM üzerinde bulunan BOOT tuşuna 10 saniye boyunca basılı tutun.',
reset2: 'Ayrıntılı adımlar için dökümana göz atın:',
reset3: 'Arayüz varsayılan hesap:',
reset4: 'Güvenli Kabuk Bağlantısı (SSH) varsayılan hesap:',
change1: 'Bu işlem şu şifreleri değiştiricektir:',
change2: 'Arayüz giriş şifresi',
change3: 'Sistem yöneticisi şifresi (Güvenli Kabuk Bağlantısı (SSH) giriş şifresi)',
change4: 'Şifreleri sıfırlamak için NanoKVM üzerinde bulunan BOOT tuşuna 10 saniye boyunca basılı tutun.'
}
},
wifi: {
title: 'Wi-Fi',
description: 'NanoKVM için Wi-Fi ayarlarını ayarlayın',
success: 'NanoKVM\'in bağlantı durumunu kontrol edin ve yeni IP adresini ziyaret edin.',
failed: 'İşlem başarısız oldu, lütfen tekrar deneyiniz.',
confirmBtn: 'Tamam',
finishBtn: 'Bitti'
},
screen: {
title: 'Ekran',
video: 'Görüntü modu',
videoDirectTips: 'kullanmak için "Ayarlar > Cihaz" HTTPS aktif edin',
resolution: 'Çözünürlük',
auto: 'Otomatik',
autoTips:
"Belirli çözünürlüklerde ekran yırtılması veya fare kayması meydana gelebilir. Bu durumda uzak ana bilgisayarın çözünürlüğünü ayarlamayı ya da otomatik modu devre dışı bırakmayı deneyin.",
fps: 'Saniyedeki kare sayısı',
customizeFps: 'Kişiselleştir',
quality: 'Kalite',
qualityLossless: 'Kayıpsız',
qualityHigh: 'Yüksek',
qualityMedium: 'Orta',
qualityLow: 'Düşük',
frameDetect: 'Kareleri algıla',
frameDetectTip:
"Gönderilen kareler arasındaki farkı hesaplar. Uzak ana bilgisayardan gönderilen yayında bir değişiklik yoksa görüntü yayınını durdurur.",
resetHdmi: 'HDMI sıfırla'
},
keyboard: {
title: 'Klavye',
paste: 'Yapıştır',
tips: 'Sadece standart klavye harfleri ve sembolleri desteklenmektedir.',
placeholder: 'Girdi',
submit: 'Gönder',
virtual: 'Klavye',
ctrlaltdel: 'Ctrl+Alt+Del'
},
mouse: {
title: 'Fare',
cursor: 'İmleç sitili',
default: 'Varsayılan imleç',
pointer: 'Nokta imleç',
cell: 'Artı imleç',
text: 'Yazı imleç',
grab: 'El imleç',
hide: 'İmleci gizle',
mode: 'Fare modu',
absolute: 'Mutlak fare modu',
relative: 'Bağıl fare modu',
speed: 'Tekerlek hızı',
fast: 'Hızlı',
slow: 'Yavaş',
requestPointer: 'Bağıl fare modu kullanılıyor. Masaüstüne tıklayarak imleç elde edinin.',
resetHid: 'İnsan Arayüz Cihazı\'nı sıfırlayın',
hidOnly: {
title: 'Sadece İnsan Arayüz Cihazı modu',
desc: "Eğer fare ve klavye çalışmayı bırakırsa ve İnsan Arayüz Cihazı\'nı sıfırlamak sorunu çözmezse bunun sebebi cihazınız ve NanoKVM arasındaki bir uyumluluk sorunu olabilir. Uyumluluğu arttırmak için Sadece İnsan Arayüz Cihazı modunu kullanın.",
tip1: 'Sadece İnsan Arayüz Cihazı modunu aktifleştirmek, sanal U-diski ve sanal ağı devre dışı bırakacaktır.',
tip2: 'Sadece İnsan Arayüz Cihazı modunda disk imajı bağlama özelliği devre dışı bırakılır',
tip3: 'NanoKVM mod değişiminden sonra kendiliğinden yeniden başlatılacaktır',
enable: 'Sadece İnsan Arayüz Cihazı modunu aktifleştir',
disable: 'Sadece İnsan Arayüz Cihazı modunu devre dışı bırak'
}
},
image: {
title: 'Disk İmajları',
loading: 'Yükleniyor...',
empty: 'Hiçbir şey bulunamadı',
cdrom: 'Disk imajını CD-ROM modunda bağlayın.',
mountFailed: 'Bağlantı başarısız oldu',
mountDesc:
"Bazı sistemlerde, disk imajını bağlamadan önce uzak ana bilgisayardaki sanal diski çıkarmak gerekir.",
refresh: 'Disk imajı listesini yenile',
tips: {
title: 'Nasıl yüklenir',
usb1: 'NanoKVM\'i bilgisayarınıza USB ile bağlayın.',
usb2: 'Sanal diskin bağlı olduğundan emin olun (Ayarlar - Sanal Disk).',
usb3: 'Sanal diski bilgisayarınızda açın ve disk imajı dosyanızı sanal diskin kök dizinine kopyalayın.',
scp1: 'NanoKVM ve bilgisayarınızın aynı yerel ağda bulunduğundan emin olun.',
scp2: 'Bilgisayarınızda uçbirimi açın ve disk imajı dosyanını SCP komudunu kullanarak NanoKVM\'in /data dizinine yükleyin.',
scp3: 'Örnek: scp senin-disk-imajı-dizinin root@senin-nanokvm-ip:/data',
tfCard: 'micro SD kart',
tf1: 'Bu yöntem Linux sistemlerde desteklenmektedir.',
tf2: 'NanoKVM\'den micro SD kartı çıkartın(TAM sürüm için öncelikle kutuyu sökün).',
tf3: 'micro SD kartı kart okuyucusuna takın ve bilgisayarınıza bağlayın.',
tf4: 'Disk imajı dosyanını micro SD kartın /data dizinine kopyalayın.',
tf5: 'micro SD kartı NanoKVM\'e geri yerleştirin.'
}
},
script: {
title: 'Betikler',
upload: 'Yükle',
run: 'Çalıştır',
runBackground: 'Arka planda çalıştır',
runFailed: 'Çalıştırma başarısız oldu',
attention: 'Dikkat',
delDesc: 'Bu dosyayı silmek istediğinden emin misin?',
confirm: 'Evet',
cancel: 'Hayır',
delete: 'Sil',
close: 'Kapat'
},
terminal: {
title: 'Uçbirim',
nanokvm: 'NanoKVM Uçbirimi',
serial: 'Serial Port Uçbirimi',
serialPort: 'Serial Port',
serialPortPlaceholder: 'Lütfen serial portunu giriniz',
baudrate: 'Baud hızı',
parity: 'Eşlik kontrolü',
parityNone: 'Yok',
parityEven: 'Çift',
parityOdd: 'Tek',
flowControl: 'Akış kontrolü',
flowControlNone: 'Yok',
flowControlSoft: 'Yazılımsal',
flowControlHard: 'Donanımsal',
dataBits: 'Veri bitleri',
stopBits: 'Dur bitleri',
confirm: 'Tamam'
},
wol: {
title: 'Ağ Üzerinden Uyandırma (WOL)',
sending: 'Komut gönderiliyor...',
sent: 'Komut gönderildi',
input: 'MAC adresi girin',
ok: 'Tamam'
},
download: {
title: 'Disk İmajı İndirici',
input: 'Uzaktaki disk imajı adresini girin',
ok: 'Tamam',
disabled: '/data bölüntüsü salt okunur modda, disk imajı indirilemiyor.'
},
power: {
title: 'Güç',
showConfirm: 'Doğrulama',
showConfirmTip: 'Güç ile ilgili işlemler fazladan doğrulama gerektirir',
reset: 'Sıfırla',
power: 'Güç',
powerShort: 'Güç tuşu (bas-çek)',
powerLong: 'Güç tuşu (uzun bas)',
resetConfirm: 'Sıfırlama işlemine devam etmek istediğinizden emin misiniz?',
powerConfirm: 'Güç işlemine devam etmek istediğinizden emin misiniz?',
okBtn: 'Evet',
cancelBtn: 'Hayır'
},
settings: {
title: 'Ayarlar',
about: {
title: 'NanoKVM Hakkında',
information: 'Bilgi',
ip: 'IP',
mdns: 'mDNS',
application: 'Uygulama sürümü',
applicationTip: 'NanoKVM web uygulaması sürümü',
image: 'İmaj Sürümü',
imageTip: 'NanoKVM sistem imajı sürümü',
deviceKey: 'Cihaz Anahtarı',
community: 'Topluluk',
hostname: 'Hostname',
hostnameUpdated: 'Hostname güncellendi. Uygulamak için yeniden başlatın.',
ipType: {
Wired: 'Kablolu bağlantı',
Wireless: 'Kablosuz bağlantı',
Other: 'Diğer'
}
},
appearance: {
title: 'Görünüm',
display: 'Ekran',
language: 'Dil',
menuBar: 'Menü çubuğu',
menuBarDesc: 'Menü çubuğunda ikonları göster',
webTitle: 'Site başlığı',
webTitleDesc: 'Görünen site başlığını güncelleyin'
},
device: {
title: 'Cihaz',
oled: {
title: 'OLED',
description: 'Oled ekranı ... sonra kapatın',
0: 'Hiçbir zaman',
15: '15 saniye',
30: '30 saniye',
60: '1 dakika',
180: '3 dakika',
300: '5 dakika',
600: '10 dakika',
1800: '30 dakika',
3600: '1 saat'
},
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi ayarlayın',
setBtn: 'Ayarla'
},
ssh: {
description: 'Güvenli Kabuk Bağlantısı (SSH) aktif et',
tip: 'Aktifleştirmeden önce güçlü bir şifreye sahip olduğunuzdan emin olun (Hesap - Şifremi Değiştir)'
},
tls: {
description: 'HTTPS protokolünü etkinleştir',
tip: 'HTTPS protokolü bağlantıda gecikmeye sebep olabilir, özellikle MJPEG görüntü modu ile.'
},
advanced: 'Gelişmiş Ayarlar',
swap: {
title: 'Swap',
disable: 'Aktifleştir',
description: 'Swap dosyasının boyutunu belirle',
tip: "Bu özelliği aktifleştirmek micro SD kartınızın ömrünü kısaltabilir!"
},
mouseJiggler: {
title: 'Fare Oynatıcı',
description: 'Uzak ana bilgisayarın uykuya geçmesini engeller',
disable: 'Devre dışı bırak',
absolute: 'Mutlak mod',
relative: 'Bağıl mod'
},
mdns: {
description: 'mDNS keşif hizmetini etkinleştir',
tip: "Kullanmıyorsanız devre dışı bırakabilirsiniz"
},
hdmi: {
description: 'HDMI/Momitör çıktısını aktifleştir'
},
hidOnly: 'Sadece İnsan Arayüz Cihazı modu',
disk: 'Sanal Disk',
diskDesc: 'Sanal U-disk\'i uzak ana bilgisayara bağla',
network: 'Sanal Ağ',
networkDesc: 'Sanal ağ kartını uzak ana bilgisayara bağla',
reboot: 'Yeniden Başlat',
rebootDesc: 'NanoKVM\'i yeniden başlatmak istediğinizden emin misiniz?',
okBtn: 'Evet',
cancelBtn: 'Hayır'
},
tailscale: {
title: 'Tailscale',
memory: {
title: 'Bellek optimizasyonu',
tip: "Bellek kullanımı sınırı aştığında, belleği boşaltmak amacıyla çöp toplama işlemi daha agresif bir şekilde gerçekleştirilir. Tailscale kullanıyorsanız bu değerin 75 MB olarak ayarlanması önerilir. Değişikliğin etkili olabilmesi için Tailscale'in yeniden başlatılması gerekir.",
disable: 'Devre dışı bırak'
},
restart: 'Tailscale\'i yeniden başlat?',
stop: 'Tailscale\'i durdur?',
stopDesc: 'Tailscale oturumundan çıkış yap ve başlangıçta çalışmasını devre dışı bırak.',
loading: 'Yükleniyor...',
notInstall: 'Tailscale bulunamadı! Lütfen indirin.',
install: 'İndir',
installing: 'İndiriliyor',
failed: 'İndirme başarısız oldu',
retry: 'Lütfen sayfayı yenileyin ve tekrar deneyin, ya da manuel indirin',
download: 'İndir',
package: 'yükleme paketi',
unzip: 'sıkışmış dosyayıın',
upTailscale: 'tailscale dosyasını NanoKVM\'in /usr/bin dizinine yükleyin',
upTailscaled: 'tailscaled dosyasını NanoKVM\'in /usr/sbin dizinine yükleyin',
refresh: 'İçinde bulunduğunuz sayfayı yenileyin',
notLogin:
'Cihaz bağlı değil. Lütfen giriş yapıp cihazınızı hesabınıza bağlayın.',
urlPeriod: 'Adres sadece 10 ndakika boyunca geçerlidir',
login: 'Giriş yap',
loginSuccess: 'Giriş yapıldı',
enable: 'Tailscale\'i etkinleştir',
deviceName: 'Cihaz Adı',
deviceIP: 'Cihaz IP adresi',
account: 'Hesap',
logout: ıkış yap',
logoutDesc: ıkış yapmak istediğinizden emin misiniz?',
uninstall: 'Tailscale\'i kaldır',
okBtn: 'Evet',
cancelBtn: 'Hayır'
},
update: {
title: 'Güncelleştirmeleri kontrol et',
queryFailed: 'Sürüm bilgisi alınamadı',
updateFailed: 'Güncelleme başarısız oldu. Lütfen tekrar deneyin.',
isLatest: 'En yeni sürüme sahipsiniz.',
available: 'Bir güncelleme indirilebilir. Şimdi güncellemek istediğinizden emin misiniz?',
updating: 'Güncelleme başlatıldı. Lütfen bekleyin...',
confirm: 'Onayla',
cancel: 'İptal',
preview: 'Ön İzleme Güncellemeleri',
previewDesc: 'En son geliştirmelere ve özelliklere erken erişin',
previewTip:
'Ön izleme güncellemelerinin tamamlanmamış olduğunu ve sorunlara sebep olabileceğini unutmayın!'
},
account: {
title: 'Hesap',
webAccount: 'Web Hesap Adı',
password: 'Şifre',
updateBtn: 'Değiştir',
logoutBtn: ıkış yap',
logoutDesc: ıkış yapmak istediğinizden emin misiniz?',
okBtn: 'Evet',
cancelBtn: 'Hayır'
}
},
error: {
title: "Bir hata oldu!",
refresh: 'Yenile'
},
fullscreen: {
toggle: 'Tam ekrana geç'
},
menu: {
collapse: 'Menüyü küçült',
expand: 'Menüyü genişlet'
}
}
};
export default tr;

View File

@@ -46,7 +46,9 @@ const zh_tw = {
finishBtn: '完成'
},
screen: {
title: '螢幕',
video: '編碼格式',
videoDirectTips: '本模式需先啟用HTTPS請前往「設定 -> 設備」中開啟',
resolution: '解析度',
auto: '自動',
autoTips:
@@ -63,6 +65,7 @@ const zh_tw = {
resetHdmi: '重置 HDMI'
},
keyboard: {
title: 'Keyboard',
paste: '貼上',
tips: '僅支援標準鍵盤字母和符號',
placeholder: '請輸入內容',
@@ -71,6 +74,8 @@ const zh_tw = {
ctrlaltdel: 'Ctrl+Alt+Del'
},
mouse: {
title: '滑鼠',
cursor: '游標樣式',
default: '預設游標',
pointer: '懸浮游標',
cell: '儲存格游標',
@@ -80,8 +85,20 @@ const zh_tw = {
mode: '滑鼠模式',
absolute: '絕對模式',
relative: '相對模式',
speed: '滾輪速度',
fast: '快',
slow: '慢',
requestPointer: '使用相對模式。請按一下桌面以取得滑鼠游標。',
resetHid: '重設 HID'
resetHid: '重設 HID',
hidOnly: {
title: 'HID-Only 模式',
desc: "如果您的滑鼠和鍵盤停止回應,且重設 HID 無效,可能是 NanoKVM 與裝置間的相容性問題。請嘗試啟用 HID-Only 模式以獲得更好的相容性。",
tip1: '啟用 HID-Only 模式將會卸載虛擬隨身碟和虛擬網卡',
tip2: '在 HID-Only 模式下,映像掛載功能將被停用',
tip3: 'NanoKVM 將在切換模式後自動重新啟動',
enable: '啟用 HID-Only 模式',
disable: '停用 HID-Only 模式'
}
},
image: {
title: '映像',
@@ -90,6 +107,7 @@ const zh_tw = {
cdrom: '以CD-ROM模式掛載',
mountFailed: '掛載失敗',
mountDesc: '在某些系統中,在掛載映像之前需要中斷遠端主機上的虛擬磁碟。',
refresh: 'Refresh the image list',
tips: {
title: '如何上傳',
usb1: '透過 USB 將 NanoKVM 連接到您的電腦。',
@@ -126,6 +144,16 @@ const zh_tw = {
serialPort: 'Serial Port',
serialPortPlaceholder: '請輸入 Serial Port',
baudrate: 'Baud rate',
parity: 'Parity',
parityNone: 'None',
parityEven: 'Even',
parityOdd: 'Odd',
flowControl: 'Flow control',
flowControlNone: 'None',
flowControlSoft: 'Soft',
flowControlHard: 'Hard',
dataBits: 'Data bits',
stopBits: 'Stop bits',
confirm: '確定'
},
wol: {
@@ -143,10 +171,16 @@ const zh_tw = {
},
power: {
title: '電源',
showConfirm: 'Confirmation',
showConfirmTip: 'Power operations require an extra confirmation',
reset: '重新啟動',
power: '電源',
powerShort: '電源 (短按)',
powerLong: '電源 (長按)'
powerLong: '電源 (長按)',
resetConfirm: '執行重新啟動按鍵操作?',
powerConfirm: '執行電源按鍵操作?',
okBtn: '是',
cancelBtn: '取消'
},
settings: {
title: '設定',
@@ -160,14 +194,23 @@ const zh_tw = {
image: '映像版本',
imageTip: 'NanoKVM 系统镜像版本',
deviceKey: '設備序號',
community: '社群'
community: '社群',
hostname: '主機名稱',
hostnameUpdated: '已更新主機名稱. 請重新啟動以生效',
ipType: {
Wired: '有線',
Wireless: '無線',
Other: '其他'
}
},
appearance: {
title: '外觀',
display: '顯示',
language: '語言',
menuBar: '選單列',
menuBarDesc: '是否在選單列中顯示圖案'
menuBarDesc: '是否在選單列中顯示圖案',
webTitle: '網頁標題',
webTitleDesc: '自訂網頁標題'
},
device: {
title: '設備',
@@ -193,10 +236,40 @@ const zh_tw = {
description: '啟用SSH',
tip: '啟用前請務必設定強密碼(帳號 - 更改密碼)'
},
tls: {
description: '啟用 HTTPS',
tip: '啟用 HTTPS 可以提高安全性,但可能會增加延遲,特別是使用 MJPEG 編碼格式時。'
},
advanced: '進階設定',
swap: {
title: 'Swap',
disable: '停用',
description: '設定Swap檔大小',
tip: "啟用此功能可能會減少SD卡的使用壽命"
},
mouseJiggler: {
title: 'Mouse Jiggler',
description: '避免遠端主機進入休眠狀態',
disable: '停用',
absolute: '絕對模式',
relative: '相對模式'
},
mdns: {
description: '啟用mDNS服務',
tip: "若無需求,建議關閉此功能"
},
hdmi: {
description: '啟用 HDMI/螢幕 輸出'
},
hidOnly: 'HID-Only 模式',
disk: '虛擬隨身碟',
diskDesc: '在遠端主機上連接虛擬隨身碟',
network: '虛擬網卡',
networkDesc: '在遠端主機上新增虛擬網卡'
networkDesc: '在遠端主機上新增虛擬網卡',
reboot: '重新啟動',
rebootDesc: '你確定要重新啟動 NanoKVM?',
okBtn: '是',
cancelBtn: '取消'
},
tailscale: {
title: 'Tailscale',
@@ -229,7 +302,7 @@ const zh_tw = {
deviceIP: '裝置 IP',
account: '帳號',
logout: '登出',
logout2: '確認登出?',
logoutDesc: '確認登出?',
uninstall: '移除 Tailscale',
okBtn: '確認',
cancelBtn: '取消'
@@ -242,26 +315,33 @@ const zh_tw = {
available: '有可用更新。確定要更新嗎?',
updating: '正在更新。請稍等...',
confirm: '確定',
cancel: '取消'
cancel: '取消',
preview: '預覽更新',
previewDesc: '搶先體驗新功能和改進',
previewTip:
'請注意,預覽版本可能包含錯誤或功能不完整!'
},
account: {
title: '帳號',
webAccount: '網頁帳號',
password: '密碼',
updateBtn: '修改',
logoutBtn: '登出'
logoutBtn: '登出',
logoutDesc: '你確定要登出嗎?',
okBtn: '是',
cancelBtn: '取消'
}
},
error: {
title: '我們遇到了一些問題',
refresh: '重新整理',
refresh: '重新整理'
},
fullscreen: {
toggle: '進入全螢幕模式',
toggle: '進入全螢幕模式'
},
menu: {
collapse: '收起選單',
expand: '展開選單',
expand: '展開選單'
}
}
};

View File

@@ -85,6 +85,7 @@ export const KeyboardCodes: Map<string, number> = new Map([
['Backslash', 49],
['IntlBackslash', 49],
['Backquote_azerty', 100],
['IntlBackslash_qwertz', 100],
['Semicolon', 51],
['Quote', 52],

View File

@@ -46,6 +46,7 @@ export const VirtualKeyboard = () => {
const languages = [
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'French' },
{ value: 'de', label: 'Deutsch' },
{ value: 'ru', label: 'Russian' }
];
@@ -65,6 +66,7 @@ export const VirtualKeyboard = () => {
const layoutMap = new Map([
['en', 'default'],
['ru', 'rus'],
['de', 'qwertz'],
['fr', 'azerty']
]);
@@ -126,6 +128,17 @@ export const VirtualKeyboard = () => {
// all other labels use their own code
return KeyboardCodes.get(base);
}
if (keyboardLanguage === 'de' && key.endsWith('_qwertz')) {
const base = key.replace('_qwertz', '');
// Tausch
if (base === 'KeyZ') return KeyboardCodes.get('KeyY');
if (base === 'KeyY') return KeyboardCodes.get('KeyZ');
if (base === 'IntlBackslash') return KeyboardCodes.get('IntlBackslash_qwertz');
// all other labels use their own code
return KeyboardCodes.get(base);
}
const specialKey = specialKeyMap.get(key);
if (specialKey) {

View File

@@ -29,6 +29,14 @@ export const keyboardOptions = {
'{shiftleft} RusZ RusX RusC RusV RusB RusN RusM RusComma RusPeriod RusSlash {shiftright}',
'{controlleft} {winleft} {altleft} {space} {altright} {winright} {menu} {controlright}'
],
qwertz: [
'{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12',
'Backquote_qwertz Digit1_qwertz Digit2_qwertz Digit3_qwertz Digit4_qwertz Digit5_qwertz Digit6_qwertz Digit7_qwertz Digit8_qwertz Digit9_qwertz Digit0_qwertz Minus_qwertz Equal_qwertz {backspace}',
'{tab} KeyQ_qwertz KeyW KeyE_qwertz KeyR KeyT KeyZ_qwertz KeyU KeyI KeyO KeyP BracketLeft_qwertz BracketRight_qwertz',
'{capslock} KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon_qwertz Quote_qwertz Backslash_qwertz {enter}',
'{shiftleft} IntlBackslash_qwertz KeyY_qwertz KeyX KeyC KeyV KeyB KeyN KeyM Comma_qwertz Period_qwertz Slash_qwertz {shiftright}',
'{controlleft_qwertz} {winleft} {altleft} {space} {altright_qwertz} {winright} {menu} {controlright_qwertz}'
],
azerty: [
'{escape} F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12',
'Backquote_azerty Digit1_azerty Digit2_azerty Digit3_azerty Digit4_azerty Digit5_azerty Digit6_azerty Digit7_azerty Digit8_azerty Digit9_azerty Digit0_azerty Minus_azerty Equal_azerty {backspace}',
@@ -55,6 +63,39 @@ export const keyboardOptions = {
Equal: '+<br/>=',
'{backspace}': 'Backspace',
Backquote_qwertz: '^<br/>°',
Digit1_qwertz: '1<br/>!',
Digit2_qwertz: '2<br/>"',
Digit3_qwertz: '3<br/>§',
Digit4_qwertz: '4<br/>$',
Digit5_qwertz: '5<br/>%',
Digit6_qwertz: '6<br/>&',
Digit7_qwertz: '7{/',
Digit8_qwertz: '8[(',
Digit9_qwertz: '9])',
Digit0_qwertz: '0}=',
Minus_qwertz: 'ß\\?',
Equal_qwertz: '´<br/>`',
KeyY_qwertz: 'Y',
KeyZ_qwertz: 'Z',
KeyQ_qwertz: 'Q<br/>@',
KeyE_qwertz: 'E<br/>€',
BracketRight_qwertz: '+~*',
Backslash_qwertz: '#<br/>\'',
IntlBackslash_qwertz: '<|>',
Comma_qwertz: ',<br/>;',
Period_qwertz: '.<br/>:',
Slash_qwertz: '-<br/>_',
'{controlleft_qwertz}': 'STRG',
'{altright_qwertz}': 'ALT GR',
'{controlright_qwertz}': 'STRG',
BracketLeft_qwertz: 'Ü',
Semicolon_qwertz: 'Ö',
Quote_qwertz: 'Ä',
'{tab}': 'Tab',
KeyQ: 'Q',
KeyW: 'W',
@@ -275,7 +316,12 @@ export const specialKeyMap = new Map([
['{arrowright}', 'ArrowRight'],
['{arrowleft}', 'ArrowLeft'],
['{arrowdown}', 'ArrowDown'],
['{arrowup}', 'ArrowUp']
['{arrowup}', 'ArrowUp'],
//DE
['{controlleft_qwertz}', 'ControlLeft'],
['{controlright_qwertz}', 'ControlRight'],
['{altright_qwertz}', 'AltRight']
]);
// modifier keys
@@ -289,7 +335,12 @@ export const modifierKeys = [
'{controlright}',
'{altright}',
'{metaright}',
'{winright}'
'{winright}',
// DE-spezifische Modifier-Tokens (für qwertz-Layout)
'{controlleft_qwertz}',
'{controlright_qwertz}',
'{altright_qwertz}'
];
// double line display buttons

View File

@@ -1,5 +1,5 @@
import { ChangeEvent, useRef, useState } from 'react';
import { Button, Input, Modal, message, type InputRef } from 'antd';
import { Button, Input, Modal, Select, message, type InputRef } from 'antd';
import clsx from 'clsx';
import { useSetAtom } from 'jotai';
import { ClipboardIcon, ClipboardPasteIcon } from 'lucide-react';
@@ -8,6 +8,8 @@ import { useTranslation } from 'react-i18next';
import { paste } from '@/api/hid';
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
const { Option } = Select;
const { TextArea } = Input;
export const Paste = () => {
@@ -20,6 +22,7 @@ export const Paste = () => {
const [isLoading, setIsLoading] = useState(false);
const [errMsg, setErrMsg] = useState('');
const [isReadingClipboard, setIsReadingClipboard] = useState(false);
const [langue, setLangue] = useState('en');
const inputRef = useRef<InputRef>(null);
@@ -87,7 +90,7 @@ export const Paste = () => {
if (isLoading) return;
setIsLoading(true);
paste(inputValue)
paste(inputValue, langue)
.then((rsp) => {
if (rsp.code !== 0) {
setErrMsg(rsp.msg);
@@ -144,6 +147,13 @@ export const Paste = () => {
<div className="pb-3 text-xs text-neutral-500">{t('keyboard.tips')}</div>
<div className="space-y-2">
<Select
value={langue}
onChange={(value) => setLangue(value)}
style={{ width: '100%', marginBottom: '12px' }} >
<Option value="en">{t('keyboard.dropdownEnglish')}</Option>
<Option value="de">{t('keyboard.dropdownGerman')}</Option>
</Select>
<div className="flex items-center gap-2">
<Button
icon={<ClipboardPasteIcon size={16} />}