nixos/i18n: allow imperative locale management via localectl

Signed-off-by: Kajus Naujokaitis <kajusn@gmail.com>
This commit is contained in:
Kajus Naujokaitis
2026-03-26 14:45:04 +02:00
parent 4c0afb54e5
commit bc2562685c
4 changed files with 69 additions and 35 deletions
+18 -6
View File
@@ -6,6 +6,7 @@
}:
let
cfg = config.console;
i18nCfg = config.i18n;
makeColor = i: lib.concatMapStringsSep "," (x: "0x" + lib.substring (2 * i) 2 x);
@@ -162,8 +163,10 @@ in
environment.systemPackages = [ pkgs.kbd ];
# Let systemd-vconsole-setup.service do the work of setting up the
# virtual consoles.
environment.etc."vconsole.conf".source = vconsoleConf;
# virtual consoles. Skip when imperative so localectl can manage it.
environment.etc."vconsole.conf" = lib.mkIf (!i18nCfg.imperativeLocale) {
source = vconsoleConf;
};
# Provide kbd with additional packages.
environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
@@ -209,13 +212,22 @@ in
"systemd-vconsole-setup.service"
];
# When imperative, seed /etc/vconsole.conf on first boot from declared
# defaults so the keymap isn't lost before localectl is ever used
systemd.tmpfiles.rules = lib.mkIf i18nCfg.imperativeLocale [
"C /etc/vconsole.conf - - - - ${vconsoleConf}"
];
systemd.services.reload-systemd-vconsole-setup = {
description = "Reset console on configuration changes";
wantedBy = [ "multi-user.target" ];
restartTriggers = [
vconsoleConf
(consoleEnv pkgs.kbd)
];
restartTriggers =
lib.optionals (!i18nCfg.imperativeLocale) [
vconsoleConf
]
++ [
(consoleEnv pkgs.kbd)
];
reloadIfChanged = true;
serviceConfig = {
RemainAfterExit = true;
+45 -21
View File
@@ -5,24 +5,29 @@
...
}:
let
cfg = config.i18n;
localeConf = pkgs.writeText "locale.conf" ''
LANG=${cfg.defaultLocale}
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n}=${v}") cfg.extraLocaleSettings)}
'';
sanitizeUTF8Capitalization =
lang: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] lang);
aggregatedLocales =
lib.optionals (config.i18n.defaultLocale != "C") [
"${config.i18n.defaultLocale}/${config.i18n.defaultCharset}"
lib.optionals (cfg.defaultLocale != "C") [
"${cfg.defaultLocale}/${cfg.defaultCharset}"
]
++ lib.pipe config.i18n.extraLocaleSettings [
++ lib.pipe cfg.extraLocaleSettings [
# See description of extraLocaleSettings for why is this ignored here.
(x: lib.removeAttrs x [ "LANGUAGE" ])
(lib.mapAttrs (n: v: (sanitizeUTF8Capitalization v)))
# C locales are always installed
(lib.filterAttrs (n: v: v != "C"))
(lib.mapAttrsToList (LCRole: lang: lang + "/" + (config.i18n.localeCharsets.${LCRole} or "UTF-8")))
(lib.mapAttrsToList (LCRole: lang: lang + "/" + (cfg.localeCharsets.${LCRole} or "UTF-8")))
]
++ (map sanitizeUTF8Capitalization (
lib.optionals (builtins.isList config.i18n.extraLocales) config.i18n.extraLocales
lib.optionals (builtins.isList cfg.extraLocales) cfg.extraLocales
))
++ (lib.optional (builtins.isString config.i18n.extraLocales) config.i18n.extraLocales);
++ (lib.optional (builtins.isString cfg.extraLocales) cfg.extraLocales);
in
{
###### interface
@@ -33,8 +38,8 @@ in
glibcLocales = lib.mkOption {
type = lib.types.path;
default = pkgs.glibcLocales.override {
allLocales = lib.elem "all" config.i18n.supportedLocales;
locales = config.i18n.supportedLocales;
allLocales = lib.elem "all" cfg.supportedLocales;
locales = cfg.supportedLocales;
};
defaultText = lib.literalExpression ''
pkgs.glibcLocales.override {
@@ -143,6 +148,16 @@ in
'';
};
imperativeLocale = lib.mkEnableOption ''
imperative locale and keyboard management via localectl.
When enabled, locale and keyboard settings can be changed at runtime
using `localectl set-locale` and `localectl set-keymap`.
When disabled (the default), these settings are managed declaratively
through {option}`i18n.defaultLocale`, {option}`i18n.extraLocaleSettings`,
and {option}`console.keyMap`.
'';
};
};
@@ -154,8 +169,8 @@ in
lib.optional
(
!(
(lib.subtractLists config.i18n.supportedLocales aggregatedLocales) == [ ]
|| lib.elem "all" config.i18n.supportedLocales
(lib.subtractLists cfg.supportedLocales aggregatedLocales) == [ ]
|| lib.elem "all" cfg.supportedLocales
)
)
''
@@ -171,25 +186,34 @@ in
environment.systemPackages =
# We increase the priority a little, so that plain glibc in systemPackages can't win.
lib.optional (config.i18n.supportedLocales != [ ]) (lib.setPrio (-1) config.i18n.glibcLocales);
lib.optional (cfg.supportedLocales != [ ]) (lib.setPrio (-1) cfg.glibcLocales);
environment.sessionVariables = {
LANG = config.i18n.defaultLocale;
LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
}
// config.i18n.extraLocaleSettings;
# When imperative, leave LANG/LC_* to pam_systemd so /etc/set-environment
# does not override what localectl wrote to /etc/locale.conf.
// lib.optionalAttrs (!cfg.imperativeLocale) (
{
LANG = cfg.defaultLocale;
}
// cfg.extraLocaleSettings
);
systemd.globalEnvironment = lib.mkIf (config.i18n.supportedLocales != [ ]) {
LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
systemd.globalEnvironment = lib.mkIf (cfg.supportedLocales != [ ]) {
LOCALE_ARCHIVE = "${cfg.glibcLocales}/lib/locale/locale-archive";
};
# /etc/locale.conf is used by systemd.
environment.etc."locale.conf".source = pkgs.writeText "locale.conf" ''
LANG=${config.i18n.defaultLocale}
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings
)}
'';
# If imperative, see below
environment.etc."locale.conf" = lib.mkIf (!cfg.imperativeLocale) {
source = localeConf;
};
# When imperative, seed /etc/locale.conf on first boot from declared defaults
# so the system doesnt fall back to C.UTF-8
systemd.tmpfiles.rules = lib.mkIf cfg.imperativeLocale [
"C /etc/locale.conf - - - - ${localeConf}"
];
};
}
-4
View File
@@ -86,10 +86,6 @@ in
# This way services are restarted when tzdata changes.
systemd.globalEnvironment.TZDIR = tzdir;
systemd.services.systemd-timedated.environment = lib.optionalAttrs (config.time.timeZone != null) {
NIXOS_STATIC_TIMEZONE = "1";
};
environment.etc = {
zoneinfo.source = tzdir;
}
+6 -4
View File
@@ -826,10 +826,12 @@ in
# because either the overlay is mutable (and users can legitimately change
# values without them being overridden) or it is immutable and systemd will
# suggest to only make runtime changes.
systemd.services."systemd-localed".environment = lib.mkIf (!config.system.etc.overlay.enable) {
SYSTEMD_ETC_LOCALE_CONF = "/etc/static/locale.conf";
SYSTEMD_ETC_VCONSOLE_CONF = "/etc/static/vconsole.conf";
};
systemd.services."systemd-localed".environment =
lib.mkIf (!config.system.etc.overlay.enable && !config.i18n.imperativeLocale)
{
SYSTEMD_ETC_LOCALE_CONF = "/etc/static/locale.conf";
SYSTEMD_ETC_VCONSOLE_CONF = "/etc/static/vconsole.conf";
};
systemd.services."systemd-timedated".environment =
lib.mkIf (!config.system.etc.overlay.enable && config.time.timeZone != null)
{