/
fbt.js
273 lines (248 loc) · 8.46 KB
/
fbt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/**
* (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
*
* @format
* @flow strict
* @emails oncall+internationalization
*/
'use strict';
/**
* Translated string from an `fbt()` call.
*
* This is an opaque type so you may _only_ create an `FbtString` by calling
* `fbt()` or one of its methods.
*
* You may use an `FbtString` as any normal string, but you can't create a new
* `FbtString` without `fbt()`.
*
* @warning Because fbt.param() accepts any value,
* we can't guarantee that this Fbt contents isn't made of React elements
*/
declare opaque type FbtString: string;
/**
* Translated string from an `<fbt>` element.
*
* Unlike `FbtString`, you cannot use `FbtElement` like any normal string. Since
* `<fbt>` can have nested React nodes its internal structure is hidden from the
* end user.
*
* There are some string-like properties and methods you may use, like `length`
* and `toString()`.
*
* This should have been an opaque type so you may _only_ create an `FbtElement` by using an
* `<fbt>` element, but we can't do it yet since there are flow issues with React's defaultProps.
*
* See:
* - An Fbt in defaultProps breaks: https://fburl.com/cer2jdjd
* - Opaque types with truthy bounds aren't excluded from the type of
* logical 'and' expressions. https://fburl.com/9b5vm9wh
*/
declare type FbtElement = $FbtResultBase;
/**
* All translated strings. Could either be a translated string returned by an
* `fbt()` call or a translated string returned by an `<fbt>` element.
*/
declare type FbtWithoutString = FbtString | FbtElement;
/**
* All translated strings wrapped in `fbt` and the `string` type. `string` is
* mostly included for legacy purposes.
*
* NOTE: If you want to use a type that requires your string to be wrapped in
* `fbt` use `FbtWithoutString`. Not this type. It may be wise to run a
* codemod which renames this `Fbt` type to `Fbt | string` and renames
* `FbtWithoutString` to `Fbt` so that all future uses of `Fbt` require
* translated strings.
*/
declare type Fbt = string | FbtWithoutString;
/**
* All translated strings wrapped in `fbs()` or `<fbs>` type.
* If this is composed of "string parameters" (fbs.param),
* then it'll only accept plain string values, or other `Fbs` objects.
* @see {@link https://fburl.com/wiki/ix5srv2p} for more info
*/
declare type Fbs = FbtPureStringResult;
// Read-only array of Fbt items. Use this only when your code is meant to
// handle a list of Fbts from different source code locations.
// Avoid returning an array of Fbt like [<fbt/>, <fbt/>] for a single site
// because it's an anti-pattern similar to string concatenation.
// Favor using a single <fbt/> element as often as possible.
declare type FbtCollection = Fbt | $ReadOnlyArray<Fbt>;
// Similar to React$Node without `Iterable<React$Node>`
declare type $FbtContentItem =
| boolean
| FbtElement
| FbtPureStringResult
| FbtString
| null
| number
| React$Element<any>
| React$Portal
| string
| void;
declare type $NestedFbtContentItems = $ReadOnlyArray<
$FbtContentItem | $NestedFbtContentItems,
>;
declare type FbtErrorContext = {
hash: ?string,
translation: string,
...
};
/**
* A delegate used in FbtResult for handling errors when toString
* can't serialize due to non-string and non-Fbt elements in the
* interpolated payload (e.g. React nodes, DOM nodes, etc).
*/
declare interface IFbtErrorListener {
constructor(context: FbtErrorContext): void;
/**
* Handle the error scenario where the FbtResultBase contains non-string elements
* (usually React components) and tries to run .toString()
*
* Example of bad usage of <fbt> with rich contents that will trigger this error
*
* render() {
* const text = (
* <fbt desc="...">
* I have <Link href="#">no name</Link>.
* </fbt>
* );
* return (
* <div className={cx('FiddleCSS/root')}>
* <p>Text = "{text}"</p>
* <p>Truncated Text = "{text.substr(0, 9)}"</p> // will output: "I have ."
* <em>You might have expected "I have no name" but we don't support
* this in the FBT API.</em>
* </div>
* );
* }
*/
+onStringSerializationError?: (content: $FbtContentItem) => void;
+onStringMethodUsed?: (method: string) => void;
}
declare interface IFbtResultBase {
constructor(
contents: $NestedFbtContentItems,
errorListener: ?IFbtErrorListener,
): void;
getContents(): $NestedFbtContentItems;
// This relies on toString() which contains i18n logging logic to track impressions.
// I.e. If you use this, i18n will register the string as displayed!
toJSON(): string;
// Hack for allowing FBTResult to play nice in React components
_store?: {validated: boolean, ...};
}
declare class $FbtResultBase implements IFbtResultBase {
constructor(
contents: $ReadOnlyArray<any>,
errorListener: ?IFbtErrorListener,
): void;
getContents(): $NestedFbtContentItems;
toJSON(): string;
// TODO(T27672828) Move code of toString() inside unwrap()
// Returns the translated string value (similar to a `toString()` method)
// This is deliberately named differently to avoid making this class behave
// in a "stringish" manner.
// unwrap(): string;
}
// String result wrapper intended for ComponentScript.
// Similar to FbtResultBase except that it can only be assembled from strings, not React elements.
declare class FbtPureStringResult extends $FbtResultBase {}
// Represents the input of an fbt.param
type $FbtParamInput = React$Node;
type $FbsParamInput = FbtPureStringResult | string;
// Represents the output of an fbt.param, fbt.enum, etc...
// It's voluntarily not an accurate representation of the real output.
// Non-internal i18n code should not need to know its actual type.
opaque type $FbsParamOutput = mixed;
opaque type $FbtParamOutput: $FbsParamOutput = $FbsParamOutput;
// NOTE: DO NOT USE THE $-prefixed versions of these types;
// import them from their respective JS modules instead.
opaque type $IntlVariationsEnum: number = number;
opaque type $GenderConstEnum: number = number;
// i18n INTERNAL USE ONLY! DO NOT USE THIS TYPE OUTSIDE OF THIS FILE!
// Defines the fbt or fbs common procedural-style API
type $GenericFbtFunctionAPI<Input, Output, ParamInput, ParamOutput> = {
(
text: Input,
description: string,
options?: {
author?: string,
project?: string,
...
},
): Output,
param: (
name: string,
value: ParamInput,
options?: {
number?: boolean | number,
gender?: $IntlVariationsEnum,
...
},
) => ParamOutput,
enum: (
value: string,
range: $ReadOnlyArray<string> | {[key: string]: string, ...},
) => ParamOutput,
name: (
tokenName: string,
value: string,
gender: $IntlVariationsEnum,
) => ParamOutput,
plural: (
label: string,
count: number,
options?: {
many?: string,
showCount?: 'ifMany' | 'no' | 'yes',
name?: string, // token name
value?: $FbtContentItem, // optional value to replace token (rather than count)
},
) => ParamOutput,
pronoun: (
usage: 'object' | 'possessive' | 'reflexive' | 'subject',
gender: $GenderConstEnum,
options?: {
capitalize?: boolean,
human?: boolean,
...
},
) => ParamOutput,
sameParam: (name: string) => ParamOutput,
c: (text: string) => Output,
jsonEncode: boolean,
replaceParams: boolean,
// Only used in React Native in fbsource
enableJsonExportMode: () => void,
// Only used in React Native in fbsource
disableJsonExportMode: () => void,
isFbtInstance: (value: mixed) => boolean,
...
};
type $StringBasedFbtFunctionAPI<Output, ParamInput, ParamOutput> =
$GenericFbtFunctionAPI<string, Output, ParamInput, ParamOutput>;
/**
* NOTE how the fbs() functional API relies on using an array of content items
* instead of the legacy string concatenation pattern.
*
* This is needed because we have to define the accepted types of the array items in Flow
* (which isn't possible if we used the string concatenation code pattern)
*/
type $ArrayBasedFbtFunctionAPI<Output, ParamInput> = $GenericFbtFunctionAPI<
$ReadOnlyArray<string | $FbtParamOutput>,
Output,
ParamInput,
$FbtParamOutput,
>;
type $FbtFunctionAPI = $StringBasedFbtFunctionAPI<
FbtWithoutString,
$FbtParamInput,
string,
> &
$ArrayBasedFbtFunctionAPI<FbtWithoutString, $FbtParamInput>;
type $FbsFunctionAPI = $StringBasedFbtFunctionAPI<
FbtPureStringResult,
$FbsParamInput,
$FbtParamOutput,
> &
$ArrayBasedFbtFunctionAPI<FbtPureStringResult, $FbsParamInput>;