Skip to content

feat(intl): use a plain string translation for every plural form and match case - #246

Merged
dangreen merged 1 commit into
mainfrom
feat/intl-string-translation
Sep 8, 2026
Merged

feat(intl): use a plain string translation for every plural form and match case#246
dangreen merged 1 commit into
mainfrom
feat/intl-string-translation

Conversation

@dangreen

@dangreen dangreen commented Sep 8, 2026

Copy link
Copy Markdown
Member

Why

Translation data is Record<string, any> at runtime, so a locale file can hold a plain string where the scheme uses plural or match, for example a Japanese translation attendees: '{count}人参加' next to an English { one, other } object. That string reached the key resolvers untouched:

plural('count')(ctx, '{count} going')({ count: 3 })             -> TypeError: Cannot use 'in' operator to search for '3' in {count} going
plural('count', forms({ one, other }))(ctx, '{count} going')(3) -> "u"        (the string was spread into characters)
match('kind', other('b'))(ctx, 'str')({ kind: 'a' })            -> TypeError

What

  • match short-circuits a string translation: it becomes the message for every case and every plural form, {param} placeholders included, and the cases(...) / forms(...) resolvers and key resolvers are skipped for it.
  • The input types of match and plural accept string, so typed translation data with a string on such a key passes type-checking.
  • The API page mentions the rule in the plural and match sections.
  • Tests: three for match (bare, with cases, with other), two for plural (bare, with forms).

Wrapping the string as { other: input } was considered and rejected: a forms({ one, other }) resolver declares one with an undefined value, so count: 1 would still resolve to undefined, and match without other('other') never looks at the other key.

Size

The extra branch costs bytes in match, which is part of the All publics and Basic set bundles. Limits are re-pinned on the 0.05 kB step:

bundle before after limit
All publics (Gzip) 1993 B 2009 B 2 kB → 2.05 kB
All publics (Brotli) 1819 B 1831 B 1.85 kB
Basic set (Gzip) 998 B 1013 B 1 kB → 1.05 kB
Basic set (Brotli) 888 B 899 B 900 B

Checks

  • oxlint, tsc --noEmit, vitest run (122 tests) and size-limit in packages/intl pass.

…match case

Translation data is untyped JSON at runtime, and a plain string where `plural` or `match` expected an object of forms reached the key resolvers as is: `count in forms` threw a `TypeError` for `plural` and for `match` with `other()`, and a `forms(...)` or `cases(...)` resolver spread the string into its characters. A string translation is now the message for every form and case, which is what a locale without plural distinctions wants to write, and the input types of `plural` and `match` accept it. The `intl` size limits are re-pinned for the extra branch: `All publics` grows by 16 B and `Basic set` by 15 B gzipped.
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.28%. Comparing base (6b3d44e) to head (4c6cf6f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #246      +/-   ##
==========================================
- Coverage   83.32%   83.28%   -0.04%     
==========================================
  Files          98       98              
  Lines        2554     2554              
  Branches      549      550       +1     
==========================================
- Hits         2128     2127       -1     
  Misses        314      314              
- Partials      112      113       +1     

☔ 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 82d45cf into main Sep 8, 2026
9 of 10 checks passed
@dangreen
dangreen deleted the feat/intl-string-translation branch September 8, 2026 15:46
@github-actions github-actions Bot mentioned this pull request Sep 8, 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