Let's have some fun with the
type system
Let's have some fun with the
type system


Marco Schumacher
Senior Software Engineer
schummar
marco@schumacher.dev
Augsburg, Munich
Pentland Firth
pentlandfirth.com
Never heard of TypeScript?
The type system
Let's get started
Motivation
Let's have some fun with the
type system
Never heard of TypeScript?









Motivation: Translate my app
export default { welcomeMessage: 'Hi, {name}', currentTime: 'It is now {time, time, short}' }
<div> <FormattedMessage id="myMessage" defaultMessage="Today is {ts, date, ::yyyyMMdd}" values={{ts: Date.now()}} /> </div>
<div> <Trans i18nKey="userMessagesUnread" count={count}> Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. </Trans> </div>
<div> {t('welcomeMessage', { name: 'Marco' })} </div>
Motivation: Translate my app
export default { welcomeMessage: 'Hi, {name}', currentTime: 'It is now {time, time, short}' }
<div> {t('weclomeMesage', { user: 'Marco' })} </div>

Wouldn't it be nice...
export default { welcomeMessage: 'Hi, {name}', currentTime: 'It is now {time, time, short}' }
My goal: Create a translation library
- Code completion on ids and values
- Support ICU formatting
- Simple, lean
- No preprocessing, no generated files
- Pure TypeScript

And now for something completely different
const userName: string = 'someone'; const age: number = 32; const isBanned: boolean = false;
Primitives
Arrays, Tuples
const array: string[] = ['a', 'b', 'c']; const tuple: [string, number, boolean] = ['a', 1, true];
Functions
function add(x: number, y: number): number { return x + y; } const referenceToAdd: (x: number, y: number) => number = add;
Basic types
Classes
class Account { userName: string, password: string, } const account: Account = new Account();
Objects
type User = { firstName: string, lastName: string, }
const enum Direction { Up, Down, Left, Right, } const dir: Direction = Direction.Down;
Enums
Unions
Advanced types
type ClickEvent = { kind: 'click', pos: Position }; type KeyDownEvent = { kind: 'keydown', key: string }; let event: ClickEvent | KeyDownEvent; if (event.kind === 'click') { console.log(event.pos); } else { console.log(event.key); }
Intersections
type Animal = { name: string } type Bear = Animal & { honey: boolean } const bear: Bear = getBear(); console.log(bear.name, bear.honey);
Literal types
let message: 'hello'; message = 'hello'; // OK message = 'world'; // ERROR
type Audience = 'world' | 'Web&Wine'; type Message = `Hello ${Audience}!` const msg1: Message = 'Hello world!'; // Ok const msg1: Message = 'Hello Web&Wine!'; // Ok const msg1: Message = 'Hello Augsburg'; // Error
Template Literal Types

function doSomethingWithUser(user: User) {} const user = new NotAUser(); doSomethingWithUser(user); // Error doSomethingWithUser(user as any); // Ok
A word about any
type Collection<T extends { id: string }> = { items: T[], }
Type manipulation
Generic Types
type User = { name: string, birthday: Date, } type Keys = keyof User; // type Keys = 'name' | 'birthday'
keyof operator
const user = { name: 'Marco', birthday: new Date('1988-10-16'), } type User = typeof User; // type User = { name: string, birthday: Date }
typeof operator
type User = { name: string, birthday: Date, } type V1 = User['name' | 'birthday']; // type V1 = string | Date type V2 = User[keyof User]; // type V2 = string | Date
Indexed access
type Example1 = Dog extends Animal ? number : string;
Conditional Types
type WithId<T> = T extends { id: any } ? T : T & { id: string }; type T1 = WithId<{ id:number, name: string }>; // type T1 = { id: number, name: string } type T2 = WithId<{ name: string }>; // type T2 = { id: string, name: string }
type UnwrapPromise<T> = T extends Promise<infer S> ? UnwrapPromise<S> : T; type T1 = UnwrapPromise<string>; // type T1 = string type T2 = UnwrapPromise<Promise<string>>; // type T2 = string type T3 = UnwrapPromise<Promise<Promise<string>>>; // type T3 = string
Conditional Types with inference
type Partial<T> = { [K in keyof T]?: T[K] } type Book = { title: string, description: string, length: number }; type BookUpdate = Partial<Book>; // type BookUpdate = { title?: string, description?: string, length?: number }
Mapped Types
type StringValues<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] }; type Book = { title: string, description: string, length: number }; type BookStringValues = StringValues<Book>; // type BookStringValues = { title: string, description: string }
Fun fact: The TypeScript type system is turing complete

