Renderingla

Reader locale

Reader locale is Faber's system for rendering source code, compiler diagnostics, and language keywords in the reader's human language — without forking the semantics. A Thai programmer can read and write Faber source with Thai keywords, receive compiler errors in Thai, and collaborate through the same HIR as a Latin or Chinese user. The mechanism that localises code from Latin to Thai is the same mechanism that emits code to Rust: HIR → surface — neither is privileged.

Problem

Large language models have localised the conversation around programming — a Thai computer scientist can ask an LLM for help in Thai — but not the durable artifact. The generated code, APIs, compiler errors, and documentation remain English-shaped. English proficiency becomes a gate to computer science, not merely to conversation.

Reader locale is the design response: the language a human uses to understand Faber — source, diagnostics, and optionally stdlib spellings — without English as a prerequisite. It is not application internationalisation (complete string-matrix coverage). It is reader dialect support: opt-in, partial packs over a semantic core that does not fork.

> Product thesis: English should not be the required review language for > software intent. LLMs have localised the conversation around programming; > reader locale localises the durable artifact.

How it works

A reader-locale pack maps Faber keywords, primitive type spellings, and diagnostic templates into a target language. Packs are TOML files with three tables:

  • [keywords] — maps keyword names to their localised spellings
  • [types] — maps primitive type names to localised spellings
  • [diagnostics.*] — maps diagnostic codes to localised message templates
  • [llm] — system prompt snippets and exemplars for LLM code generation

The compiler validates packs against a generated Latin scaffold — every keyword and type must have a defined spelling or explicitly inherit from Latin. Missing rows produce visible fallback rather than silent gaps.

Select a locale at the command line or in faber.toml:

faber check --reader-locale th-TH program.fab
# faber.toml
[reader]
locale = "zh-Hans"

What localises and what does not

LayerIn the HIR?Behaviour
Keywords, types, paired phrasesYesLossless across all renderings
Glyphs ← → ∴ ≡ ∪ ⇥Yes (invariant)Identical in every rendering
Type-first structureYesIdentical in every rendering
NumeralsASCII only in all locales
CommentsNoOut of compiler scope; LLM-mediated, opt-in
Identifier namesNoPreserved byte-for-byte
Stdlib spellingsNoPer-locale overlay

The critical architectural guarantee: any locale surface can become any other, including Latin, at any time. A localised Faber file is never a trap because it is never the only form the code can take. faber format --canonical is exactly faber format --reader-locale=la.

Shipped packs

Seven packs ship with Radix today:

CodeLanguageScriptStatus
laLatina (Latin)LatinCanonical
th-THไทยThaiReference proof
zh-Hans简体中文Simplified ChineseCoverage proof
zh-Hant繁體中文Traditional ChineseCoverage proof
arالعربيةArabicCoverage proof
hiहिन्दीDevanagariCoverage proof
viTiếng ViệtVietnamese (Latin)Coverage proof

The five non-Latin reference locales are chosen for **collective architectural stress** — together they force every Unicode and emission problem the substrate must survive:

LocaleAccessArchitectural stress
th-THHighSpaceless script — the tokeniser stress test
zh-Hans / zh-HantVery highPaired keywords; sibling pack inheritance; NFKC width collapse
arHighRight-to-left; bidi isolation in diagnostics
hiVery highMatra/virama clusters; Indic numerals
viHighHeavy diacritics on Latin script; NFKC edge cases

The reference set is chosen for architectural coverage, not population. Population alone would prove nothing the substrate did not already handle.

Localised source examples

Each of the six non-Latin locales has a complete Faber package under examples/reader-locale/ with localised source, diagnostic test cases, and a faber.toml manifest. The same greet program rendered across all shipped locales:

Latin lacanonical

functio salve(textus nomen) → textus {
    fixum textus msg ← "Salve, §!"(nomen)
    redde msg
}

incipit {
    fixum textus m ← salve("munde")
    nota m
}

The canonical rendering. faber format --canonical is exactly faber format --reader-locale=la. Latin keywords map to themselves; type names are the canonical spellings.

ไทย th-THreference proof

ฟังก์ชัน salve(ข้อความ nomen) → ข้อความ {
    ค่าคงที่ ข้อความ msg ← "Salve, §!"(nomen)
    คืนค่า msg
}

เริ่มต้น {
    ค่าคงที่ ข้อความ m ← salve("มุนเด")
    แจ้ง m
}

The access-wedge proof. Thai is a spaceless script — no inter-word boundaries — making it the tokeniser stress test and the original architectural driver for the reader-locale system. Every token boundary must be resolved by the lexer through keyword matching alone.

简体中文 zh-Hans

函数 问候(文本 名字) → 文本 {
    常量 文本 问候语 ← "你好,§!"(名字)
    返回 问候语
}

