|
1 |
| -// See https://github.com/schummar/schummar-translate/issues/28 |
| 1 | +// schummar is the best, he published his ICU type parser for next-intl: |
| 2 | +// https://github.com/schummar/schummar-translate/issues/28 |
| 3 | +import {GetICUArgs, GetICUArgsOptions} from '@schummar/icu-type-parser'; |
2 | 4 |
|
3 |
| -export type Flatten<T> = T extends object |
4 |
| - ? { |
5 |
| - [P in keyof T]: T[P]; |
6 |
| - } |
7 |
| - : T; |
8 |
| - |
9 |
| -type OtherString = string & {__type: 'other'}; |
10 |
| - |
11 |
| -type Whitespace = ' ' | '\t' | '\n' | '\r'; |
12 |
| - |
13 |
| -/** Remove leading and tailing whitespace */ |
14 |
| -type Trim<T> = T extends `${Whitespace}${infer Rest}` |
15 |
| - ? Trim<Rest> |
16 |
| - : T extends `${infer Rest}${Whitespace}` |
17 |
| - ? Trim<Rest> |
18 |
| - : T extends string |
19 |
| - ? T |
20 |
| - : never; |
21 |
| - |
22 |
| -/** Returns an array of top level blocks */ |
23 |
| -type FindBlocks<Text> = Text extends `${string}{${infer Right}` // find first { |
24 |
| - ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] |
25 |
| - ? [Block, ...FindBlocks<Tail>] // read block and find next block for tail |
26 |
| - : [{}] |
27 |
| - : []; // no {, return empty result |
28 |
| - |
29 |
| -/** Find blocks for each tuple entry */ |
30 |
| -type TupleFindBlocks<T> = T extends readonly [infer First, ...infer Rest] |
31 |
| - ? [...FindBlocks<First>, ...TupleFindBlocks<Rest>] |
32 |
| - : []; |
33 |
| - |
34 |
| -/** Read tail until the currently open block is closed. Return the block content and rest of tail */ |
35 |
| -type ReadBlock< |
36 |
| - Block extends string, |
37 |
| - Tail extends string, |
38 |
| - Depth extends string |
39 |
| -> = Tail extends `${infer L1}}${infer R1}` // find first } |
40 |
| - ? L1 extends `${infer L2}{${infer R2}` // if preceeded by {, this opens a nested block |
41 |
| - ? ReadBlock<`${Block}${L2}{`, `${R2}}${R1}`, `${Depth}+`> // then continue search right of this { |
42 |
| - : Depth extends `+${infer Rest}` // else if depth > 0 |
43 |
| - ? ReadBlock<`${Block}${L1}}`, R1, Rest> // then finished nested block, continue search right of first } |
44 |
| - : [`${Block}${L1}`, R1] // else return full block and search for next |
45 |
| - : []; // no }, return emptry result |
46 |
| - |
47 |
| -/** Parse block, return variables with types and recursively find nested blocks within */ |
48 |
| -type ParseBlock<Block, ICUArgument, ICUNumberArgument, ICUDateArgument> = |
49 |
| - Block extends `${infer Name},${infer Format},${infer Rest}` |
50 |
| - ? Trim<Format> extends 'select' |
51 |
| - ? SelectOptions< |
52 |
| - Trim<Name>, |
53 |
| - Trim<Rest>, |
54 |
| - ICUArgument, |
55 |
| - ICUNumberArgument, |
56 |
| - ICUDateArgument |
57 |
| - > |
58 |
| - : { |
59 |
| - [K in Trim<Name>]: VariableType< |
60 |
| - Trim<Format>, |
61 |
| - ICUArgument, |
62 |
| - ICUNumberArgument, |
63 |
| - ICUDateArgument |
64 |
| - >; |
65 |
| - } & TupleParseBlock< |
66 |
| - TupleFindBlocks<FindBlocks<Rest>>, |
67 |
| - ICUArgument, |
68 |
| - ICUNumberArgument, |
69 |
| - ICUDateArgument |
70 |
| - > |
71 |
| - : Block extends `${infer Name},${infer Format}` |
72 |
| - ? { |
73 |
| - [K in Trim<Name>]: VariableType< |
74 |
| - Trim<Format>, |
75 |
| - ICUArgument, |
76 |
| - ICUNumberArgument, |
77 |
| - ICUDateArgument |
78 |
| - >; |
79 |
| - } |
80 |
| - : {[K in Trim<Block>]: ICUArgument}; |
81 |
| - |
82 |
| -/** Parse block for each tuple entry */ |
83 |
| -type TupleParseBlock<T, ICUArgument, ICUNumberArgument, ICUDateArgument> = |
84 |
| - T extends readonly [infer First, ...infer Rest] |
85 |
| - ? ParseBlock<First, ICUArgument, ICUNumberArgument, ICUDateArgument> & |
86 |
| - TupleParseBlock<Rest, ICUArgument, ICUNumberArgument, ICUDateArgument> |
87 |
| - : {}; |
88 |
| - |
89 |
| -type VariableType< |
90 |
| - T extends string, |
91 |
| - ICUArgument, |
92 |
| - ICUNumberArgument, |
93 |
| - ICUDateArgument |
94 |
| -> = T extends 'number' | 'plural' | 'selectordinal' |
95 |
| - ? ICUNumberArgument |
96 |
| - : T extends 'date' | 'time' |
97 |
| - ? ICUDateArgument |
98 |
| - : ICUArgument; |
99 |
| - |
100 |
| -// Select ////////////////////////////////////////////////////////////////////// |
101 |
| - |
102 |
| -type SelectOptions< |
103 |
| - Name extends string, |
104 |
| - Rest, |
105 |
| - ICUArgument, |
106 |
| - ICUNumberArgument, |
107 |
| - ICUDateArgument |
108 |
| -> = KeepAndMerge< |
109 |
| - ParseSelectBlock<Name, Rest, ICUArgument, ICUNumberArgument, ICUDateArgument> |
110 |
| ->; |
111 |
| - |
112 |
| -type ParseSelectBlock< |
113 |
| - Name extends string, |
114 |
| - Rest, |
115 |
| - ICUArgument, |
116 |
| - ICUNumberArgument, |
117 |
| - ICUDateArgument |
118 |
| -> = Rest extends `${infer Left}{${infer Right}` |
119 |
| - ? ReadBlock<'', Right, ''> extends [infer Block, infer Tail] |
120 |
| - ? |
121 |
| - | ({[K in Name]: HandleOther<Trim<Left>>} & TupleParseBlock< |
122 |
| - FindBlocks<Block>, |
123 |
| - ICUArgument, |
124 |
| - ICUNumberArgument, |
125 |
| - ICUDateArgument |
126 |
| - >) |
127 |
| - | ParseSelectBlock< |
128 |
| - Name, |
129 |
| - Tail, |
130 |
| - ICUArgument, |
131 |
| - ICUNumberArgument, |
132 |
| - ICUDateArgument |
133 |
| - > |
134 |
| - : never |
135 |
| - : never; |
136 |
| - |
137 |
| -type HandleOther<T> = 'other' extends T ? Exclude<T, 'other'> | OtherString : T; |
138 |
| - |
139 |
| -type KeepAndMerge<T extends object> = T | MergeTypeUnion<T>; |
140 |
| - |
141 |
| -type KeysFromUnion<T> = T extends T ? keyof T : never; |
142 |
| - |
143 |
| -type SimpleTypeMerge<T, K extends keyof any> = T extends {[k in K]?: any} |
144 |
| - ? T[K] extends OtherString |
145 |
| - ? string & {} |
146 |
| - : T[K] |
147 |
| - : never; |
148 |
| - |
149 |
| -type MergeTypeUnion<T extends object> = { |
150 |
| - [k in KeysFromUnion<T>]: SimpleTypeMerge<T, k>; |
151 |
| -}; |
152 |
| - |
153 |
| -// Escapes ///////////////////////////////////////////////////////////////////// |
154 |
| - |
155 |
| -type EscapeLike = `'${'{' | '}' | '<' | '>'}`; |
156 |
| -type StripEscapes<T> = T extends `${infer Left}''${infer Right}` |
157 |
| - ? `${Left}${Right}` |
158 |
| - : T extends `${infer Start}${EscapeLike}${string}'${infer End}` |
159 |
| - ? `${Start}${StripEscapes<End>}` |
160 |
| - : T extends `${infer Start}${EscapeLike}${string}` |
161 |
| - ? Start |
162 |
| - : T; |
163 |
| - |
164 |
| -// Export ////////////////////////////////////////////////////////////////////// |
165 |
| - |
166 |
| -/** Calculates an object type with all variables and their types in the given ICU format string */ |
167 |
| -type ICUArgs< |
168 |
| - Message extends string, |
169 |
| - ICUArgument, |
170 |
| - ICUNumberArgument, |
171 |
| - ICUDateArgument |
172 |
| -> = |
| 5 | +type ICUArgs<Message extends string, Options extends GetICUArgsOptions> = |
173 | 6 | // This is important when `t` is returned from a function and there's no
|
174 | 7 | // known `Message` yet. Otherwise, we'd run into an infinite loop.
|
175 |
| - string extends Message |
176 |
| - ? {} |
177 |
| - : Flatten< |
178 |
| - TupleParseBlock< |
179 |
| - FindBlocks<StripEscapes<Message>>, |
180 |
| - ICUArgument, |
181 |
| - ICUNumberArgument, |
182 |
| - ICUDateArgument |
183 |
| - > |
184 |
| - >; |
| 8 | + string extends Message ? {} : GetICUArgs<Message, Options>; |
185 | 9 |
|
186 | 10 | export default ICUArgs;
|
0 commit comments