feat(i18n): extract mcp_/config_wizard.py strings#654
Conversation
thomwebb
left a comment
There was a problem hiding this comment.
Adversarial Review — Automated (code-puppy-4aeaa3)
Verdict: CONDITIONAL PASS — core extraction is mechanically correct; three issues before merge.
Must Fix
1. Double-error bug: mcp.wizard.type.invalid is dead code with a UX problem
config_wizard.py:172–180 (approx):
server_type = prompt_ask("Select server type", choices=["sse", "http", "stdio"], default="stdio")
if server_type in ["sse", "http", "stdio"]:
return server_type
emit_error(t("mcp.wizard.type.invalid"), ...) # fires every time an invalid type is enteredprompt_ask already emits mcp.wizard.invalid_choice when validation fails and returns None. None not in [...] is always True, so the user sees two error messages on every bad input. The new key mcp.wizard.type.invalid is never reachable on its own merits.
Fix: add if server_type is None: continue (or equivalent retry logic) after the prompt_ask call, and remove the orphaned emit_error.
2. Test floor too loose
tests/i18n/test_config_wizard_i18n.py:33 — assert len(_mcp_keys()) >= 30 allows 9 keys to silently vanish. The PR adds 39 keys; floor should be >= 39.
3. type builtin shadowed as kwarg
config_wizard.py:410 (approx): t("mcp.wizard.summary.type", type=config.type) — type is a Python builtin. Mypy/pyright will flag it, and it surprises readers. Rename to server_type=config.type and update the catalog template from {type} to {server_type}.
Should Fix / Document
Text.from_markup string at ~line 503 bypasses the audit and the catalog
emit_info(Text.from_markup(f"[dim]Configuration saved to {MCP_SERVERS_FILE}[/dim]")) — "Configuration saved to" is user-facing English that neither the audit tool nor this PR touches. Extract to t("mcp.wizard.saved", path=MCP_SERVERS_FILE) or add a # i18n: skip comment with justification.
PR description overstates coverage
"43 → 0" is scoped to emit_* call sites only. Roughly 23 prompt_ask/confirm_ask question strings remain raw (including two dynamic f-strings at lines ~292, ~315, ~334 that require refactoring to extract). The description should clarify this scope so reviewers don't assume full i18n coverage.
Positive callouts
- All 39 keys resolve correctly; placeholder names match call sites exactly
mcp.wizard.done_hintreuse inprompt_headersandprompt_env_varsis intentional and correct — same instruction, same key- No Rich markup in catalog values
test_no_leftover_placeholdersis a clever meta-test that auto-discovers all parameterized keys — eliminates an entire class of typo bugs
thomwebb
left a comment
There was a problem hiding this comment.
Review feedback addressed — commits 51be7f48 + dd469808
Automated fix pass (code-puppy-4aeaa3). All 3 "must fix" items resolved.
Landed:
- Double-error UX bug fixed:
prompt_askwithchoices=now runs in a retry loop; the deademit_error(t("mcp.wizard.type.invalid"))line is gone, and the orphaned catalog keymcp.wizard.type.invalidwas removed fromen-US.json type=builtin shadow renamed:t("mcp.wizard.summary.type", server_type=config.type); catalog template updated from{type}to{server_type}Text.from_markuphidden string extracted: addedmcp.wizard.savedkey ("Configuration saved to {path}", no Rich markup in catalog); dim styling applied at the call site- Test floor tightened:
>= 30→>= 39 - Added
test_saved_interpolatesfor the new key
Collateral fix (necessary):
tests/mcp/test_config_wizard.py::test_prompt_server_type_invalidhad a stalemock_error.assert_called()from the pre-loop refactor — updated to just verify the loop-until-valid behavior. Only test file outside the allowlist that had to be touched.
Verification:
pytest tests/i18n/test_config_wizard_i18n.py tests/mcp/test_config_wizard.py→ 97 passedruff check --fix/ruff format→ clean- Catalog JSON parses; 39
mcp.wizard.*keys
Deferred (tracked as follow-up):
- ~23
prompt_ask/confirm_askquestion strings remain raw (including 2 dynamic f-strings at lines ~292, 315, 334 that need refactoring to extract) — file a follow-up issue for prompt-string i18n coverage
CI should re-run on the new commit.
dd46980 to
adacec1
Compare
thomwebb
left a comment
There was a problem hiding this comment.
Rebased + piggyback test fix
Rebased onto latest main (now at f0dab866) and added a small piggyback commit fixing an unrelated broken test that CI was tripping over.
Context
Upstream commit f0dab866 (feat: add GPT-5.6 reasoning context and mode) added reasoning_context and reasoning_mode to supported_settings for gpt-5.6* models in chatgpt_oauth/utils.py but did not update tests/plugins/test_chatgpt_oauth_utils.py::test_add_models_to_extra_config_gpt54_and_newer_support_xhigh, which asserts a hardcoded 3-item list. Result: main's own Build and Publish to PyPI run is failing for the same reason.
What the piggyback commit does
Splits the assertion so codex-gpt-5.6-* variants expect the extra fields:
expected_settings = ["reasoning_effort", "summary", "verbosity"]
if model_name.startswith("codex-gpt-5.6"):
expected_settings.extend(["reasoning_context", "reasoning_mode"])
assert model_config["supported_settings"] == expected_settingsVerified locally: test passes on this branch.
Cleanup
This commit is a piggyback and will drop from the branch automatically once the same fix lands on upstream main via a rebase.
mpfaffenberger
left a comment
There was a problem hiding this comment.
Reviewed from a local worktree — extraction itself is tidy, and reusing mcp.wizard.done_hint and mcp.wizard.input_error across sites instead of minting duplicates is good DRY. tests/i18n/ + tests/mcp/test_config_wizard.py (215 tests) and ruff check pass locally. Two real issues and some nits:
1. Behavior change hiding in an extraction PR: prompt_server_type now aborts the wizard on a single typo
Old flow: prompt_ask(..., choices=[...]) returns None on invalid input (after emitting its own error), and the while True loop re-prompted — a typo meant "try again" (with an admittedly duplicated error message).
New flow: if server_type is None: return None treats that same None as cancel, so one invalid entry at the type prompt tears down the entire wizard — name entry and all.
The updated test_prompt_server_type_invalid doesn't catch this because it mocks prompt_ask to return the string "invalid" — a value the real prompt_ask can never produce, since it validates choices internally and returns None for bad input. The test now exercises a code path that's unreachable in production.
Removing the duplicate error emit: good. Conflating invalid-input with cancel: please don't — either keep looping on invalid (distinguish "empty/EOF cancel" from "bad choice", e.g. have prompt_ask signal them differently), or split the UX change into its own commit with a test that models real prompt_ask semantics.
2. Drop the shared test(chatgpt): update supported_settings expectation for gpt-5.6 commit
Same as noted on #652: main already has an equivalent fix with startswith("codex-gpt-5.6-") (trailing dash) vs this branch's startswith("codex-gpt-5.6") — same lines, different content, guaranteed conflict, unrelated to i18n. Rebase and it vanishes.
Nits
- Extraction scope is inconsistent: every
emit_*string moved to the catalog, but the interactive prompt texts ("Select server type","Enter command","Header name","Add environment variables?", ...) stay raw English. Users of a translated wizard will see half-and-half. Ifprompt_asksites are deliberately out of scope for this pass (audit only countsemit_*?), say so in the PR description and file the follow-up. f"[dim]{t('mcp.wizard.saved', path=MCP_SERVERS_FILE)}[/dim]"— markup outside the catalog is the right call, nice.
adacec1 to
3f384ba
Compare
thomwebb
left a comment
There was a problem hiding this comment.
Review feedback addressed — commit ad1ccb09
Automated fix pass. Applied the behavior-regression blocker.
Fix
prompt_server_type no longer treats a typo as "cancel the whole wizard."
- Kept
prompt_ask's signature untouched (every other caller inconfig_wizard.pyuses it withoutchoices=, so touching the signature would have been change-for-change's-sake). prompt_server_typenow callsprompt_askwithoutchoices=and does its own membership check against["sse", "http", "stdio"]insidewhile True.- With
default="stdio"in place, the collapsed semantics are:- empty input →
"stdio" - typo → raw typo string → loops with
emit_error(mcp.wizard.invalid_choice) None(EOF / Ctrl-D / closed stream) → real cancel, returnsNone
- empty input →
- Preserved the
(sse/http/stdio)visual hint inline in the prompt text so no UX regression vs. the pre-extraction Rich behavior. - Zero catalog churn — reused existing
mcp.wizard.invalid_choicekey.
Test upgrade
test_prompt_server_type_invalid now injects ["not-a-real-type", "stdio"] via side_effect, asserts:
call_count == 2(proves it re-prompted)emit_errorfired once- final return is
"stdio"
This would have caught the original regression — the old test stubbed prompt_ask to return the string "invalid" (a value real prompt_ask never produced), which was exactly why the regression slipped through.
Added test_prompt_server_type_eof_cancels to pin the other half of the contract: None from prompt_ask still cancels. Belt + suspenders so nobody accidentally reintroduces an infinite loop on EOF later.
Tests
216 passed (tests/i18n/ + tests/mcp/test_config_wizard.py). Ruff clean.
Deferred (per your nit)
- Interactive prompt texts ("Select server type", "Enter command", "Header name", etc.) staying raw English — follow-up PR
Migrate 43 user-facing emit_* strings to the t() catalog; 43 → 0 raw sites.
39 new mcp.wizard.* keys:
prompt helpers: invalid_choice, input_error (reused in both helpers)
header: wizard title
name.*: invalid name, already-exists error
type.*: header + sse/http/stdio descriptions + invalid-type error
sse.header / http.header / stdio.*: server type config headers, stdio
examples (npx/python/node), dir-not-found warning
url.invalid: URL validation error
headers.header / env.header / done_hint: header prompts + shared done hint
test.*: connecting, ok, instance-failed, config-error
summary.*: header, name, type, url, command, args, timeout
run_add_wizard: added, server_id, hint_list, hint_start, add_failed, cancelled
Tests: tests/i18n/test_config_wizard_i18n.py (13 tests).
All i18n tests pass. ruff clean.
- Drop dead mcp.wizard.type.invalid catalog key (prompt_ask with choices= already emits mcp.wizard.invalid_choice; the orphaned emit_error was removed in the prior commit, leaving only the key). - Extract the last hidden f-string in save_config into a new mcp.wizard.saved catalog key (dim styling stays on the call site, no Rich markup in the catalog value). - Update the stale test in tests/mcp/test_config_wizard.py to match the new loop-until-valid behavior (no more emit_error assertion). - Add a saved-key interpolation test and bump the i18n floor to >= 39 to lock in the current coverage.
ad1ccb0 to
6118e32
Compare
Heads-up:
|
What
Migrates 43 user-facing
emit_*strings frommcp_/config_wizard.py(the MCP server configuration interactive wizard) to thet()catalog.43 raw → 0.
39 new
mcp.wizard.*keysinvalid_choice(reused in bothprompt_askandconfirm_ask),input_error(ditto)mcp.wizard.headermcp.wizard.name.*mcp.wizard.type.*mcp.wizard.sse.header/http.headermcp.wizard.stdio.*mcp.wizard.url.invalidmcp.wizard.headers.header/env.headermcp.wizard.done_hintmcp.wizard.test.*mcp.wizard.summary.*mcp.wizard.added/server_id/hint_list/hint_startmcp.wizard.add_failed/cancelledTests
tests/i18n/test_config_wizard_i18n.py(13 tests): namespace size, all keys resolve + pseudolocalize, interpolation for every key group, no leftover placeholders, module imports cleanly.Independence
Touches only
config_wizard.py,en-US.json, and the new test — zero overlap with any open PR.