-
-
Notifications
You must be signed in to change notification settings - Fork 355
/
Copy pathruntime.ts
445 lines (408 loc) · 11.4 KB
/
runtime.ts
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
import { HelperNameMap } from '@intlify/message-compiler'
import {
assign,
create,
isArray,
isFunction,
isNumber,
isObject,
isPlainObject,
isString,
join,
toDisplayString
} from '@intlify/shared'
import type { Path } from './resolver'
import type { IsNever, StringConvertable } from './types'
/**
*
* The interface used for narrowing types using generated types.
*
* @remarks
*
* The type generated by 3rd party (e.g. nuxt/i18n)
*
* @example
* ```ts
* // generated-i18n-types.d.ts (`.d.ts` file at your app)
*
* declare module '@intlify/core-base' {
* interface GeneratedTypeConfig {
* locale: "en" | "ja"
* }
* }
* ```
*/
export interface GeneratedTypeConfig {}
/**
* Generated locale which resolves to `never` if left unset
*/
export type GeneratedLocale =
GeneratedTypeConfig extends Record<'locale', infer CustomLocale>
? CustomLocale
: never
/** @VueI18nGeneral */
export type Locale =
IsNever<GeneratedLocale> extends true ? string : GeneratedLocale
/** @VueI18nGeneral */
// prettier-ignore
export interface LocaleDetector<Args extends any[] = any[]> { // eslint-disable-line @typescript-eslint/no-explicit-any
(...args: Args): Locale | Promise<Locale>
resolvedOnce?: boolean
}
/** @VueI18nGeneral */
export type FallbackLocale =
| Locale
| Locale[]
| { [locale in string]: Locale[] }
| false
export type CoreMissingType =
| 'translate'
| 'datetime format'
| 'number format'
| 'translate exists'
export type MessageType<T = string> = T extends string
? string
: StringConvertable<T>
/** @VueI18nGeneral */
export type MessageFunctionReturn<T = string> = T extends string
? MessageType<T>
: MessageType<T>[]
export type MessageFunctionCallable = <T = string>(
ctx: MessageContext<T>
) => MessageFunctionReturn<T>
export type MessageFunctionInternal<T = string> = {
(ctx: MessageContext<T>): MessageFunctionReturn<T>
key?: string
locale?: string
source?: string
}
/**
* The Message Function.
*
* @param ctx - A {@link MessageContext}
*
* @return A resolved format message, that is string type basically.
*
* @VueI18nGeneral
*/
export type MessageFunction<T = string> =
| MessageFunctionCallable
| MessageFunctionInternal<T>
export type MessageFunctions<T = string> = Record<string, MessageFunction<T>>
export type MessageResolveFunction<T = string> = (
key: string,
useLinked: boolean
) => MessageFunction<T>
export type MessageNormalize<T = string> = (
values: MessageType<T>[]
) => MessageFunctionReturn<T>
export type MessageInterpolate<T = string> = (val: unknown) => MessageType<T>
export interface MessageProcessor<T = string> {
type?: string
interpolate?: MessageInterpolate<T>
normalize?: MessageNormalize<T>
}
export type PluralizationRule = (
choice: number,
choicesLength: number,
orgRule?: PluralizationRule
) => number
/** @VueI18nGeneral */
export type PluralizationRules = { [locale: string]: PluralizationRule }
export type PluralizationProps = {
n?: number
count?: number
}
export type LinkedModify<T = string> = (
value: T,
type: string
) => MessageType<T>
/** @VueI18nGeneral */
export type LinkedModifiers<T = string> = { [key: string]: LinkedModify<T> }
/** @VueI18nGeneral */
export type NamedValue<T = {}> = T & Record<string, unknown>
// TODO: list and named type definition more improvements
export interface MessageContextOptions<T = string, N = {}> {
parent?: MessageContext<T>
locale?: string
list?: unknown[]
named?: NamedValue<N>
modifiers?: LinkedModifiers<T>
pluralIndex?: number
pluralRules?: PluralizationRules
messages?: MessageFunctions<T> | MessageResolveFunction<T> // TODO: need to design resolve message function?
processor?: MessageProcessor<T>
}
export interface LinkedOptions {
/**
* The message type of linked message
*/
type?: string
/**
* The modifier of linked message
*/
modifier?: string
}
// TODO: list and named type definition more improvements
/**
* The message context.
*
* @VueI18nGeneral
*/
export interface MessageContext<T = string> {
/**
* Resolve message value from list.
*
* @param index - An index of message values.
*
* @returns A resolved message value.
*
* @example
* ```js
* const messages = {
* en: {
* greeting: ({ list }) => `hello, ${list(0)}!`
* }
* }
* ```
*/
list(index: number): unknown
/**
* Resolve message value from named.
*
* @param key - A key of message value.
*
* @returns A resolved message value.
*
* @example
* ```js
* const messages = {
* en: {
* greeting: ({ named }) => `hello, ${named('name')}!`
* }
* }
* ```
*/
named(key: string): unknown
/**
* Resolve message with plural index.
*
* @remarks
* That's resolved with plural index with translation function.
*
* @param messages - the messages, that is resolved with plural index with translation function.
*
* @returns A resolved message.
*
* @example
* ```js
* const messages = {
* en: {
* car: ({ plural }) => plural(['car', 'cars']),
* apple: ({ plural, named }) =>
* plural([
* 'no apples',
* 'one apple',
* `${named('count')} apples`
* ])
* }
* }
* ```
*/
plural(messages: T[]): T
/**
* Resolve linked message.
*
* @param key - A message key
* @param modifier - A modifier
*
* @returns A resolve message.
*/
linked(key: Path, modifier?: string): MessageType<T>
/**
* Overloaded `linked`
*
* @param key - A message key
* @param modifier - A modifier
* @param type - A message type
*
* @returns A resolve message.
*/
linked(key: Path, modifier?: string, type?: string): MessageType<T>
/**
* Overloaded `linked`
*
* @param key - A message key
* @param optoins - An {@link LinkedOptions | linked options}
*
* @returns A resolve message.
*/
linked(key: Path, optoins?: LinkedOptions): MessageType<T>
/** @internal */
message(key: Path): MessageFunction<T>
/**
* The message type to be handled by the message function.
*
* @remarks
* Usually `text`, you need to return **string** in message function.
*/
type: string
/** @internal */
interpolate: MessageInterpolate<T>
/** @internal */
normalize: MessageNormalize<T>
/**
* The message values.
*
* @remarks
* The message values are the argument values passed from translation function, such as `$t`, `t`, or `translate`.
*
* @example
* vue-i18n `$t` (or `t`) case:
* ```html
* <p>{{ $t('greeting', { name: 'DIO' }) }}</p> <!-- `{ name: 'DIO' }` is message values -->
* ```
*
* `@intlify/core` (`@intlify/core-base`) `translate` case:
* ```js
* translate(context, 'foo.bar', ['dio']) // `['dio']` is message values
* ```
*/
values: Record<string, unknown>
}
const DEFAULT_MODIFIER = (str: string): string => str
const DEFAULT_MESSAGE = (ctx: MessageContext<string>): string => '' // eslint-disable-line
export const DEFAULT_MESSAGE_DATA_TYPE = 'text'
const DEFAULT_NORMALIZE = (values: string[]): string =>
values.length === 0 ? '' : join(values)
const DEFAULT_INTERPOLATE = toDisplayString
function pluralDefault(choice: number, choicesLength: number): number {
choice = Math.abs(choice)
if (choicesLength === 2) {
// prettier-ignore
return choice
? choice > 1
? 1
: 0
: 1
}
return choice ? Math.min(choice, 2) : 0
}
function getPluralIndex<T, N>(options: MessageContextOptions<T, N>): number {
// prettier-ignore
const index = isNumber(options.pluralIndex)
? options.pluralIndex
: -1
// prettier-ignore
return options.named && (isNumber(options.named.count) || isNumber(options.named.n))
? isNumber(options.named.count)
? options.named.count
: isNumber(options.named.n)
? options.named.n
: index
: index
}
function normalizeNamed(pluralIndex: number, props: PluralizationProps): void {
if (!props.count) {
props.count = pluralIndex
}
if (!props.n) {
props.n = pluralIndex
}
}
export function createMessageContext<T = string, N = {}>(
options: MessageContextOptions<T, N> = {}
): MessageContext<T> {
const locale = options.locale
const pluralIndex = getPluralIndex(options)
const pluralRule =
isObject(options.pluralRules) &&
isString(locale) &&
isFunction(options.pluralRules[locale])
? options.pluralRules[locale]
: pluralDefault
const orgPluralRule =
isObject(options.pluralRules) &&
isString(locale) &&
isFunction(options.pluralRules[locale])
? pluralDefault
: undefined
const plural = (messages: T[]): T => {
return messages[pluralRule(pluralIndex, messages.length, orgPluralRule)]
}
const _list = options.list || []
const list = (index: number): unknown => _list[index]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _named = options.named || (create() as any)
isNumber(options.pluralIndex) && normalizeNamed(pluralIndex, _named)
const named = (key: string): unknown => _named[key]
function message(key: Path, useLinked?: boolean): MessageFunction<T> {
// prettier-ignore
const msg = isFunction(options.messages)
? options.messages(key, !!useLinked)
: isObject(options.messages)
? options.messages[key]
: false
return !msg
? options.parent
? options.parent.message(key) // resolve from parent messages
: (DEFAULT_MESSAGE as unknown as MessageFunction<T>)
: msg
}
const _modifier = (name: string): LinkedModify<T> =>
options.modifiers
? options.modifiers[name]
: (DEFAULT_MODIFIER as unknown as LinkedModify<T>)
const normalize =
isPlainObject(options.processor) && isFunction(options.processor.normalize)
? options.processor.normalize
: (DEFAULT_NORMALIZE as unknown as MessageNormalize<T>)
const interpolate =
isPlainObject(options.processor) &&
isFunction(options.processor.interpolate)
? options.processor.interpolate
: (DEFAULT_INTERPOLATE as unknown as MessageInterpolate<T>)
const type =
isPlainObject(options.processor) && isString(options.processor.type)
? options.processor.type
: DEFAULT_MESSAGE_DATA_TYPE
const linked = (key: Path, ...args: unknown[]): MessageType<T> => {
const [arg1, arg2] = args
let type = 'text'
let modifier = ''
if (args.length === 1) {
if (isObject(arg1)) {
modifier = arg1.modifier || modifier
type = arg1.type || type
} else if (isString(arg1)) {
modifier = arg1 || modifier
}
} else if (args.length === 2) {
if (isString(arg1)) {
modifier = arg1 || modifier
}
if (isString(arg2)) {
type = arg2 || type
}
}
const ret = message(key, true)(ctx)
const msg =
// The message in vnode resolved with linked are returned as an array by processor.nomalize
type === 'vnode' && isArray(ret) && modifier
? ret[0]
: (ret as MessageType<T>)
return modifier ? _modifier(modifier)(msg as T, type) : msg
}
const ctx = {
[HelperNameMap.LIST]: list,
[HelperNameMap.NAMED]: named,
[HelperNameMap.PLURAL]: plural,
[HelperNameMap.LINKED]: linked,
[HelperNameMap.MESSAGE]: message,
[HelperNameMap.TYPE]: type,
[HelperNameMap.INTERPOLATE]: interpolate,
[HelperNameMap.NORMALIZE]: normalize,
[HelperNameMap.VALUES]: assign(create(), _list, _named)
}
return ctx
}