Skip to content

Zod: Detect Configured Locale for Custom Messages - #890

Merged
bachmacintosh merged 1 commit into
v3from
claude/zod-setlang-side-effect-qf07zv
Oct 9, 2026
Merged

bachmacintosh merged 1 commit into
v3from
claude/zod-setlang-side-effect-qf07zv

Conversation

@bachmacintosh

Copy link
Copy Markdown
Collaborator

Description

Replaces the internal setLang() in the Zod package with automatic detection of the locale Zod is configured with, so users get our custom error messages without calling a second function.

How it works

Zod doesn't record which locale was passed to z.config(). It only keeps that locale's localeError map, and each en() call returns a new closure, so we can't compare by identity. Instead, getLocale() in lang/_internal.ts:

  1. Reads z.config().localeError when an issue is being finalized (so it's lazy, not run at import).
  2. Passes it a fixed probe issue (invalid_type, expected string, input 0) and compares the message with what each supported Zod locale returns for that issue.
  3. Caches the result per error map in a WeakMap, so each configured locale is only probed once.

Results:

  • Zod Classic (en set by default when the first schema is built) → our English messages
  • Zod Mini with z.config(en()) → our English messages
  • No locale configured (Zod Mini default) → no override; Zod's own fallback ("Invalid input") is used
  • A locale we don't support yet (e.g. ja) or a custom localeError → Zod's locale message is used

Because detection happens when parsing, z.config() can be called before or after this package is imported, and changing locales later is picked up.

Locale typing

Zod doesn't export a union type of its locale tags, but it does export each locale by name from zod/locales. Our Locale type is now Extract<keyof typeof locales, "en">, a subset of Zod's own locale names. If Zod ever renamed one, it would show up as a type error.

Other changes

  • setLang is removed. It was only exported from the internal lang/index.ts and never from the package entrypoints, so the public API doesn't change.
  • Existing tests no longer call setLang("en"). reviews.test.ts now calls z.config(en()) like the other test files that check messages.
  • New tests/v20170710/lang.test.ts covers detection (none / en / Zod Classic default / unsupported locale / custom maps, including { message } objects and null / switching locales at runtime) and checks all three custom messages through the real schemas.
  • The coverage exclusion for src/v20170710/lang/** is narrowed to the Valibot package, so the Zod lang folder now counts toward coverage (it's fully covered).

Behavior change to note: Zod Mini users who haven't configured any locale used to get our English messages (because lang defaulted to English). They now get Zod's generic "Invalid input", which matches how Zod Mini behaves elsewhere.

Nature of Pull Request

This Pull Request:

  • Adds/Updates Type(s)/Schemas
  • Adds/Updates Helper Functions/Type Guards
  • Updates Documentation
  • Updates Project and/or Community Health files (e.g. README, GitHub Actions, etc.)

Its changes constitutes a:

  • Non-code change (i.e. no version bump)
  • Bug Fix or Other Small Changes (i.e. patch version bump)
  • Forward-compatible Enhancement (i.e. minor version bump)
  • ⚠️ BREAKING CHANGE regarding TypeScript, Package, and/or WaniKani API version(s) (i.e. MAJOR version bump)

Contribution Terms

I understand that by submitting this Pull Request:

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bb6sv3VomENtuqfvmd7rcF


Generated by Claude Code

Replace the internal setLang() with detection of the locale Zod is
configured with. Zod only stores the configured locale's error map, so
getLocale() probes it with a known issue and compares the message to the
ones our supported locales give, caching the result per error map.

Detection runs lazily when an issue is finalized, so our custom messages
appear for Zod Classic's default en locale or after z.config(en()) in
Zod Mini, regardless of whether z.config() runs before or after import.
With no locale or an unsupported one, Zod's own messages are used.

Supported locales are typed as a subset of the locale names Zod exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bb6sv3VomENtuqfvmd7rcF
Signed-off-by: Claude <noreply@anthropic.com>
@bachmacintosh
bachmacintosh changed the base branch from main to v3 October 9, 2026 20:51
@bachmacintosh bachmacintosh reopened this Oct 9, 2026
@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
packages/zod/src/v20170710/lang/_internal.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bachmacintosh
bachmacintosh added this pull request to the merge queue Oct 9, 2026
Merged via the queue into v3 with commit 9baf6eb Oct 9, 2026
8 checks passed
@bachmacintosh
bachmacintosh deleted the claude/zod-setlang-side-effect-qf07zv branch October 9, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants