From 96c6e27d983ce909acfa6cec42b3ec45910d1305 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Wed, 30 Sep 2026 00:50:53 +0300 Subject: [PATCH] ludic.i18n: a key's text glued into an English line is made where it sits - L decodes each marked name inside a line that is not a key of its own ("A {1}, {2} cm." given a name that is a key, name + "\n" + blurb), in the language in use, then the line takes its old English lookup; kept in the keyed cache. Interim until every such line is a key (26.9). keys_test covers the stop at a sentence's full stop, a key at the head with words after, a mixed argument in a key's hole, and a key whole left alone. Since the data's text became keys (26.4), a game's English patterns that take a table's name (Notify, txt_pat, a concatenation) drew the raw mark and key. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.i18n/index.ludic | 1 + packages/ludic.i18n/keyed_render.ludic | 2 +- packages/ludic.i18n/mixed.ludic | 58 +++++++++++++++++++++++ packages/ludic.i18n/tests/keys_test.ludic | 11 +++++ packages/ludic.i18n/translate.ludic | 1 + 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 packages/ludic.i18n/mixed.ludic diff --git a/packages/ludic.i18n/index.ludic b/packages/ludic.i18n/index.ludic index 6466e3bc..b4ab8e80 100644 --- a/packages/ludic.i18n/index.ludic +++ b/packages/ludic.i18n/index.ludic @@ -15,4 +15,5 @@ import "keys.ludic" import "keyed.ludic" import "keyed_render.ludic" import "keyed_args.ludic" +import "mixed.ludic" import "plural.ludic" diff --git a/packages/ludic.i18n/keyed_render.ludic b/packages/ludic.i18n/keyed_render.ludic index aeb8e7b9..cda9f5ab 100644 --- a/packages/ludic.i18n/keyed_render.ludic +++ b/packages/ludic.i18n/keyed_render.ludic @@ -57,7 +57,7 @@ function kr_fill(i18n_st: mut I18nState, t: string, args: []string) -> string { if h >= 1 { var v = "" if h < len(args) { v = args[h] } - if i18n_is_key(v) { v = keyed(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) { 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 i = hole_end(t, i) seg = i diff --git a/packages/ludic.i18n/mixed.ludic b/packages/ludic.i18n/mixed.ludic new file mode 100644 index 00000000..13bbeb49 --- /dev/null +++ b/packages/ludic.i18n/mixed.ludic @@ -0,0 +1,58 @@ +# mixed.ludic - a key's text glued into an English line ("A {1}, {2} cm." given a name that is a key, or a +# name + "\n" + its blurb): each marked name made in the language in use, then the line as English. Interim, +# until every such line is a key of its own (26.9); a mark with arguments cannot be told from the line after it + +# a key's name: its alphabet, and a full stop at its end left to the sentence +function kx_name_byte(c: int) -> bool { return (c >= 97 and c <= 122) or (c >= 65 and c <= 90) or (c >= 48 and c <= 57) or c == 95 or c == 46 or c == 45 } + +function kx_name_end(s: string, i: int) -> int { + var e = i + while e < len(s) and kx_name_byte(s[e]) { e += 1 } + while e > i and s[e - 1] == 46 { e -= 1 } + return e +} + +# a string with a mark that is not the whole of it: a mark past its start, or words after a key's name +export function kx_mixed(s: string) -> bool { + let n = len(s) + if n == 0 { return false } + if i18n_is_key(s) { + let e = kx_name_end(s, 1) + return e < n and s[e] != I18N_SEP + } + for i in 1 .. n { if s[i] == I18N_KEY or s[i] == I18N_PLURAL { 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_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] } + var r = kx_unmix(i18n_st, s) + if i18n_st.on { r = whole(i18n_st, r) } + if Dict.size(i18n_st.kc) >= CACHE_MAX { kc_clear(i18n_st) } + @alloc_ok("a line with a key glued in, 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 key glued in: as above") + push(i18n_st.kc_out, r) + return r +} + +@alloc_ok("a line with a key glued in (a cache miss): once per distinct line") +function kx_unmix(i18n_st: mut I18nState, s: string) -> string { + var r = "" + var start = 0 + var i = 0 + while i < len(s) { + if s[i] == I18N_KEY or s[i] == I18N_PLURAL { + let e = kx_name_end(s, i + 1) + r = r + s[start..i] + keyed(i18n_st, s[i..e]) + start = e + i = e + } else { + i += 1 + } + } + return r + s[start..len(s)] +} diff --git a/packages/ludic.i18n/tests/keys_test.ludic b/packages/ludic.i18n/tests/keys_test.ludic index 851898eb..5ddbf7da 100644 --- a/packages/ludic.i18n/tests/keys_test.ludic +++ b/packages/ludic.i18n/tests/keys_test.ludic @@ -85,4 +85,15 @@ program I18nKeysTest { expect(L(i18n_st, trf(k"catch.saw", "Day 3", "5:30", "", "")) == "5:30 saatinde 3. gun gordun.") # a pattern expect(L(i18n_st, trf(k"catch.saw", "Nowhere known", "7", "", "")) == "7 saatinde Nowhere known gordun.") # no line: as it is } + + test "a key's text glued into an English line is made where it sits, until the line is a key of its own" (i18n_st: mut I18nState) { + start (i18n_st) + let elk = tr(k"species.elk.a_name") + expect(L(i18n_st, "Saw " + elk + ", 30 cm.") == "Saw an elk, 30 cm." and L(i18n_st, "Saw " + elk + ".") == "Saw an elk.") + expect(L(i18n_st, elk + ": 30 cm") == "an elk: 30 cm" and L(i18n_st, elk + "\n" + tr(k"pause.quit")) == "an elk\nQuit") + expect(L(i18n_st, trf(k"catch.saw", "Seen: " + elk, "5:30", "", "")) == "You saw Seen: an elk at 5:30.") + i18n_load_text(i18n_st, tt()) + expect(L(i18n_st, "Day " + tr(k"pause.resume")) == "Devam. gun") # the name made, then the line's own pattern + expect(L(i18n_st, trf(k"catch.saw", elk, "5:30", "", "")) == "5:30 saatinde bir geyik gordun.") # a key whole is untouched + } } diff --git a/packages/ludic.i18n/translate.ludic b/packages/ludic.i18n/translate.ludic index e5a2d70c..fe946450 100644 --- a/packages/ludic.i18n/translate.ludic +++ b/packages/ludic.i18n/translate.ludic @@ -1,6 +1,7 @@ # 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 export function L(i18n_st: mut I18nState, s: string) -> string { + if kx_mixed(s) { return kx_line(i18n_st, s) } if i18n_is_key(s) { return keyed(i18n_st, s) } if not i18n_st.on { return s } let ci = Dict.get_or(i18n_st.cache, s, -1)