ludic.i18n: the English path is out of L (26.9) - a plain string is drawn as it is in every language (a player named Settings stays Settings), a key, a key glued into text and a bracketed line are made as before. The pattern tables, the sentence and padding lookups, Ln, i18n_pattern_count and kr_words' English-argument lookup are gone; a language's .po is read for keys and plurals. Tests move to keys; README and a changeset say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e2aa928e95
commit
8882331421
18 changed files with 91 additions and 516 deletions
9
changes/i18n-keys-only.md
Normal file
9
changes/i18n-keys-only.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
bump: minor
|
||||
type: change
|
||||
**ludic.i18n draws plain text as it is: the English path is gone (phase 26.9).** `L` makes a key, a
|
||||
key glued into text, or a line bracketed inside another; anything else - a player's name, a chat
|
||||
line, a number - is drawn as it is, in every language, so a player named "Settings" stays
|
||||
"Settings". Removed with it: the lookup of English words (exact lines, patterns with holes, a
|
||||
paragraph a sentence at a time, padding), `Ln` (use `trn(kn"...")`), `i18n_pattern_count`, and an
|
||||
English argument's own lookup inside a key's hole. A language `.po` is read for its keys and
|
||||
plurals only. A game still on English msgids draws them untranslated until they are keys.
|
||||
|
|
@ -26,7 +26,7 @@ section. The rules are in [ludic.base](ludic.base/README.md).
|
|||
| [ludic.devlink](ludic.devlink/README.md) | an editor's live link into a running dev build: loopback UDP, one request a frame, verbs as ports |
|
||||
| [ludic.fishing](ludic.fishing/README.md) | a rod at the water: cast, bite, the reel's fight, the landing; species as an open registry |
|
||||
| [ludic.hints](ludic.hints/README.md) | a rail of what is true now, most urgent first, each a card that teaches it and can be muted by key |
|
||||
| [ludic.i18n](ludic.i18n/README.md) | a game in any language: gettext `.po` files, patterns with holes, plurals, a mod folder, a font per language |
|
||||
| [ludic.i18n](ludic.i18n/README.md) | a game in any language by keys: gettext `.po` files (en.po the English), holes, plurals, a mod folder, a font per language |
|
||||
| [ludic.inventory](ludic.inventory/README.md) | a pack: a count per kind, with room the game decides |
|
||||
| [ludic.minimap](ludic.minimap/README.md) | a map's logic: the explored fog over a grid and its land share, the player's marks, and the view (world to map and back, zoom, pan, the camp, you, focus) |
|
||||
| [ludic.needs](ludic.needs/README.md) | a body's warmth, food, water and energy, and the countdown to a collapse |
|
||||
|
|
|
|||
|
|
@ -1,19 +1,39 @@
|
|||
# ludic.i18n
|
||||
|
||||
A game in any language, from gettext files anyone can add. English is the source and stays in the
|
||||
code: nothing here, and nothing a translator does, rewrites a string in the game. A language is a
|
||||
`.po` keyed by the English exactly as the game draws it, and text is translated at the moment it is
|
||||
drawn, so a string that reaches the screen is covered wherever it was built. Uses nothing - not
|
||||
even `ludic.base`; the font atlas and the mod folder are the game's, through a port.
|
||||
A game in any language, from gettext files anyone can add. The code names text by KEYS, never by its
|
||||
English: a key says what a text is FOR (`pause.resume`), `en.po` says what it says in English, and
|
||||
every other language is a `.po` keyed the same way. Text is made at the moment it is drawn, so a
|
||||
line that reaches the screen is covered wherever it was built. Uses nothing - not even
|
||||
`ludic.base`; the font atlas and the mod folder are the game's, through a port.
|
||||
|
||||
```ludic
|
||||
import "ludic.i18n"
|
||||
i18n_use("tr") # from settings; the atlas follows
|
||||
ov_text(x, y, 16, L("Settings"), ...)
|
||||
ov_text(x, y, 16, L(`Day {day}`), ...) # "Day {1}" in the file matches "Day 12"
|
||||
ov_text(x, y, 16, Ln("{1} trout", "{1} trout", n), ...)
|
||||
ov_text(x, y, 16, L(tr(k"pause.resume")), ...) # "Resume"; "Devam" in Turkish
|
||||
ov_text(x, y, 16, L(trf(k"hud.day", `{day}`, "", "", "")), ...)
|
||||
ov_text(x, y, 16, L(trn(kn"pack.items", n, "", "", "")), ...)
|
||||
```
|
||||
|
||||
## Keys
|
||||
|
||||
A key travels as a string with a marker byte in front (the compiler's `k"pause.resume"` is exactly
|
||||
that; `kn"..."` a plural key), so code that makes text never takes the language's state: the text is
|
||||
made where it is drawn, by `L`.
|
||||
|
||||
- A key's text is the language in use's, else en.po's (read from the languages' directory the first
|
||||
time a key is asked for), else the key itself - `[[key]]` in a developer's build (`i18n_loud`),
|
||||
said once.
|
||||
- Holes `{1}` .. `{4}` take `trf`'s arguments in the language's own order, and a translation may
|
||||
reorder or drop them; an argument that is a key is made into its text first. A plural's count is
|
||||
`{1}`, and its form is the language's `Plural-Forms` rule's.
|
||||
- A key's line inside another's hole survives: an argument's own separators are escaped outside
|
||||
brackets, and a line bracketed with bytes 29 .. 30 (a game's nested line) rides raw and is made
|
||||
first, outermost in.
|
||||
- **Anything without a marker is drawn as it is**, in every language: a player's name, a chat line,
|
||||
a number, a path. There is no lookup by English words, so a player named "Settings" stays
|
||||
"Settings". A key glued into such text (`"Photo: " + tr(k"...")`) is still made where it sits,
|
||||
an interim help for code on its way to a key of its own.
|
||||
|
||||
## Where languages come from
|
||||
|
||||
Later ones replace earlier ones with the same code:
|
||||
|
|
@ -22,40 +42,8 @@ Later ones replace earlier ones with the same code:
|
|||
- every `*.po` in the mod folder the port names - a player drops a file in and picks it. No build.
|
||||
|
||||
`X-Language-Name` in the header is what a menu shows; `X-Font` names a font atlas directory beside
|
||||
the `.po`, for a script the default atlas does not carry; `Plural-Forms` picks the plural rule.
|
||||
|
||||
## What a msgid can be
|
||||
|
||||
- `"Hold to break off deadwood"` - an exact string.
|
||||
- `"Day {1}"` -> `"{1}. Gün"` - a pattern: the English around the holes must match, the holes are
|
||||
whatever the game put there, and a translation may reorder or drop them. A hole's own text is
|
||||
translated too when the file knows it - an item name, or "a deer" with its English article taken
|
||||
off. Of several patterns that match, the one pinning down the most English wins.
|
||||
- A paragraph the game assembled from sentences is tried whole, then a sentence at a time; the
|
||||
padding a layout put round a word (" owned") is taken off and put back.
|
||||
- `msgid_plural` / `msgstr[k]` - `Ln(one, many, n)`, by the file's `Plural-Forms`.
|
||||
|
||||
Whatever has no translation draws in English, so a partial file is always playable.
|
||||
|
||||
## Keys (phase 26: the form that replaces English in the code)
|
||||
|
||||
A key names what a text is FOR (`pause.resume`), and `assets/lang/en.po` says what it says in English,
|
||||
like any other language. A key travels as a string with a marker byte in front (the compiler's
|
||||
`k"pause.resume"` is exactly that; `kn"..."` a plural key), so code that makes text never takes the
|
||||
language's state: the text is made where it is drawn, by `L`.
|
||||
|
||||
```ludic
|
||||
L(tr(k"pause.resume")) # "Resume"; "Devam" in Turkish
|
||||
L(trf(k"catch.saw", tr(k"species.elk.a_name"), "5:30", "", "")) # "You saw an elk at 5:30."
|
||||
L(trn(kn"pack.items", n, "", "", "")) # "{1} items" by the language's plural rule
|
||||
```
|
||||
|
||||
- A key's text is the language in use's, else en.po's (read from the languages' directory the first
|
||||
time a key is asked for), else the key itself - `[[key]]` in a developer's build (`i18n_loud`),
|
||||
said once.
|
||||
- Holes `{1}` .. `{4}` take `trf`'s arguments in the language's own order; an argument that is a key
|
||||
is made into its text first. A plural's count is `{1}`.
|
||||
- A string with no marker takes the English path below, so a game moves over a file at a time.
|
||||
the `.po`, for a script the default atlas does not carry; `Plural-Forms` picks the plural rule. A
|
||||
language that lacks a key draws its English, so a partial file is always playable.
|
||||
|
||||
## Config and port
|
||||
|
||||
|
|
@ -73,8 +61,7 @@ port I18nWorld {
|
|||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `L(s) -> string` | what the game draws, in the language in use (cached) |
|
||||
| `Ln(one, many, n) -> string` | a count's form, with `{1}` the count; English is one for 1, many otherwise (the old form) |
|
||||
| `L(s) -> string` | what the game draws, in the language in use (made once per distinct line, kept) |
|
||||
| `tr(k: Key)`, `trf(k: Key, a, b, c, d)`, `trn(k: Key, n, a, b, c) -> string` | a key (`k"..."`), a key with its holes' arguments, a plural key (`kn"..."`) by `n`: made into text by `L` |
|
||||
| `i18n_key(name)`, `i18n_key_plural(name)`, `key_text(k)`, `i18n_is_key(s)` | a key named at run time, its name without the marker, whether a string is one |
|
||||
| `i18n_base_text(text)`, `i18n_loud(on)` | the base (en.po) from text, for a test; a developer's build's loud missing key |
|
||||
|
|
@ -82,21 +69,16 @@ port I18nWorld {
|
|||
| `i18n_use(code)`, `i18n_off()`, `i18n_on()`, `i18n_current()` | choosing one; whether anything is translating |
|
||||
| `i18n_font_base(dir)`, `i18n_font_now()` | the atlas the overlay was opened with, and the one it has now |
|
||||
| `i18n_load_text(text)`, `i18n_parse(text) -> []PoEntry`, `i18n_header(text, field)` | a `.po` from text, outright |
|
||||
| `i18n_exact_count()`, `i18n_pattern_count()`, `i18n_plural_rule()`, `i18n_plural_form(rule, n)`, `PL_*` | what the file gave |
|
||||
| `i18n_exact_count()`, `i18n_plural_rule()`, `i18n_plural_form(rule, n)`, `PL_*` | what the file gave |
|
||||
| `i18n_find`, `i18n_starts`, `i18n_ends` | the byte helpers, for a game that wants them |
|
||||
|
||||
## Keeping an extractor working
|
||||
|
||||
A game's extractor finds the English by reading its own source for literals (Maroon Lake's
|
||||
`tools/i18n/extract.py`); nothing here changes what it reads. Build sentences a translator can
|
||||
find: one template literal with holes, not fragments glued with `+`.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
ludic test packages/ludic.i18n
|
||||
```
|
||||
|
||||
A shipped list and a mod folder in the temp directory: exact lines, patterns with translated holes
|
||||
and articles, paragraphs, padding, plurals (a Russian-shaped rule), the font and its fallback, a mod
|
||||
replacing a shipped code, a forced code, and English untouched.
|
||||
`keys_test`: keys, holes, plurals, a key glued into text, plain text as it is. `nest_test`: lines
|
||||
inside lines, bare, bracketed and escaped. `i18n_test`: a shipped list and a mod folder in the temp
|
||||
directory, a language's keys, plurals (a Russian-shaped rule), the font and its fallback, a mod
|
||||
replacing a shipped code, a forced code.
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import "bytes.ludic"
|
|||
import "po.ludic"
|
||||
import "langs.ludic"
|
||||
import "use.ludic"
|
||||
import "patterns.ludic"
|
||||
import "match.ludic"
|
||||
import "translate.ludic"
|
||||
import "keys.ludic"
|
||||
import "keyed.ludic"
|
||||
|
|
|
|||
|
|
@ -21,13 +21,6 @@ function kr_unescape(s: string) -> string {
|
|||
return text_of(buf, n)
|
||||
}
|
||||
|
||||
# an argument with letters in it (not a number, a time, a sign): while the game still hands English
|
||||
# into a key's holes (until its code and data are keys, phase 26.9), it gets the old English lookup
|
||||
function kr_words(s: string) -> bool {
|
||||
for i in 0 .. len(s) { if (s[i] >= 65 and s[i] <= 90) or (s[i] >= 97 and s[i] <= 122) { return true } }
|
||||
return false
|
||||
}
|
||||
|
||||
# a count written as decimal digits (a leading - allowed); anything else is 0
|
||||
function kr_int(s: string) -> int {
|
||||
var v = 0
|
||||
|
|
|
|||
|
|
@ -60,7 +60,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) 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) }
|
||||
if i18n_is_key(v) or kx_mixed(v) or kx_nested(v) { v = L(i18n_st, v) }
|
||||
r = r + t[seg..i] + v
|
||||
i = hole_end(t, i)
|
||||
seg = i
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
# match.ludic - which pattern produces a string, the best one being the one that pins down the most
|
||||
# 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(i18n_st: mut I18nState, p: int, s: string) -> bool {
|
||||
let n = len(s)
|
||||
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 = 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
|
||||
for k in 0 .. f_n { if s[k] != first[k] { return false } }
|
||||
for k in 0 .. l_n { if s[n - l_n + k] != last[k] { return false } }
|
||||
let lim = n - l_n
|
||||
var pos = f_n
|
||||
for g in 0 .. nl - 1 {
|
||||
var stop = lim
|
||||
var adv = 0
|
||||
if g < nl - 2 {
|
||||
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 }
|
||||
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(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 = 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
|
||||
}
|
||||
|
||||
@alloc_ok("a translation cache miss (L): once per distinct text")
|
||||
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(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(i18n_st, i18n_st.gen_head, i18n_st.nx_g, s, best) }
|
||||
if best < 0 { return s }
|
||||
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, i18n_st.caps[g]) }
|
||||
let cs = new []string
|
||||
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 }
|
||||
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(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(i18n_st.exact, c[bare..n], -1)
|
||||
if e2 >= 0 { return i18n_st.out[e2] }
|
||||
}
|
||||
if depth < 3 {
|
||||
let r = pattern(i18n_st, c, depth)
|
||||
if i18n_st.hit { return r }
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# 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,
|
||||
# name + "\n" + its blurb): each marked name made in the language in use; the words round it drawn as they are. 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: a-z, 0-9, _ and . (every key the compiler derives or a game writes), a full stop at its
|
||||
|
|
@ -30,8 +30,7 @@ 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) }
|
||||
let r = kx_unmix(i18n_st, s)
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
# patterns.ludic - a msgid with holes ("Day {1}"): its literal pieces, how much English they pin
|
||||
# down, and a chain by its first (else last) four bytes so a lookup tries only its own kind
|
||||
function holes(s: string) -> int {
|
||||
let n = len(s)
|
||||
var h = 0
|
||||
var i = 0
|
||||
while i < n {
|
||||
if hole_at(s, i) >= 0 {
|
||||
h += 1
|
||||
i = hole_end(s, i)
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
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
|
||||
var nl = 0
|
||||
var score = 0
|
||||
while i < n {
|
||||
let num = hole_at(id, i)
|
||||
if num >= 0 {
|
||||
push(i18n_st.lits, id[seg..i])
|
||||
score += i - seg
|
||||
i18n_st.pat_ph[l0 + nl] = num
|
||||
nl += 1
|
||||
i = hole_end(id, i)
|
||||
seg = i
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
push(i18n_st.lits, id[seg..n])
|
||||
score += n - seg
|
||||
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(i18n_st: mut I18nState, p: int, first: string, last: string) -> void {
|
||||
if len(first) >= 4 {
|
||||
let key: string = first[0..4]
|
||||
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)]
|
||||
i18n_st.nx_s[p] = Dict.get_or(i18n_st.sfx, key, 0)
|
||||
Dict.set(i18n_st.sfx, key, p + 1)
|
||||
} else {
|
||||
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(i18n_st: I18nState, t: string, l0: int, nl: int, cs: []string) -> string {
|
||||
let n = len(t)
|
||||
var r = ""
|
||||
var seg = 0
|
||||
var i = 0
|
||||
while i < n {
|
||||
let num = hole_at(t, i)
|
||||
if num >= 0 {
|
||||
r = r + t[seg..i]
|
||||
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
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return r + t[seg..n]
|
||||
}
|
||||
|
|
@ -41,36 +41,3 @@ function plural_add(i18n_st: mut I18nState, e: PoEntry) -> void {
|
|||
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(i18n_st: I18nState, one: string, many: string, n: int) -> string {
|
||||
var t = many
|
||||
if n == 1 { t = one }
|
||||
if i18n_st.on {
|
||||
let k = Dict.get_or(i18n_st.plural, one, -1)
|
||||
if k >= 0 {
|
||||
let f = i18n_st.forms[k * 3 + i18n_plural_form(i18n_st.plural_rule, n)]
|
||||
if not blank(f) { t = f }
|
||||
}
|
||||
}
|
||||
return count_in(t, n)
|
||||
}
|
||||
|
||||
@alloc_ok("a counted line (Ln) made when the game asks for one, outside a frame; its caller keeps it")
|
||||
function count_in(t: string, n: int) -> string {
|
||||
let m = len(t)
|
||||
var r = ""
|
||||
var seg = 0
|
||||
var i = 0
|
||||
while i < m {
|
||||
if hole_at(t, i) == 1 {
|
||||
r = r + t[seg..i] + `{n}`
|
||||
i = hole_end(t, i)
|
||||
seg = i
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return r + t[seg..m]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,23 +24,6 @@ export state I18nState {
|
|||
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_*
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
|
||||
const CACHE_MAX: int = 4000
|
||||
const CAPS: int = 16
|
||||
|
||||
export function i18n_on(i18n_st: I18nState) -> bool { return i18n_st.on }
|
||||
export function i18n_current(i18n_st: I18nState) -> string { return i18n_st.cur }
|
||||
|
|
@ -12,7 +11,6 @@ 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(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(i18n_st: mut I18nState, path: string) -> void { i18n_st.font_now = path }
|
||||
|
|
|
|||
|
|
@ -3,22 +3,6 @@
|
|||
property I18nTables {
|
||||
exact: Dict = null
|
||||
out: []string = null
|
||||
lits: []string = null
|
||||
pat_out: []string = null
|
||||
pat_l0: words = null
|
||||
pat_nl: words = null
|
||||
pat_sc: words = null
|
||||
pat_ph: words = null
|
||||
pat_n: int = 0
|
||||
pfx: Dict = null
|
||||
sfx: Dict = null
|
||||
gen_head: int = 0
|
||||
nx_p: words = null
|
||||
nx_s: words = null
|
||||
nx_g: words = null
|
||||
cache: Dict = null
|
||||
cache_out: []string = null
|
||||
caps: []string = null
|
||||
plural: Dict = null
|
||||
forms: []string = null
|
||||
plural_rule: int = 0
|
||||
|
|
@ -29,22 +13,6 @@ function tables_keep(i18n_st: I18nState) -> I18nTables {
|
|||
let t = new I18nTables
|
||||
t.exact = i18n_st.exact
|
||||
t.out = i18n_st.out
|
||||
t.lits = i18n_st.lits
|
||||
t.pat_out = i18n_st.pat_out
|
||||
t.pat_l0 = i18n_st.pat_l0
|
||||
t.pat_nl = i18n_st.pat_nl
|
||||
t.pat_sc = i18n_st.pat_sc
|
||||
t.pat_ph = i18n_st.pat_ph
|
||||
t.pat_n = i18n_st.pat_n
|
||||
t.pfx = i18n_st.pfx
|
||||
t.sfx = i18n_st.sfx
|
||||
t.gen_head = i18n_st.gen_head
|
||||
t.nx_p = i18n_st.nx_p
|
||||
t.nx_s = i18n_st.nx_s
|
||||
t.nx_g = i18n_st.nx_g
|
||||
t.cache = i18n_st.cache
|
||||
t.cache_out = i18n_st.cache_out
|
||||
t.caps = i18n_st.caps
|
||||
t.plural = i18n_st.plural
|
||||
t.forms = i18n_st.forms
|
||||
t.plural_rule = i18n_st.plural_rule
|
||||
|
|
@ -54,22 +22,6 @@ function tables_keep(i18n_st: I18nState) -> I18nTables {
|
|||
function tables_put(i18n_st: mut I18nState, t: I18nTables) -> void {
|
||||
i18n_st.exact = t.exact
|
||||
i18n_st.out = t.out
|
||||
i18n_st.lits = t.lits
|
||||
i18n_st.pat_out = t.pat_out
|
||||
i18n_st.pat_l0 = t.pat_l0
|
||||
i18n_st.pat_nl = t.pat_nl
|
||||
i18n_st.pat_sc = t.pat_sc
|
||||
i18n_st.pat_ph = t.pat_ph
|
||||
i18n_st.pat_n = t.pat_n
|
||||
i18n_st.pfx = t.pfx
|
||||
i18n_st.sfx = t.sfx
|
||||
i18n_st.gen_head = t.gen_head
|
||||
i18n_st.nx_p = t.nx_p
|
||||
i18n_st.nx_s = t.nx_s
|
||||
i18n_st.nx_g = t.nx_g
|
||||
i18n_st.cache = t.cache
|
||||
i18n_st.cache_out = t.cache_out
|
||||
i18n_st.caps = t.caps
|
||||
i18n_st.plural = t.plural
|
||||
i18n_st.forms = t.forms
|
||||
i18n_st.plural_rule = t.plural_rule
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# i18n_test.ludic - languages from a shipped list and a mod folder, exact lines, patterns with their
|
||||
# holes translated, a paragraph a sentence at a time, padding, plurals, the font a language names,
|
||||
# and English untouched throughout; every file written to the temp directory
|
||||
# i18n_test.ludic - languages from a shipped list and a mod folder, a language's keys and their holes,
|
||||
# plurals, the font a language names, and plain text drawn as it is; every file written to the
|
||||
# temp directory
|
||||
import "ludic.i18n"
|
||||
program I18nTest {
|
||||
numbers float
|
||||
|
|
@ -30,11 +30,10 @@ program I18nTest {
|
|||
# a shipped "tt" with a font of its own, and a Russian-shaped plural
|
||||
function tt() -> string {
|
||||
var s = header("Test Tongue", "\"X-Font: font_tt\\n\"\n\"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : 2);\\n\"")
|
||||
s = s + po_line("Settings", "Zettings") + po_line("Plant fiber", "Lif") + po_line("a deer", "") + po_line("deer", "geyik")
|
||||
s = s + po_line("Day {1}", "{1}. Gun") + po_line("+{1} {2}", "{2} +{1}") + po_line("You saw {1}.", "{1} gordun.")
|
||||
s = s + po_line("You need to eat.", "Yemek lazim.") + po_line("owned", "senin")
|
||||
s = s + "msgid \"{1} trout\"\nmsgid_plural \"{1} trout\"\nmsgstr[0] \"{1} alabalik\"\nmsgstr[1] \"{1} alabaliga\"\nmsgstr[2] \"{1} alabaliklar\"\n\n"
|
||||
s = s + "msgid \"Multi\"\n\"line\"\nmsgstr \"Cok\"\n\"satir\\n\"\n"
|
||||
s = s + po_line("menu.settings", "Zettings") + po_line("items.fiber.name", "Lif") + po_line("hud.day", "{1}. Gun")
|
||||
s = s + po_line("pack.gain", "{2} +{1}") + po_line("empty.one", "")
|
||||
s = s + "msgid \"catch.count\"\nmsgid_plural \"catch.count\"\nmsgstr[0] \"{1} alabalik\"\nmsgstr[1] \"{1} alabaliga\"\nmsgstr[2] \"{1} alabaliklar\"\n\n"
|
||||
s = s + "msgid \"multi.line\"\nmsgstr \"Cok\"\n\"satir\\n\"\n"
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
@ -62,39 +61,25 @@ program I18nTest {
|
|||
expect(L(i18n_st, "Settings") == "Settings")
|
||||
}
|
||||
|
||||
test "exact lines, patterns with translated holes, and English where unknown" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
test "a language's keys, their holes, and plain text drawn as it is" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_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)
|
||||
expect(L(i18n_st, tr(k"menu.settings")) == "Zettings")
|
||||
expect(L(i18n_st, trf(k"hud.day", "12", "", "", "")) == "12. Gun")
|
||||
expect(L(i18n_st, trf(k"pack.gain", "2", tr(k"items.fiber.name"), "", "")) == "Lif +2")
|
||||
expect(L(i18n_st, tr(k"multi.line")) == "Coksatir\n")
|
||||
expect(L(i18n_st, "Settings") == "Settings")
|
||||
expect_eq(i18n_exact_count(i18n_st), 5)
|
||||
}
|
||||
|
||||
test "a paragraph a sentence at a time, and a layout's padding kept" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
test "plurals by the file's rule" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_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" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_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(L(i18n_st, trn(kn"catch.count", 21, "", "", "")) == "21 alabalik")
|
||||
expect(L(i18n_st, trn(kn"catch.count", 5, "", "", "")) == "5 alabaliklar")
|
||||
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)
|
||||
|
|
@ -117,28 +102,28 @@ program I18nTest {
|
|||
test "a language switched back to is its kept tables, not its file read again; a new probe reads it" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_st)
|
||||
i18n_use(i18n_st, "tt")
|
||||
let first = L(i18n_st, "Settings")
|
||||
Fs.write_text(dir() + "/tt.po", header("Tongue", "") + po_line("Settings", "Changed"))
|
||||
let first = L(i18n_st, tr(k"menu.settings"))
|
||||
Fs.write_text(dir() + "/tt.po", header("Tongue", "") + po_line("menu.settings", "Changed"))
|
||||
i18n_use(i18n_st, "en")
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "Settings") == first)
|
||||
expect(L(i18n_st, tr(k"menu.settings")) == first)
|
||||
i18n_init(i18n_st)
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "Settings") == "Changed")
|
||||
expect(L(i18n_st, tr(k"menu.settings")) == "Changed")
|
||||
}
|
||||
|
||||
test "a mod in the player's folder is found, replaces a shipped code, and goes when removed" (i18n_st: mut I18nState, i18n_test_st: mut I18nTestState) {
|
||||
fresh(i18n_st, i18n_test_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"))
|
||||
Fs.write_text(mods() + "/zz.po", header("Mod Tongue", "") + po_line("menu.settings", "Mettings"))
|
||||
Fs.write_text(mods() + "/tt.po", header("Tongue Two", "") + po_line("menu.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, tr(k"menu.settings")) == "Mettings")
|
||||
expect(L(i18n_st, "Day 3") == "Day 3")
|
||||
i18n_use(i18n_st, "tt")
|
||||
expect(L(i18n_st, "Settings") == "Z2")
|
||||
expect(L(i18n_st, tr(k"menu.settings")) == "Z2")
|
||||
Fs.remove(mods() + "/zz.po")
|
||||
Fs.remove(mods() + "/tt.po")
|
||||
i18n_init(i18n_st)
|
||||
|
|
@ -158,9 +143,9 @@ program I18nTest {
|
|||
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(i18n_st, po_line("Quit", "Cik"))
|
||||
expect(L(i18n_st, "Quit") == "Cik")
|
||||
i18n_load_text(i18n_st, po_line("pause.quit", "Cik"))
|
||||
expect(L(i18n_st, tr(k"pause.quit")) == "Cik")
|
||||
i18n_off(i18n_st)
|
||||
expect(L(i18n_st, "Quit") == "Quit")
|
||||
expect(not (L(i18n_st, tr(k"pause.quit")) == "Cik")) # back to English, whatever en.po is found
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# keys_test.ludic - keys where English was: en.po as the base under every language, a key's text from the
|
||||
# language in use else English else the key ([[key]] in a developer's build), holes filled by arguments
|
||||
# that may be keys themselves, plural forms by each language's rule, and the old English form beside them
|
||||
# that may be keys themselves, plural forms by each language's rule, and plain text drawn as it is
|
||||
import "ludic.i18n"
|
||||
program I18nKeysTest {
|
||||
numbers float
|
||||
|
|
@ -68,24 +68,14 @@ program I18nKeysTest {
|
|||
expect(L(i18n_st, trn(kn"catch.count", 2, "", "", "")) == "2 trout") # the language lacks it
|
||||
}
|
||||
|
||||
test "the old English form still works beside keys, so a game can move over a file at a time" (i18n_st: mut I18nState) {
|
||||
test "a plain string is drawn as it is in every language: a player named Settings stays Settings" (i18n_st: mut I18nState) {
|
||||
start (i18n_st)
|
||||
expect(L(i18n_st, "Settings") == "Settings")
|
||||
i18n_load_text(i18n_st, tt())
|
||||
expect(L(i18n_st, "Settings") == "Ayarlar")
|
||||
expect(L(i18n_st, tr(k"pause.resume")) == "Devam")
|
||||
expect(L(i18n_st, "Settings") == "Settings" and L(i18n_st, tr(k"pause.resume")) == "Devam")
|
||||
expect(L(i18n_st, trf(k"catch.saw", "Settings", "5:30", "", "")) == "5:30 saatinde Settings gordun.") # a name in a hole too
|
||||
expect(not i18n_is_key("Settings") and i18n_is_key(i18n_key("x")) and i18n_is_key(i18n_key_plural("x")))
|
||||
}
|
||||
|
||||
test "an English value handed into a key's hole still gets its old English line, until it is a key itself" (i18n_st: mut I18nState) {
|
||||
start (i18n_st)
|
||||
expect(L(i18n_st, trf(k"catch.saw", "Settings", "5:30", "", "")) == "You saw Settings at 5:30.")
|
||||
i18n_load_text(i18n_st, tt())
|
||||
expect(L(i18n_st, trf(k"catch.saw", "Settings", "5:30", "", "")) == "5:30 saatinde Ayarlar gordun.") # exact
|
||||
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")
|
||||
|
|
@ -94,7 +84,7 @@ program I18nKeysTest {
|
|||
expect(L(i18n_st, "Not owned. " + tr(k"pause.quit")) == "Not owned. Quit" and L(i18n_st, elk + "Quit") == "an elkQuit")
|
||||
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, "Day " + tr(k"pause.resume")) == "Day Devam") # the name made; the words round it as they are
|
||||
expect(L(i18n_st, trf(k"catch.saw", elk, "5:30", "", "")) == "5:30 saatinde bir geyik gordun.") # a key whole is untouched
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ program I18nNestTest {
|
|||
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")
|
||||
expect(L(i18n_st, "Settings " + day) == "Settings 3. Gun") # the words round it drawn as they are
|
||||
expect(L(i18n_st, b("Settings")) == "Settings") # a name in a bracket too
|
||||
}
|
||||
|
||||
test "a bracket with no partner draws nothing of itself" (i18n_st: mut I18nState) {
|
||||
|
|
|
|||
|
|
@ -1,83 +1,10 @@
|
|||
# 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
|
||||
# translate.ludic - what the game draws, in the language in use: a key (keyed_render.ludic), a line with
|
||||
# a key glued into it (mixed.ludic) or bracketed inside it (nest.ludic); anything else is drawn as it is -
|
||||
# a player's name, a chat line, a number. English lives in en.po under keys, never looked up by its words
|
||||
export function L(i18n_st: mut I18nState, s: string) -> string {
|
||||
let mixed = kx_mixed(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 }
|
||||
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)
|
||||
List.clear(i18n_st.cache_out)
|
||||
}
|
||||
@alloc_ok("a translation cache miss: the entry is kept, up to CACHE_MAX")
|
||||
Dict.set(i18n_st.cache, s, len(i18n_st.cache_out))
|
||||
@alloc_ok("a translation cache miss: as above")
|
||||
push(i18n_st.cache_out, r)
|
||||
return r
|
||||
}
|
||||
|
||||
@alloc_ok("a translation cache miss (L): once per distinct text, bounded by CACHE_MAX")
|
||||
function whole(i18n_st: mut I18nState, s: string) -> string {
|
||||
let n = len(s)
|
||||
if n == 0 { return s }
|
||||
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
|
||||
while a < n and s[a] == 32 { a += 1 }
|
||||
while b > a and s[b - 1] == 32 { b -= 1 }
|
||||
if a > 0 or b < n {
|
||||
if b <= a { return s }
|
||||
let core: string = s[a..b]
|
||||
let t = whole(i18n_st, core)
|
||||
if t == core { return s }
|
||||
return s[0..a] + t + s[b..n]
|
||||
}
|
||||
return sentences(i18n_st, s)
|
||||
}
|
||||
|
||||
# a paragraph built from sentences, one at a time; English stays where a sentence is unknown
|
||||
@alloc_ok("a translation cache miss (L): once per distinct text")
|
||||
function sentences(i18n_st: mut I18nState, s: string) -> string {
|
||||
let n = len(s)
|
||||
var r = ""
|
||||
var start = 0
|
||||
var any = false
|
||||
var pieces = 0
|
||||
var i = 0
|
||||
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(i18n_st, s[start..i + 1])
|
||||
if i18n_st.hit { any = true }
|
||||
r = r + t + " "
|
||||
start = i + 2
|
||||
pieces += 1
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
if pieces == 0 { return s }
|
||||
let t = sentence(i18n_st, s[start..n])
|
||||
if i18n_st.hit { any = true }
|
||||
if not any { return s }
|
||||
return r + t
|
||||
}
|
||||
|
||||
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 {
|
||||
i18n_st.hit = true
|
||||
return i18n_st.out[e]
|
||||
}
|
||||
return pattern(i18n_st, p, 0)
|
||||
return s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,45 +40,16 @@ export function i18n_load_text(i18n_st: mut I18nState, text: string) -> void {
|
|||
kc_clear(i18n_st)
|
||||
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) {
|
||||
if blank(es[i].id) or blank(es[i].str) { continue }
|
||||
let h = holes(es[i].id)
|
||||
if h > 0 {
|
||||
np += 1
|
||||
nlit += h + 1
|
||||
}
|
||||
}
|
||||
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(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
i18n_st.on = true
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue