compiler: text keys' follow-up (Master's rulings) - a Key in a template literal's hole is an error (the parser marks each hole's string(), TPL_HOLE; write trf(k"...", ...)), trn takes only a plural key kn"..." and tr / trf refuse one (with or without en.po), and a program with no lang line (a package test) uses key literals unchecked and silently - the warning stays for a lang line whose en.po is missing; rejected/i18n_keys gains key_hole (2) and key_rules (3), key_missing's plural moves to trn; LANGUAGE.md and the changeset say so; reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
66b8e51026
commit
9f9ac4a7ce
13 changed files with 72839 additions and 72164 deletions
|
|
@ -836,7 +836,9 @@ results, list elements and registry values like any other type (`null` is none).
|
||||||
its text after a marker byte - byte 1, or 2 for `kn"..."` - so `k"pause.resume"` is the string
|
its text after a marker byte - byte 1, or 2 for `kn"..."` - so `k"pause.resume"` is the string
|
||||||
`"\x01pause.resume"`: the runtime's `tr` is a cast, and a translator knows a key from English.
|
`"\x01pause.resume"`: the runtime's `tr` is a cast, and a translator knows a key from English.
|
||||||
`string(key)` gives that marked text, for a runtime that needs it; a program itself makes text with
|
`string(key)` gives that marked text, for a runtime that needs it; a program itself makes text with
|
||||||
`tr`. (A program that declares its own type named `Key` keeps it; the builtin is then out of reach.)
|
`tr`, and a key in a template literal's hole (`` `at {k}` ``) is an error - write `trf(k"...", ...)`.
|
||||||
|
`trn` takes a plural key and `tr` / `trf` refuse one, with or without a `lang` line. (A program that
|
||||||
|
declares its own type named `Key` keeps it; the builtin is then out of reach.)
|
||||||
|
|
||||||
**The data.** A registry's `@Text` field is text by derivation: its key is
|
**The data.** A registry's `@Text` field is text by derivation: its key is
|
||||||
`<registry>.<row>.<field>` - the registry's name in snake case (`Items` -> `items`, `GearKinds` ->
|
`<registry>.<row>.<field>` - the registry's name in snake case (`Items` -> `items`, `GearKinds` ->
|
||||||
|
|
@ -876,9 +878,10 @@ each diagnostic at its `file:line:col`:
|
||||||
- one English under several keys (a split) wants a `#.` description on each, for a translator to
|
- one English under several keys (a split) wants a `#.` description on each, for a translator to
|
||||||
tell them apart: a **warning** at the `.po`'s line.
|
tell them apart: a **warning** at the `.po`'s line.
|
||||||
|
|
||||||
With no `lang` line, or no source `.po`, nothing is checked, and the first key literal says so once.
|
With no `lang` line nothing is checked and nothing is said (a package test uses key literals freely); a
|
||||||
|
`lang` line whose source `.po` is missing checks nothing, and the first key literal says so once.
|
||||||
The source `.po` is gettext's: `msgid` (the key), `msgid_plural` and `msgstr[n]`, `#,` flags
|
The source `.po` is gettext's: `msgid` (the key), `msgid_plural` and `msgstr[n]`, `#,` flags
|
||||||
(`fuzzy`), `#.` descriptions, `msgctxt` (read, keying nothing), strings over several lines; `#~`
|
(`fuzzy`), `#.` descriptions, `msgctxt` (read and ignored: the key is the context), strings over several lines; `#~`
|
||||||
entries are obsolete. `ludic schema` carries a `"lang"` object (null without a `lang` line): every
|
entries are obsolete. `ludic schema` carries a `"lang"` object (null without a `lang` line): every
|
||||||
key used with its kind (`code`, `template`, `data`), its sites, its English, whether it is a plural,
|
key used with its kind (`code`, `template`, `data`), its sites, its English, whether it is a plural,
|
||||||
and its description; `unused` (the source's keys used nowhere), `undescribed`, `split`, and per other
|
and its description; `unused` (the source's keys used nowhere), `undescribed`, `split`, and per other
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,6 @@ holes, text still written in English (a template's words, a `@Text` row's value
|
||||||
`english_left` by `ludic deps`) and one English split into keys without `#.` descriptions are
|
`english_left` by `ludic deps`) and one English split into keys without `#.` descriptions are
|
||||||
warnings. `ludic schema` gains `"lang"`: every key with its sites, unused and undescribed keys, splits,
|
warnings. `ludic schema` gains `"lang"`: every key with its sites, unused and undescribed keys, splits,
|
||||||
and each other language's missing, fuzzy and extra keys.
|
and each other language's missing, fuzzy and extra keys.
|
||||||
|
A `Key` in a template literal's hole is an error (write `trf(k"...", ...)`), `trn` takes only a plural
|
||||||
|
key and `tr` / `trf` refuse one; a program with no `lang` line (a package test) uses key literals
|
||||||
|
unchecked and silently.
|
||||||
|
|
|
||||||
9
examples/rejected/i18n_keys/key_hole.ludic
Normal file
9
examples/rejected/i18n_keys/key_hole.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# phase 26: a Key in a template literal's {hole} is refused - it would draw its marked name, never
|
||||||
|
# translated; text is made with tr / trf
|
||||||
|
program KeyHole {
|
||||||
|
entry {
|
||||||
|
let k = k"pause.resume"
|
||||||
|
print(`{k}`)
|
||||||
|
print(`at {k}!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
program KeyMissing {
|
program KeyMissing {
|
||||||
function tr(key: Key) -> string { return string(key) }
|
function tr(key: Key) -> string { return string(key) }
|
||||||
function trf(key: Key, a: string, b: string) -> string { return string(key) + a + b }
|
function trf(key: Key, a: string, b: string) -> string { return string(key) + a + b }
|
||||||
|
function trn(key: Key, n: int) -> string { return string(key) }
|
||||||
entry {
|
entry {
|
||||||
print(tr(k"pause.gone"))
|
print(tr(k"pause.gone"))
|
||||||
print(tr(kn"pause.resume"))
|
print(trn(kn"pause.resume", 1))
|
||||||
print(trf(k"hud.day", "1", "2"))
|
print(trf(k"hud.day", "1", "2"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
examples/rejected/i18n_keys/key_rules.ludic
Normal file
12
examples/rejected/i18n_keys/key_rules.ludic
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# phase 26: trn wants a plural key kn"...", and tr / trf refuse one
|
||||||
|
program KeyRules {
|
||||||
|
function tr(key: Key) -> string { return string(key) }
|
||||||
|
function trf(key: Key, a: string) -> string { return string(key) + a }
|
||||||
|
function trn(key: Key, n: int) -> string { return string(key) }
|
||||||
|
entry {
|
||||||
|
print(trn(k"hud.day", 2))
|
||||||
|
print(trf(kn"catch.count", "1"))
|
||||||
|
print(tr(kn"catch.count"))
|
||||||
|
print(tr(k"pause.resume"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,3 +16,9 @@ msgstr "Close"
|
||||||
|
|
||||||
msgid "map.close"
|
msgid "map.close"
|
||||||
msgstr "Close"
|
msgstr "Close"
|
||||||
|
|
||||||
|
#. The catch on the HUD. {1} = how many
|
||||||
|
msgid "catch.count"
|
||||||
|
msgid_plural "catch.count"
|
||||||
|
msgstr[0] "{1} fish"
|
||||||
|
msgstr[1] "{1} fish"
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
# the key checks' refusals (key_missing, t_missing, key_mixup, data_missing): lang/en.po is the source
|
# the key checks' refusals (key_missing, t_missing, key_mixup, data_missing, key_hole, key_rules): lang/en.po is the source
|
||||||
lang "lang" en
|
lang "lang" en
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ function ck_builtin(e: Node, name: pointer) -> pointer {
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return "string"
|
return "string"
|
||||||
}
|
}
|
||||||
|
if (name == "string") and ck_tpl_hole(e) { return ck_hole(e) }
|
||||||
if (name == "string") or (name == "int") or (name == "float") or (name == "fixed") or (name == "long") or (name == "double") {
|
if (name == "string") or (name == "int") or (name == "float") or (name == "fixed") or (name == "long") or (name == "double") {
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return name
|
return name
|
||||||
|
|
@ -313,3 +314,12 @@ function ck_emit(s: Node) -> void {
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a template literal's {hole}: text, never a key - a Key there would draw its marked name untranslated
|
||||||
|
function ck_tpl_hole(e: Node) -> bool {
|
||||||
|
return e.a != null and (e.a.uns == TPL_HOLE or e.a.uns == TPL_COPY) and len(e.kids) == 1
|
||||||
|
}
|
||||||
|
function ck_hole(e: Node) -> pointer {
|
||||||
|
let t = ck_expr(e.kids[0])
|
||||||
|
if ck_is_key(t) { ck_err("key", e.kids[0], "a Key in a template's {hole}: a key names a text, it is not one - translate it with trf(k\"...\", ...) (tr(key) for no values), or say string(key) outside the template if the marked name is really wanted") }
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -225,15 +225,14 @@ function i18n_ks_name(kind: int) -> pointer {
|
||||||
if kind == KS_DATA { return "data" }
|
if kind == KS_DATA { return "data" }
|
||||||
return "code"
|
return "code"
|
||||||
}
|
}
|
||||||
# a key used: in en.po, and a plural one as a plural there. Nothing to check against: said once
|
# a key used: in en.po, and a plural one as a plural there. A lang line with no en.po: said once;
|
||||||
|
# no lang line at all (a package test) checks nothing and says nothing
|
||||||
function i18n_use(key: pointer, plural: bool, kind: int, file: pointer, line: int, col: int) -> void {
|
function i18n_use(key: pointer, plural: bool, kind: int, file: pointer, line: int, col: int) -> void {
|
||||||
if not i18n_site(key, kind, file, line, col) { return }
|
if not i18n_site(key, kind, file, line, col) { return }
|
||||||
if g_i18n_en == null {
|
if g_i18n_en == null {
|
||||||
if kind != KS_TEMPLATE and not g_i18n_nopo_warned {
|
if g_i18n_on and kind != KS_TEMPLATE and not g_i18n_nopo_warned {
|
||||||
g_i18n_nopo_warned = true
|
g_i18n_nopo_warned = true
|
||||||
var why = "this program's package.ludic has no `lang \"assets/lang\" en` line"
|
pwarn_at(file, line, col, `k"{key}": no en.po to check against - {g_i18n_en_path} is not there; no key is checked`)
|
||||||
if g_i18n_on { why = `{g_i18n_en_path} is not there` }
|
|
||||||
pwarn_at(file, line, col, `k"{key}": no en.po to check against - {why}; no key is checked`)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +311,7 @@ function i18n_walk(n: Node) -> void {
|
||||||
if i18n_is_key(n) and not i18n_is_data_file(n.file) {
|
if i18n_is_key(n) and not i18n_is_data_file(n.file) {
|
||||||
i18n_use(i18n_key_text(n), n.ival == KEY_PLURAL, KS_CODE, n.file, n.line, i18n_col(n))
|
i18n_use(i18n_key_text(n), n.ival == KEY_PLURAL, KS_CODE, n.file, n.line, i18n_col(n))
|
||||||
}
|
}
|
||||||
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and g_i18n_en != null { i18n_call(n) }
|
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID { i18n_call(n) }
|
||||||
i18n_walk(n.a)
|
i18n_walk(n.a)
|
||||||
i18n_walk(n.b)
|
i18n_walk(n.b)
|
||||||
i18n_walk(n.c)
|
i18n_walk(n.c)
|
||||||
|
|
@ -322,15 +321,25 @@ function i18n_walk(n: Node) -> void {
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# trf(k"...", a, b) / trn(kn"...", n, a): the values after the key against its holes
|
# tr(k"...") / trf(k"...", a, b) / trn(kn"...", n, a): a plural key only with trn, and trn only with
|
||||||
|
# one (en.po or not); then the values after the key against its holes
|
||||||
function i18n_call(n: Node) -> void {
|
function i18n_call(n: Node) -> void {
|
||||||
let nm = vis_plain(n.a.s)
|
let nm = vis_plain(n.a.s)
|
||||||
if not (nm == "trf") and not (nm == "trn") { return }
|
if not (nm == "tr") and not (nm == "trf") and not (nm == "trn") { return }
|
||||||
var k = 0
|
var k = 0
|
||||||
while k < len(n.kids) and not i18n_is_key(n.kids[k]) { k += 1 }
|
while k < len(n.kids) and not i18n_is_key(n.kids[k]) { k += 1 }
|
||||||
if k >= len(n.kids) { return }
|
if k >= len(n.kids) { return }
|
||||||
let e = n.kids[k]
|
let e = n.kids[k]
|
||||||
i18n_holes(i18n_key_text(e), len(n.kids) - k - 1, nm, e.file, e.line, i18n_col(e))
|
let key = i18n_key_text(e)
|
||||||
|
let plural = e.ival == KEY_PLURAL
|
||||||
|
if nm == "trn" and not plural {
|
||||||
|
perr_at(e.file, e.line, i18n_col(e), `trn(k"{key}", ...): trn counts, so its key is a plural one - write kn"{key}" (and give en.po a msgid_plural)`)
|
||||||
|
}
|
||||||
|
if plural and not (nm == "trn") {
|
||||||
|
perr_at(e.file, e.line, i18n_col(e), `{nm}(kn"{key}", ...): a plural key needs its count - write trn(kn"{key}", n, ...)`)
|
||||||
|
}
|
||||||
|
if nm == "tr" or g_i18n_en == null { return }
|
||||||
|
i18n_holes(key, len(n.kids) - k - 1, nm, e.file, e.line, i18n_col(e))
|
||||||
}
|
}
|
||||||
# a node's column in its file: a template literal's hole is lexed on its own, so its tokens' columns
|
# a node's column in its file: a template literal's hole is lexed on its own, so its tokens' columns
|
||||||
# are the hole's - the file offset says where it really is
|
# are the hole's - the file offset says where it really is
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ function interp_add(acc: Node, part: Node) -> Node {
|
||||||
return mkbin("+", acc, part)
|
return mkbin("+", acc, part)
|
||||||
}
|
}
|
||||||
function interp_str(e: Node) -> Node { # wrap a hole in string(...)
|
function interp_str(e: Node) -> Node { # wrap a hole in string(...)
|
||||||
let c = node(E_CALL); let id = node(E_ID); id.s = "string"; c.a = id; push(c.kids, e); return c
|
let c = node(E_CALL); let id = node(E_ID); id.s = "string"; id.uns = TPL_HOLE; c.a = id; push(c.kids, e); return c
|
||||||
}
|
}
|
||||||
var g_interp_line: int = 1 # the line the interpolated string sits on
|
var g_interp_line: int = 1 # the line the interpolated string sits on
|
||||||
var g_interp_pos: int = -1 # the backtick's place in its file
|
var g_interp_pos: int = -1 # the backtick's place in its file
|
||||||
|
|
@ -297,6 +297,7 @@ function parse_interp(raw: pointer) -> Node {
|
||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
const TPL_COPY: int = 91 # on the callee of a template's lone string(hole)
|
const TPL_COPY: int = 91 # on the callee of a template's lone string(hole)
|
||||||
|
const TPL_HOLE: int = 92 # on the callee of every other string(hole) (a Key is refused there)
|
||||||
|
|
||||||
# emit E(field: v, ...) — shared by the statement form and the expression form.
|
# emit E(field: v, ...) — shared by the statement form and the expression form.
|
||||||
# As an expression it yields a cancellable event's cancelled flag (0/1); a
|
# As an expression it yields a cancellable event's cancelled flag (0/1); a
|
||||||
|
|
|
||||||
70737
selfhost/ludicc.seed.ll
70737
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -1310,6 +1310,9 @@ function cmd_dev_test() -> int {
|
||||||
diag_json_case("rejected/i18n_keys/key_mixup", 3, "argument 1 of show wants a string and this is a Key", "a Key is not a string")
|
diag_json_case("rejected/i18n_keys/key_mixup", 3, "argument 1 of show wants a string and this is a Key", "a Key is not a string")
|
||||||
diag_json_case("rejected/i18n_keys/key_mixup", 3, "argument 1 of name_of wants a Key and this is a string", "a string is not a Key")
|
diag_json_case("rejected/i18n_keys/key_mixup", 3, "argument 1 of name_of wants a Key and this is a string", "a string is not a Key")
|
||||||
diag_json_case("rejected/i18n_keys/key_mixup", 3, "+ on a Key", "a Key joins no text")
|
diag_json_case("rejected/i18n_keys/key_mixup", 3, "+ on a Key", "a Key joins no text")
|
||||||
|
diag_json_case("rejected/i18n_keys/key_hole", 2, "a Key in a template's {hole}", "a Key in a template literal's hole is refused: it would draw its marked name, untranslated")
|
||||||
|
diag_json_case("rejected/i18n_keys/key_rules", 3, "trn counts, so its key is a plural one", "trn wants a plural key, kn\"...\"")
|
||||||
|
diag_json_case("rejected/i18n_keys/key_rules", 3, "a plural key needs its count", "tr / trf refuse a plural key")
|
||||||
diag_json_case("rejected/i18n_keys/data_missing", 1, "things.axe.name (the derived key of a @Text field the row gives no value): the key things.axe.name is not in", "a @Text Key field a row leaves out: its derived key must be in en.po")
|
diag_json_case("rejected/i18n_keys/data_missing", 1, "things.axe.name (the derived key of a @Text field the row gives no value): the key things.axe.name is not in", "a @Text Key field a row leaves out: its derived key must be in en.po")
|
||||||
diag_json_case("rejected/i18n_keys/data_missing", 1, "English left: things.axe.blurb is", "a @Text string field still holding English: a warning")
|
diag_json_case("rejected/i18n_keys/data_missing", 1, "English left: things.axe.blurb is", "a @Text string field still holding English: a warning")
|
||||||
diag_json_case("rejected/i18n_keys/t_missing", 1, "t('menu.gone'): the key menu.gone is not in", "a template's t('key') is checked against en.po")
|
diag_json_case("rejected/i18n_keys/t_missing", 1, "t('menu.gone'): the key menu.gone is not in", "a template's t('key') is checked against en.po")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue