@charles_lindecker/pvzf-console
v1.6.0
Published
Translation toolkit for Plants vs Zombies: Fusion. Scans localization files, generates per-locale reports, exports Trello CSVs, and turns the weekly PR recap into contributor documentation.
Maintainers
Readme
🌱 PVZ Fuzion Console Manager
Translation toolkit for Plants vs Zombies: Fusion. Scans every locale against the English source, generates per-locale Markdown reports, migrates the new tips format, checks for duplicates, exports Trello-ready CSV backlogs, and turns the weekly PR recap into contributor documentation.
📑 Table of contents
- What it does
- Prerequisites
- Install & run
- Features
- Interactive menu reference
- Headless CLI
- Generated files
- Settings
- Project structure
- Build & distribute
- Troubleshooting
- Contributing
- Security
- Credits
- License
🎯 What it does
- Detects untranslated content across every file type the game uses: plants, zombies, achievements, UI strings, regex translations, tips (I, Zombie + Fusion Showcase), Abyss buffs, Travel buffs.
- Writes per-locale Markdown reports so translators see the exact source JSON blocks they need to translate, and optionally emits machine-readable JSON diff files that can be re-injected into the locale.
- Migrates the new tips format — rebuilds
tips_iz.json/tips_fs.jsonfrom the legacytranslation_strings.json, all-or-nothing. - Checks for duplicates — surfaces duplicate JSON keys and values shared by multiple keys, per locale.
- Exports Trello CSV backlogs — one CSV per category, ready for the Import to Trello by Blue Cat Power-Up.
- Writes the contributor documentation — splits the weekly translation-PR recap into one Markdown block per contributor, counters and reviews included, ready to archive.
- Ships as a single npm package (
@charles_lindecker/pvzf-console) bundled to one file with tsup. No Python — just Node and two small runtime dependencies (@clack/prompts,picocolors).
📋 Prerequisites
| Tool | Version | Why | | ----------- | ---------- | ---------------------------------------------------- | | Node.js | ≥ 20 | The whole engine is TypeScript bundled to ESM. | | Git | any | To clone and contribute. |
You also need the PvZ_Fusion_Translator/ folder. By default the tool probes
four locations (next to the install → one level up → cwd → cwd parent), and the
path is configurable from the Settings menu.
🚀 Install & run
Option A — via npm (recommended)
npm install -g @charles_lindecker/pvzf-console # once published
pvzf-console # interactive TUI
pvzf-console diff --lang French # non-interactiveOr run it without installing:
npx @charles_lindecker/pvzf-console
npx @charles_lindecker/pvzf-console diff --lang FrenchOption B — from source (recommended for devs)
git clone https://github.com/LINDECKER-Charles/PVZ-Fuzion-ConsolManager.git
cd PVZ-Fuzion-ConsolManager
npm install
# Launch the interactive TUI live (no build step, via tsx)
npm run dev
# Or build the bundle and run it
npm run build
node dist/cli.jsThe published package bundles everything into dist/cli.js (the data/ banner
ships alongside it), so the global bin works straight after install.
✨ Features
Diff engine
- Detects missing entries by primary-key lookup (O(n + m) using set membership, not a naive O(n·m) nested loop).
- Detects empty values: keys present in the locale but with an empty translation string — reported in a separate section of each report.
- Handles both flat dicts (
translation_strings.json,abyss_buffs.json, …) and nested travel buffs (category:id, preserving their fullname/descobject). - Source of truth is configurable: diff against another locale (default
English) or against the raw gameDumps/(Dumpssource mode). - Gracefully logs malformed JSON and continues instead of crashing the run.
Report generators
- Almanac reports (plants / zombies / achievements): condensed
name + idheader plus a fenced```jsonblock with the raw source entry, ready to copy/translate/paste. - Flat reports (strings / regex / tips / buffs): two sections — Missing Keys (compact JSON object) and Empty Values (table).
- Optional JSON diff files (
*_diff.json) alongside the Markdown, holding the missing entries in the file's native shape so they can be merged straight back into the locale. Enabled with the--with-diffCLI flag or the prompt in the TUI.
Tips migration (Translator tool)
- Rebuilds
tips_iz.json/tips_fs.jsonfrom matches intranslation_strings.json. - All-or-nothing: if even one source tip text is missing from the translation strings, the migration aborts for that file and reports the gap count. No partial writes.
- Already-present files are preserved (no overwrite); files with no dump source
in
Dumps/are skipped with a notice.
Duplicate checker (Translator tool)
- Scans every flat / nested string file for duplicate JSON keys (keys that
appear more than once in the raw text —
JSON.parsesilently keeps the last, so a strict recursive-descent scanner catches them) and repeated values (one translation reused across multiple keys). - Writes a
duplicates.mdreport underreports/<Locale>/for any locale with matches; clean locales produce nothing.
Trello CSV export (Translator tool)
- One CSV per category (Plants / Zombies / Achievements / Strings / Regex / Tips IZ / Tips FS / Abyss Buffs / Travel Buffs) → keeps the Trello UI responsive even on the largest locales.
- Columns:
Name,Description,Labels,List. - Descriptions are JSON-fenced (
```json … ```). Almanac entries keep the full source JSON (with braces), flat entries render as"key": "value"— always with escape sequences (\n,\", …) preserved literally so translators see the file exactly as it will appear on disk. - Companion
trello_README.mdis generated alongside each export:- The exact Trello label to create.
- The exact Trello lists to create (derived from the CSVs actually produced).
- Full Import to Trello by Blue Cat Power-Up walkthrough.
Contributor documentation (Documentation tab)
- Turns the weekly translation-PR recap into one Markdown block per contributor: week header, PR link, counters (new / adjusted / reviews), then the detail of every item they touched.
- The lead maintainer's Reviews block is derived, not written: every item
authored by someone else is mirrored under the lead, grouped by section then
by author. Sections whose name contains
checkare proof-reading passes and are excluded. - Counters come from the section names (
new/nouveau/nouvelle→ Nouvelles traductions;modified/modifi→ Traductions ajustées). - The lead's identity is a setting, so any locale maintainer can use the
tool: a canonical display name plus the aliases the recap may spell them with
(
@LINDECKER-Charles,LINDECKER-Charles, …) are folded into one block. - Nothing is written before you have seen it: the recap is rendered and its stats shown first, and the file is written only on confirmation.
- No GitHub API call — the PR URL you pass in is only reinjected into each block's header.
- Ported from the standalone
pvzf-make-prCLI; output is byte-identical.
Terminal UI
- Built on
@clack/prompts: arrow-key menus with hints, type-to-filter locale picker, inline validation, boxed status and result panels. - The locale picker switches to a filterable autocomplete past ten locales — the game ships more than twenty.
Esc/Ctrl+Cleave the current screen instead of killing the process, and never trigger the action they interrupted; the main menu's Exit is the way out.- Theme still honours the settings (accent color, emoji, banner) and falls back
to
[OK] / [!] / [X]when emoji are off.
Persistent settings
- Stored per user, outside the package — see Where settings live.
- Live-editable from
[4] Settingsin the TUI. - Theme applied immediately (color, accent, density, emoji/banner toggles).
- On-disk format keeps snake_case keys for backward compatibility, unknown keys are ignored on load, and a hand-edited alias list is filtered down to usable strings.
CLI
- Hand-rolled sub-command dispatch mirroring an
argparse-style surface. pvzf-console diff --lang <locale> [--out <dir>] [--with-diff]runs every diff type for one locale in non-interactive mode.pvzf-console pr-resume [--input <file>] [--output <file>]generates the contributor summary headlessly.- Exit codes:
0on success,1on a runtime failure (unreadable recap),2on invalid arguments, locale or project root.
Auto-discovery
PvZ_Fusion_Translator/is probed in four places (install sibling → one level up → cwd → cwd parent) — not recursive, single pass.
Cross-platform
- Pure Node runtime, ESM, no shell scripts required.
- Windows VT100 enablement so ANSI colors and emoji render on every modern terminal; emoji and banner can be toggled off for legacy hosts.
Error hygiene
- Invalid project root is detected at startup (warning) and before every
tool action (gated with a friendly
❌message). No stack traces leak to the user.
🧭 Interactive menu reference
Navigate with ↑ ↓, confirm with Enter, leave a screen with Esc.
┌ PVZF CONSOLE
│
◇ Status ──────────────────────────────────────────╮
│ │
│ ✅ project …/PvZ_Fusion_Translator │
│ ✅ source English │
│ ✅ reports reports/ │
│ │
├───────────────────────────────────────────────────╯
│
◆ Main menu
│ ● Show what's missing diff locales · write reports
│ ○ Translator tools migrate · trello · duplicates
│ ○ Documentation PR recap → contributor docs
│ ○ Settings
│ ○ Exit
└[1] Show what's missing
Pick a locale (or * for all), then a type:
| Option | Source file | Output file (under reports/<Locale>/) |
| -------------- | ----------------------------------------------------- | --------------------------------------- |
| [0] All types | every type below | every file below |
| [1] Plants | Almanac/LawnStringsTranslate.json | missing_plants.md |
| [2] Zombies | Almanac/ZombieStringsTranslate.json | missing_zombies.md |
| [3] Achievements | Almanac/AchievementsTextTranslate.json | missing_achievements.md |
| [4] Strings (UI) | Strings/translation_strings.json | missing_strings.md |
| [5] Regex | Strings/translation_regexs.json | missing_regexs.md |
| [6] Tips (IZ + FS) | Strings/tips_iz.json, Strings/tips_fs.json | missing_tips_iz.md, missing_tips_fs.md |
| [7] Abyss buffs | Strings/abyss_buffs.json | missing_abyss_buffs.md |
| [8] Travel buffs | Strings/travel_buffs.json (nested) | missing_travel_buffs.md |
After picking a type, the TUI asks whether to also export JSON diff files.
Answer yes to write *_diff.json next to each Markdown report.
[2] Translator tools
| Option | What it does |
| ------ | ------------ |
| [1] Migrate tips & buffs | Rebuilds tips_iz.json / tips_fs.json / abyss_buffs.json / travel_buffs.json from translation_strings.json. Single-locale only. All-or-nothing. |
| [2] Migrate custom levels | Builds customlevel_strings / customlevel_regexs / custom_level_data; key set from the source locale, translations from the target. |
| [3] Export Trello CSV | Produces one CSV per category under exports/<Locale>/ plus a trello_README.md with the full import walkthrough. |
| [4] Check duplicates | Scans every string file for duplicate keys and repeated values; writes duplicates.md per locale with matches. |
| [0] Back | Returns to the main menu. |
[3] Documentation
| Option | What it does |
| ------ | ------------ |
| [1] PR recap → contributor summary | Reads the weekly PR recap and writes one Markdown block per contributor. |
| [0] Back | Returns to the main menu. |
The tool asks for the recap file (pre-filled with the first candidate .md in
the working directory) and the output file (defaults to the Docs output
setting), renders the result, shows the per-contributor stats, and writes only
once you confirm.
Expected input — the first two non-empty lines are the header:
2026-04-01..2026-04-07 ← period (or already-formatted `01/04/26 → 07/04/26`)
https://github.com/owner/repo/pull/123 ← PR URL; the number is read from /pull/(\d+)
## 🌱 Newly Added Plants ← section
@lafourmiedugaming-collab : ← contributor: @handle, or [Name](link) :
* **Briseur de Machoir** (`seedType: 1390`) ← item
## 🔧 Modified Achievements
@Kurodatenshi :
* **D'où est-ce que je viens ?** (`achievement: 7`)Output — one block per contributor, plus the lead's derived Reviews:
## 👤 @Kurodatenshi
### 📅 Semaine — `01/04/26 → 07/04/26`
> [PR#123](https://github.com/owner/repo/pull/123)
**Résumé de la semaine**
* Nouvelles traductions : **0**
* Traductions ajustées : **1**
* Reviews effectuées : **0**
---
#### Détail
## 🔧 Modified Achievements
* **D'où est-ce que je viens ?** (`achievement: 7`)The rendered output stays in French by design — it lands verbatim in the French contributor files.
[4] Settings
| Key | Default | Notes |
| --------------- | ------------------ | ---------------------------------------------- |
| Project root | auto-discovered | Absolute path to PvZ_Fusion_Translator/ |
| Source locale | English | Reference used for diffs (Dumps for raw dumps) |
| Text color | default | Primary text |
| Accent color | cyan | Headers, prompt, option keys |
| Density | comfortable | compact · comfortable · spacious |
| Show emoji | true | Fallback: [OK] / [!] / [X] |
| Show banner | true | ASCII banner at startup |
| Trello label | To be translated | Label written to every exported card |
| Docs lead | Charles LINDECKER | Whose Reviews block the Documentation tab derives |
| Docs aliases | @LINDECKER-Charles, LINDECKER-Charles | Other spellings of the lead, folded into one block |
| Docs output | contribution-summary.md | Default target of the contributor summary |
Supported ANSI colors: default, red, green, yellow, blue, magenta, cyan,
white plus their bright_* variants.
Where settings live
settings.json is stored per user, outside the package, so a global install in
a root-owned prefix (/usr/local/lib/node_modules, C:\Program Files\nodejs)
stays writable:
| Platform | Path |
| ----------- | ----------------------------------------------------------- |
| macOS | ~/Library/Application Support/pvzf-console/settings.json |
| Linux / BSD | $XDG_CONFIG_HOME/pvzf-console/settings.json, else ~/.config/pvzf-console/settings.json |
| Windows | %APPDATA%\pvzf-console\settings.json |
$XDG_CONFIG_HOME is honored on macOS too when set to an absolute path. Set
PVZF_CONSOLE_SETTINGS=/path/to/settings.json to pin the file somewhere else
(portable installs, CI). The active path is shown in [4] Settings under
Settings file.
Upgrading from ≤ 1.4.1, which kept settings.json inside the package: that file
is still read on first launch and copied to the per-user location on the next
save. The old copy is left in place, so an older install alongside keeps working
— but from then on the two no longer track each other.
⚡ Headless CLI
pvzf-console diff --lang French
pvzf-console diff --lang German --out ./out --with-diff
pvzf-console pr-resume --input recap.md --output docs/contributions.md| Command | Effect |
| ---------------------------------------------------- | --------------------------------------------------------------------------- |
| pvzf-console | Launch the interactive TUI. |
| pvzf-console diff --lang French | Run every diff type for French, write to ./reports/French/. |
| pvzf-console diff --lang French --out X | Same, writing to X/French/ instead. |
| pvzf-console diff --lang French --with-diff | Same, plus *_diff.json next to each Markdown report. |
| pvzf-console pr-resume | Contributor summary from the first candidate .md in the cwd → contribution-summary.md. |
| pvzf-console pr-resume --input R.md --output O.md | Same, with both paths given. Aliases: none — long flags only. |
Auto-detection of the recap skips README.md, README.dist.md, CHANGELOG.md,
LICENSE.md, CONTRIBUTING.md, release.md and the output file itself.
Exit codes:
| Code | Meaning |
| ---- | ---------------------------------------------------- |
| 0 | Success. |
| 1 | Runtime failure — recap missing, unreadable, or lacking its two header lines. |
| 2 | Invalid arguments, invalid locale, source locale rejected, or invalid project root (missing Localization/ subfolder). |
Use the diff command in CI to fail builds when a locale regresses, and
pr-resume to regenerate the contributor documentation when the weekly PR
merges.
📁 Generated files
Every artifact is grouped by locale.
reports/
├── Arabic/
│ ├── missing_plants.md
│ ├── missing_zombies.md
│ ├── missing_strings.md
│ ├── missing_regexs.md
│ └── missing_travel_buffs.md
└── French/
├── missing_plants.md
├── missing_zombies.md
├── missing_strings.md
├── missing_regexs.md
├── duplicates.md # only when duplicates were found
└── strings_diff.json # only with --with-diff / TUI opt-in
exports/
└── French/
├── trello_Plants.csv
├── trello_Zombies.csv
├── trello_Strings.csv
├── trello_Regex.csv
├── trello_Tips_IZ.csv
├── trello_Tips_FS.csv
├── trello_Abyss_Buffs.csv
├── trello_Travel_Buffs.csv
└── trello_README.md
contribution-summary.md # Documentation tab / pr-resume (path configurable)Empty categories (0 missing entries) do not produce a file — keeps the output tidy.
🗂️ Project structure
PVZ-Fuzion-ConsolManager/
├── package.json # npm package metadata + bin entry (dist/cli.js)
├── tsconfig.json # strict TypeScript, ESM, Bundler resolution
├── tsup.config.ts # bundle config: src/cli.ts → dist/cli.js (ESM)
├── vitest.config.ts # test + coverage config (75% gate)
├── README.md # this file
├── README.dist.md # end-user README shipped in the npm tarball
├── data/ # ASCII banner (title.md, logo.md)
├── tests/ # Vitest suites mirroring src/ layout
└── src/
├── cli.ts # executable entry point (shebang injected by tsup)
├── config.ts # paths, source locale, auto-discovery
├── settings.ts # AppSettings + load/save (snake_case on disk)
├── cli/
│ ├── app.ts # App orchestration + argv parsing (DI seam)
│ ├── banner.ts # title renderer (reads data/title.md)
│ ├── menus.ts # prompts, sections, locale picker, ConsoleIO
│ └── theme.ts # ANSI colors + density + emoji toggle
├── core/
│ ├── diff.ts # missingById (O(n + m))
│ └── models.ts # AlmanacEntry / StringEntry / TrelloCard …
├── parsers/
│ ├── loaders.ts # JSON reader + locale enumeration + Dumps source
│ ├── almanac.ts # plant / zombie / achievement loaders
│ └── strings.ts # flat + nested diff helpers
├── reporting/
│ ├── markdown.ts # per-locale Markdown writers + duplicates report
│ ├── diff-json.ts # *_diff.json writers (native file shape)
│ └── trello-csv.ts # per-category CSVs + README template
└── tools/
├── migration.ts # tips/buffs + custom-level builders (all-or-nothing)
├── trello-export.ts # collects cards + calls the writer
├── duplicate-checker.ts # duplicate keys + repeated values scanner
└── pr-resume/ # PR recap → contributor documentation
├── index.ts # orchestration + input/output resolution
├── parser.ts # header split, section/contributor/bullet detection
├── service.ts # per-contributor aggregation + derived reviews
├── renderer.ts # final Markdown rendering
└── period.ts # ISO range → dd/mm/yy🏗️ Build & distribute
Bundle
npm run build # tsup: src/cli.ts → dist/cli.js (ESM, sourcemaps)
node dist/cli.jstsup emits a single ESM bundle targeting Node 20, injecting the
#!/usr/bin/env node shebang so dist/cli.js is directly executable. The
data/ banner is resolved at runtime relative to the package root, so it still
renders from a global install.
npm package
npm install
npm run build # or rely on prepublishOnly
npm publish # prepublishOnly re-runs tsup automaticallyThe published tarball ships dist/, data/, and both README files (see the
files array in package.json). The bin entry maps pvzf-console →
dist/cli.js, so the command works the moment install finishes — Node is the
only requirement.
🛟 Troubleshooting
| Symptom | Fix |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| command not found: pvzf-console | Install Node ≥ 20, then npm install -g @charles_lindecker/pvzf-console (or use npx). |
| Directory does not exist: …PvZ_Fusion_Translator | Settings → [1] Change PvZ_Fusion_Translator folder, paste the correct absolute path. |
| Missing 'Localization' subfolder in … | Same as above — the configured path exists but isn't the translator bundle. |
| <locale>: tips_iz.json missing | Run Translator Tools → [1] Migrate tips for that locale first, then rerun the diff. |
| Settings could not be saved — … | The per-user config directory isn't writable. Fix its permissions, or point PVZF_CONSOLE_SETTINGS at a writable file. Settings still apply for the session. |
| Skipped tips_iz.json — N source string(s) missing | Your translation_strings.json is still missing some source tip keys. Finish those first, then rerun the migration. |
| Emoji show up as ?? in your terminal | [4] Settings → [6] Toggle emoji for the [OK] / [!] / [X] fallback. |
| Banner ASCII art renders as mojibake | [4] Settings → [7] Toggle ASCII banner. The tool enables VT100 on Windows but some legacy hosts still fail. |
| Report file says 0 entries / no file at all | 0 missing / empty-values is intentionally silent — the tool only writes a file when there's something to report. |
| Travel-buffs keys look like advancedBuffs:0 | Expected: presence is checked by category:id; a missing ID keeps its complete {name, desc} object in the report. |
🤝 Contributing
Issues and PRs welcome. To get hacking:
git clone https://github.com/LINDECKER-Charles/PVZ-Fuzion-ConsolManager.git
cd PVZ-Fuzion-ConsolManager
npm install
npm run dev # run the TUI live (tsx)
npm run typecheck # tsc --noEmit (strict)
npm test # vitest run
npm run test:cov # vitest run --coverage (75% gate)Guidelines
- Respect the layer boundaries (one responsibility per directory, IO
pushed to the edges):
core/— pure data models + comparison logic, no IO.parsers/— JSON loading + locale enumeration + diff helpers.reporting/— every output format lives here (Markdown, JSON diff, CSV).tools/— higher-level orchestrations that combine parsers + reporting.cli/— prompts, menus, argv parsing, theming; nothing domain-specific.settings.ts— persistence only.
- Keep it dependency-free at runtime. The engine is plain Node + the
standard library (
node:fs,node:path, …). Dev tooling (tsup, tsx, vitest, typescript) is the only alloweddevDependencies. - ESM + strict TypeScript.
"type": "module",strict: true,noImplicitOverride, Bundler module resolution. Usenode:import specifiers; intra-package imports are extension-less (./menus,../core/diff) and the bundler resolves them. - The app is built around a DI seam.
cli/app.tsexposes anAppclass holding mutable state and an injectableAppDepsbundle of every UI/menu function (ESM bindings can't be monkeypatched), so tests script answers and capture writes. Keep new UI interactions behindAppDeps. - Adding a new translation type is a 4-step drill:
- Drop a loader in
parsers/(almanac.tsfor typed entities,strings.tsfor flat/nested key-value files). - Add the corresponding
diff<Kind>helper inparsers/strings.ts(or reusemissingByIdfromcore/diff.tsfor almanac types). - Add
build<Kind>Reportinreporting/markdown.ts,build<Kind>Diffinreporting/diff-json.ts, and wire the category intotools/trello-export.ts. - Register it in
App.translationTypesincli/app.ts(the numbered dispatch map drives both the menu andrunAll).
- Drop a loader in
- Don't rebuild the wheel for a new Trello list: reuse the flat-file and
almanac card collectors in
tools/trello-export.tsand let the CSV writer inreporting/trello-csv.tsdo the per-list grouping. - Commit style:
type(scope): summary. Seen so far:feat(reports),fix(cli),chore(build),docs(readme). - Keep settings JSON backward-compatible —
loadSettingsalready filters unknown keys and the on-disk format stays snake_case; don't rename fields, add new ones with a default inAppSettings.
npm scripts
| Script | What it does |
| ------------------- | ----------------------------------------------------- |
| npm run dev | Run the TUI live from source via tsx. |
| npm run build | Bundle src/cli.ts → dist/cli.js with tsup. |
| npm run typecheck | tsc --noEmit against src + tests (strict). |
| npm test | Run the Vitest suite once. |
| npm run test:cov | Run tests with V8 coverage (75% gate on lines/functions/branches/statements). |
🔐 Security
Found a vulnerability? Don't open a public issue — report it privately via a GitHub security advisory or by email. Supported versions, scope and response targets live in SECURITY.md.
Every push and PR to main (plus a weekly schedule) runs the Security
workflow: npm audit (blocking on high/critical production advisories), the
ESLint security ruleset, Semgrep and Gitleaks.
👤 Credits
Charles Lindecker — senior backend dev, translation-pipeline enthusiast, long-time PvZ fan.
- GitHub: @LINDECKER-Charles
- Email: [email protected]
Huge thanks to the PVZ Fusion translator community — especially
@cassidy [BLMS] for the original migrate.py / migrate_odyssey.py
scripts that inspired the Translator Tools.
📜 License
MIT. Do what you want, keep the copyright notice, no warranty.
