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
|
|
@ -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), ...)
|
||||
i18n_use("tr") # from settings; the atlas follows
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue