@schummar/icu-type-parser
@schummar/icu-type-parser is a type-only TypeScript utility that reads a literal ICU MessageFormat string at compile time and produces the object type for its arguments. `GetICUArgs<'Hello {name}'>` requires a `name` property, while number, plural, date, time, select, nested blocks, and tuples of messages receive more specific treatment. It does not parse, validate, or format anything when JavaScript runs; the published package exports only declarations.
A sharp, tiny building block for authors of TypeScript-first i18n wrappers, not something most applications should install directly. If your messages are not literal types or you need runtime output, choose a real ICU parser or formatter.
Use it if
- You store ICU messages as TypeScript string literals and want missing interpolation values to fail type checking
- You are building a typed translation wrapper and need to infer argument objects from each dictionary value
- You want number, plural, date, time, and select arguments to map to application-defined TypeScript types
- You need selected arguments, such as locale or tenant, to become optional because another layer supplies them
- You need to parse, validate, or format ICU messages at runtime: the package export map contains only a `types` target and provides no callable JavaScript API
- Your messages come from JSON, a database, or a translation service as plain `string`; useful inference depends on preserving literal types, usually with `as const`
- You want a documented standalone tool: the package has no package README, and the linked repository README documents the larger schummar-translate library rather than these two exported types
- You need full ICU syntax diagnostics or an abstract syntax tree: the implementation is a set of template-literal conditional types, not the FormatJS ICU parser
- Your translation catalog contains very long or deeply nested messages and compiler speed is already a problem: recursive type parsing is performed by TypeScript for every instantiated literal
Setup reality
Install it as a development dependency because it has no runtime behavior: `npm install -D @schummar/icu-type-parser`. Import `GetICUArgs` with `import type`; a value import cannot work because version 1.26.1 exposes only `dist/index.d.ts` through its export map. The first surprise is that the default inferred property types are `unknown`. To get useful value checking, pass a second type argument that assigns your choices to `ICUArgument`, `ICUNumberArgument`, and `ICUDateArgument`. The second surprise is literal widening. A message declared with `let`, annotated as `string`, loaded from JSON, or returned by an untyped API loses its exact characters, so the compiler has nothing concrete to parse. Keep dictionaries `as const` and thread their literal types through generic helpers. `select` deserves tests in your own code: branches form a union, while an `other` branch intentionally permits arbitrary strings through a branded string type. `ProvidedArgs` is a union of argument names that should become optional because your wrapper supplies them. The parser understands nested braces and ICU apostrophe escapes through recursive conditional types, but it performs no runtime syntax check and ships no formatter. Pair it with `intl-messageformat` or a translation framework if strings must actually be rendered, and run type-check benchmarks before applying it to a very large catalog because all parsing work happens inside the TypeScript compiler.
Patterns
Install as a development dependencyinstall-type-only
npm install --save-dev @schummar/icu-type-parserThe package exposes declarations only; do not add it to code that must execute at runtime.
Infer a simple ICU argument objectinfer-simple-placeholder
import type {GetICUArgs} from '@schummar/icu-type-parser';
type WelcomeArgs = GetICUArgs<'Hello, {name}!'>;
const args: WelcomeArgs = {name: 'Ada'};Without custom options, a plain placeholder is required but its value type is unknown.
Choose application-level value typesconfigure-argument-types
import type {GetICUArgsOptions} from '@schummar/icu-type-parser';
interface AppICUOptions extends GetICUArgsOptions {
ICUArgument: string;
ICUNumberArgument: number;
ICUDateArgument: Date | number;
}Use a reusable options type or plain placeholders remain unknown and number and date formats accept any value.
Require numbers for number formatstype-number-argument
type PriceArgs = GetICUArgs<
'Total: {amount, number}',
{ICUNumberArgument: number}
>;
const args: PriceArgs = {amount: 19.95};The same ICUNumberArgument option is used for `number`, `plural`, and `selectordinal` formats.
Type date and time placeholderstype-date-and-time
type EventArgs = GetICUArgs<
'Starts {when, date, short} at {when, time, short}',
{ICUDateArgument: Date | number}
>;
const args: EventArgs = {when: new Date()};Both `date` and `time` resolve through ICUDateArgument; the type parser does not format the Date.
Infer a plural counttype-plural-count
type ResultArgs = GetICUArgs<
'{count, plural, =0 {No results} one {One result} other {# results}}',
{ICUNumberArgument: number}
>;
const args: ResultArgs = {count: 3};Plural and selectordinal selectors use ICUNumberArgument, but plural rules still require a runtime ICU formatter.
Infer named select choicestype-select-options
type LayoutArgs = GetICUArgs<
'{layout, select, compact {Small} full {Large}}'
>;
const compact: LayoutArgs = {layout: 'compact'};
const full: LayoutArgs = {layout: 'full'};Without an `other` branch, the selector is limited to the option labels found in the literal message.
Account for an ICU other branchallow-select-other
type StatusArgs = GetICUArgs<
'{status, select, ready {Ready} other {Unknown}}'
>;
const known: StatusArgs = {status: 'ready'};
const future: StatusArgs = {status: 'queued'};When `other` exists, the parser intentionally widens the selector to allow strings beyond the named options.
Collect arguments inside select branchesinfer-nested-arguments
type GreetingArgs = GetICUArgs<
'{role, select, admin {Hello {adminName}} member {Hello {memberName}}}',
{ICUArgument: string}
>;
const admin: GreetingArgs = {role: 'admin', adminName: 'Ada'};
const member: GreetingArgs = {role: 'member', memberName: 'Lin'};Select branches form a union, so branch-specific placeholders can follow the selector as discriminated properties.
Infer arguments across several messagescombine-message-tuple
const messages = [
'Hello {name}',
'You have {count, number} alerts',
] as const;
type PageArgs = GetICUArgs<
typeof messages,
{ICUArgument: string; ICUNumberArgument: number}
>;
const args: PageArgs = {name: 'Ada', count: 2};The input may be a readonly string tuple; `as const` preserves each message instead of widening the array to string[].
Mark wrapper-supplied arguments optionalmake-provided-args-optional
type RequestArgs = GetICUArgs<
'Hello {name} from {tenant}',
{ICUArgument: string; ProvidedArgs: 'tenant'}
>;
const callerArgs: RequestArgs = {name: 'Ada'};ProvidedArgs accepts argument names and makes those properties optional; your runtime wrapper still has to supply them.
Ignore apostrophe-escaped ICU syntaxignore-escaped-braces
type HelpArgs = GetICUArgs<
"Write '{name}' literally, then greet {user}",
{ICUArgument: string}
>;
const args: HelpArgs = {user: 'Ada'};ICU apostrophe escapes are stripped during type parsing, so `{name}` here is literal text rather than a required argument.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @formatjs/icu-messageformat-parser | npm | You need runtime ICU syntax parsing, validation, source locations, or an abstract syntax tree |
| intl-messageformat | npm | You need to compile and format ICU messages at runtime with locale-aware values |
| schummar-translate | npm | You want a complete typed translation library for React and Node rather than only argument inference |