When I’m talking about keyboard shortcuts to things like Ctrl-S or even one key shortcuts like ‘A’ (used to Archive an email on Gmail). On the other side the mnemonics, known as hotkeys or the underlined letters in menus or other GUI controls are clearly something that should be localized and I not going to argue about this.

I’ve did some research to see how different companies are dealing with the shortcuts when people are using different languages and different keyboard layouts. Here are a short list of my results:

  • If possible the shortcut is not changed
  • If a key is not available on a specific layout we should try to use the same physical position
  • There should be no link between application language and keyboard layouts. It’s a mistake to consider that application language will match the keyboard layout. I’ll give you just an example: in Romania over 95% of the computers are using US keyboard layout and the other 5% are using one of the 4-5 different Romanian keyboard layouts.
  • some people are using multiple keyboard layouts - usually they are power users and we can’t ignore them
  • keyboard layouts can be switched anytime
  • characters are safe to be used only if they are Latin

Physical position rules

I think that the physical position of a key is more important than the letter printed on it. Here are my arguments:

  • human brain is learning the shortcuts by hand movements just like the dog in Pavlov’s experiment
  • it’s compatible with the usage of multiple keyboard layouts

But it’s not always so simple: we can’t expect that the French users would swap the usage of Ctrl-Q with Ctrl-A ( Quit vs Select All) just because they are using different layouts. So a basic rule would be to stick to the layout for letters and numbers BUT try to use the physical position for other keys.

Anyway is high likely that people using multiple layouts would choose alike layouts (derived from the same root).

One-to-one key matching

Because the number of keys is the same we have to find a way of remapping the keys so all shortcuts working on US to be usable on other layouts. When I’m referring to keys, I’m referring to the hardware keys.

Case study

Here is an example:

key category US French Turkish Arabic Details
letters (26) Q swapped to A moved where = was same pos. diff char We should follow layout movements for all letter keys.
signs (OEMs) (11-12) -_ (minus after 0) now -_ is moved where =+ was obtained with Shift-/ - Some of the OEM keys are used in pairs and it's more important to keep key-pairs together than keeping the symbols on them.
signs (OEMs) (11-12) =+ (plus before backspace) =+ is moved where / was. note1: as you can observe these keys are no longer in pairs. moved in the place of '" note1: as you can observe these keys are no longer in pairs. -
numeric(10) 1 1 (produced with Shift) 1 1 We should not request French users to press Ctrl+Shift+1 instead of Ctrl+1 just because their keyboard are producing numbers only with shift pressed.
others (...) F1, Backspace, Space, numpad ones, .... We should have no problems with these if we are using virtual keys/scan codes.

How do I make OS X tell me that the key pressed by the user was ‘q’ when current layout is Arabic?

There are few rules that have to be followed:

  • we are not allowed to assume it’s a ‘q’ just because the key code is the same, maybe it’s a Arabic DVORAK keyboard!.
  • the shortcuts cannot be hardcoded (user can edit them)

On Windows it does work 98% using virtual key codes and you can use them to extract this info, but on OS X the key codes (even Apple call them sometimes virtual key codes) they are some kind of scan codes. For the rest of 2% you have to do some small hacks with the OEMs.

Current code

case kEventRawKeyDown:
  {
    unsigned char macChar;
    unsigned int keycode;
    unsigned int modifiers;
    unsigned int keyboardtype;
    OSStatus eventParamStatus = GetEventParameter( inEvent, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(macChar), NULL, &macChar);
    GetEventParameter(inEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof(keycode), NULL, &keycode);
    GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers);
    GetEventParameter(inEvent, kEventParamKeyboardType, typeUInt32, NULL, sizeof(keyboardtype), NULL, &keyboardtype);
   // kEventParamKeyMacCharCodes
   // for Latin characters it does behaves like Windows
   // for !Latin it does not return consistent data: on Arabic it does return the same as on US but on French it does return French specific characters for the OEM keys.
...
), NULL, &keyboardtype);
   // kEventParamKeyMacCharCodes
   // for Latin characters it does behaves like Windows
   // for !Latin it does not return consistent data: on Arabic it does return the same as on US but on French it does return French specific characters for the OEM keys.
   ...