i18n: per-player EN/PT via Adventure GlobalTranslator #1
Reference in New Issue
Block a user
Delete Branch "feat/ia-grounding"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Foundation (step 1) + commands module (step 2) of the i18n spec.
🤖 Generated with Claude Code
Two features requested after the IA grounding deploy. IA player-stats grounding: the IA could not answer "quantos blocos eu minerei?" because its context carried only generic server facts, never the asker's own numbers. OfflineStats now reads one player's headline stats (blocks mined, time played, distance, deaths, mob kills) from their vanilla stats JSON and Ai.compose() injects them as a system turn, gated by ia.estatisticas-jogador (default true). ~30 tokens per question; always on so it never misses a stat question. Pure formatSummary extracted for tests. Comic deaths (mortes module): replaces the vanilla death message with a cause-based pt-BR comic line plus a death counter ("Fulano foi achatado como panqueca (47ª morte)") and sends the death coordinates privately to the dead player (Java click-to-copy, Bedrock plain text) so they can run back to their dropped items. No storage, no command — a PlayerDeathEvent side effect gated by modulos.mortes. DeathFlavor is a pure cause->phrase map, unit-tested. Coexists with the luto [F] handler. DEATHS stat timing: assumes Paper fires PlayerDeathEvent before awarding minecraft:deaths, so the counter shows +1 to include the current death; a log line confirms the raw stat on the first real death so the +1 can be dropped if the server increments first. 117 tests green (101 + 13 DeathFlavor + 3 OfflineStatsSummary). Co-Authored-By: Claude <noreply@anthropic.com>Players had nowhere to write anything down. Coordinates got pasted into Discord, or lost. This adds notes to chat, with two scopes. A private note is visible only to its author and everyone may write one (canalhandia.nota, default true). A public note is broadcast and readable by all, and writing one needs canalhandia.nota.publica (default op) — public notes are a curated board, not a graffiti wall. Both permissions are declared in plugin.yml: an undeclared Bukkit permission silently falls back to op-only, which would have stopped normal players writing anything. Every note stores where it was written. On a Minecraft server a note is nearly always about a place — where the base is, where the spawner was found — so coordinates are part of the model rather than optional metadata. Java players get click-to-copy on them, the same affordance the death-coords message uses; Bedrock renders no click event and gets the plain text. No teleport: the plugin does not touch gameplay. /save is the quick path. /save and /save coords pin the spot; /save <texto> pins it with a note. Private on purpose — it is the command someone types without reading help first, and the safe default there is the one that cannot surprise anyone by broadcasting. /nota publica <texto> is the explicit way to share. Private notes are never sent to the AI, at any setting. The AI call leaves this server for a third-party API, so a private note reaching it would be a disclosure the author never agreed to. The filter lives inside Notes.publicSummary — the only method the AI path calls — rather than at the call site, so a future caller cannot get it wrong by accident. Public notes are sent (ia.contexto-notas, default 10), which is what lets the AI answer "onde fica a base?" from what players actually wrote down. A note the viewer cannot see is reported as missing rather than as forbidden: saying "that one is private" would confirm it exists, which is itself a leak. Search runs through the same visibility filter, so it cannot become a way to probe for someone else's text. Ids are never reused after a deletion, or "/nota ver 2" would point at a different note than the one someone wrote down a minute ago. Text is stripped of the section sign and control characters, because a note is echoed into chat and could otherwise forge a line that looks like it came from the server. Scope parsing accepts the masculine and plural forms ("publico", "publicas") alongside the canonical ones. Spelled out rather than derived: a blanket a-to-o rewrite turns "privada" into "privodo", and the plural is exactly what tab completion suggests, so it has to parse or the suggested command fails. preflight.sh now checks all 18 commands and both new permissions. 218 tests, up from 176. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pEyCmrAYHBFgpYjwFxKxhMove achievements and milestones out of code and into editable YAML, so operators add or retune titles by editing a file and reloading — no rebuild. - conquistas-catalogo.yml: each title is a key with titulo/descricao and a list of condicoes. Conditions are a tiny grammar ("metrica operador alvo", target may be a number, another metric, or metrica/numero), evaluated on friendly units (km, hours) normalised from the raw stats. Achievement is now a class loaded from this file, not an enum. - marcos-catalogo.yml: milestone tracks (statistica/verbo/unidade/limiares), loaded by Milestones at startup and reload. - /canalhandia reload now reloads both catalogues (plus config.yml) live and reports the counts. - Shipped catalogue expanded 10 -> 29 titles and every milestone track deepened. - Silent backfill on load/reload (Achievements.syncCatalogue, Milestones .resyncSilently, keyed on a stored signature): a grown catalogue banks the history it already implies instead of spamming returning players. Tests: AchievementTest covers the condition grammar and validates the shipped catalogue; TitlesTest loads the real catalogue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>- On respawn a dead player privately gets a harmless comic item (funeral poppy, wilted bush, ...) with a pt-BR jab. Config-driven under mortes.presente ("MATERIAL | Nome | mensagem"), defaults baked in, hot-reloadable via /canalhandia reload. Overflow drops at their feet. - Fix: a title-holder's chat text rendered grey. The title chip was rooted on a DARK_GRAY component and the message, appended to it, inherited that colour. Root on Component.empty() so the message falls back to white. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Per-player i18n: register a TranslationStore (key canalhandia) into the GlobalTranslator from lang/messages_{pt,en}.properties. Paper renders each Component.translatable in the recipient's own locale at send time, so one broadcast shows every player their own language (pt/en), Java and Bedrock alike. defaultLocale=en; pt + pt_BR fall back to the PT bundle; unknown locale -> en. - I18n: loader + registry install; removeSource before addSource so /canalhandia reload does not stack sources. - Lang.tr(key, args) facade over Component.translatable. - Canalhandia.onEnable wires it; /canalhandia reload now reloadI18n(). - Proof migration: onDeath mourning lines -> Lang.tr (broadcast -> per-viewer). - I18nTest: key parity PT/EN + per-locale render + pt_BR fallback + unknown -> default. 316/316 pass. Bundles seeded with the mourning keys; bulk module-by-module migration and per-locale number/date formatting are next, one PR each per the spec. Co-Authored-By: Claude <noreply@anthropic.com>Player-facing Msg.* call sites in commands module now resolve per-locale via Adventure GlobalTranslator (PT source-of-truth + EN translation). Admin-tuning, help blocks, OfflineStats.summary and enum labels deferred. Bundles: +6 keys (ia.uso, nota.lista.{tudo,escopo,busca}, nota.nao-encontrada, recado.desconhecido), -2 unused (curiosidade.uso-buscar, nota.ver-uso). PT/EN key parity holds; I18nTest green (316/316). Co-Authored-By: Claude <noreply@anthropic.com>