Translations/themes#28
Conversation
Captures the agreed design for per-user language/theme support: catalog mechanism for source-code literals, file-path resolution with fallback for on-disk content, frame primitives in strings.yml so themes can redefine box edges and rule patterns, and a six-phase migration that keeps the talker working at every step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MOTDFILES is the actual constant; ADMINFILES is a sixth translatable category that was missed during brainstorming. Both corrections are discovered codebase facts, not design changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the first phase of localisation: vendor libyaml, move six content categories under files/langs/en_GB/, implement the file-path resolver with user-locale fallback to default, and sweep every call site. Each of the six categories is converted in its own commit and the talker stays bootable throughout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No functional code uses libyaml yet; this is build-system prep for the string-catalog mechanism in Phase 2 of the localisation plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The guard in yaml_private.h is #if HAVE_CONFIG_H; the macro must be defined BEFORE config.h is opened for the include to fire. The Makefile flag -DHAVE_CONFIG_H is what actually makes that work. The define inside config.h itself was dead and misleading — removing it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No implementation yet; declarations only. Lets later tasks reference the locale_*() functions without forward-decl clutter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
locale.h declared struct locale_catalog and struct locale_state as opaque forward decls but no function signature in the header references them. They are deferred to Phase 2 (catalog) and Task 5 (locale_state in globals.h) respectively. Removing the dead scaffolding from the header. The plan's file-summary listed locale_discover and locale_set_user as prototypes to add in Task 2; the concrete step did not. The step is correct — locale_discover is internal, locale_set_user is Phase 2. Fixed the summary to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New INIT-section key; defaults to en_GB if absent. Stored in amsys->default_locale, ready for the locale resolver to consume. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Use strcpy after the bounds guard (matching all other string fields in parse_init_option), use *initopt + the "too long" template for the error message, and move the INITOPT_LIST entry out of the room-defaults cluster. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds <LANGS_ROOT>/<default_locale>/<category>/<name> and stats it. Returns 1 if present, 0 otherwise. No callers yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
<sys/types.h>, <dirent.h>, and <string.h> were forward-looking for Task 5 directory enumeration; only adding them when the consumer code lands keeps the TU honest with the codebase's "include what you use" convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enumerates files/langs/* directories into amsys->locales. Not yet called from boot — that wires in once the directory tree exists. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes from review:
- Replace magic 64 with MAX_LOCALES (defines.h, two call sites).
- Track default_seen before the cap guard so a default locale
enumerated past the cap is not falsely reported as missing.
- Reject overlong names in is_valid_locale_dirname so the strncpy
cannot silently truncate a name that does not match any real dir.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds user->locale field (zero-init via existing calloc). Phase 1 leaves the field empty at runtime, so locale_path behaves as locale_default_path. Phase 2 will wire up persistence and the set lang command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
locale.h duplicated declarations from prototypes.h with no other content — single-source-of-truth convention says prototypes.h wins. Doc comments folded into locale.c above each function definition. user->locale relocates from the identity-fields cluster to the string-preference cluster (alongside in_phrase/out_phrase) where it belongs semantically and where Phase 2 save/load logic will expect it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Complete inventory of every reference to ADMINFILES/DATAFILES/HELPFILES/ MISCFILES/MOTDFILES/TEXTFILES in source. Drives the sweep tasks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locks in the shape every sweep task uses so style doesn't drift across the six per-category commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The six translatable category directories move under files/langs/en_GB/. The category constants stay absolute for now, pointing at the new locations, so all existing fopen sites continue to work. Tasks 11-16 flip each constant to a bare name as its call sites are converted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Discovery + default-locale validation runs after config parsing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every ADMINFILES call site now resolves through locale_*_path; the constant flips from an absolute path to the bare category name. The two ADMINFILES sites listed in the Phase 1 audit ledger live at src/commands/display.c:32 and src/commands/display.c:61. Both compose paths of the form "<TEXTFILES>/<ADMINFILES>/<file>", where ADMINFILES is being used as a subdirectory name under the textfiles tree rather than as a top-level category. With ADMINFILES now equal to the literal string "adminfiles", the existing sprintf calls still produce the correct path (TEXTFILES remains absolute until Task 16). The structural conversion of these lines to locale_path will be performed in Task 16 alongside the TEXTFILES sweep, since the two constants are entangled on the same lines and converting one without the other would yield a doubled path that won't resolve. Verified: grep "ADMINFILES.*%s" returns no matches. Build deferred: local environment lacks clang/make and the docker daemon is not currently available. The user will verify on their normal environment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Includes the boot-time config load and room-file loaders. The constant is now a bare category name; absolute path resolution moves entirely into the locale helpers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 9 extended MISCFILES from "files/miscfiles" (15 chars) to "files/langs/en_GB/miscfiles" (27 chars) but suggestions.c declared its filename buffer as 30 bytes, which is just too small for the new path + "/suggestions" + NUL = 40 bytes. Stack overflow on every suggest/rsug invocation. Resize to 80 to match every other file. The MISCFILES sprintf sites in this file remain unchanged — those convert to locale_*_path in Task 14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Uses the per-user resolver so future non-default locales automatically pick up their translated help articles. Build deferred: local environment lacks clang/make and the Docker daemon is not currently available. The user will verify on their normal environment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routes rules/news/wizrules through locale_path (per-user lookup so a non-default user locale picks up its translated copy) and routes the suggestions board, hangman dictionary and boot-time suggestion count through locale_default_path (shared server state, default locale). Build deferred: local environment lacks clang/make and the Docker daemon is not currently available. The user will verify on their normal environment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MOTD now resolves via the per-user helper for post-login displays, ready for per-locale MOTDs once Phase 2 wires up set lang. Pre-login MOTD and count_motds use default-locale resolution since no user context is available. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Includes the deferred display.c sites where TEXTFILES and ADMINFILES were entangled on the same line; ADMINFILES now appears as the subdirectory component of the name argument passed to locale_path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a partial fallback test locale (one overridden motd) so the fallback resolution can be smoke-tested when the talker is run in a working build environment. Seed the migration ledger. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The locale-aware path "files/langs/<locale>/<cat>/<file>" is ~27 chars longer than the pre-Phase-1 paths. Buffers at 80 bytes still avoid overflow (snprintf truncates safely) but truncation manifests as silent fopen failures with vague errors. Upsizing to 128 in admin.c and 160 in display.c's admin-files branch gives comfortable headroom for any realistic locale name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce struct lang_entry and struct locale_catalog, extend struct locale_state with a catalog table, and add a non-owning catalog pointer to UR_OBJECT. Pure type declarations; no behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the dangling CATALOG_BUCKETS reference (the constant lives in catalog.c, not in any header) and document default_index's pre-init sentinel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
align_into's prototype references enum align_value; without the include, gnu23 -Wpedantic would warn at the prototype site (the enum isn't visible until uibuilders.h is included separately). Including it here keeps prototypes.h self-contained. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default frame appearance matches the existing +----+ boxes used throughout the talker. Phase 3 builders consume these; Phase 4 conversions of frame-heavy commands then become byte-identical for en_GB users while remaining themeable for non-default locales. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Composes lcap + fill-pattern + optional inline " label " + remaining fill + rcap over a fixed visible width. Every length calculation goes through visible_strlen so colour escapes don't push the right border. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ox_close
Stateful box rendering with opaque BOX handle. Top/separator/bottom
edges drawn from ui.box.{top,sep,bot}.* keys; body rows padded via
align_into so the right border doesn't drift on coloured cells.
box_close frees the handle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tables share the box frame and split inner width into N visible-column columns. table_emit_row wraps each cell on word boundaries (hard-break mid-word as a fallback) and emits multiple body lines until every cell is consumed. Header gets a separator underneath; close delegates to box_close. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Overrides only meta.* and ui.* — everything else falls back to en_GB. Used by the Phase 3 pilot to demonstrate end-to-end themed frames under a non-default locale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First end-to-end consumer of the locale catalog + lang_* API. Every server-authored literal in wiz_list() is lifted into wizlist.* keys in files/langs/en_GB/strings.yml; en_GB output is byte-identical to before, and a themed locale can re-skin the section dividers by overriding the wizlist.frame.* keys. Notable deviation from the rough pattern in the Phase 3 plan: the wizlist body has no |...| side rails, so the per-section frames are stored as opaque catalog values rather than rendered through box_open / rule(). A table_open pass would have shifted bytes. Variable-width formatting (%-*.*s with widths derived from teslen) is performed locally with snprintf before the result is handed to the catalog format string as a plain %s, because the catalog signature framework rejects %* specifiers. Ledger updated with a "Wizlist (Phase 3 pilot)" section so the sweep table has a home before Phase 4 Task 1 promotes it formally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark Phase 3 converted in the migration ledger and append a verification block describing the smoke-test steps for a Linux/macOS host. The wizlist pilot section (already present from Task 8) is left in place as the canonical example of the conversion pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lock down the six-step checklist every command conversion follows so the sweep stays consistent. Seed the ledger with the Phase 4 commands. Notes the wizlist-pilot lessons: opaque frame literals where no side rails exist; pre-format %* widths with snprintf since the catalog signature validator rejects them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the inline frame-drawing in show_igusers (in src/amnuts.c) in favour of the Phase 3 box_open/box_line/box_separator/box_close pipeline, and lift every server-authored literal — the title, the per-name cell format, and the empty-state message — into show_igusers.* keys in files/langs/en_GB/strings.yml. The row body is assembled by concatenating show_igusers.name_cell (a single " %1$s" cell holding a pre-snprintf'd 24-col-padded name) up to three times per logical row, then handed to box_line; the builder's ALIGN_LEFT pad fills out the box's 76 inner visible columns, reproducing the trailing-space padding the original sprintf chain produced manually. The title catalog value carries a load-bearing leading space to preserve the "| <content>" inset of the pre-conversion byte stream. Output on en_GB is byte-identical to the pre-conversion command; themed locales can re-skin both the frame primitives (via ui.*) and the visible strings (via the show_igusers.* overrides). Ledger row in docs/superpowers/specs/localisation-migration.md flipped to converted (2026-05-12). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the inline frame-drawing in grep_users in favour of the Phase 3 box_open/box_line/box_separator/box_close pipeline, and lift every server-authored literal — the four early-exit messages, the title row, the per-match body row halves, the orphan-row padding, the empty-state message, and the summary footer — into grepusers.* keys in files/langs/en_GB/strings.yml. The 78-col frame, title row, empty-state rows, and summary footer are all 78 visible cols wide and survive the box_line pipeline at inner = 76 cols, including byte-identical reproduction of the align_string(ALIGN_LEFT, 78, 1, "|", ...) trailing line — box_line's ALIGN_LEFT pad fills the inner span the same way align_string's %*.*s pad did, and the leading space inside grepusers.title / grepusers.empty_message / grepusers.footer is load-bearing (it reproduces the first inset byte that align_string used to overwrite with the marker). The per-match body rows are NOT handed to box_line. The original layout deliberately overruns the frame: two halves are concatenated for a paired row at 86 visible cols between the outer rails, and a dangling first half is padded out to 82 visible cols. These row pieces ship as opaque catalog values (grepusers.row_first / grepusers.row_second / grepusers.row_orphan_pad) that the C side concatenates and hands to write_user directly, so byte-identity is preserved without lying to box_line about the inner width per row. Names and level labels are pre-padded with snprintf so the row signatures stay plain %s — the catalog signature validator does not accept width-via-arg (%-*s) specifiers (wizlist pilot lesson). Output on en_GB is byte-identical to the pre-conversion command; themed locales can re-skin both the frame primitives (via ui.*) and the visible strings (via the grepusers.* overrides), with the row columns themselves staying server-side since shifting them needs matching C-side row assembly. Ledger row in docs/superpowers/specs/localisation-migration.md flipped to converted (2026-05-12). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
defines.h has had #define ALIGN_LEFT/CENTRE/RIGHT macros since long before Phase 3, used by the pre-existing align_string() in src/strings.c. Phase 3's enum align_value redeclaration collided with the macros at every translation unit that included both headers — preprocessor expanded the enum identifiers into integers, producing a syntax error. Drop the enum entirely; use int consistent with the existing align_string convention. ALIGN_LEFT et al. remain macros in defines.h and are now the single source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lift every server-authored literal in the `lban` WIZ command into the listbans.* keys of files/langs/en_GB/strings.yml. The four subcommand banners (sites/users/swears/new) ship as opaque catalog frame values rather than being synthesised through box_open: the original layout has no |...| body rails, just a "~BB*** ... ***" header followed by either paged file content or an inline swear-word list, so a box_open / box_line pass would shift bytes. The wizlist pilot's un-railed-frame pattern applies cleanly here. The three subcommands that page their bodies via more() against SITEBAN / USERBAN / NEWBAN are deliberately untouched: paged file content is outside the catalog surface and already routes through the Phase 1 locale-aware locale_default_path resolver. Quirk preserved: the source's `if (strcmp(word[1], "new"))` is inverted, so the usage key only fires when the user types `lban new` exactly; any other unrecognised arg (or the no-arg case) falls into the new-bans banner. Ledger row for listbans flipped from `pending` to `converted`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lift every server-authored literal in the `system` admin command into
system.* keys in files/langs/en_GB/strings.yml — five sections (header,
users, netlinks, rooms, memory) plus the port-info header matrix, the
inner separator, the "For other options" footer, the "Netlinks not
compiled" notice, and the usage line. 57 keys in total.
The five sections chain through one box_open / box_line / box_separator
/ box_close pipeline at 78 visible cols total (inner = 76). When `-a`
is given, all five sections render as one continuous box; for the
standalone `-u`/`-n`/`-r`/`-m` switches, only the relevant section is
drawn (still inside a freshly-opened-and-closed box of its own). The
no-arg case is Section A plus a "For other options" footer mini-section.
Two divergences from the standard box flow are preserved:
1. The inner separator that follows each section's title row uses
`|` caps, not the `+` caps box_separator emits. It therefore ships
as the opaque catalog literal `system.inner_sep` and is written
direct via write_user — there's no way to coax box_separator into
producing `|---|` without breaking other commands.
2. The port-info header has 8 NETLINKS x IDENTD-state x WIZPORT
variants; each ships as a (label, value) pair under
system.ports.label.* and system.ports.value.*. The same #ifdef
cascade as the pre-conversion code picks one pair per build.
Section-section boundaries inside `-a` (and the +--+ between any
section's main rows and its subsection rows) flow through box_separator
normally — those use the standard `+---+` caps. The final close at the
end of each invocation is a `+---+` from box_close followed by an extra
"\n", matching the original's `+---+\n\n` trailer.
Every row label is baked into its catalog format string with the
appropriate inline whitespace padding so themed locales can rewrite
both wording and column geometry. The server side passes only the
dynamic values; the inline literal label columns and gutter whitespace
are catalog data. Each row's body, once filled, is at most 76 visible
cols, and box_line right-pads to 76 with spaces — so rows that ship a
hair short still emit byte-identically to the pre-conversion
vwrite_user formats (which baked the trailing spaces into the format
string).
Two preserved quirks:
- The memory section's "total" row mixes %12.3f (Mb) and %d (bytes).
The catalog signature validator rejects %f, so the Mb value is
pre-formatted via snprintf to a 12-char string locally and shipped
through `%12s`. Output bytes are identical to the original
%12.3f for any value that fits in 12 cols at three decimal places.
- The Section U "users at level" row uses ` : ` (space-colon-space)
like Section A's general-info rows, while every other Section U/N/R/M
row uses `: ` (no leading space). That asymmetry is preserved verbatim
from the original format strings; the labels carry the appropriate
inline padding so each row's colon lands at the same column the
original vwrite_user formats put it at.
Output on en_GB is byte-identical to the pre-conversion command, verified
by a positional-printf-aware comparison against the original format
strings across all rows + title cells + port-info variants. A themed
locale can re-skin both the frame primitives (via ui.*) and the visible
strings (via the system.* overrides) without further C-side changes.
Ledger row in docs/superpowers/specs/localisation-migration.md flipped
from `pending` to `converted (2026-05-12)`; File column set to
`src/commands/system.c`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts the framing parts of the `help` command into the locale catalog:
unknown-topic / ambiguous-match messages, the level- and function-grouped
commands-list screens, and the Amnuts + NUTS credits screens. The
per-topic helpfile path (more() against files/langs/<locale>/helpfiles/)
is OUT OF SCOPE — that path is already locale-aware from Phase 1 and
stays untouched.
Picked approach (a) from the Phase 4 plan: keep the existing
align_string() pipeline for the rail-bearing rows and feed it the
catalog format via lang(user, "key") rather than switching to
box_open/box_line. align_string already emits the byte stream the
original sprintf chain produced (including the |…| rails baked into the
marker arg), so this is the safer byte-identical conversion. The two
per-row in-loop format strings (`~FR%s~RS%s %s` / `%s %s` and the
function-grouped pair) ship as help.commands.row.{level,function}_{xcom,plain}
keys; the row LOOP stays in C and the assembled cell is fed to
align_string via the existing sds-based concatenation.
The credits screens are flat (no rails) and ship as three catalog
values each (header, version, body); each body is one double-quoted
scalar containing all remaining static lines from the original chain
of write_user calls — long but byte-identical, and lang_user's
internal ARR_SIZE * 2 (2000 byte) buffer comfortably holds either
credits body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five commands converted (show_igusers, grepusers, listbans, system, help). Each landed in its own commit with matching en_GB catalog entries and a ledger update. Notable preserved quirks: grepusers' 86- vs-78-col pair-row overrun, listbans' new-branch reverse-logic on unrecognised args, system's asymmetric ` : ` / `: ` spacing and `|---|` inner separators. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5 conversions begin to migrate vwrite_level call sites that pass notify_invis = 0 or record_flag = 1; before this landed, the flags were silently ignored and a one-shot warning syslog was the only safety net. Now lang_level fully matches vwrite_level so migrations are byte-equivalent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three Python tools supporting the Phase 5 sweep and the
translator workflow:
- extract.py walks one C source file and suggests catalog keys.
- refs.py cross-checks lang_*("key", ...) callers against the
default catalog, reporting missing keys and orphans.
- check.py replicates the C-side format-signature validation
as a standalone CLI for translators.
All three are dev-only; not shipped with the talker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esc, suicide, unmuzzle, wake 10 small commands converted to lang_user / lang_room + catalog keys. Each is a 1-10 string trivial conversion; batched into one commit because individually they'd swamp the log. 41 new keys added to files/langs/en_GB/strings.yml under <command>.* namespaces. Every escape (~OL/~FC/~FR/etc.) and every whitespace byte is preserved verbatim from the C source. Two non-ASCII control bytes survive the YAML round-trip cleanly: cls.escape_seq ships the ANSI ESC[2J via "\x1B" and suicide.confirm_prompt ships the original "\07" bell via "\a" — both libyaml-supported escapes. Three calls leave write_user / write_room behind: - wake.c retains write_user(user, notloggedon) — `notloggedon` is a shared global string outside the per-command catalog surface. - home.c's invisible-arrival branch still routes through write_room_except with the global `invisenter` constant for the same reason. - The muzzle / unmuzzle "You have been muzzled!" mail-or-write path is unchanged — that text is also persisted to mail spool and is sender-authored, not recipient-locale content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5 is the bulk inline-string conversion sweep — open-ended by design. The catalog framework, the lang_* API, the UI builders, and the translator tooling (extract.py / refs.py / check.py) are all in place. Coverage grows commit by commit. The ledger now reflects: - Phase 5 row marked in progress (framework + tooling shipped). - Sweep tooling docs describing extract/refs/check usage. - Batch 1 (10 small commands) landed alongside the lang_level notify_invis/record_flag semantic-gap close. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A partial French translation of the Phase 5 batch-1 keys plus meta.* and the ping smoke-test key. Validated clean by tools/locale/check.py. Intentionally partial — every non-translated key falls back to en_GB via the Phase 2 catalog fallback, which is the behaviour real translators rely on as they grow their locale incrementally. Phase 6 is "translator's job, not a code phase" per design spec §9.1; this commit demonstrates the end-to-end runbook from docs/superpowers/plans/2026-05-11-localisation-phase6.md works. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related structural changes: 1. config / config2 move from files/langs/en_GB/datafiles/ to files/datafiles/. They were never user-locale content -- they're server-only configuration files. Treating them as a translatable category was a Phase 1 oversight; this commit corrects the layout. 2. The locale-aware category that previously held config alongside room descriptions, maps, boards, and banned-site lists is renamed from "datafiles" to "locations" -- a more accurate name for what it actually contains now that the server-config files have moved out. Directory rename + matching defines.h constant rename (LOCATIONS replaces the bare DATAFILES) propagated through every locale_*_path call site. The DATAFILES constant is repurposed: it now means the absolute root-level files/datafiles/ directory and has exactly one consumer (load_and_parse_config) for opening the boot-time config file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3-4 conversions left full-line frame literals in the catalog (wizlist.frame.*, system.inner_sep, help.commands.frame_*). Those were a localisation dead end — themed locales couldn't truly re-skin the frames without overriding both ui.* and command-specific keys. The right abstraction is for the UI builders to synthesise each frame from ui.* primitives with the command supplying only the label text. Changes: - ui.rule.label_lpad: 6 -> 5 in en_GB (matches the +----- title -----+ codebase convention; this was the bug that originally drove the wizlist pilot to opaque frame literals). - New box_inner_separator(BOX b) for the |---| (body-rail-capped) separator used inside boxes. Distinct from box_separator's +---+ which is for between-box boundaries. - Wizlist: 4 frame keys -> 3 label keys; frames synthesised by rule(). - System: system.inner_sep dropped; box_inner_separator() emits it. - Help framing: 3 frame keys dropped; rule(user, 78, NULL) emits the bare +----+ rails. Themers can now re-skin every framed screen by overriding only the ui.* keys; command-specific catalog entries no longer carry frame appearance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Newline flood and ANSI ESC[2J clear-screen sequence are control bytes, not translatable text. Put both literals back in src/commands/cls.c as plain write_user calls; remove the two catalog keys; flip the ledger row from converted to not-applicable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Several Phase 3-5 conversions left pure glue and control bytes in
the catalog. Translators have nothing to do with these — they're
layout artefacts. Move each back into C and pull rail bytes from
ui.box.body.{lside,rside} so themed locales still re-skin the rails.
Removed:
- wizlist.{wrap_indent, level_prefix, name_cell, row.online_*}
- show_igusers.name_cell
- grepusers.{row_first, row_second, row_orphan_pad}
- listbans.swears.{row, trailer_on}
- home.room_arrival
- help.credits.amnuts.header_bar
Kept (prose with substitutions, or label keys for rule()):
- wizlist.{label.*, none_listed, invisible_count, no_wizzes_on}
- show_igusers.{title, none}
- grepusers.{usage, bad_*, title, empty_message, footer}
- listbans.{sites,users,swears,new}.{header,empty}, swears.trailer_off, usage
- home.{already_home, traverse}
- All system.* (column-coupled to label peers; separate decision)
- All help.* except the standalone header_bar
Byte-identical on en_GB. Themed locales (cowboy) now see consistent
rails wherever rails appear, including the grepusers pair-rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align catalog write-functions with the existing write_* / vwrite_* codebase convention. The lang_ prefix didn't read like a write function despite being one; the new names make the relationship to write_user / vwrite_user / vwrite_room_except / vwrite_level unambiguous. Pure rename - semantics, signatures, and behaviour unchanged. The non-write helpers (lang, lang_format, locale_*, catalog_*) keep their names since they're not analogous to a write_* sibling. tools/locale/refs.py's regex updated to recognise both old and new names while the migration tooling stabilises. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings write_user_lang, write_room_lang and write_level_lang are catalog-keyed sisters to write_user / vwrite_room_except / vwrite_level. The renamed functions now sit next to their siblings in src/amnuts.c, replacing the file-static catalog_resolve call with the public lang() lookup (same semantics, including the rate-limited missing-key warning). catalog.c keeps lang, lang_format, and everything else. Prototypes moved with them; the printf-format attribute is intentionally omitted on _lang variants because the second parameter is a catalog key, not a printf format string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
I am glad internationalization (i18n) is finally getting addressed though I wonder if this is the best strategy. I am sure this will allow much easier localization (l10n) in the future. Perhaps we will even see an official Portuguese translation? |
Which part are you not sure about? I was starting to address this as, "OK, translations", but then it really is just lends itself to "theming, but a theme could just be en-GB or pt-BR" and let the user select what they wanted to use (with the talker having a default). The reason I got there is because I was looking over my files trying to dig out some old Way Out West and remembered the absolute ball ache it was to go through all the text to make it fit into my theme. The strings.xml would address that, with me just dropping in a Seemed pretty OK to me, but very open to suggestions. I really just threw this at Claude Code to see what it could come up with under a little prompting. 😁 |
Got Claude Code to look into something that I've thought for a while (even since I had to rework Way Out West to be on a newer version of Amnuts) would be a good thing; an easier way to change the text in the talker for either translations or changing the style/tone of the output.
So here's a small set of changes as a start to that. At this point, almost exclusively untested!