Proof: https://gist.github.com/hediet/63f4844acf5ac330804801084f87a6d4
So, how do we apply this?
const dict = { key1: 'value1', nested: { key2: 'value2', key3: 'value3 {param1, plural, one {one} other {many}}' } } as const; type Dict = typeof dict;
type FlatKeys<Dict> = 'key1' | 'nested.key2' | 'nested.key3';
type GetICUArgs<'value3 {param, plural, one {one} other {many}}'> = { param: number }
type GetICUArgs<'value3 {param1, plural, one {one {param2, date}} other {many}}'> = { param: number param2: Date }
function t<Key extends FlatKeys<Dict>>(key: Key, values: GetICUArgs<DeepValue<Dict, Key>>) {}
type DeepValue<Dict, 'nested.key3'> = 'value3 {param1, plural, one {one} other {many}}'
Warmup: Trim
type Whitespace = ' ' | '\t' | '\n' | '\r'; /** Remove leading and tailing whitespace */ type Trim<T> =
type Whitespace = ' ' | '\t' | '\n' | '\r'; /** Remove leading and tailing whitespace */ type Trim<T> = T extends `${Whitespace}${infer Rest}` ? Trim<Rest> : T extends `${infer Rest}${Whitespace}` ? Trim<Rest> : T extends string ? T : never;
type Whitespace = ' ' | '\t' | '\n' | '\r'; /** Remove leading and tailing whitespace */ type Trim<T> = T extends `${Whitespace}${infer Rest}` ? Trim<Rest> : T extends `${infer Rest}${Whitespace}` ? Trim<Rest> : T extends string ? T : never;
type Whitespace = ' ' | '\t' | '\n' | '\r'; /** Remove leading and tailing whitespace */ type Trim<T> = T extends `${Whitespace}${infer Rest}` ? Trim<Rest> : T extends `${infer Rest}${Whitespace}` ? Trim<Rest> : T extends string ? T : never;
type Whitespace = ' ' | '\t' | '\n' | '\r'; /** Remove leading and tailing whitespace */ type Trim<T> = T extends `${Whitespace}${infer Rest}` ? Trim<Rest> : T extends `${infer Rest}${Whitespace}` ? Trim<Rest> : T extends string ? T : never;
type Whitespace = ' ' | '\t' | '\n' | '\r'; /** Remove leading and tailing whitespace */ type Trim<T> = T extends `${Whitespace}${infer Rest}` ? Trim<Rest> : T extends `${infer Rest}${Whitespace}` ? Trim<Rest> : T extends string ? T : never;
Task 1: FlatKeys
export type FlatKeys<T extends Record<string, unknown>> =
export type FlatKeys<T extends Record<string, unknown>> = string & keyof { [K in keyof T as T[K] extends Record<string, unknown> ? `${string & K}.${FlatKeys<T[K]>}` : K]: 1; };
key1: '...'
nested: {
key2: '...',
key3: '...'
}
key1
`nested.${ }`
key2
key3
'...' extends Record<string, unknown> => No
'...' extends Record<string, unknown> => No
'...' extends Record<string, unknown> => No
{ key2: '...', key3: '...' } extends Record<string, unknown> => Yes
'key2' | 'key3'
Task 2: DeepValue
export type DeepValue<T extends Record<string, unknown>, K extends string> =
export type DeepValue<T extends Record<string, unknown>, K extends string> = K extends `${infer Head}.${infer Rest}` ? T[Head] extends Record<string, unknown> ? DeepValue<T[Head], Rest> : never : T[K];
export type DeepValue<T extends Record<string, unknown>, K extends string> = K extends `${infer Head}.${infer Rest}` ? T[Head] extends Record<string, unknown> ? DeepValue<T[Head], Rest> : never : T[K];
export type DeepValue<T extends Record<string, unknown>, K extends string> = K extends `${infer Head}.${infer Rest}` ? T[Head] extends Record<string, unknown> ? DeepValue<T[Head], Rest> : never : T[K];
Task 3.1: FindBlocks
Some text { param }
Right
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
L1
R1
Result: ['param']
-
-
-
Task 3.1: FindBlocks
Some text { param, plural, one {1st} }
Right
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
L1
R1
L2
R2
=> ReadBlock with
Block = 'param, plural, one {' Tail = '1st}}' Depth = '+'
Task 3.1: FindBlocks
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
Block = 'param, plural, one {' Tail = '1st}}' Depth = '+'
L1
R1
=> ReadBlock with
Block = 'param, plural, one {1st}' Tail = '}' Depth = ''
Text
Text
Task 3.1: FindBlocks
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
type FindBlocks<Text> = Text extends `${string}{${infer Right}` //find first { ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail : never : []; // no {, return empty result type ReadBlock<Block extends string, Tail extends string, Depth extends string> = Tail extends `${infer L1}}${infer R1}` // find first } ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { : Depth extends `+${infer Rest}` // else if depth > 0 ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } : [`${Block}${L1}`, R1] // else return full block and search for next : []; // no }, return emptry result
Block = 'param, plural, one {1st}' Tail = '}' Depth = ''
L1
R1
Result: ['param, plural, one {1st}']
Text
Text
Text
Task 3.2: ParseBlock
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type ParseBlock<Block> = Block extends `${infer Name},${infer Format},${infer Rest}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } & TupleParseBlock<TupleFindBlocks<FindBlocks<Rest>>> : Block extends `${infer Name},${infer Format}` ? { [K in Trim<Name>]: VariableType<Trim<Format>> } : { [K in Trim<Block>]: Value };
type VariableType<T extends string> = T extends 'number' | 'plural' | 'selectordinal' ? number : T extends 'date' | 'time' ? Date : Value;
export type GetICUArgs<T> = T extends string ? TupleParseBlock<FindBlocks<T>> : never;
'param' => { param: Value }
'param, plural, one { 1st {nested, Date} }' => {}
'param, plural, one { 1st {nested, Date} }' => { param: }
'param, plural, one { 1st {nested, Date} }' => { param: number }
'param, plural, one { 1st {nested, Date} }' => { param: number } & {}
'param, plural, one { 1st {nested, Date} }' => { param: number } & { nested: }
'param, plural, one { 1st {nested, Date} }' => { param: number } & { nested: Date }
DEMO

schummar/schummar-translate
npm schummar-translate
Let's have some fun with the TypeScript type system
By Marco Schumacher
Let's have some fun with the TypeScript type system
- 240