diff --git a/server/service/hid/paste.go b/server/service/hid/paste.go index 1f8e701..587e1b0 100644 --- a/server/service/hid/paste.go +++ b/server/service/hid/paste.go @@ -20,25 +20,25 @@ type PasteReq struct { } func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { - // wenn kein lang angegeben → Standardmap zurück + // if no language is specified → return base map if lang == "" { return base } - // immer Kopie erstellen + // always create a copy of the base map m := copyMap(base) switch lang { case "de": - // Y tauschen + // swap Y m['y'] = Char{0, 29} m['Y'] = Char{2, 29} - // Z tauschen + // swap Z m['z'] = Char{0, 28} m['Z'] = Char{2, 28} - // deutsche Sonderzeichen hinzufügen oder remappen + // add German special characters or remap them m['\u00E4'] = Char{0, 52} // ä m['\u00C4'] = Char{2, 52} // Ä m['\u00F6'] = Char{0, 51} // ö @@ -47,8 +47,8 @@ func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { m['\u00DC'] = Char{2, 47} // Ü m['\u00DF'] = Char{0, 45} // ß - //Tauschen - m['^'] = Char{0, 53} // muss doppelt sein + // swap special characters + m['^'] = Char{0, 53} // must be double m['/'] = Char{2, 36} // Shift + 7 m['('] = Char{2, 37} // Shift + 8 m['&'] = Char{2, 35} // Shift + 6 @@ -75,7 +75,7 @@ func LangueSwitch(base map[rune]Char, lang string) map[rune]Char { m['-'] = Char{0, 56} // Shift + - m['_'] = Char{2, 56} // Shift + - - //neu + // new special characters m['\u00B4'] = Char{0, 46} // ´ m['\u00B0'] = Char{2, 53} // ° m['\u00A7'] = Char{2, 32} // § diff --git a/web/src/i18n/locales/de.ts b/web/src/i18n/locales/de.ts index 5658f42..5afeec6 100644 --- a/web/src/i18n/locales/de.ts +++ b/web/src/i18n/locales/de.ts @@ -74,7 +74,8 @@ const de = { virtual: 'Tastatur', ctrlaltdel: 'Ctrl+Alt+Del', dropdownEnglish: 'Englisch', - dropdownGerman: 'Deutsch' + dropdownGerman: 'Deutsch', + dropdownRussian: 'Russisch' }, mouse: { title: 'Maus', diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index dce9b5a..5c2c834 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -80,7 +80,8 @@ const en = { 'Clipboard permission denied. Please allow clipboard access in your browser.', clipboardReadError: 'Failed to read clipboard', dropdownEnglish: 'English', - dropdownGerman: 'German' + dropdownGerman: 'German', + dropdownRussian: 'Russian' }, mouse: { title: 'Mouse', diff --git a/web/src/i18n/locales/ru.ts b/web/src/i18n/locales/ru.ts index 9eb3b77..da88138 100644 --- a/web/src/i18n/locales/ru.ts +++ b/web/src/i18n/locales/ru.ts @@ -73,7 +73,10 @@ const ru = { placeholder: 'Текст для ввода', submit: 'Вставить', virtual: 'Клавиатура', - ctrlaltdel: 'Ctrl+Alt+Del' + ctrlaltdel: 'Ctrl+Alt+Del', + dropdownEnglish: 'Английский', + dropdownGerman: 'Немецкий', + dropdownRussian: 'Русский' }, mouse: { title: 'Мышь', diff --git a/web/src/pages/desktop/menu/keyboard/paste.tsx b/web/src/pages/desktop/menu/keyboard/paste.tsx index 5d4ff1b..29b17d7 100644 --- a/web/src/pages/desktop/menu/keyboard/paste.tsx +++ b/web/src/pages/desktop/menu/keyboard/paste.tsx @@ -28,7 +28,8 @@ export const Paste = () => { const languages = [ { value: 'en', label: t('keyboard.dropdownEnglish') }, - { value: 'de', label: t('keyboard.dropdownGerman') } + { value: 'de', label: t('keyboard.dropdownGerman') }, + { value: 'ru', label: t('keyboard.dropdownRussian') } ]; useEffect(() => { @@ -37,7 +38,7 @@ export const Paste = () => { function onChange(e: ChangeEvent) { const value = e.target.value; - setStatus(isASCII(value) ? '' : 'error'); + setStatus(isValidForLanguage(value, langue) ? '' : 'error'); setInputValue(value); } @@ -61,12 +62,50 @@ export const Paste = () => { setIsReadingClipboard(false); } } + + // Extended RU → EN translation including punctuation + function translateRuToEnWithPunctuation(value: string): string { + const letterMap: Record = { + 'ё': '`', + 'й': 'q', 'ц': 'w', 'у': 'e', 'к': 'r', 'е': 't', 'н': 'y', 'г': 'u', 'ш': 'i', + 'щ': 'o', 'з': 'p', 'х': '[', 'ъ': ']', + 'ф': 'a', 'ы': 's', 'в': 'd', 'а': 'f', 'п': 'g', 'р': 'h', 'о': 'j', 'л': 'k', + 'д': 'l', 'ж': ';', 'э': '\'', + 'я': 'z', 'ч': 'x', 'с': 'c', 'м': 'v', 'и': 'b', 'т': 'n', 'ь': 'm', 'б': ',', 'ю': '.', + }; + + const punctuationMap: Record = { + '"': '@', + '№': '#', + ';': '$', + ':': '^', + '?': '&', + 'Ё': '~', + '/': '|', + '.': '/', + ',': '?', + }; + + return Array.from(value).map((ch) => { + const lower = ch.toLowerCase(); + if (letterMap[lower]) { + const translated = letterMap[lower]; + return ch === lower ? translated : translated.toUpperCase(); + } + if (punctuationMap[ch]) { + return punctuationMap[ch]; + } + return ch; + }).join(''); + } function submit() { if (isLoading || !inputValue) return; setIsLoading(true); - paste(inputValue, langue) + const textToSend = langue === 'ru' ? translateRuToEnWithPunctuation(inputValue) : inputValue; + + paste(textToSend, langue) .then((rsp) => { if (rsp.code !== 0) { setErrMsg(rsp.msg); @@ -91,9 +130,28 @@ export const Paste = () => { setIsKeyboardEnable(!open); } - function isASCII(value: string) { - for (let i = 0; i < value.length; i++) { - if (value.charCodeAt(i) > 127) { + function isValidForLanguage(value: string, selectedLangue: string) { + const isRussian = selectedLangue === 'ru'; + + for (const ch of value) { + const code = ch.codePointAt(0) ?? 0; + + if (isRussian) { + // For Russian language, allow Cyrillic letters and special characters + // that can be typed on Russian keyboard (but not English letters) + if ( + (code >= 0x0410 && code <= 0x042F) || // (А-Я) + (code >= 0x0430 && code <= 0x044F) || // (а-я) + code === 0x0401 || // (Ё) + code === 0x0451 || // (ё) + (code >= 0x20 && code <= 0x7E && !(code >= 0x41 && code <= 0x5A) && !(code >= 0x61 && code <= 0x7A)) // Special chars, digits, space, but not English letters + ) { + continue; + } + return false; + } else { + // For English/German, only allow ASCII + if (code <= 0x7F) continue; return false; } }