NextBSD · research · 2026-09-24
Whether the loader's two-second countdown should become a language list, what the loader can actually draw, and what Apple in fact does — which turns out not to be this.
Options research, not a commitment. Companion to nextbsd/nextbsd#505 (silent normal boot). Every Apple claim below was read out of released source or verified on a Darwin 27 machine; the two places I am inferring are marked.
These are not interchangeable. Each has a different ceiling, and the ceiling is what decides this.
PlatformLang / PlatformLangCodes EFI varsloader_lua.efi, the 2 s countdowncolor.lua has 8 colours (BLACK=0…WHITE=7)kenv for the kernelgfx_fb_putimage(png_t*…) via pnglite, gfx_fb_drawrect()vt_font covers — see belowkenvAppleLanguages, AppleLocale, NVRAM, CDIS.customApple never asks for a language in the bootloader. It keeps a hint in firmware and asks once, graphically, in userland. The whole chain is observable, and most of it is reachable from open source:
prev-lang:kbd, verified on this Mac as
en:0 — <language>:<keyboard-layout-id>. Language Chooser errors with
prev-lang:kbd is not two parts. if it is malformed. A sibling skip-language-setup
boolean suppresses the question. Open source: system_cmds/nvram/nvram.c:113 reaches it with
IORegistryEntryFromPath(…, "IODeviceTree:/options") and IORegistryEntrySetCFProperty()
— plain IOKit, which NextBSD has./var/db/.AppleSetupDone (ask only if absent), /var/db/.AppleSetupResume, and
/var/db/.RunLanguageChooserToo — which is not a stamp but an XML plist,
{ kGuessedCountries = ["US"]; kVoiceOver = false; }.loginwindow makes the call, in
-[Login1 setLanguagePrefIfNeeded]. Its log format string names every input it weighs:
should_run_language_chooser enter, appleSetupDonePresent:%d, … runLanguageChooserTooPresent:%d,
skipLanguageChooserNVRAMValue:%d, prevlangNVRAMSetup:%d.AppleLanguages, AppleLocale,
AppleTextDirection, Country into .GlobalPreferences.plist —
written to both /Library/Preferences and root's own copy, because for a root-context process
root's copy wins the CFPreferences search order. Plus /var/log/CDIS.custom
(LANGUAGE=en, still present on Darwin 27) and the NVRAM variable from step 1.This is the hard constraint, and it is not a matter of effort.
color.lua defines BLACK=0 through WHITE=7. The grey we designed the splash
around does not exist there; the nearest is ANSI 7, #aaaaaa. That is survivable — inverted, it
looks like this, which is honestly not bad:
Note lines 6–10 have already lost their diacritics, and line 11 is what Japanese, Korean, Greek and Arabic become. That is the real problem, and it is the font, not the palette.
FreeBSD ships exactly these vt fonts — gallant.hex, terminus-b32.hex,
tom-thumb.hex, vgarom-8x8/8x14/8x16/16x32.hex and the two vgarom-thin
variants. All are Latin-plus-box-drawing. None has a CJK, Arabic, Hebrew, Devanagari, Thai or Vietnamese
glyph. Counting Apple's own list against that:
This is exactly why languagesetup(1) has an -English mode that prints ISO 639 codes
instead of names — Apple hit the same wall in a text context and solved it by giving up on endonyms. A loader list
would have to do the same, and a language picker that cannot show you your own language's name is not doing its
job.
Worth being precise, because "beyond Lua" does have an answer. stand/common/gfx_fb.c is a genuine
framebuffer layer, already in the tree:
gfx_fb_putimage(png_t *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) — it decodes
and blits PNG, via the vendored pnglite. This is how the boot logo is drawn.gfx_fb_drawrect(), plus a full teken terminal on top.struct vt_font tg_font, setup_font(), font_lookup() — it loads vt
bitmap fonts at run time, and font_list_t fonts means more than one can be present.So a grey splash with the cube as a PNG is straightforwardly possible today. Text in 29 of the 44 languages is not, unless someone builds and ships a vt font with CJK coverage — a bitmap CJK font is on the order of megabytes in the loader's own format, loaded before the kernel, for one screen seen once.
#505 is "nothing on the console between the loader and the login prompt", and you were emphatic: not one line
of text unless single-user or verbose. A language menu is not a splash — it is an interactive text screen, it
requires io.getchar(), and it necessarily waits for a human. Adding it would make the boot louder and
slower in exchange for a question asked once per machine lifetime.
The loader can still participate silently, and should: PlatformLang is already parsed in
stand/efi/libefi/env.c:597, inside efi_print_global() — today it is display-only, reachable
from efi-show. Promoting it from "printable" to "handed to the kernel as a kenv hint" is a
small, silent change.
menu.lua already has the machinery. But 8 colours, no endonyms for 29 of 44,
and it breaks #505. Not recommended.unicode.pf2 plus gfxterm renders CJK in a
boot menu. It is also GPL, a whole second bootloader, and nothing like Apple. Rejected, but it is
the honest answer to "has anyone done this graphically".PlatformLang silently and passes a kenv hint; a durable choice lives in an
EFI variable (our prev-lang:kbd equivalent); the question is asked once in userland, full screen, with
real endonyms, gated by a .AppleSetupDone-shaped marker. Strongest of the five.PlatformLang where env.c:597 already parses it, and set a
kenv hint. No UI, no countdown change, no console output.<lang>:<kbd> shape. Reachable from
userland; survives reinstall, which a file in /var/db does not..AppleSetupDone pattern, so it is asked once.AppleLanguages, AppleLocale,
AppleTextDirection, Country — to both /Library/Preferences and root's copy,
via CFPreferencesSetValue + CFPreferencesSynchronize on the exact domain triple
(AnyApplication, AnyUser, CurrentHost; note it is CurrentHost that means
/Library/Preferences). Plus /var/log/CDIS.custom, which Darwin still ships.LANG question, deliberately. See §7 — this is a decision, not a detail.Darwin does not keep AppleLanguages and LANG in sync, and I want that on the record
before we build something that does. Verified on this machine: with AppleLocale = en_US and
LANG unset, setlocale(LC_ALL,"") returns C and the codeset is US-ASCII.
login(1) sets only TERM, SHELL, HOME, LOGNAME,
USER, PATH — no locale.login.conf at all. In shell_cmds, su.c:90 and
env.c:36 wrap <login_cap.h> in #ifndef __APPLE__, and Libc ships no
login_cap. FreeBSD's lang/charset capabilities have no Darwin counterpart —
so the obvious BSD mechanism is not available to copy./etc/rc sourcing
/var/log/CDIS.custom and exporting LANGUAGE, for localized startup messages._cupsAppleLocale() in
cups/cups/language.c:248, Apache-2 and vendorable, including the hand-maintained exception table
({"en","en_US"}, {"nb","no"}, {"zh-Hans","zh_CN"}…) that CF will not give you.
Tellingly cupsd exports both LANG and a separate APPLE_LANGUAGE rather
than choosing — Apple concluded LANG could not carry an Apple language id faithfully.Modern macOS's only system-wide LANG is a hardcoded fallback in the open-source
zsh/zprofile: if [ -z "$LANG" ]; then export LANG=C.UTF-8; fi. It never consults
AppleLanguages.
LANG is a deliberate divergence from
Darwin. I think it is the right one — a BSD userland where ls cannot print a filename in your
language is worse than being faithful — but it should be written down as a choice, not slipped in as an
implementation detail.Locale column checked against the 293 locales (83 UTF-8) installed on this Darwin 27 machine; NextBSD ships FreeBSD's set, so re-check before relying on it. "needs glyphs" means no vt font we ship has the characters.
| tag | name, as the user reads it | English | POSIX locale | loader |
|---|---|---|---|---|
| en | English | English | en_AU.UTF-8 | ASCII |
| en-GB | English (UK) | English | en_GB.UTF-8 | ASCII |
| zh-Hans | 简体中文 | Chinese, Simplified | zh_CN.UTF-8 | needs glyphs |
| zh-Hant | 繁體中文 | Chinese, Traditional | zh_CN.UTF-8 | needs glyphs |
| zh-HK | 繁體中文(香港) | Chinese, Hong Kong | zh_HK.UTF-8 | needs glyphs |
| fr | Français | French | fr_BE.UTF-8 | needs glyphs |
| fr-CA | Français canadien | French, Canadian | fr_CA.UTF-8 | needs glyphs |
| de | Deutsch | German | de_AT.UTF-8 | ASCII |
| ja | 日本語 | Japanese | ja_JP.UTF-8 | needs glyphs |
| es | Español | Spanish | es_AR.UTF-8 | needs glyphs |
| es-419 | Español (Latinoamérica) | Spanish, Latin American | es_AR.UTF-8 | needs glyphs |
| it | Italiano | Italian | it_CH.UTF-8 | ASCII |
| ko | 한국어 | Korean | ko_KR.UTF-8 | needs glyphs |
| pt-BR | Português (Brasil) | Portuguese, Brazilian | pt_BR.UTF-8 | needs glyphs |
| pt-PT | Português (Portugal) | Portuguese | pt_PT.UTF-8 | needs glyphs |
| ru | Русский | Russian | ru_RU.UTF-8 | needs glyphs |
| nl | Nederlands | Dutch | nl_BE.UTF-8 | ASCII |
| sv | Svenska | Swedish | sv_FI.UTF-8 | ASCII |
| nb | Norsk bokmål | Norwegian | nb_NO.UTF-8 | needs glyphs |
| da | Dansk | Danish | da_DK.UTF-8 | ASCII |
| fi | Suomi | Finnish | fi_FI.UTF-8 | ASCII |
| pl | Polski | Polish | pl_PL.UTF-8 | ASCII |
| tr | Türkçe | Turkish | tr_TR.UTF-8 | needs glyphs |
| uk | Українська | Ukrainian | uk_UA.UTF-8 | needs glyphs |
| cs | Čeština | Czech | cs_CZ.UTF-8 | needs glyphs |
| sk | Slovenčina | Slovak | sk_SK.UTF-8 | needs glyphs |
| hu | Magyar | Hungarian | hu_HU.UTF-8 | ASCII |
| ca | Català | Catalan | ca_AD.UTF-8 | needs glyphs |
| hr | Hrvatski | Croatian | hr_HR.UTF-8 | ASCII |
| el | Ελληνικά | Greek | el_GR.UTF-8 | needs glyphs |
| he | עברית | Hebrew | he_IL.UTF-8 | needs glyphs |
| ro | Română | Romanian | ro_RO.UTF-8 | needs glyphs |
| th | ไทย | Thai | — | needs glyphs |
| vi | Tiếng Việt | Vietnamese | — | needs glyphs |
| id | Indonesia | Indonesian | — | ASCII |
| ms | Melayu | Malay | — | ASCII |
| ar | العربية | Arabic | ar_AE.UTF-8 | needs glyphs |
| hi | हिन्दी | Hindi | hi_IN.UTF-8 | needs glyphs |
| sl | Slovenščina | Slovenian | sl_SI.UTF-8 | needs glyphs |
| ru-RU | Русский | Russian | ru_RU.UTF-8 | needs glyphs |
| en-AU | English (Australia) | English | en_AU.UTF-8 | ASCII |
| en-IN | English (India) | English | en_IN.UTF-8 | ASCII |
| es-MX | Español (México) | Spanish | es_MX.UTF-8 | needs glyphs |
| de-AT | Deutsch (Österreich) | German | de_AT.UTF-8 | needs glyphs |
The four with no locale at all — Indonesian, Malay, Thai, Vietnamese — would be offered a UI language with no
working LC_CTYPE to match, which is its own small decision. es-419 (Latin American Spanish)
has no POSIX equivalent by construction; it is a region-less region.
loginwindow grows it — it is the natural owner, and it
already has the framebuffer and the fonts.nextbsd-installer should ever ask, or inherit
silently.en:0) and I have deliberately not
designed for it.languagesetup's exact write set. The logic is in the closed
InternationalSupport.framework, which lives in the dyld shared cache, so the four-key list is inferred
from the resulting plist plus binary strings — not from a syscall trace.