Skip to content

Add a Keyboard Language

This HowTo walks through adding a new keyboard-language layout — a new xx-YY entry in the on-screen language picker (see Languages & Unicode Input for the user-facing side).

The key idea: PolyKybd only relabels the keycaps; the host OS generates the characters. So a layout is really three things — a per-key codepoint map drawn on the OLEDs, a font that can render those codepoints, and a host hint that switches the OS input source. The firmware never composes characters and needs no input-method engine.

It’s a hands-on job: you edit a spreadsheet in a spreadsheet program, run a couple of code generators, and check the result in a preview tool. Nothing exotic, but there are several files to keep in step. The authoritative deep-dive — with the full decision table and every locale already implemented — lives in the firmware repo at keyboards/polykybd/lang/FUTURE_LANGUAGES.md.

All paths below are relative to keyboards/polykybd/ in the qmk_firmware fork (branch PolyKybd), except the host files, which live in PolyKybdHost.

Before you start

Set up the firmware toolchain (see Build & Flash from Source) and a Python environment with openpyxl cogapp pyyaml fonttools pillow. You only need the pinned fontconvert build if the language introduces a new font or flag (see Display Graphics & Fonts).

Steps

  1. Classify the layout. This decides how much work it is:

    KindWhenWork
    FoldThe OS layout is plain US-QWERTY (e.g. Indonesian)No key column — falls back to en-US; just a host fold xx=us
    CloneA distinct layout identical to one already presentCopy the source column; inherits its AltGr legends
    Clone + overrideA base layout plus a few extra AltGr letters (Welsh, Irish)Clone, then override the handful of keys
    New mappingA genuinely different keymap or new script (Sami, Pashto)Transcribe from xkb; maybe a new font

    Rule of thumb: identical keymap → fold; different keymap → its own column.

  2. Get the layout data from an authoritative source. xkb is ground truth (/usr/share/X11/xkb/symbols/<file>). Its level-3 (AltGr) legends match Linux and Windows exactly; macOS Option diverges, and the firmware carries one legend set, so treat AltGr as Linux/Windows-canonical. The HID protocol carries a fixed 2+2-char code, so an ISO-639-2/3 language needs a 2-char pseudo-code stored verbatim (Hawaiian hw, Sorani ku-IQ).

  3. Add the per-key column. The language layouts live in a real spreadsheet, lang/lang_lut.xlsx — open it in LibreOffice Calc (or Excel) to see how the existing languages are laid out, one colour-coded column group per language, one row per key. You add your language as a new column of per-key codepoints, either by hand for a small clone/override or by extending one of the lang/_gen_*_cols.py helper scripts (model it on the existing ones) that emit the column and patch the sheet for you.

    lang_lut.xlsx open in LibreOffice Calc — colour-coded column groups, one per language, with a row per physical key

  4. Handle fonts, if needed. Latin-1, Latin Extended-A/B are already present, so most European languages need zero new font work. A new script (Cherokee, Ethiopic) needs a Noto source font added to the font pipeline and a new category — see Display Graphics & Fonts and the Font Packs page.

  5. Regenerate the code-generated files. The spreadsheet feeds several files through cog. Run them all (or the language ends up half-wired and “not selectable”):

    Terminal window
    cd keyboards/polykybd
    bash lang/run_cog.sh # re-cogs lang_lut, named glyphs, keycode helper, hid_com, poly_keymap
  6. Rebuild the region tables and flags. Regenerate the region-tab tables (which submenu the language appears under) and the country-flag glyph:

    Terminal window
    bash fonts/gen-lang-fonts.sh # appends the new flag to flag_fonts.h
  7. Wire the host. In PolyKybdHost, the country→region map (polyhost/services/lang_regions.py) already covers every standard ISO country, so most languages need no edit. Add a fold entry only for a country with no native xkb layout. A new pseudo-code (non-ISO-639-1 language) must be appended to the frozen index table iso_lang_country.py — which is byte-identical across three repos (firmware, host, test rig), so copy it to all three and verify with cmp.

  8. Verify the keycaps without hardware. Use the preview tool — it reproduces the firmware draw exactly, including the baseline shift, and flags clipped glyphs and overlaps:

    Terminal window
    cd PolyKybdHost/tools
    python oled_preview.py --lang xx-YY # contact sheet of all keys
    python oled_preview.py --lang xx-YY --key KC_Q --overshoot 8 --cell-scale 8 # measure clipping
    python oled_preview.py --lang xx-YY --channels # base/Shift/AltGr overlap check
  9. Build and commit. Compile both variants (split72 and split42 share one keymap, so a language lands on both at once) and commit the firmware, host, and test-rig changes on their respective branches. Document the addition in lang/FUTURE_LANGUAGES.md.

Common pitfalls

  • A key with a NULL base legend falls back to en-US entirely for that key — its Shift/AltGr preview is never shown. Any key carrying a preview must also set a base.
  • AltGr descenders (ŷ ą ę, µ, ¶) can clip the bottom of the 40 px panel. Prefer a per-glyph nudge over lowering a whole category. Always measure with oled_preview --overshoot.
  • Re-cog all the generated files, and keep the frozen iso_lang_country.py identical in all three repos.

See Contributing for how to open the pull requests.