ludic.i18n: a line bracketed inside another (bytes 29 .. 30) is made by L itself - each bracket outermost in, a key whole through keyed() so its escaped arguments stay whole, and a split on 31 never inside a bracket (a game's txt_key puts its nested line raw); a stray bracket draws nothing. The game's tx_L did this alone, so L in a test or anywhere else drew the brackets (26.9's move, made now). nest_test holds it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d703d79feb
commit
81e1f7d8e6
5 changed files with 129 additions and 4 deletions
|
|
@ -16,4 +16,5 @@ import "keyed.ludic"
|
||||||
import "keyed_render.ludic"
|
import "keyed_render.ludic"
|
||||||
import "keyed_args.ludic"
|
import "keyed_args.ludic"
|
||||||
import "mixed.ludic"
|
import "mixed.ludic"
|
||||||
|
import "nest.ludic"
|
||||||
import "plural.ludic"
|
import "plural.ludic"
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,15 @@ function kr_make(i18n_st: mut I18nState, s: string) -> string {
|
||||||
let args = new []string
|
let args = new []string
|
||||||
var start = 1
|
var start = 1
|
||||||
var i = 1
|
var i = 1
|
||||||
|
var depth = 0
|
||||||
while i <= len(s) {
|
while i <= len(s) {
|
||||||
if i < len(s) and s[i] == I18N_ESC {
|
if i < len(s) and s[i] == I18N_ESC {
|
||||||
i += 2 # an escaped byte belongs to its argument
|
i += 2 # an escaped byte belongs to its argument
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if i >= len(s) or s[i] == I18N_SEP {
|
if i < len(s) and s[i] == I18N_OPEN { depth += 1 } # a bracketed line's own 31s are its own
|
||||||
|
if i < len(s) and s[i] == I18N_CLOSE and depth > 0 { depth -= 1 }
|
||||||
|
if i >= len(s) or (s[i] == I18N_SEP and depth == 0) {
|
||||||
var e = i
|
var e = i
|
||||||
if e > len(s) { e = len(s) }
|
if e > len(s) { e = len(s) }
|
||||||
push(args, kr_unescape(s[start..e]))
|
push(args, kr_unescape(s[start..e]))
|
||||||
|
|
@ -57,7 +60,7 @@ function kr_fill(i18n_st: mut I18nState, t: string, args: []string) -> string {
|
||||||
if h >= 1 {
|
if h >= 1 {
|
||||||
var v = ""
|
var v = ""
|
||||||
if h < len(args) { v = args[h] }
|
if h < len(args) { v = args[h] }
|
||||||
if i18n_is_key(v) or kx_mixed(v) { v = L(i18n_st, v) } else if i18n_st.on and kr_words(v) { v = L(i18n_st, v) }
|
if i18n_is_key(v) or kx_mixed(v) or kx_nested(v) { v = L(i18n_st, v) } else if i18n_st.on and kr_words(v) { v = L(i18n_st, v) }
|
||||||
r = r + t[seg..i] + v
|
r = r + t[seg..i] + v
|
||||||
i = hole_end(t, i)
|
i = hole_end(t, i)
|
||||||
seg = i
|
seg = i
|
||||||
|
|
|
||||||
77
packages/ludic.i18n/nest.ludic
Normal file
77
packages/ludic.i18n/nest.ludic
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
# nest.ludic - a line bracketed inside another (bytes 29 .. 30 round it: a game's txt_key / txt_line, a
|
||||||
|
# key's line in another's hole or glued to words): each bracket made into its text first, outermost in, then
|
||||||
|
# the whole. A line wholly inside one bracket is made already, and not looked up again as English;
|
||||||
|
# a key whole takes this path only through its arguments, which keyed() has unescaped by then
|
||||||
|
export const I18N_OPEN: int = 29
|
||||||
|
export const I18N_CLOSE: int = 30
|
||||||
|
|
||||||
|
export function kx_nested(s: string) -> bool {
|
||||||
|
let p: string = s
|
||||||
|
for i in 0 .. len(p) { if p[i] == I18N_OPEN or p[i] == I18N_CLOSE { return true } }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
# what L does with such a line: made once per distinct line in this language, kept with the keys
|
||||||
|
function kx_nest_line(i18n_st: mut I18nState, s: string) -> string {
|
||||||
|
if i18n_st.kc == null { kc_clear(i18n_st) }
|
||||||
|
let ci = Dict.get_or(i18n_st.kc, s, -1)
|
||||||
|
if ci >= 0 { return i18n_st.kc_out[ci] }
|
||||||
|
let q: string = s
|
||||||
|
var r = kx_flat(i18n_st, s)
|
||||||
|
if not (q[0] == I18N_OPEN and q[len(q) - 1] == I18N_CLOSE) { r = L(i18n_st, r) }
|
||||||
|
if Dict.size(i18n_st.kc) >= CACHE_MAX { kc_clear(i18n_st) }
|
||||||
|
@alloc_ok("a line with a line inside it, made into text: kept, up to CACHE_MAX")
|
||||||
|
Dict.set(i18n_st.kc, s, len(i18n_st.kc_out))
|
||||||
|
@alloc_ok("a line with a line inside it: as above")
|
||||||
|
push(i18n_st.kc_out, r)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
# each outermost bracket made into its text (what is inside is made the same way, by L), then any
|
||||||
|
# bracket left without a partner taken out
|
||||||
|
@alloc_ok("a line with a line inside it (a cache miss): once per distinct line")
|
||||||
|
function kx_flat(i18n_st: mut I18nState, s: string) -> string {
|
||||||
|
let p: string = s
|
||||||
|
var r = ""
|
||||||
|
var seg = 0
|
||||||
|
var i = 0
|
||||||
|
while i < len(p) {
|
||||||
|
let c = kx_close(p, i)
|
||||||
|
if p[i] == I18N_OPEN and c > i {
|
||||||
|
r = r + s[seg..i] + L(i18n_st, s[i + 1..c])
|
||||||
|
seg = c + 1
|
||||||
|
i = c + 1
|
||||||
|
} else {
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kx_strip(r + s[seg..len(p)])
|
||||||
|
}
|
||||||
|
|
||||||
|
# where the bracket opening at i closes, or -1 when it never does
|
||||||
|
function kx_close(p: string, i: int) -> int {
|
||||||
|
var depth = 0
|
||||||
|
for j in i .. len(p) {
|
||||||
|
if p[j] == I18N_OPEN { depth += 1 }
|
||||||
|
if p[j] == I18N_CLOSE {
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0 { return j }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# a close with no open before it (or an open with no close): no stray byte drawn
|
||||||
|
@alloc_ok("as above")
|
||||||
|
function kx_strip(s: string) -> string {
|
||||||
|
let p: string = s
|
||||||
|
var r = ""
|
||||||
|
var seg = 0
|
||||||
|
for i in 0 .. len(p) {
|
||||||
|
if p[i] == I18N_OPEN or p[i] == I18N_CLOSE {
|
||||||
|
r = r + s[seg..i]
|
||||||
|
seg = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r + s[seg..len(p)]
|
||||||
|
}
|
||||||
42
packages/ludic.i18n/tests/nest_test.ludic
Normal file
42
packages/ludic.i18n/tests/nest_test.ludic
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# nest_test.ludic - a line bracketed inside another (bytes 29 .. 30) is made first, innermost out, in the
|
||||||
|
# language in use; a line wholly in one bracket is not looked up again, and a stray bracket draws nothing
|
||||||
|
import "ludic.i18n"
|
||||||
|
program I18nNestTest {
|
||||||
|
numbers float
|
||||||
|
function q(s: string) -> string { return "\"" + s + "\"" }
|
||||||
|
function po_line(id: string, tr: string) -> string { return "msgid " + q(id) + "\nmsgstr " + q(tr) + "\n\n" }
|
||||||
|
function en() -> string { return po_line("hud.day", "Day {1}") + po_line("pause.quit", "Quit") + po_line("shop.ada", "Ada: {1}") }
|
||||||
|
function tt() -> string { return po_line("hud.day", "{1}. Gun") + po_line("pause.quit", "Cik") + po_line("Settings", "Ayarlar") }
|
||||||
|
function b(s: string) -> string {
|
||||||
|
let o = buffer(1)
|
||||||
|
o[0] = I18N_OPEN
|
||||||
|
let c = buffer(1)
|
||||||
|
c[0] = I18N_CLOSE
|
||||||
|
return text_of(o, 1) + s + text_of(c, 1)
|
||||||
|
}
|
||||||
|
function start(i18n_st: mut I18nState) -> void {
|
||||||
|
i18n_off(i18n_st)
|
||||||
|
i18n_loud(i18n_st, false)
|
||||||
|
i18n_base_text(i18n_st, en())
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a bracketed line is made first, and a line wholly in one bracket is its text" (i18n_st: mut I18nState) {
|
||||||
|
start(i18n_st)
|
||||||
|
let day = b(trf(k"hud.day", "3", "", "", ""))
|
||||||
|
expect(L(i18n_st, day) == "Day 3")
|
||||||
|
expect(L(i18n_st, "[E] " + day) == "[E] Day 3")
|
||||||
|
expect(L(i18n_st, trf(k"shop.ada", day, "", "", "")) == "Ada: Day 3")
|
||||||
|
expect(L(i18n_st, b(trf(k"shop.ada", day, "", "", ""))) == "Ada: Day 3")
|
||||||
|
i18n_load_text(i18n_st, tt())
|
||||||
|
expect(L(i18n_st, "Settings " + day) == "Settings 3. Gun") # the rest is English, looked up as it was
|
||||||
|
expect(L(i18n_st, b("Settings")) == "Ayarlar")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a bracket with no partner draws nothing of itself" (i18n_st: mut I18nState) {
|
||||||
|
start(i18n_st)
|
||||||
|
let c = buffer(1)
|
||||||
|
c[0] = I18N_CLOSE
|
||||||
|
expect(L(i18n_st, "Quit" + text_of(c, 1)) == "Quit")
|
||||||
|
expect(kx_nested("Quit") == false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
# translate.ludic - what the game draws, in the language in use: a key (keyed_render.ludic); else, the old
|
# translate.ludic - what the game draws, in the language in use: a key (keyed_render.ludic); else, the old
|
||||||
# English form, exact, else a pattern, else padding taken off, else a paragraph a sentence at a time
|
# English form, exact, else a pattern, else padding taken off, else a paragraph a sentence at a time
|
||||||
export function L(i18n_st: mut I18nState, s: string) -> string {
|
export function L(i18n_st: mut I18nState, s: string) -> string {
|
||||||
if kx_mixed(s) { return kx_line(i18n_st, s) }
|
let mixed = kx_mixed(s)
|
||||||
if i18n_is_key(s) { return keyed(i18n_st, s) }
|
if i18n_is_key(s) and not mixed { return keyed(i18n_st, s) } # a key whole: its arguments are made in it
|
||||||
|
if kx_nested(s) { return kx_nest_line(i18n_st, s) }
|
||||||
|
if mixed { return kx_line(i18n_st, s) }
|
||||||
if not i18n_st.on { return s }
|
if not i18n_st.on { return s }
|
||||||
let ci = Dict.get_or(i18n_st.cache, s, -1)
|
let ci = Dict.get_or(i18n_st.cache, s, -1)
|
||||||
if ci >= 0 { return i18n_st.cache_out[ci] }
|
if ci >= 0 { return i18n_st.cache_out[ci] }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue