Skip to content

feat(intl): add wrap to decide what a message renders - #311

Merged
dangreen merged 1 commit into
mainfrom
feat/intl-wrap
Oct 26, 2026
Merged

dangreen merged 1 commit into
mainfrom
feat/intl-wrap

Conversation

@dangreen

Copy link
Copy Markdown
Member

Why

Some messages need code to decide what they render: a template that applies only when every part is present, values changed before they are filled in, a locale-aware change of the result. Today that logic lives in helpers at the call sites, like a getGardenPlace(t, garden) that renders '{city}, {country}' only when both parts are present and the part alone otherwise. Every call site has to remember the helper, and the message type is declared again by hand next to it.

wrap moves that logic into the scheme, so the message renders the same everywhere:

const [$t] = messages('garden', {
  location: wrap(params({
    city: text(),
    country: text()
  }), (place, message) => (place.city && place.country ? message(place) : place.city || place.country))
})

$t().location(garden.location)

What

  • wrap(format, fn) works with any format. A value, like the result of text(), reaches fn as is with the active locale: (message, locale). A message function, like one of params, plural or match, reaches it as is after the values of its call read from signals and accessors: (values, message, locale). fn calls the message with the values it chooses, changed ones included, or returns something else instead.
  • The wrapped message renders only when fn calls it: a guard that skips it neither fills placeholders nor warns about a parameter without a value, and a value returned instead is used as is.
  • One signature with the conditional WrapArgs<R> and Wrapped<R, O> types instead of overloads: with overloads, TypeScript infers unknown for the message of an inline call with arguments, like datetime({ dateStyle: 'medium' }).
  • A format() message is wrapped from the inside, format(wrap(datetime(), fn)), as with capitalize. Wrapped from the outside, messages() would take it for a translated message and warn that it is missing, since it tells the two apart by the arity of the format.
  • Docs: a "Wrapping Messages" section on the intl API page, a line in the format list of Core Concepts, a convention in the nano-kit-intl skill and its regenerated DOCS.md.

Size

Entry Before After Limit
@nano_kit/intl All publics (Gzip) 2234 B 2303 B 2.25 → 2.35 kB
@nano_kit/intl All publics (Brotli) 2074 B 2140 B 2.1 → 2.15 kB

Minimal set and Basic set are unchanged. Importing wrap next to { intl, text, params } adds 78 B gzip.

Verification

oxlint, tsc --noEmit and vitest are clean in @nano_kit/intl (144 tests, 11 of them for wrap) and size-limit passes. The pre-push pnpm test passes across the monorepo, pnpm skills:docs --check passes and the website builds. Every example of the new docs section was compiled and run against the package.

@codecov

codecov Bot commented Oct 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.66%. Comparing base (c9266af) to head (661f1e0).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #311      +/-   ##
==========================================
+ Coverage   83.62%   83.66%   +0.03%     
==========================================
  Files         228      229       +1     
  Lines        4849     4860      +11     
  Branches     1074     1075       +1     
==========================================
+ Hits         4055     4066      +11     
  Misses        553      553              
  Partials      241      241              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@dangreen
dangreen merged commit 2b556cc into main Oct 26, 2026
11 checks passed
@dangreen
dangreen deleted the feat/intl-wrap branch October 26, 2026 16:15
@github-actions github-actions Bot mentioned this pull request Oct 26, 2026
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.

1 participant