Locale, timezone, and keyboard
There are two ways to set these, and both are supported on purpose:
- Before a build — declare them in the layered config. They are resolved, recorded in the image’s provenance, and baked in. Nothing asks a question at boot.
- On a running image —
dpkg-reconfigurethe relevant package, exactly as on any Debian system, with no network. This works because the image already shipslocales,keyboard-configuration, andconsole-setup, and because the locales are already compiled onto the disk.
The second is the reason the first is not enough. A pre-built image is something you hand to someone else. Typing on a German keyboard must not cost them a rebuild, or getting the board onto a network.
The knobs
| field | layer | default | what it sets |
|---|---|---|---|
locale | base.toml | C.UTF-8 | LANG in /etc/locale.conf |
locales_generate | base.toml | 17 widely-spoken locales | extra locales compiled into the image |
timezone | base.toml | UTC | the /etc/localtime symlink |
keymap | devices/<board>.toml | none | /etc/default/keyboard (the XKB variables) |
Each is overridable in a recipe. resolve and doctor additionally take --locale,
--locale-gen (repeatable), --timezone, and --keymap. You can therefore see what a
different choice resolves to before committing it to config:
boot2deb resolve asus-c201/forky \
--locale de_DE.UTF-8 --timezone Europe/Berlin --keymap de
build takes none of them, and that is the design. An image’s localization comes from
the config its lock was resolved against. Changing what an image ships therefore means
changing base.toml or the recipe, not a flag at build time. resolve closes the loop
rather than leaving you to find that out. When an override it accepts is one build
does not, it prints the recipe to write, with the keys already filled in.
note: --locale, --timezone, --keymap are resolve-only — `build` reads those axes from
the config its lock was resolved against, not from a flag. To build this point, write it
down: recipes/asus-c201/<leaf>.toml with
device = "asus-c201"
locale = "de_DE.UTF-8"
timezone = "Europe/Berlin"
keymap = "de"
then `boot2deb update asus-c201/<leaf>` to pin it.
resolve shows what a build will bake in:
locale : C.UTF-8 (generated: C.UTF-8, en_US.UTF-8, en_GB.UTF-8, de_DE.UTF-8, ...)
timezone : UTC
keymap : us [pc105]
Why the locale and the keymap live on different layers
The locale and the timezone are distro policy: no board has an opinion about them,
so they sit in base.toml.
A keymap is different. Whether a console keymap configures anything at all is a property of the hardware. The C201 and the C100P are laptops with keyboards under the user’s hands and a US layout. The Turing RK1 and the H96 are headless, and a layout declared for a console nobody types at is a claim the config cannot back.
So keymap sits on the device, and a headless board simply omits it. boot2deb then
writes no /etc/default/keyboard, and Debian’s own default (pc105 / us) stands.
The Chromebit CS10 shows what the field is really asking. It has no keyboard at all, and
it declares keymap = "us" anyway, because it is not headless. It drives an HDMI
console, and a USB keyboard is the only way to type at it. The question is “does a
console layout configure anything here?”, not “does the board ship keys”. It does, so it
answers.
You can still pass --keymap to a headless board. console-setup ships on every image,
so a keymap is always actionable. Plugging a USB keyboard into the RK1’s HDMI console
is a real thing to do. A headless board just has no reason to default one.
Why the default locale is C.UTF-8 and not en_US.UTF-8
C.UTF-8 is a complete UTF-8 locale built into glibc. It is also neutral: this project
targets no one country, and a US locale is not a better default than any other.
en_US.UTF-8 is nevertheless generated into every image, and that is not a
contradiction — see the next section.
Which languages ship
Every image carries these compiled, in addition to the system locale:
en_US en_GB de_DE fr_FR es_ES it_IT nl_NL pt_BR pl_PL uk_UA ru_RU
vi_VN ja_JP ko_KR zh_CN zh_HK zh_TW — all .UTF-8.
It is a set of widely-spoken languages, not a complete one, and it is deliberately not just English. Two reasons it can afford to be this wide:
- The glibc locale archive shares data aggressively. Measured on forky/arm64,
/usr/lib/locale/locale-archiveis 2.9 MiB withC.UTF-8+en_US.UTF-8alone, and 19.2 MiB with the full set. That is about 1 MiB per added language, not the several MiB a standalone locale suggests. - A locale can only be compiled at build time.
locale-genruns during the image build, and no package a user installs later will generate one. A first-run desktop wizard therefore offers exactly the languages the image chose. A graphical installer that lists one language is showing the truth about the image, not a bug in the desktop.
Anything outside the set is still one dpkg-reconfigure locales away with no network —
see Adding a language.
The Setting locale failed warning
SSH into a fresh board and you can see this:
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
Nothing on the image is broken. Debian’s stock openssh-server ships
AcceptEnv LANG LC_*, so your client forwards its own LANG into the session. If
that locale was never generated on the target, setlocale() fails and every Perl-based
tool says so.
That is one of the reasons en_US.UTF-8 leads locales_generate: it makes the common
client’s forwarded locale resolve.
The shipped set covers most clients, but it is not a general fix. A client forwarding a locale outside it still warns, and chasing every locale by pre-generating it is whack-a-mole. The actual fix is that the locale is changeable, which is what the rest of this page is about. To silence it for one session without changing anything:
LANG=C.UTF-8 ssh board
Do not “fix” this by removing AcceptEnv LANG LC_* from sshd_config. It is
standard Debian behavior, and silently dropping it surprises anyone who relies on it.
Changing them on a running image, offline
All three are the ordinary Debian commands. None of them needs the network, because the packages and the locale data are already on the disk.
Locale. dpkg-reconfigure is the authoritative path — it generates the locale and
sets the default:
sudo dpkg-reconfigure locales # tick the locales to generate, then pick the default
localectl also works on a boot2deb image, and it is worth knowing why. Debian builds
systemd-localed with locale-gen support, so localectl set-locale adds the locale
to /etc/locale.gen and runs locale-gen itself. That happens only if
/usr/sbin/locale-gen exists, which means only if the locales package is installed.
On an image without it, localectl would set a LANG naming a locale that was never
generated. boot2deb ships locales, so:
sudo localectl set-locale LANG=de_DE.UTF-8
is safe here. Reconnect for it to take effect on your session.
Timezone. Either command works, and both write the /etc/localtime symlink. That
is the only thing that reads as the system timezone (forky’s tzdata no longer keeps an
/etc/timezone file at all):
sudo timedatectl set-timezone America/New_York
sudo dpkg-reconfigure tzdata # the menu-driven equivalent
The zone decides how the clock is rendered, not whether it is right. These boards have no battery-backed RTC, so the clock is stale for the first seconds of every boot. See The clock and time sync for what the image does about that, and how to point it at a different NTP server.
Console keymap.
sudo dpkg-reconfigure keyboard-configuration # then: sudo setupcon
setupcon applies the new layout to the current console without a reboot.
Why dpkg-reconfigure opens on the right values
boot2deb writes /etc/locale.gen, /etc/locale.conf, /etc/default/keyboard, and the
/etc/localtime symlink before the packages that own them are configured, not
after. Debian’s locales, keyboard-configuration, and tzdata each seed their
debconf answers from those exact files when they install. The shipped files, the
debconf database, and the console-setup cached keymap therefore all agree.
The practical consequence: dpkg-reconfigure locales on the running board opens with
your locales already ticked and your default already selected — not Debian’s. Had
the files been written after the packages, they would still be correct on disk, and
debconf would still be holding Debian’s defaults underneath them.
Adding a language
Two places to do it, depending on whether you want it on this board or on every image you build.
On a running board, no network needed:
sudo dpkg-reconfigure locales # tick the extra languages, keep or change the default
Tick as many as you like and leave the default alone if you only want the language available. A desktop’s language picker reads the generated set, not the default. The new locales are compiled on the spot.
In an image, so every board built from it ships the language: edit
locales_generate in base.toml (all images) or in the recipe (that build point only).
There is no build-time flag for it, deliberately — see The knobs. Each
entry is a full locale name with its codeset, so sv_SE.UTF-8 and not sv_SE.
Resolution rejects the bare form rather than let locale-gen fail mid-build. The system
locale is always generated whether or not it appears in the list, so it never needs
repeating.
Valid names come from /usr/share/i18n/SUPPORTED on any Debian system. It lists
language_TERRITORY.codeset pairs, not language names, so search by the two-letter
language code and take the .UTF-8 line:
grep '^sv_.*UTF-8' /usr/share/i18n/SUPPORTED # sv_FI.UTF-8, sv_SE.UTF-8
Budget roughly 1 MiB of image per added language, and check the result with
resolve before building:
boot2deb resolve h96-max-m9/forky | grep locale
There is no locales-all option here, and that is on purpose: it carries every locale
Debian has for 231 MiB installed.
Notes for the curious
/etc/locale.conf, not/etc/default/locale. Debian makes the latter a symlink to the former, andsystemd-tmpfilesre-asserts that link with a forcing rule (L+). A regular file written at/etc/default/localeis therefore deleted and replaced by the symlink on the next boot. Writing the symlink’s target satisfies every reader.pam_envreads through the link,systemd/localectlread it directly, and thelocalespackage’s config script reads that path to learn the current default.- The system locale is always generated, even
C.UTF-8, which glibc would provide ungenerated. Thelocalespackage builds the choice list thatdpkg-reconfigure localesoffers for the default locale out of/etc/locale.gen. A system locale missing from that file is one the user cannot see or re-select on the board. - Not
locales-all. It carries every locale Debian has, at 231 MiB installed. The three packages boot2deb ships cost about 44 MiB installed (measured on forky/arm64), plus 19.2 MiB for the generated locale archive — call it ~63 MiB on the image. On a 2 GiB rootfs that is about 3%, and it compresses well into the shipped.img.xz.