Skip to content

Commit f44ae15

Browse files
authored
feat: Adopt shared ICU type parser (#1549)
1 parent 3ff1923 commit f44ae15

File tree

4 files changed

+23
-182
lines changed

4 files changed

+23
-182
lines changed

packages/use-intl/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
],
5959
"dependencies": {
6060
"@formatjs/fast-memoize": "^2.2.0",
61+
"@schummar/icu-type-parser": "1.21.5",
6162
"intl-messageformat": "^10.5.14"
6263
},
6364
"peerDependencies": {
+5-181
Original file line numberDiff line numberDiff line change
@@ -1,186 +1,10 @@
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';
24

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> =
1736
// This is important when `t` is returned from a function and there's no
1747
// 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>;
1859

18610
export default ICUArgs;

packages/use-intl/src/core/createTranslator.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ import {Prettify} from './types.tsx';
3030
type ICUArgsWithTags<
3131
MessageString extends string,
3232
TagsFn extends RichTagsFunction | MarkupTagsFunction = never
33-
> = ICUArgs<MessageString, ICUArg, ICUNumber, ICUDate> &
33+
> = ICUArgs<
34+
MessageString,
35+
{
36+
ICUArgument: ICUArg;
37+
ICUNumberArgument: ICUNumber;
38+
ICUDateArgument: ICUDate;
39+
}
40+
> &
3441
([TagsFn] extends [never] ? {} : ICUTags<MessageString, TagsFn>);
3542

3643
type OnlyOptional<T> = Partial<T> extends T ? true : false;

pnpm-lock.yaml

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)