wip(0.S3): the packages migrated by ludic migrate state packages - every package test green
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1e8b5b0523
commit
07505e7ef2
294 changed files with 14996 additions and 14675 deletions
|
|
@ -1,18 +1,18 @@
|
|||
# langs.ludic - where languages come from, later ones replacing earlier ones with the same code:
|
||||
# <lang_dir>/languages.txt (one code per line, # a comment) and every .po in the mod folder
|
||||
export function i18n_init() -> void {
|
||||
codes = new []string
|
||||
names = new []string
|
||||
paths = new []string
|
||||
fonts = new []string
|
||||
lang_add("en", "English", "", "")
|
||||
let idx = Fs.read_text(lang_dir + "/languages.txt")
|
||||
export function i18n_init(i18n_st: mut I18nState) -> void {
|
||||
i18n_st.codes = new []string
|
||||
i18n_st.names = new []string
|
||||
i18n_st.paths = new []string
|
||||
i18n_st.fonts = new []string
|
||||
lang_add(i18n_st, "en", "English", "", "")
|
||||
let idx = Fs.read_text(i18n_st.lang_dir + "/languages.txt")
|
||||
if idx != null {
|
||||
let ls = lines_of(idx)
|
||||
for i in 0 .. len(ls) {
|
||||
let ln = trim(ls[i])
|
||||
if len(ln) == 0 or ln[0] == 35 { continue }
|
||||
probe(ln, `{lang_dir}/{ln}.po`, lang_dir)
|
||||
probe(i18n_st, ln, `{i18n_st.lang_dir}/{ln}.po`, i18n_st.lang_dir)
|
||||
}
|
||||
}
|
||||
let md = i18n_mod_dir()
|
||||
|
|
@ -23,60 +23,60 @@ export function i18n_init() -> void {
|
|||
for i in 0 .. len(files) {
|
||||
let nm = files[i]
|
||||
let n = len(nm)
|
||||
if n > 3 and i18n_ends(nm, ".po") { probe(nm[0..n - 3], md + "/" + nm, md) }
|
||||
if n > 3 and i18n_ends(nm, ".po") { probe(i18n_st, nm[0..n - 3], md + "/" + nm, md) }
|
||||
}
|
||||
}
|
||||
|
||||
# where a player's own .po files go; "" when the game has none
|
||||
export function i18n_mod_dir() -> string { return I18nWorld.mod_dir() }
|
||||
|
||||
function lang_add(code: string, name: string, path: string, font: string) -> void {
|
||||
for i in 0 .. len(codes) {
|
||||
if codes[i] == code {
|
||||
names[i] = name
|
||||
paths[i] = path
|
||||
fonts[i] = font
|
||||
function lang_add(i18n_st: mut I18nState, code: string, name: string, path: string, font: string) -> void {
|
||||
for i in 0 .. len(i18n_st.codes) {
|
||||
if i18n_st.codes[i] == code {
|
||||
i18n_st.names[i] = name
|
||||
i18n_st.paths[i] = path
|
||||
i18n_st.fonts[i] = font
|
||||
return
|
||||
}
|
||||
}
|
||||
push(codes, code)
|
||||
push(names, name)
|
||||
push(paths, path)
|
||||
push(fonts, font)
|
||||
push(i18n_st.codes, code)
|
||||
push(i18n_st.names, name)
|
||||
push(i18n_st.paths, path)
|
||||
push(i18n_st.fonts, font)
|
||||
}
|
||||
|
||||
function probe(code: string, path: string, dir: string) -> void {
|
||||
function probe(i18n_st: mut I18nState, code: string, path: string, dir: string) -> void {
|
||||
let text = Fs.read_text(path)
|
||||
if text == null { return }
|
||||
var name = i18n_header(text, "X-Language-Name")
|
||||
if len(name) == 0 { name = code }
|
||||
var font = i18n_header(text, "X-Font")
|
||||
if len(font) > 0 { font = dir + "/" + font }
|
||||
lang_add(code, name, path, font)
|
||||
lang_add(i18n_st, code, name, path, font)
|
||||
}
|
||||
|
||||
export function i18n_count() -> int {
|
||||
if codes == null { return 1 }
|
||||
return len(codes)
|
||||
export function i18n_count(i18n_st: I18nState) -> int {
|
||||
if i18n_st.codes == null { return 1 }
|
||||
return len(i18n_st.codes)
|
||||
}
|
||||
|
||||
export function i18n_index(code: string) -> int {
|
||||
if codes != null { for i in 0 .. len(codes) { if codes[i] == code { return i } } }
|
||||
export function i18n_index(i18n_st: I18nState, code: string) -> int {
|
||||
if i18n_st.codes != null { for i in 0 .. len(i18n_st.codes) { if i18n_st.codes[i] == code { return i } } }
|
||||
return 0
|
||||
}
|
||||
|
||||
export function i18n_code_at(i: int) -> string {
|
||||
if codes == null or i < 0 or i >= len(codes) { return "en" }
|
||||
return codes[i]
|
||||
export function i18n_code_at(i18n_st: I18nState, i: int) -> string {
|
||||
if i18n_st.codes == null or i < 0 or i >= len(i18n_st.codes) { return "en" }
|
||||
return i18n_st.codes[i]
|
||||
}
|
||||
|
||||
export function i18n_name_at(i: int) -> string {
|
||||
if names == null or i < 0 or i >= len(names) { return "English" }
|
||||
return names[i]
|
||||
export function i18n_name_at(i18n_st: I18nState, i: int) -> string {
|
||||
if i18n_st.names == null or i < 0 or i >= len(i18n_st.names) { return "English" }
|
||||
return i18n_st.names[i]
|
||||
}
|
||||
|
||||
# the font atlas a language names with X-Font, beside its .po; "" is the default
|
||||
export function i18n_font_at(i: int) -> string {
|
||||
if fonts == null or i < 0 or i >= len(fonts) { return "" }
|
||||
return fonts[i]
|
||||
export function i18n_font_at(i18n_st: I18nState, i: int) -> string {
|
||||
if i18n_st.fonts == null or i < 0 or i >= len(i18n_st.fonts) { return "" }
|
||||
return i18n_st.fonts[i]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
# English, and the text that filled each hole translated in turn
|
||||
|
||||
# does pattern p produce s? The holes land in caps, in hole order
|
||||
function try_pattern(p: int, s: string) -> bool {
|
||||
function try_pattern(i18n_st: mut I18nState, p: int, s: string) -> bool {
|
||||
let n = len(s)
|
||||
let l0 = pat_l0[p]
|
||||
let nl = pat_nl[p]
|
||||
let first = lits[l0]
|
||||
let l0 = i18n_st.pat_l0[p]
|
||||
let nl = i18n_st.pat_nl[p]
|
||||
let first = i18n_st.lits[l0]
|
||||
let f_n = len(first)
|
||||
let last = lits[l0 + nl - 1]
|
||||
let last = i18n_st.lits[l0 + nl - 1]
|
||||
let l_n = len(last)
|
||||
if nl - 1 > CAPS { return false }
|
||||
if n < f_n + l_n + nl - 1 { return false } # every hole takes at least a byte
|
||||
|
|
@ -20,76 +20,76 @@ function try_pattern(p: int, s: string) -> bool {
|
|||
var stop = lim
|
||||
var adv = 0
|
||||
if g < nl - 2 {
|
||||
let mid = lits[l0 + g + 1]
|
||||
let mid = i18n_st.lits[l0 + g + 1]
|
||||
stop = i18n_find(s, lim, pos + 1, mid, len(mid))
|
||||
if stop < 0 { return false }
|
||||
adv = len(mid)
|
||||
}
|
||||
if stop <= pos { return false }
|
||||
caps[g] = s[pos..stop]
|
||||
i18n_st.caps[g] = s[pos..stop]
|
||||
pos = stop + adv
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
# the best of a chain that produces s, or `best` as it was
|
||||
function best_of(head: int, next: words, s: string, best: int) -> int {
|
||||
function best_of(i18n_st: mut I18nState, head: int, next: words, s: string, best: int) -> int {
|
||||
var b = best
|
||||
var c = head
|
||||
while c > 0 {
|
||||
let p = c - 1
|
||||
var sc = -1
|
||||
if b >= 0 { sc = pat_sc[b] }
|
||||
if pat_sc[p] > sc and try_pattern(p, s) { b = p }
|
||||
if b >= 0 { sc = i18n_st.pat_sc[b] }
|
||||
if i18n_st.pat_sc[p] > sc and try_pattern(i18n_st, p, s) { b = p }
|
||||
c = next[p]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
function pattern(s: string, depth: int) -> string {
|
||||
hit = false
|
||||
if pat_n == 0 { return s }
|
||||
function pattern(i18n_st: mut I18nState, s: string, depth: int) -> string {
|
||||
i18n_st.hit = false
|
||||
if i18n_st.pat_n == 0 { return s }
|
||||
let n = len(s)
|
||||
var best = -1
|
||||
if n >= 4 {
|
||||
best = best_of(Dict.get_or(pfx, s[0..4], 0), nx_p, s, best)
|
||||
best = best_of(Dict.get_or(sfx, s[n - 4..n], 0), nx_s, s, best)
|
||||
best = best_of(i18n_st, Dict.get_or(i18n_st.pfx, s[0..4], 0), i18n_st.nx_p, s, best)
|
||||
best = best_of(i18n_st, Dict.get_or(i18n_st.sfx, s[n - 4..n], 0), i18n_st.nx_s, s, best)
|
||||
}
|
||||
if n < 80 { best = best_of(gen_head, nx_g, s, best) }
|
||||
if n < 80 { best = best_of(i18n_st, i18n_st.gen_head, i18n_st.nx_g, s, best) }
|
||||
if best < 0 { return s }
|
||||
try_pattern(best, s)
|
||||
let l0 = pat_l0[best]
|
||||
let nl = pat_nl[best]
|
||||
try_pattern(i18n_st, best, s)
|
||||
let l0 = i18n_st.pat_l0[best]
|
||||
let nl = i18n_st.pat_nl[best]
|
||||
# copied out first: translating a hole may match another pattern and reuse caps
|
||||
let raw = new []string
|
||||
for g in 0 .. nl - 1 { push(raw, caps[g]) }
|
||||
for g in 0 .. nl - 1 { push(raw, i18n_st.caps[g]) }
|
||||
let cs = new []string
|
||||
for g in 0 .. nl - 1 { push(cs, piece(raw[g], depth + 1)) }
|
||||
let r = fill_holes(pat_out[best], l0, nl, cs)
|
||||
for g in 0 .. nl - 1 { push(cs, piece(i18n_st, raw[g], depth + 1)) }
|
||||
let r = fill_holes(i18n_st, i18n_st.pat_out[best], l0, nl, cs)
|
||||
# a pattern that gives the English straight back has translated nothing, and must not stop
|
||||
# the sentence-by-sentence pass
|
||||
if r == s { return s }
|
||||
hit = true
|
||||
i18n_st.hit = true
|
||||
return r
|
||||
}
|
||||
|
||||
# the text that filled a hole: known as it stands, known without the English article the game
|
||||
# glued on ("a deer", "the Dark King"), or itself a pattern
|
||||
function piece(c: string, depth: int) -> string {
|
||||
let e = Dict.get_or(exact, c, -1)
|
||||
if e >= 0 { return out[e] }
|
||||
function piece(i18n_st: mut I18nState, c: string, depth: int) -> string {
|
||||
let e = Dict.get_or(i18n_st.exact, c, -1)
|
||||
if e >= 0 { return i18n_st.out[e] }
|
||||
let n = len(c)
|
||||
var bare = -1
|
||||
if n > 4 and (i18n_starts(c, "the ") or i18n_starts(c, "The ")) { bare = 4 }
|
||||
else if n > 3 and (i18n_starts(c, "an ") or i18n_starts(c, "An ")) { bare = 3 }
|
||||
else if n > 2 and (i18n_starts(c, "a ") or i18n_starts(c, "A ")) { bare = 2 }
|
||||
if bare > 0 {
|
||||
let e2 = Dict.get_or(exact, c[bare..n], -1)
|
||||
if e2 >= 0 { return out[e2] }
|
||||
let e2 = Dict.get_or(i18n_st.exact, c[bare..n], -1)
|
||||
if e2 >= 0 { return i18n_st.out[e2] }
|
||||
}
|
||||
if depth < 3 {
|
||||
let r = pattern(c, depth)
|
||||
if hit { return r }
|
||||
let r = pattern(i18n_st, c, depth)
|
||||
if i18n_st.hit { return r }
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ function holes(s: string) -> int {
|
|||
return h
|
||||
}
|
||||
|
||||
function add_pattern(id: string, tr: string) -> void {
|
||||
let p = pat_n
|
||||
pat_n += 1
|
||||
let l0 = len(lits)
|
||||
function add_pattern(i18n_st: mut I18nState, id: string, tr: string) -> void {
|
||||
let p = i18n_st.pat_n
|
||||
i18n_st.pat_n += 1
|
||||
let l0 = len(i18n_st.lits)
|
||||
let n = len(id)
|
||||
var seg = 0
|
||||
var i = 0
|
||||
|
|
@ -27,9 +27,9 @@ function add_pattern(id: string, tr: string) -> void {
|
|||
while i < n {
|
||||
let num = hole_at(id, i)
|
||||
if num >= 0 {
|
||||
push(lits, id[seg..i])
|
||||
push(i18n_st.lits, id[seg..i])
|
||||
score += i - seg
|
||||
pat_ph[l0 + nl] = num
|
||||
i18n_st.pat_ph[l0 + nl] = num
|
||||
nl += 1
|
||||
i = hole_end(id, i)
|
||||
seg = i
|
||||
|
|
@ -37,33 +37,33 @@ function add_pattern(id: string, tr: string) -> void {
|
|||
}
|
||||
i += 1
|
||||
}
|
||||
push(lits, id[seg..n])
|
||||
push(i18n_st.lits, id[seg..n])
|
||||
score += n - seg
|
||||
nl += 1
|
||||
pat_l0[p] = l0
|
||||
pat_nl[p] = nl
|
||||
pat_sc[p] = score
|
||||
push(pat_out, tr)
|
||||
chain(p, lits[l0], lits[l0 + nl - 1])
|
||||
i18n_st.pat_l0[p] = l0
|
||||
i18n_st.pat_nl[p] = nl
|
||||
i18n_st.pat_sc[p] = score
|
||||
push(i18n_st.pat_out, tr)
|
||||
chain(i18n_st, p, i18n_st.lits[l0], i18n_st.lits[l0 + nl - 1])
|
||||
}
|
||||
|
||||
function chain(p: int, first: string, last: string) -> void {
|
||||
function chain(i18n_st: mut I18nState, p: int, first: string, last: string) -> void {
|
||||
if len(first) >= 4 {
|
||||
let key: string = first[0..4]
|
||||
nx_p[p] = Dict.get_or(pfx, key, 0)
|
||||
Dict.set(pfx, key, p + 1)
|
||||
i18n_st.nx_p[p] = Dict.get_or(i18n_st.pfx, key, 0)
|
||||
Dict.set(i18n_st.pfx, key, p + 1)
|
||||
} else if len(last) >= 4 {
|
||||
let key: string = last[len(last) - 4..len(last)]
|
||||
nx_s[p] = Dict.get_or(sfx, key, 0)
|
||||
Dict.set(sfx, key, p + 1)
|
||||
i18n_st.nx_s[p] = Dict.get_or(i18n_st.sfx, key, 0)
|
||||
Dict.set(i18n_st.sfx, key, p + 1)
|
||||
} else {
|
||||
nx_g[p] = gen_head
|
||||
gen_head = p + 1
|
||||
i18n_st.nx_g[p] = i18n_st.gen_head
|
||||
i18n_st.gen_head = p + 1
|
||||
}
|
||||
}
|
||||
|
||||
# a translation with its holes filled from caps, by number: a translation may reorder or drop them
|
||||
function fill_holes(t: string, l0: int, nl: int, cs: []string) -> string {
|
||||
function fill_holes(i18n_st: I18nState, t: string, l0: int, nl: int, cs: []string) -> string {
|
||||
let n = len(t)
|
||||
var r = ""
|
||||
var seg = 0
|
||||
|
|
@ -72,7 +72,7 @@ function fill_holes(t: string, l0: int, nl: int, cs: []string) -> string {
|
|||
let num = hole_at(t, i)
|
||||
if num >= 0 {
|
||||
r = r + t[seg..i]
|
||||
for g in 0 .. nl - 1 { if pat_ph[l0 + g] == num { r = r + cs[g] } }
|
||||
for g in 0 .. nl - 1 { if i18n_st.pat_ph[l0 + g] == num { r = r + cs[g] } }
|
||||
i = hole_end(t, i)
|
||||
seg = i
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ function plural_rule_of(h: string) -> int {
|
|||
return PL_NOT_ONE
|
||||
}
|
||||
|
||||
export function i18n_plural_rule() -> int { return plural_rule }
|
||||
export function i18n_plural_rule(i18n_st: I18nState) -> int { return i18n_st.plural_rule }
|
||||
|
||||
# which msgstr[k] a count takes under a rule
|
||||
export function i18n_plural_form(rule: int, n: int) -> int {
|
||||
|
|
@ -33,23 +33,23 @@ export function i18n_plural_form(rule: int, n: int) -> int {
|
|||
return 0
|
||||
}
|
||||
|
||||
function plural_add(e: PoEntry) -> void {
|
||||
function plural_add(i18n_st: mut I18nState, e: PoEntry) -> void {
|
||||
if len(e.forms) == 0 or blank(e.forms[0]) { return }
|
||||
Dict.set(plural, e.id, len(forms) / 3)
|
||||
Dict.set(i18n_st.plural, e.id, len(i18n_st.forms) / 3)
|
||||
for k in 0 .. 3 {
|
||||
if k < len(e.forms) { push(forms, e.forms[k]) } else { push(forms, e.forms[len(e.forms) - 1]) }
|
||||
if k < len(e.forms) { push(i18n_st.forms, e.forms[k]) } else { push(i18n_st.forms, e.forms[len(e.forms) - 1]) }
|
||||
}
|
||||
}
|
||||
|
||||
# `one` or `many` by the count, in the language in use, with every {1} the count: Ln("{1} trout",
|
||||
# "{1} trout", 3). English (or a line the file lacks) is one for 1 and many otherwise
|
||||
export function Ln(one: string, many: string, n: int) -> string {
|
||||
export function Ln(i18n_st: I18nState, one: string, many: string, n: int) -> string {
|
||||
var t = many
|
||||
if n == 1 { t = one }
|
||||
if on {
|
||||
let k = Dict.get_or(plural, one, -1)
|
||||
if i18n_st.on {
|
||||
let k = Dict.get_or(i18n_st.plural, one, -1)
|
||||
if k >= 0 {
|
||||
let f = forms[k * 3 + i18n_plural_form(plural_rule, n)]
|
||||
let f = i18n_st.forms[k * 3 + i18n_plural_form(i18n_st.plural_rule, n)]
|
||||
if not blank(f) { t = f }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,44 @@ function i18n__no_code() -> string { return "" }
|
|||
function i18n__no() -> bool { return false }
|
||||
function i18n__no_font(dir: string) -> bool { return false }
|
||||
|
||||
var lang_dir: string = "assets/lang"
|
||||
var font_default: string = "assets/kit/font"
|
||||
export state I18nState {
|
||||
lang_dir: string = "assets/lang"
|
||||
font_default: string = "assets/kit/font"
|
||||
on: bool = false # a language other than English is drawing
|
||||
cur: string = "en"
|
||||
codes: []string = null # every language found, "en" first
|
||||
names: []string = null
|
||||
paths: []string = null
|
||||
fonts: []string = null # a font atlas directory, "" for the game's own
|
||||
font_now: string = ""
|
||||
exact: Dict = null # msgid -> index into out
|
||||
out: []string = null
|
||||
lits: []string = null # every pattern's literal pieces, one run per pattern
|
||||
pat_out: []string = null
|
||||
pat_l0: words = null # where a pattern's pieces start in lits
|
||||
pat_nl: words = null # how many pieces (holes + 1)
|
||||
pat_sc: words = null # bytes of English it pins down: more is a better match
|
||||
pat_ph: words = null # by piece: the hole number that follows it
|
||||
pat_n: int = 0
|
||||
pfx: Dict = null # a pattern's first four bytes -> its chain head + 1
|
||||
sfx: Dict = null # else its last four
|
||||
gen_head: int = 0 # else the short list, tried on short strings only
|
||||
nx_p: words = null
|
||||
nx_s: words = null
|
||||
nx_g: words = null
|
||||
cache: Dict = null # English as drawn -> index into cache_out
|
||||
cache_out: []string = null
|
||||
caps: []string = null
|
||||
hit: bool = false
|
||||
plural: Dict = null # a plural msgid (the singular) -> index into forms / 3
|
||||
forms: []string = null # three forms per entry, "" where the file has fewer
|
||||
plural_rule: int = 0 # PL_*
|
||||
}
|
||||
|
||||
# where the shipped languages are (a directory with languages.txt and <code>.po), and the atlas a
|
||||
# language with no X-Font draws in
|
||||
export function i18n_config(dir: string, default_font: string) -> void {
|
||||
lang_dir = dir
|
||||
font_default = default_font
|
||||
codes = null
|
||||
export function i18n_config(i18n_st: mut I18nState, dir: string, default_font: string) -> void {
|
||||
i18n_st.lang_dir = dir
|
||||
i18n_st.font_default = default_font
|
||||
i18n_st.codes = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,18 @@
|
|||
# state.ludic - the languages found, the one in use, and the loaded file's lookups
|
||||
var on: bool = false # a language other than English is drawing
|
||||
var cur: string = "en"
|
||||
var codes: []string = null # every language found, "en" first
|
||||
var names: []string = null
|
||||
var paths: []string = null
|
||||
var fonts: []string = null # a font atlas directory, "" for the game's own
|
||||
var font_now: string = ""
|
||||
|
||||
var exact: Dict = null # msgid -> index into out
|
||||
var out: []string = null
|
||||
var lits: []string = null # every pattern's literal pieces, one run per pattern
|
||||
var pat_out: []string = null
|
||||
var pat_l0: words = null # where a pattern's pieces start in lits
|
||||
var pat_nl: words = null # how many pieces (holes + 1)
|
||||
var pat_sc: words = null # bytes of English it pins down: more is a better match
|
||||
var pat_ph: words = null # by piece: the hole number that follows it
|
||||
var pat_n: int = 0
|
||||
var pfx: Dict = null # a pattern's first four bytes -> its chain head + 1
|
||||
var sfx: Dict = null # else its last four
|
||||
var gen_head: int = 0 # else the short list, tried on short strings only
|
||||
var nx_p: words = null
|
||||
var nx_s: words = null
|
||||
var nx_g: words = null
|
||||
var cache: Dict = null # English as drawn -> index into cache_out
|
||||
var cache_out: []string = null
|
||||
var caps: []string = null
|
||||
var hit: bool = false
|
||||
|
||||
var plural: Dict = null # a plural msgid (the singular) -> index into forms / 3
|
||||
var forms: []string = null # three forms per entry, "" where the file has fewer
|
||||
var plural_rule: int = 0 # PL_*
|
||||
|
||||
const CACHE_MAX: int = 4000
|
||||
const CAPS: int = 16
|
||||
|
||||
export function i18n_on() -> bool { return on }
|
||||
export function i18n_current() -> string { return cur }
|
||||
export function i18n_font_now() -> string { return font_now }
|
||||
export function i18n_exact_count() -> int {
|
||||
if out == null { return 0 }
|
||||
return len(out)
|
||||
export function i18n_on(i18n_st: I18nState) -> bool { return i18n_st.on }
|
||||
export function i18n_current(i18n_st: I18nState) -> string { return i18n_st.cur }
|
||||
export function i18n_font_now(i18n_st: I18nState) -> string { return i18n_st.font_now }
|
||||
export function i18n_exact_count(i18n_st: I18nState) -> int {
|
||||
if i18n_st.out == null { return 0 }
|
||||
return len(i18n_st.out)
|
||||
}
|
||||
export function i18n_pattern_count() -> int { return pat_n }
|
||||
export function i18n_pattern_count(i18n_st: I18nState) -> int { return i18n_st.pat_n }
|
||||
|
||||
# the overlay's own font, as the overlay was opened with it
|
||||
export function i18n_font_base(path: string) -> void { font_now = path }
|
||||
export function i18n_font_base(i18n_st: mut I18nState, path: string) -> void { i18n_st.font_now = path }
|
||||
|
|
|
|||
|
|
@ -4,18 +4,20 @@
|
|||
import "ludic.i18n"
|
||||
program I18nTest {
|
||||
numbers float
|
||||
var forced_code: string = ""
|
||||
var fonts_set: []string = null
|
||||
var font_ok: bool = true
|
||||
state I18ntestState {
|
||||
forced_code: string = ""
|
||||
fonts_set: []string = null
|
||||
font_ok: bool = true
|
||||
}
|
||||
|
||||
function dir() -> string { return Os.temp_dir() + "/ludic-i18n-test" }
|
||||
function mods() -> string { return dir() + "/mods" }
|
||||
function t_mods() -> string { return mods() }
|
||||
function t_forced() -> string { return forced_code }
|
||||
function t_forced(i18ntest_st: I18ntestState) -> string { return i18ntest_st.forced_code }
|
||||
function t_ready() -> bool { return true }
|
||||
function t_font(d: string) -> bool {
|
||||
push(fonts_set, d)
|
||||
return font_ok
|
||||
function t_font(i18ntest_st: mut I18ntestState, d: string) -> bool {
|
||||
push(i18ntest_st.fonts_set, d)
|
||||
return i18ntest_st.font_ok
|
||||
}
|
||||
bind I18nWorld { mod_dir: fn t_mods, forced: fn t_forced, font_ready: fn t_ready, set_font: fn t_font }
|
||||
|
||||
|
|
@ -36,116 +38,116 @@ program I18nTest {
|
|||
return s
|
||||
}
|
||||
|
||||
function fresh() -> void {
|
||||
forced_code = ""
|
||||
fonts_set = new []string
|
||||
font_ok = true
|
||||
function fresh(i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) -> void {
|
||||
i18ntest_st.forced_code = ""
|
||||
i18ntest_st.fonts_set = new []string
|
||||
i18ntest_st.font_ok = true
|
||||
Fs.mkdir(dir())
|
||||
Fs.mkdir(mods())
|
||||
Fs.write_text(dir() + "/languages.txt", "# shipped\ntt\nnope\n")
|
||||
Fs.write_text(dir() + "/tt.po", tt())
|
||||
Fs.remove(mods() + "/zz.po")
|
||||
i18n_config(dir(), "font_default")
|
||||
i18n_init()
|
||||
i18n_config(i18n_st, dir(), "font_default")
|
||||
i18n_init(i18n_st)
|
||||
}
|
||||
|
||||
test "the shipped list and English are found, a missing file is not" {
|
||||
fresh()
|
||||
expect_eq(i18n_count(), 2)
|
||||
expect(i18n_code_at(0) == "en")
|
||||
expect_eq(i18n_index("tt"), 1)
|
||||
expect_eq(i18n_index("nope"), 0)
|
||||
expect(i18n_name_at(1) == "Test Tongue")
|
||||
expect(i18n_font_at(1) == dir() + "/font_tt")
|
||||
expect(L("Settings") == "Settings")
|
||||
test "the shipped list and English are found, a missing file is not" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
expect_eq(i18n_count(i18n_st), 2)
|
||||
expect(i18n_code_at(i18n_st, 0) == "en")
|
||||
expect_eq(i18n_index(i18n_st, "tt"), 1)
|
||||
expect_eq(i18n_index(i18n_st, "nope"), 0)
|
||||
expect(i18n_name_at(i18n_st, 1) == "Test Tongue")
|
||||
expect(i18n_font_at(i18n_st, 1) == dir() + "/font_tt")
|
||||
expect(L(i18n_st, "Settings") == "Settings")
|
||||
}
|
||||
|
||||
test "exact lines, patterns with translated holes, and English where unknown" {
|
||||
fresh()
|
||||
i18n_use("tt")
|
||||
expect(i18n_on())
|
||||
expect(i18n_current() == "tt")
|
||||
expect(L("Settings") == "Zettings")
|
||||
expect(L("Day 12") == "12. Gun")
|
||||
expect(L("+2 Plant fiber") == "Lif +2")
|
||||
expect(L("You saw a deer.") == "geyik gordun.")
|
||||
expect(L("zzqx words nobody wrote") == "zzqx words nobody wrote")
|
||||
expect(L("Multiline") == "Coksatir\n")
|
||||
expect_eq(i18n_exact_count(), 6)
|
||||
expect_eq(i18n_pattern_count(), 3)
|
||||
test "exact lines, patterns with translated holes, and English where unknown" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(i18n_on(i18n_st))
|
||||
expect(i18n_current(i18n_st) == "tt")
|
||||
expect(L(i18n_st, "Settings") == "Zettings")
|
||||
expect(L(i18n_st, "Day 12") == "12. Gun")
|
||||
expect(L(i18n_st, "+2 Plant fiber") == "Lif +2")
|
||||
expect(L(i18n_st, "You saw a deer.") == "geyik gordun.")
|
||||
expect(L(i18n_st, "zzqx words nobody wrote") == "zzqx words nobody wrote")
|
||||
expect(L(i18n_st, "Multiline") == "Coksatir\n")
|
||||
expect_eq(i18n_exact_count(i18n_st), 6)
|
||||
expect_eq(i18n_pattern_count(i18n_st), 3)
|
||||
}
|
||||
|
||||
test "a paragraph a sentence at a time, and a layout's padding kept" {
|
||||
fresh()
|
||||
i18n_use("tt")
|
||||
expect(L("You need to eat. zzqx words nobody wrote.") == "Yemek lazim. zzqx words nobody wrote.")
|
||||
expect(L("zzqx. zzqy.") == "zzqx. zzqy.")
|
||||
expect(L(" owned") == " senin")
|
||||
expect(L(" zzqx") == " zzqx")
|
||||
test "a paragraph a sentence at a time, and a layout's padding kept" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "You need to eat. zzqx words nobody wrote.") == "Yemek lazim. zzqx words nobody wrote.")
|
||||
expect(L(i18n_st, "zzqx. zzqy.") == "zzqx. zzqy.")
|
||||
expect(L(i18n_st, " owned") == " senin")
|
||||
expect(L(i18n_st, " zzqx") == " zzqx")
|
||||
}
|
||||
|
||||
test "plurals by the file's rule, and English by one and many" {
|
||||
fresh()
|
||||
expect(Ln("{1} trout", "{1} trouts", 1) == "1 trout")
|
||||
expect(Ln("{1} trout", "{1} trouts", 3) == "3 trouts")
|
||||
i18n_use("tt")
|
||||
expect_eq(i18n_plural_rule(), PL_SLAVIC)
|
||||
expect(Ln("{1} trout", "{1} trouts", 21) == "21 alabalik")
|
||||
expect(Ln("{1} trout", "{1} trouts", 5) == "5 alabaliklar")
|
||||
expect(Ln("{1} fish", "{1} fishes", 2) == "2 fishes")
|
||||
test "plurals by the file's rule, and English by one and many" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
expect(Ln(i18n_st, "{1} trout", "{1} trouts", 1) == "1 trout")
|
||||
expect(Ln(i18n_st, "{1} trout", "{1} trouts", 3) == "3 trouts")
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect_eq(i18n_plural_rule(i18n_st), PL_SLAVIC)
|
||||
expect(Ln(i18n_st, "{1} trout", "{1} trouts", 21) == "21 alabalik")
|
||||
expect(Ln(i18n_st, "{1} trout", "{1} trouts", 5) == "5 alabaliklar")
|
||||
expect(Ln(i18n_st, "{1} fish", "{1} fishes", 2) == "2 fishes")
|
||||
expect_eq(i18n_plural_form(PL_NOT_ONE, 0), 1)
|
||||
expect_eq(i18n_plural_form(PL_ABOVE_ONE, 0), 0)
|
||||
expect_eq(i18n_plural_form(PL_SLAVIC, 3), 1)
|
||||
expect_eq(i18n_plural_form(PL_ONE_FORM, 7), 0)
|
||||
}
|
||||
|
||||
test "the font follows the language, and falls back when it will not load" {
|
||||
fresh()
|
||||
i18n_font_base("font_default")
|
||||
i18n_use("tt")
|
||||
expect_eq(len(fonts_set), 1)
|
||||
expect(i18n_font_now() == dir() + "/font_tt")
|
||||
i18n_use("en")
|
||||
expect(i18n_font_now() == "font_default")
|
||||
font_ok = false
|
||||
i18n_use("tt")
|
||||
expect(i18n_font_now() == "font_default")
|
||||
test "the font follows the language, and falls back when it will not load" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
i18n_font_base(i18n_st, "font_default")
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect_eq(len(i18ntest_st.fonts_set), 1)
|
||||
expect(i18n_font_now(i18n_st) == dir() + "/font_tt")
|
||||
i18n_use(i18n_st, "en")
|
||||
expect(i18n_font_now(i18n_st) == "font_default")
|
||||
i18ntest_st.font_ok = false
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(i18n_font_now(i18n_st) == "font_default")
|
||||
}
|
||||
|
||||
test "a mod in the player's folder is found, replaces a shipped code, and goes when removed" {
|
||||
fresh()
|
||||
test "a mod in the player's folder is found, replaces a shipped code, and goes when removed" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
Fs.write_text(mods() + "/zz.po", header("Mod Tongue", "") + po_line("Settings", "Mettings"))
|
||||
Fs.write_text(mods() + "/tt.po", header("Tongue Two", "") + po_line("Settings", "Z2"))
|
||||
i18n_init()
|
||||
expect_eq(i18n_count(), 3)
|
||||
expect(i18n_name_at(i18n_index("zz")) == "Mod Tongue")
|
||||
i18n_use("zz")
|
||||
expect(L("Settings") == "Mettings")
|
||||
expect(L("Day 3") == "Day 3")
|
||||
i18n_use("tt")
|
||||
expect(L("Settings") == "Z2")
|
||||
i18n_init(i18n_st)
|
||||
expect_eq(i18n_count(i18n_st), 3)
|
||||
expect(i18n_name_at(i18n_st, i18n_index(i18n_st, "zz")) == "Mod Tongue")
|
||||
i18n_use(i18n_st, "zz")
|
||||
expect(L(i18n_st, "Settings") == "Mettings")
|
||||
expect(L(i18n_st, "Day 3") == "Day 3")
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "Settings") == "Z2")
|
||||
Fs.remove(mods() + "/zz.po")
|
||||
Fs.remove(mods() + "/tt.po")
|
||||
i18n_init()
|
||||
expect_eq(i18n_index("zz"), 0)
|
||||
i18n_use("en")
|
||||
expect(not i18n_on())
|
||||
expect(L("Settings") == "Settings")
|
||||
i18n_init(i18n_st)
|
||||
expect_eq(i18n_index(i18n_st, "zz"), 0)
|
||||
i18n_use(i18n_st, "en")
|
||||
expect(not i18n_on(i18n_st))
|
||||
expect(L(i18n_st, "Settings") == "Settings")
|
||||
}
|
||||
|
||||
test "a forced code wins over the one asked for" {
|
||||
fresh()
|
||||
forced_code = "tt"
|
||||
i18n_use("en")
|
||||
expect(i18n_current() == "tt")
|
||||
test "a forced code wins over the one asked for" (i18n_st: mut I18nState, i18ntest_st: mut I18ntestState) {
|
||||
fresh(i18n_st, i18ntest_st)
|
||||
i18ntest_st.forced_code = "tt"
|
||||
i18n_use(i18n_st, "en")
|
||||
expect(i18n_current(i18n_st) == "tt")
|
||||
}
|
||||
|
||||
test "a header field, and a .po loaded from text" {
|
||||
test "a header field, and a .po loaded from text" (i18n_st: mut I18nState) {
|
||||
expect(i18n_header(tt(), "X-Language-Name") == "Test Tongue")
|
||||
expect(i18n_header(tt(), "X-Nothing") == "")
|
||||
i18n_load_text(po_line("Quit", "Cik"))
|
||||
expect(L("Quit") == "Cik")
|
||||
i18n_off()
|
||||
expect(L("Quit") == "Quit")
|
||||
i18n_load_text(i18n_st, po_line("Quit", "Cik"))
|
||||
expect(L(i18n_st, "Quit") == "Cik")
|
||||
i18n_off(i18n_st)
|
||||
expect(L(i18n_st, "Quit") == "Quit")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
# translate.ludic - what the game draws, in the language in use: exact, else a pattern, else the
|
||||
# padding a layout put round it taken off, else a paragraph a sentence at a time; English otherwise
|
||||
export function L(s: string) -> string {
|
||||
if not on { return s }
|
||||
let ci = Dict.get_or(cache, s, -1)
|
||||
if ci >= 0 { return cache_out[ci] }
|
||||
let r = whole(s)
|
||||
if Dict.size(cache) >= CACHE_MAX {
|
||||
Dict.clear(cache)
|
||||
cache_out = new []string
|
||||
export function L(i18n_st: mut I18nState, s: string) -> string {
|
||||
if not i18n_st.on { return s }
|
||||
let ci = Dict.get_or(i18n_st.cache, s, -1)
|
||||
if ci >= 0 { return i18n_st.cache_out[ci] }
|
||||
let r = whole(i18n_st, s)
|
||||
if Dict.size(i18n_st.cache) >= CACHE_MAX {
|
||||
Dict.clear(i18n_st.cache)
|
||||
i18n_st.cache_out = new []string
|
||||
}
|
||||
Dict.set(cache, s, len(cache_out))
|
||||
push(cache_out, r)
|
||||
Dict.set(i18n_st.cache, s, len(i18n_st.cache_out))
|
||||
push(i18n_st.cache_out, r)
|
||||
return r
|
||||
}
|
||||
|
||||
function whole(s: string) -> string {
|
||||
function whole(i18n_st: mut I18nState, s: string) -> string {
|
||||
let n = len(s)
|
||||
if n == 0 { return s }
|
||||
let e = Dict.get_or(exact, s, -1)
|
||||
if e >= 0 { return out[e] }
|
||||
let r = pattern(s, 0)
|
||||
if hit { return r }
|
||||
let e = Dict.get_or(i18n_st.exact, s, -1)
|
||||
if e >= 0 { return i18n_st.out[e] }
|
||||
let r = pattern(i18n_st, s, 0)
|
||||
if i18n_st.hit { return r }
|
||||
# the padding a layout put round a word: " owned"
|
||||
var a = 0
|
||||
var b = n
|
||||
|
|
@ -29,15 +29,15 @@ function whole(s: string) -> string {
|
|||
if a > 0 or b < n {
|
||||
if b <= a { return s }
|
||||
let core: string = s[a..b]
|
||||
let t = whole(core)
|
||||
let t = whole(i18n_st, core)
|
||||
if t == core { return s }
|
||||
return s[0..a] + t + s[b..n]
|
||||
}
|
||||
return sentences(s)
|
||||
return sentences(i18n_st, s)
|
||||
}
|
||||
|
||||
# a paragraph built from sentences, one at a time; English stays where a sentence is unknown
|
||||
function sentences(s: string) -> string {
|
||||
function sentences(i18n_st: mut I18nState, s: string) -> string {
|
||||
let n = len(s)
|
||||
var r = ""
|
||||
var start = 0
|
||||
|
|
@ -47,8 +47,8 @@ function sentences(s: string) -> string {
|
|||
while i < n - 1 {
|
||||
let c = s[i]
|
||||
if (c == 46 or c == 33 or c == 63) and s[i + 1] == 32 {
|
||||
let t = sentence(s[start..i + 1])
|
||||
if hit { any = true }
|
||||
let t = sentence(i18n_st, s[start..i + 1])
|
||||
if i18n_st.hit { any = true }
|
||||
r = r + t + " "
|
||||
start = i + 2
|
||||
pieces += 1
|
||||
|
|
@ -58,18 +58,18 @@ function sentences(s: string) -> string {
|
|||
i += 1
|
||||
}
|
||||
if pieces == 0 { return s }
|
||||
let t = sentence(s[start..n])
|
||||
if hit { any = true }
|
||||
let t = sentence(i18n_st, s[start..n])
|
||||
if i18n_st.hit { any = true }
|
||||
if not any { return s }
|
||||
return r + t
|
||||
}
|
||||
|
||||
function sentence(p: string) -> string {
|
||||
hit = false
|
||||
let e = Dict.get_or(exact, p, -1)
|
||||
function sentence(i18n_st: mut I18nState, p: string) -> string {
|
||||
i18n_st.hit = false
|
||||
let e = Dict.get_or(i18n_st.exact, p, -1)
|
||||
if e >= 0 {
|
||||
hit = true
|
||||
return out[e]
|
||||
i18n_st.hit = true
|
||||
return i18n_st.out[e]
|
||||
}
|
||||
return pattern(p, 0)
|
||||
return pattern(i18n_st, p, 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,43 @@
|
|||
# use.ludic - choosing a language: its file loaded into the lookups, and the font atlas it names
|
||||
export function i18n_use(code: string) -> void {
|
||||
if codes == null { i18n_init() }
|
||||
on = false
|
||||
cur = "en"
|
||||
var k = i18n_index(code)
|
||||
export function i18n_use(i18n_st: mut I18nState, code: string) -> void {
|
||||
if i18n_st.codes == null { i18n_init(i18n_st) }
|
||||
i18n_st.on = false
|
||||
i18n_st.cur = "en"
|
||||
var k = i18n_index(i18n_st, code)
|
||||
let forced = I18nWorld.forced()
|
||||
if len(forced) > 0 { k = i18n_index(forced) }
|
||||
if len(forced) > 0 { k = i18n_index(i18n_st, forced) }
|
||||
var font = ""
|
||||
if k > 0 {
|
||||
let text = Fs.read_text(paths[k])
|
||||
let text = Fs.read_text(i18n_st.paths[k])
|
||||
if text != null {
|
||||
i18n_load_text(text)
|
||||
on = true
|
||||
cur = codes[k]
|
||||
font = fonts[k]
|
||||
i18n_load_text(i18n_st, text)
|
||||
i18n_st.on = true
|
||||
i18n_st.cur = i18n_st.codes[k]
|
||||
font = i18n_st.fonts[k]
|
||||
}
|
||||
}
|
||||
# the atlas follows the language, and is only reloaded when it actually changes
|
||||
if len(font) == 0 { font = font_default }
|
||||
if I18nWorld.font_ready() and not (font == font_now) {
|
||||
if I18nWorld.set_font(font) { font_now = font } else if not (font_now == font_default) {
|
||||
I18nWorld.set_font(font_default)
|
||||
font_now = font_default
|
||||
if len(font) == 0 { font = i18n_st.font_default }
|
||||
if I18nWorld.font_ready() and not (font == i18n_st.font_now) {
|
||||
if I18nWorld.set_font(font) { i18n_st.font_now = font } else if not (i18n_st.font_now == i18n_st.font_default) {
|
||||
I18nWorld.set_font(i18n_st.font_default)
|
||||
i18n_st.font_now = i18n_st.font_default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# a .po's text as the lookups, whatever the language is called (a test, a file from elsewhere)
|
||||
export function i18n_load_text(text: string) -> void {
|
||||
export function i18n_load_text(i18n_st: mut I18nState, text: string) -> void {
|
||||
let es = i18n_parse(text)
|
||||
exact = Dict.new()
|
||||
out = new []string
|
||||
cache = Dict.new()
|
||||
cache_out = new []string
|
||||
caps = new []string
|
||||
for c in 0 .. CAPS { push(caps, "") }
|
||||
plural = Dict.new()
|
||||
forms = new []string
|
||||
plural_rule = plural_rule_of(i18n_header(text, "Plural-Forms"))
|
||||
i18n_st.exact = Dict.new()
|
||||
i18n_st.out = new []string
|
||||
i18n_st.cache = Dict.new()
|
||||
i18n_st.cache_out = new []string
|
||||
i18n_st.caps = new []string
|
||||
for c in 0 .. CAPS { push(i18n_st.caps, "") }
|
||||
i18n_st.plural = Dict.new()
|
||||
i18n_st.forms = new []string
|
||||
i18n_st.plural_rule = plural_rule_of(i18n_header(text, "Plural-Forms"))
|
||||
var np = 0
|
||||
var nlit = 0
|
||||
for i in 0 .. len(es) {
|
||||
|
|
@ -48,33 +48,33 @@ export function i18n_load_text(text: string) -> void {
|
|||
nlit += h + 1
|
||||
}
|
||||
}
|
||||
pat_out = new []string
|
||||
lits = new []string
|
||||
pat_l0 = words(np + 1)
|
||||
pat_nl = words(np + 1)
|
||||
pat_sc = words(np + 1)
|
||||
pat_ph = words(nlit + 1)
|
||||
nx_p = words(np + 1)
|
||||
nx_s = words(np + 1)
|
||||
nx_g = words(np + 1)
|
||||
pfx = Dict.new()
|
||||
sfx = Dict.new()
|
||||
gen_head = 0
|
||||
pat_n = 0
|
||||
i18n_st.pat_out = new []string
|
||||
i18n_st.lits = new []string
|
||||
i18n_st.pat_l0 = words(np + 1)
|
||||
i18n_st.pat_nl = words(np + 1)
|
||||
i18n_st.pat_sc = words(np + 1)
|
||||
i18n_st.pat_ph = words(nlit + 1)
|
||||
i18n_st.nx_p = words(np + 1)
|
||||
i18n_st.nx_s = words(np + 1)
|
||||
i18n_st.nx_g = words(np + 1)
|
||||
i18n_st.pfx = Dict.new()
|
||||
i18n_st.sfx = Dict.new()
|
||||
i18n_st.gen_head = 0
|
||||
i18n_st.pat_n = 0
|
||||
for i in 0 .. len(es) {
|
||||
let e = es[i]
|
||||
if len(e.plural) > 0 { plural_add(e) } else if not (blank(e.id) or blank(e.str)) {
|
||||
if holes(e.id) > 0 { add_pattern(e.id, e.str) } else {
|
||||
Dict.set(exact, e.id, len(out))
|
||||
push(out, e.str)
|
||||
if len(e.plural) > 0 { plural_add(i18n_st, e) } else if not (blank(e.id) or blank(e.str)) {
|
||||
if holes(e.id) > 0 { add_pattern(i18n_st, e.id, e.str) } else {
|
||||
Dict.set(i18n_st.exact, e.id, len(i18n_st.out))
|
||||
push(i18n_st.out, e.str)
|
||||
}
|
||||
}
|
||||
}
|
||||
on = true
|
||||
i18n_st.on = true
|
||||
}
|
||||
|
||||
# English again: nothing loaded, nothing translated
|
||||
export function i18n_off() -> void {
|
||||
on = false
|
||||
cur = "en"
|
||||
export function i18n_off(i18n_st: mut I18nState) -> void {
|
||||
i18n_st.on = false
|
||||
i18n_st.cur = "en"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue