Sealed reader locales
Large models have localised the conversation around programming. A Thai computer scientist can ask for help in Thai. The durable artifact — the source, the compiler errors, the standard library — stayed English-shaped, so English proficiency became a gate to computer science rather than to conversation.
A reader locale is Faber's answer: the compiler renders keywords, primitive
type names and diagnostics in the reader's language, without forking the
semantics. A Thai file and a Latin file lower to exactly the same HIR. The
mechanism that renders Faber to Thai is the same one that renders it to Rust —
HIR → surface — and neither is privileged.
One file, one locale#
A source file is lexed under exactly one reader pack. Keywords from another pack are not keywords there, they are ordinary identifiers, so a file that mixes surfaces does not compile as a mixture — it fails. Sealing is what makes a locale surface reliable: inside a Thai file, every keyword is Thai, and a reader never has to hold two vocabularies at once.
fn duplica(int n) → int {
return n * 2
}return is the English spelling of the Latin keyword return; under the Latin
pack it is just a name, and the file is rejected. The fix is to pick one
surface for the file.
The glyphs never move#
Only words are localised. The glyphs (←, →, ⇥, ≡, ∪, ↦) and the
type-first order are identical in every rendering, and identifiers are
preserved byte-for-byte. A reader who knows Faber in one locale can read it in
any locale, because the structure is the part that does not change. Numerals
stay ASCII everywhere.
It is never a one-way door#
Any surface can become any other, including canonical Latin, at any time.
faber format --locale la re-emits the canonical surface, so a localised file
is never trapped in one vocabulary. This is the same canonical-versus-sugar
property as the glyph law: one defined form, many renderings of it.
What it looks like#
The same source, in canonical Latin and rendered into Thai:
fn salve(string name) → string {
const string msg ← "Salve, §!"(name)
return msg
}
main {
print salve("munde")
}$ faber convert --from la --to th-TH
ฟังก์ชัน salve(ข้อความ name) → ข้อความ {
คงที่ ข้อความ msg ← "Salve, §!"(name)
คืน msg
}
เริ่ม {
บันทึก salve("munde")
}The keywords and type names changed; the glyphs, the order, and the identifier
salve did not. The program still runs:
$ faber run
Salve, munde!Eight packs ship today: en (the base English surface), la (canonical
Latin), and th-TH, zh-Hans, zh-Hant, ar, hi, vi as the reference
set. The reference locales are chosen for architectural stress — a spaceless
script, right-to-left runs, half/full-width pairs and NFKC width collapse —
not for population.
Diagnostics are structured facts with stable codes and named arguments, so the
same message renders in any pack. The full tables are in
Reader locales.