入口 {
    常量 文本 消息 ← 问候("世界")
    显示 消息
}

Paired keywords (如果/否则 for si/secus) requiring pack keyword groups; full/half-width punctuation; NFKC width collapse at lex entry. The hardest LLM emission case due to CJK tokeniser boundaries. A sibling pack for Traditional Chinese (zh-Hant) inherits and overrides zh-Hans roots.

العربية ar

دالة salve(نص nomen) → نص {
    ثابت نص msg ← "مرحبا، §!"(nomen)
    أعد msg
}

بداية {
    ثابت نص m ← salve("عالم")
    اعرض m
}

Right-to-left script embedded inside a logical-order LTR code block. Keywords are wrapped in <bdi> (bidirectional isolation) in the compiler's HTML diagnostic output to prevent RFO (right-follows-left) distortion. The raw source uses Arabic script in logical order; the display layer handles bidi presentation.

हिन्दी hi

फलन salve(पाठ nomen) → पाठ {
    स्थिर पाठ msg ← "Salve, §!"(nomen)
    लौटा msg
}

आरंभ {
    स्थिर पाठ m ← salve("जगत")
    दिखा m
}

Devanagari script with matra/virama consonant clusters. Proves the path for the wider Indic family — Bengali, Tamil, Telugu inherit the same shaping infrastructure — though pack authoring for each remains separate work. Indic numeral glyphs (०-९) are not accepted in numeric literals; ASCII digits are preserved across all locales.

Tiếng Việt vi

hàm chào(vănbản tên) → vănbản {
    hằng vănbản lời_chào ← "Xin chào, §!"(tên)
    trả lời_chào
}

bắtđầu {
    hằng vănbản thông_điệp ← chào("thế giới")
    in thông_điệp
}

The control case: Latin-script but not English. Heavy diacritic load (ế, ệ, ả) stresses NFKC edge cases in the lexer. Prevents an architecture that works on exotic scripts but is unproven on Latin-with-diacritics. Identifiers use Vietnamese words (chào, tên, lời_chào, thông_điệp), preserved byte-for-byte by the compiler.

> The glyphs (← → ∴ ≡ ∪ ⇥), > structural positions, and identifier names are identical across all six > renderings above. Only the keywords and type names change. The HIR is exactly > the same program — the compiler treats all six as equivalent. Rendering Faber > to Thai is the same compiler operation as rendering it to Rust: > HIR → surface, with neither privileged.

Localised diagnostics

Diagnostics are structured facts before prose. Each diagnostic carries a stable code (LEX###, PARSE###, SEM###, WARN###) and named arguments; the pack owns the rendered template text. This means the diagnostic renderer can emit messages in any locale without changing the diagnostic infrastructure.

The reader-locale example packages include diagnostic test cases — type mismatches, undefined variables, non-ASCII numbers — proving the full pipeline is locale-aware:

  • examples/reader-locale/vi/src/type-mismatch.fab
  • examples/reader-locale/vi/src/undefined-variable.fab
  • examples/reader-locale/vi/src/non-ascii-number.fab
  • examples/reader-locale/vi/src/keyword-suggestion.fab
  • examples/reader-locale/vi/src/keyword-edit-distance.fab

Bidi isolation is built in: Arabic keywords inside logical-order LTR code blocks are wrapped in <bdi> elements in HTML output, preventing the RFO (right-follows-left) distortion that would otherwise make RTL runs unreadable.

Status

LayerStatus
Pack schema, aliases, inheritance, validation, diagnostics, LLM artifactsShipped
Pack-aware lexing, type resolution, manifest/CLI selection, visible fallbackShipped
Pack-owned diagnostic rendering, faber explain, bidi-isolated displayShipped
Canonical Faber formattingShipped
Localised Faber re-emission (format --reader-locale)Partial
Stdlib gloss overlays, measured LLM emission fidelity, complete locale coverageDeferred
Multilingual documentation generationProposed

The substrate prerequisite — NFKC normalisation at lex entry — has landed. Keyword tables, diagnostic named-args, the renderer, and pack delivery are shipped. The north-star layers (localised re-emission, stdlib glosses, LLM emission benchmarks, generated multilingual docs) remain explicitly partial or deferred.

References

1. radix/docs/design/reader-locale.md — full design document (69 KB) 2. examples/reader-locale/ — 6 locale packages with localised source 3. stdlib/reader/*/pack.toml — 7 installed pack definitions 4. radix/crates/radix/src/reader_locale.rs — runtime implementation 5. radix/docs/design/faber-canonical-surface.md — canonical mode and faber format 6. radix/docs/factory/lex-nfkc-normalization/ — NFKC prerequisite delivery