Merge commit '66b8e51' into lang/foundations (phase 26.2: Key, k""/kn"", derived @Text keys, the en.po checks, english_left, the schema's lang section); reseeded, bootstrap-cfree fixpoint holds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 23:41:54 +03:00
commit e3788c0a43
50 changed files with 141272 additions and 111304 deletions

View file

@ -0,0 +1,3 @@
# i18n.ludic's items: a @Text name is a Key, derived when the row gives none (items.rope.name)
rope { weight: 0.4 }
lamp { name: k"items.lamp.name", weight: 1.2 }

View file

@ -0,0 +1,41 @@
# i18n.ludic — keys, not English (LANGUAGE.md, "Text keys"). A text the player reads is named by a
# key, k"pause.resume", of the builtin type Key: not a string, and no string is one. Its English is
# lang/en.po (package.ludic says `lang "lang" en`), and the compiler checks every key against it. A
# registry's @Text Key field a row leaves out is filled with its derived key - <registry>.<row>.<field>,
# or the registry's @TextKey prefix. At run time a Key is its text after a marker byte (1, or 2 for a
# plural kn"..."), so ludic.i18n's tr() is a cast; here tr, trf and trn are stand-ins that show the key.
#
# Running it prints: pause.resume yes no 13 12 hud.day(3) catch.count[2](trout) items.rope.name items.lamp.name steps.gather_wood.hint
program I18n {
property Item {
key: string = ""
@Text name: Key = null
weight: float = 0.0
}
registry Items of Item as IT from "data/i18n_items.lres"
property StepKind {
key: string = ""
@Text hint: Key = null
}
@TextKey("steps") registry StepKinds of StepKind as SK
def StepKinds gather_wood { }
# ludic.i18n's three, stood in for: the key's text without its marker
function tr(key: Key) -> string {
let s = string(key)
return s[1..len(s)]
}
function trf(key: Key, a: string) -> string { return `{tr(key)}({a})` }
function trn(key: Key, n: int, a: string) -> string { return `{tr(key)}[{n}]({a})` }
function yn(b: bool) -> string {
if b { return "yes" }
return "no"
}
entry {
let resume = k"pause.resume"
let same = resume == k"pause.resume"
let other = resume == k"pause.close"
var out = `{tr(resume)} {yn(same)} {yn(other)} {len(string(resume))} {len(string(kn"catch.count"))} {trf(k"hud.day", "3")}`
out = out + ` {trn(kn"catch.count", 2, "trout")} {tr(Items[IT_ROPE].name)} {tr(Items[IT_LAMP].name)} {tr(StepKinds[SK_GATHER_WOOD].hint)}`
print(out)
}
}

View file

@ -0,0 +1,9 @@
# i18n_ui.ludic — a component's template against lang/en.po: every t('key', ...) in a {hole} names
# a key en.po has, and gives as many values as its English has holes. Words a player would read that
# are not a key are "English left" (a warning, counted by ludic deps as english_left) - here the
# "Paused" line; an element with translate="no" is left alone. `ludicc --check` reports both.
import "ludic.ui"
program I18nUi {
import "parts/Pause.ludic"
entry { print(1) }
}

View file

@ -0,0 +1,40 @@
# The example's source language: the English of every key i18n.ludic and i18n_ui.ludic use.
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. On the pause menu: returns to play.
msgid "pause.resume"
msgstr "Resume"
#. On the pause menu: closes the menu and returns to play.
msgid "pause.close"
msgstr "Close"
#. On the map screen: closes the map.
msgid "map.close"
msgstr "Close"
#. The day on the HUD. {1} = the day number
msgid "hud.day"
msgstr "Day {1}"
#. A catch, counted. {1} = how many, {2} = the species
msgid "catch.count"
msgid_plural "catch.count"
msgstr[0] "{1} {2} caught"
msgstr[1] "{1} {2} "
"caught"
#. An item's name: a coil of rope.
msgid "items.rope.name"
msgstr "Rope"
#. An item's name: a brass lamp.
msgid "items.lamp.name"
msgstr "Lamp"
#. What a chapter step says to do: gather firewood.
msgid "steps.gather_wood.hint"
msgstr "Gather wood"

View file

@ -0,0 +1,32 @@
# Turkish: one key missing (hud.day), one fuzzy (pause.close) and one en.po does not have (old.title)
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "pause.resume"
msgstr "Devam et"
#, fuzzy
msgid "pause.close"
msgstr "Kapat"
msgid "map.close"
msgstr "Kapat"
msgid "catch.count"
msgid_plural "catch.count"
msgstr[0] "{1} {2} tutuldu"
msgstr[1] "{1} {2} tutuldu"
msgid "items.rope.name"
msgstr "Halat"
msgid "items.lamp.name"
msgstr "Fener"
msgid "steps.gather_wood.hint"
msgstr "Odun topla"
msgid "old.title"
msgstr "Eski"

View file

@ -0,0 +1,3 @@
# the key-based text example (i18n.ludic, i18n_ui.ludic): its languages are lang/<code>.po, and
# lang/en.po is the source every key is checked against
lang "lang" en

View file

@ -0,0 +1,7 @@
# Pause.ludic - i18n_ui.ludic's component: its text is keys (Pause.xml), one of them a field the
# template reads - a Key reaches a template as its marked text, for t(expr)
component Pause {
prop day: int = 1
prop caught: int = 0
back: Key = k"pause.close"
}

View file

@ -0,0 +1,8 @@
<div class="pause">
<p>{t('pause.resume')}</p>
<button title="{t('pause.close')}">{t('hud.day', day)}</button>
<button>{t(back)}</button>
<p>{t('catch.count', caught, 'trout')}</p>
<p translate="no">Maroon Lake 0.5 <b>beta</b></p>
<p>Paused</p>
</div>

View file

@ -0,0 +1,12 @@
# phase 26: a @Text Key field a row leaves out takes its derived key (things.axe.name), and en.po must
# have it; a @Text string field still holding English is a warning, counted as english_left
program DataMissing {
property Thing {
key: string = ""
@Text name: Key = null
@Text blurb: string = ""
}
registry Things of Thing as TH
def Things axe { blurb: "A sharp axe" }
entry { print(TH_COUNT) }
}

View file

@ -0,0 +1,11 @@
# phase 26: a key literal en.po does not have is refused, and so is a plural key (kn"...") whose
# en.po entry has no msgid_plural; trf given more values than the English has holes is a warning
program KeyMissing {
function tr(key: Key) -> string { return string(key) }
function trf(key: Key, a: string, b: string) -> string { return string(key) + a + b }
entry {
print(tr(k"pause.gone"))
print(tr(kn"pause.resume"))
print(trf(k"hud.day", "1", "2"))
}
}

View file

@ -0,0 +1,11 @@
# phase 26: a Key is not a string and a string is not a Key - text is made from a key, and a key is
# written k"..."; nor does a key join text with +
program KeyMixup {
function show(s: string) -> void { print(s) }
function name_of(k: Key) -> Key { return k }
entry {
show(k"pause.resume")
let k2 = name_of("Resume")
print(k2 + "!")
}
}

View file

@ -0,0 +1,18 @@
# The source language of the refused examples: two keys, and one English under two undescribed keys.
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
#. On the pause menu: returns to play.
msgid "pause.resume"
msgstr "Resume"
#. The day on the HUD. {1} = the day number
msgid "hud.day"
msgstr "Day {1}"
msgid "shop.close"
msgstr "Close"
msgid "map.close"
msgstr "Close"

View file

@ -0,0 +1,2 @@
# the key checks' refusals (key_missing, t_missing, key_mixup, data_missing): lang/en.po is the source
lang "lang" en

View file

@ -0,0 +1,4 @@
# Menu.ludic - t_missing.ludic's component: one key en.po lacks
component Menu {
prop day: int = 1
}

View file

@ -0,0 +1,5 @@
<div class="menu">
<p>{t('pause.resume')}</p>
<p>{t('menu.gone')}</p>
<p>{t('hud.day')}</p>
</div>

View file

@ -0,0 +1,7 @@
# phase 26: a template's t('key') is checked against en.po like a key literal - refused when en.po lacks
# it; t('hud.day') with no value for its {1} is a warning
import "ludic.ui"
program TMissing {
import "parts/Menu.ludic"
entry { print(1) }
}