Skip to content

Instantly share code, notes, and snippets.

@temoncher
Last active July 9, 2025 06:57
Show Gist options
  • Select an option

  • Save temoncher/d6faa4a7af6f45a96be4baaa190b598a to your computer and use it in GitHub Desktop.

Select an option

Save temoncher/d6faa4a7af6f45a96be4baaa190b598a to your computer and use it in GitHub Desktop.
ICU format parser in typescript types
// https://unicode-org.github.io/icu/userguide/format_parse/messages/
type Trim<S extends string> =
S extends ` ${infer R}` | `${infer R} ` | `\n${infer R}` | `${infer R}\n` ? Trim<R> : S;
type AccumulateBeforeClosing<S extends string, TStack extends unknown[] = [], R extends string = ''> = S extends `${infer TBeforeClosing}}${infer TAfterClosing}`
? S extends `${infer TBeforeOpening}{${infer TAfterOpening}`
? TBeforeOpening extends `${TBeforeClosing}${string}`
? TStack['length'] extends 0
? `${R}${TBeforeClosing}`
: TStack extends [...infer TStart, infer TLast]
? AccumulateBeforeClosing<TAfterClosing, TStart, `${R}${TBeforeClosing}}`>
: never // can't happen
: AccumulateBeforeClosing<TAfterOpening, [...TStack, undefined], `${R}${TBeforeOpening}{`>
: TStack['length'] extends 0
? `${R}${TBeforeClosing}`
: TStack extends [...infer TStart, infer TLast]
? AccumulateBeforeClosing<TAfterClosing, TStart, `${R}${TBeforeClosing}}`>
: never // can't happen
: `${R}${S}`
type ICUTypes = 'number' | 'number, currency' | 'date' | 'time'
type DetectICU<S extends string> = S extends `${infer TIdentifier}, ${ICUTypes}`
? TIdentifier
: S extends `${infer TSelectIdentifier}, select,${infer RestSelect}`
? S extends `${infer TPluralIdentifier}, plural,${infer RestPlural}`
/**
* In case we got nested select and plural
* we pick smallest of TSelectIdentifier/TPluralIdentifier, because otherwise we catch something bigger than identifier
*/
? (TSelectIdentifier extends `${TPluralIdentifier}${string}` ? [TPluralIdentifier, RestPlural] : [TSelectIdentifier, RestSelect]) extends [infer TIdentifier, infer Rest extends string]
? TIdentifier | ParseInterpolation<ParseOneLevelOfInterpolation<Rest>>
: never // can't happen
: TSelectIdentifier | ParseInterpolation<ParseOneLevelOfInterpolation<RestSelect>>
: S extends `${infer TPluralIdentifier}, plural,${infer RestPlural}`
? TPluralIdentifier | ParseInterpolation<ParseOneLevelOfInterpolation<RestPlural>>
: S
type ParseOneLevelOfInterpolation<S extends string> = S extends `${string}{${infer TAfterOpening}`
? AccumulateBeforeClosing<TAfterOpening> extends infer TInside extends string
? TAfterOpening extends `${TInside}}${infer TAfterClosing}`
? Trim<TInside> extends infer TTrimmedInside extends string
? TTrimmedInside | ParseOneLevelOfInterpolation<TAfterClosing>
: never // can't happen
: never // means no interpolation strings inside S (because no "}")
: never // can't happen
: never; // means no interpolation strings inside S (because no "{")
type ParseInterpolation<S extends string> = DetectICU<ParseOneLevelOfInterpolation<S>>
type Test = ParseInterpolation<"I have {apples_count, plural, zero {# {apple_color} apples} one {# {apple_color} apple} other {# many {qualification} {apple_color} apples}} in my {color} backpack">;
// ^?
type Test2 = ParseInterpolation<'Hello {user}, you have {count, number} messages.'>;
type test_text = `{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to their party.}
=2 {{host} invites {guest} and one other person to their party.}
other {{host} invites {guest} and # other people to their party.}}}`;
type Test99 = ParseInterpolation<test_text>
// ^?
type large_test_text = `{gender_of_host, select,
female {
{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to her party.}
=2 {{host} invites {guest} and one other person to her party.}
other {{host} invites {guest} and # other people to her party.}}}
male {
{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to his party.}
=2 {{host} invites {guest} and one other person to his party.}
other {{host} invites {guest} and # other people to his party.}}}
other {
{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to their party.}
=2 {{host} invites {guest} and one other person to their party.}
other {{host} invites {guest} and # other people to their party.}}}}`;
type Test3 = ParseInterpolation<large_test_text>
// ^?
type Test4 = ParseInterpolation<' {foo} '>
// ^?
type Test5 = ParseInterpolation<'{foo} '>
// ^?
type Test6 = ParseInterpolation<' {foo}'>
// ^?
type Test7 = ParseInterpolation<'{foo}'>
// ^?
type Test8 = ParseInterpolation<'{foo}' | ' {bar} {kek}'>
// ^?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment