LmCast :: Stay tuned in

Switch between three keyboard languages

Recorded: May 30, 2026, 2:01 a.m.

Original Summarized

Switch between three keyboard languages | mnaoumov.dev

Skip to content
mnaoumov.dev
Posts

Tags

About
Archives Search Go back Switch between three keyboard languages 17 Dec, 2023 | Hi folks.
On the regular basis I use three keyboards: English, Russian and Ukrainian. I need them quite often and I need a quick way to switch between them. Default Alt + Shift and Win + Space are not good enough. Cyclical changes don’t work well sometimes and I have to click more and look in the notification error for the language label.
There are many tools for switching languages such as Punto Switcher but all that I tried work fine with two languages and don’t suggest anything nice for three language users. There are some advices how to use three languages but I didn’t find them practical.
My need is to be able to quickly switch to the desired language regardless of the current language selected.
I came up with the following idea:

Left Control to switch to English language
Right Control to switch to Russian language
Right Alt to switch to Ukrainian language

But also I need to keep the default behavior of those buttons and the shortcuts using those keys.
I would like to share the solution I came up with:
I built a script for AutoHotkey
#Requires AutoHotKey v2.0
#SingleInstance Force
#Warn All, MsgBox
#UseHook

; Based on https://www.autohotkey.com/boards/viewtopic.php?f=6&t=18519

setDefaultKeyboard(localeId) {
; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
static SPI_SETDEFAULTINPUTLANG := 0x005A

; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
static SPIF_SENDWININICHANGE := 2

; https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-inputlangchangerequest
static WM_INPUTLANGCHANGEREQUEST := 0x0050

; https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-inputlangchange
static WM_INPUTLANGCHANGE := 0x0051

; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayouta
static KLF_ACTIVATE := 0x00000001

; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayouta
pwszKLID := Format("{:08x}", localeId)
Flags := KLF_ACTIVATE
keyboardLayout := DllCall("LoadKeyboardLayout", "Str", pwszKLID, "Int", Flags)

; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
uiAction := SPI_SETDEFAULTINPUTLANG
uiParam := 0
pvParam := keyboardLayout
fWinIni := SPIF_SENDWININICHANGE
DllCall("SystemParametersInfo", "UInt", uiAction, "UInt", uiParam, "UPtr", pvParam, "UInt", fWinIni)

windowIds := WinGetList()

for windowId in windowIds {
try {
PostMessage(WM_INPUTLANGCHANGEREQUEST, 0, keyboardLayout, , "ahk_id" . windowId)
PostMessage(WM_INPUTLANGCHANGE, 0, keyboardLayout, , "ahk_id" . windowId)
} catch {
; Skip access denied windows
}
}
}

; https://stackoverflow.com/questions/14701095/how-to-get-keyboard-layout-name-from-a-keyboard-layout-identifier
; https://learn.microsoft.com/en-us/globalization/windows-keyboard-layouts

localeId_English_USA := 0x0409
localeId_Russian_Russia := 0x0419
localeId_Ukrainian_Ehnanced := 0x20422

~LControl: {
global isAltGr := false
Sleep 100
if (isAltGr) {
return
}

SetDefaultKeyboard(localeId_English_USA)
}

~RControl: SetDefaultKeyboard(localeId_Russian_Russia)

~RAlt: SetDefaultKeyboard(localeId_Ukrainian_Ehnanced)

~LControl & RAlt: {
global isAltGr := true
SetDefaultKeyboard(localeId_Ukrainian_Ehnanced)
}
Gotchas here:

I’ve added links to every WinAPI function and constant for better maintainability. Surprisingly most of WinAPI examples I see on the Internet are written very poorly.
Tilde prefix ~ (https://www.autohotkey.com/docs/v2/Hotkeys.htm)

When the hotkey fires, its key’s native function will not be blocked (hidden from the system).

Physical Right Alt button in some keyboard layouts (in my case, both Russian and Ukrainian) act as AltGr, which is an equivalent of LControl & RAlt. Therefore it requires special check to distinguish LControl & RAlt (as a physical button of Right Alt) from fair Left Control. As I figured out, it triggers LControl handler first and then LControl & RAlt, that’s why I had to add a global variable isAltGr and a small delay to handle this difference.

Stay tuned!
UPD: After some time, I found the selected shortcuts not so pleasant to use, then I realized, there is a key that I used only a few time in my life: Caps Lock, so I decided to utilize it. Now Caps Lock + 1 – English, Caps Lock + 2 – Russian, Caps Lock + 3 – Ukrainian. It’s a bit unusual to have Caps Lock as a modifier, so it took my muscle memory a bit of time to kick in, but now I am happily using it.
The changes in the script I put before
CapsLock & 1:: SetDefaultKeyboard(localeId_English_USA)
CapsLock & 2:: SetDefaultKeyboard(localeId_Russian_Russia)
CapsLock & 3:: SetDefaultKeyboard(localeId_Ukrainian_Ehnanced) migrated-from-wordpress autohotkey windows keyboard productivity
Back To Top
Share this post on: Share this post via WhatsApp Share this post on Facebook Share this post on X Share this post via Telegram Share this post on Pinterest Share this post via email Next Post Regex to find the end of file (EOF) in Visual Studio Code mnaoumov.dev on GitHub Copyright © 2026  |  All rights reserved.

The author addresses the persistent difficulty of switching between three keyboard languages—English, Russian, and Ukrainian—efficiently, noting that standard methods such as Alt Shift, Win Space, or cyclical changes are inadequate, and existing tools often only support two languages effectively. The core requirement is the ability to switch to the desired language rapidly, irrespective of the currently active setting.

To solve this, the author proposes a custom key mapping based on the control and alt keys: Left Control for English, Right Control for Russian, and Right Alt for Ukrainian, while ensuring that the default functionality of these keys is maintained.

The solution is implemented using an AutoHotkey script that interfaces with the Windows operating system via WinAPI calls to manage keyboard layout changes. The script details a function designed to set the system’s default input language by manipulating system parameters and loading keyboard layouts. This function handles the complex steps required to dynamically update the input language for active windows using messages such as WM_INPUTLANGCHANGEREQUEST and WM_INPUTLANGCHANGE. The script defines specific locale identifiers for English USA, Russian Russia, and Ukrainian Enhanced.

A critical aspect of the implementation involves managing potential conflicts, particularly concerning physical key behavior. The author recognizes that the physical Right Alt key on certain layouts may function as AltGr, necessitating a mechanism to differentiate between a simple Left Control press and a combined Right Alt press (specifically, Left Control and Right Alt simultaneously) to accurately trigger the desired language switch. The script incorporates logic using a global variable to handle this distinction and introduces a slight delay to manage the sequence of events triggered by these modifier keys.

Subsequently, the author revised the method of language switching for improved usability. Finding the custom hotkeys less intuitive, the author decided to utilize the Caps Lock key as a less frequently used modifier. The updated solution maps the language switches to combinations involving the Caps Lock key: Caps Lock plus one switches to English, Caps Lock plus two switches to Russian, and Caps Lock plus three switches to Ukrainian, providing an alternative, personalized mechanism for rapid language switching.