i18next-cli 1.24.12 → 1.24.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/parsers/expression-resolver.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/parsers/expression-resolver.js +1 -1
- package/package.json +6 -6
- package/types/cli.d.ts +3 -1
- package/types/cli.d.ts.map +1 -1
- package/types/extractor/parsers/expression-resolver.d.ts.map +1 -1
- package/CHANGELOG.md +0 -595
- package/src/cli.ts +0 -283
- package/src/config.ts +0 -215
- package/src/extractor/core/ast-visitors.ts +0 -259
- package/src/extractor/core/extractor.ts +0 -250
- package/src/extractor/core/key-finder.ts +0 -142
- package/src/extractor/core/translation-manager.ts +0 -750
- package/src/extractor/index.ts +0 -7
- package/src/extractor/parsers/ast-utils.ts +0 -87
- package/src/extractor/parsers/call-expression-handler.ts +0 -793
- package/src/extractor/parsers/comment-parser.ts +0 -424
- package/src/extractor/parsers/expression-resolver.ts +0 -353
- package/src/extractor/parsers/jsx-handler.ts +0 -488
- package/src/extractor/parsers/jsx-parser.ts +0 -1463
- package/src/extractor/parsers/scope-manager.ts +0 -445
- package/src/extractor/plugin-manager.ts +0 -116
- package/src/extractor.ts +0 -15
- package/src/heuristic-config.ts +0 -92
- package/src/index.ts +0 -22
- package/src/init.ts +0 -175
- package/src/linter.ts +0 -345
- package/src/locize.ts +0 -263
- package/src/migrator.ts +0 -208
- package/src/rename-key.ts +0 -398
- package/src/status.ts +0 -380
- package/src/syncer.ts +0 -133
- package/src/types-generator.ts +0 -139
- package/src/types.ts +0 -577
- package/src/utils/default-value.ts +0 -45
- package/src/utils/file-utils.ts +0 -167
- package/src/utils/funnel-msg-tracker.ts +0 -84
- package/src/utils/logger.ts +0 -36
- package/src/utils/nested-object.ts +0 -135
- package/src/utils/validation.ts +0 -72
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
import type { JSXElement, ObjectExpression } from '@swc/core'
|
|
2
|
-
import type { PluginContext, I18nextToolkitConfig, ExtractedKey } from '../../types'
|
|
3
|
-
import { ExpressionResolver } from './expression-resolver'
|
|
4
|
-
import { extractFromTransComponent } from './jsx-parser'
|
|
5
|
-
import { getObjectPropValue } from './ast-utils'
|
|
6
|
-
|
|
7
|
-
export class JSXHandler {
|
|
8
|
-
private config: Omit<I18nextToolkitConfig, 'plugins'>
|
|
9
|
-
private pluginContext: PluginContext
|
|
10
|
-
private expressionResolver: ExpressionResolver
|
|
11
|
-
private getCurrentFile: () => string
|
|
12
|
-
private getCurrentCode: () => string
|
|
13
|
-
private lastSearchIndex: number = 0
|
|
14
|
-
|
|
15
|
-
constructor (
|
|
16
|
-
config: Omit<I18nextToolkitConfig, 'plugins'>,
|
|
17
|
-
pluginContext: PluginContext,
|
|
18
|
-
expressionResolver: ExpressionResolver,
|
|
19
|
-
getCurrentFile: () => string,
|
|
20
|
-
getCurrentCode: () => string
|
|
21
|
-
) {
|
|
22
|
-
this.config = config
|
|
23
|
-
this.pluginContext = pluginContext
|
|
24
|
-
this.expressionResolver = expressionResolver
|
|
25
|
-
this.getCurrentFile = getCurrentFile
|
|
26
|
-
this.getCurrentCode = getCurrentCode
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Reset the search index when starting to process a new file.
|
|
31
|
-
*/
|
|
32
|
-
public resetSearchIndex (): void {
|
|
33
|
-
this.lastSearchIndex = 0
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Helper method to calculate line and column by searching for the JSX element in the code.
|
|
38
|
-
*/
|
|
39
|
-
private getLocationFromNode (node: any): { line: number, column: number } | undefined {
|
|
40
|
-
const code = this.getCurrentCode()
|
|
41
|
-
|
|
42
|
-
// For JSXElement, search for the opening tag
|
|
43
|
-
let searchText: string | undefined
|
|
44
|
-
|
|
45
|
-
if (node.type === 'JSXElement' && node.opening) {
|
|
46
|
-
const tagName = node.opening.name?.value
|
|
47
|
-
if (tagName) {
|
|
48
|
-
searchText = `<${tagName}`
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!searchText) return undefined
|
|
53
|
-
|
|
54
|
-
const position = code.indexOf(searchText, this.lastSearchIndex)
|
|
55
|
-
|
|
56
|
-
if (position === -1) return undefined
|
|
57
|
-
|
|
58
|
-
this.lastSearchIndex = position + searchText.length
|
|
59
|
-
|
|
60
|
-
const upToPosition = code.substring(0, position)
|
|
61
|
-
const lines = upToPosition.split('\n')
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
line: lines.length,
|
|
65
|
-
column: lines[lines.length - 1].length
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Processes JSX elements to extract translation keys from Trans components.
|
|
71
|
-
*
|
|
72
|
-
* Identifies configured Trans components and delegates to the JSX parser
|
|
73
|
-
* for complex children serialization and attribute extraction.
|
|
74
|
-
*
|
|
75
|
-
* @param node - JSX element node to process
|
|
76
|
-
* @param getScopeInfo - Function to retrieve scope information for variables
|
|
77
|
-
*/
|
|
78
|
-
handleJSXElement (node: JSXElement, getScopeInfo: (name: string) => { defaultNs?: string; keyPrefix?: string } | undefined): void {
|
|
79
|
-
const elementName = this.getElementName(node)
|
|
80
|
-
|
|
81
|
-
if (elementName && (this.config.extract.transComponents || ['Trans']).includes(elementName)) {
|
|
82
|
-
const extractedAttributes = extractFromTransComponent(node, this.config)
|
|
83
|
-
|
|
84
|
-
const keysToProcess: string[] = []
|
|
85
|
-
|
|
86
|
-
if (extractedAttributes) {
|
|
87
|
-
if (extractedAttributes.keyExpression) {
|
|
88
|
-
const keyValues = this.expressionResolver.resolvePossibleKeyStringValues(extractedAttributes.keyExpression)
|
|
89
|
-
keysToProcess.push(...keyValues)
|
|
90
|
-
} else {
|
|
91
|
-
keysToProcess.push(extractedAttributes.serializedChildren)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let extractedKeys: ExtractedKey[]
|
|
95
|
-
|
|
96
|
-
const { contextExpression, optionsNode, defaultValue, hasCount, isOrdinal, serializedChildren } = extractedAttributes
|
|
97
|
-
|
|
98
|
-
// Extract location information using the helper method
|
|
99
|
-
const location = this.getLocationFromNode(node)
|
|
100
|
-
const locations = location
|
|
101
|
-
? [{
|
|
102
|
-
file: this.getCurrentFile(),
|
|
103
|
-
line: location.line,
|
|
104
|
-
column: location.column
|
|
105
|
-
}]
|
|
106
|
-
: undefined
|
|
107
|
-
|
|
108
|
-
// If ns is not explicitly set on the component, try to find it from the key
|
|
109
|
-
// or the `t` prop
|
|
110
|
-
if (!extractedAttributes.ns) {
|
|
111
|
-
extractedKeys = keysToProcess.map(key => {
|
|
112
|
-
const nsSeparator = this.config.extract.nsSeparator ?? ':'
|
|
113
|
-
let ns: string | undefined
|
|
114
|
-
|
|
115
|
-
// If the key contains a namespace separator, it takes precedence
|
|
116
|
-
// over the default t ns value
|
|
117
|
-
if (nsSeparator && key.includes(nsSeparator)) {
|
|
118
|
-
let parts: string[]
|
|
119
|
-
([ns, ...parts] = key.split(nsSeparator))
|
|
120
|
-
|
|
121
|
-
key = parts.join(nsSeparator)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
key,
|
|
126
|
-
ns,
|
|
127
|
-
defaultValue: defaultValue || serializedChildren,
|
|
128
|
-
hasCount,
|
|
129
|
-
isOrdinal,
|
|
130
|
-
explicitDefault: extractedAttributes.explicitDefault,
|
|
131
|
-
locations
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const tProp = node.opening.attributes?.find(
|
|
136
|
-
attr =>
|
|
137
|
-
attr.type === 'JSXAttribute' &&
|
|
138
|
-
attr.name.type === 'Identifier' &&
|
|
139
|
-
attr.name.value === 't'
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
// Check if the prop value is an identifier (e.g., t={t})
|
|
143
|
-
if (
|
|
144
|
-
tProp?.type === 'JSXAttribute' &&
|
|
145
|
-
tProp.value?.type === 'JSXExpressionContainer' &&
|
|
146
|
-
tProp.value.expression.type === 'Identifier'
|
|
147
|
-
) {
|
|
148
|
-
const tIdentifier = tProp.value.expression.value
|
|
149
|
-
const scopeInfo = getScopeInfo(tIdentifier)
|
|
150
|
-
if (scopeInfo?.defaultNs) {
|
|
151
|
-
extractedKeys.forEach(key => {
|
|
152
|
-
if (!key.ns) {
|
|
153
|
-
key.ns = scopeInfo.defaultNs
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
const { ns } = extractedAttributes
|
|
160
|
-
extractedKeys = keysToProcess.map(key => {
|
|
161
|
-
return {
|
|
162
|
-
key,
|
|
163
|
-
ns,
|
|
164
|
-
defaultValue: defaultValue || serializedChildren,
|
|
165
|
-
hasCount,
|
|
166
|
-
isOrdinal,
|
|
167
|
-
locations
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
extractedKeys.forEach(key => {
|
|
173
|
-
// Apply defaultNS from config if no namespace was found on the component and
|
|
174
|
-
// the key does not contain a namespace prefix
|
|
175
|
-
if (!key.ns) {
|
|
176
|
-
key.ns = this.config.extract.defaultNS
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
// Handle the combination of context and count
|
|
181
|
-
if (contextExpression && hasCount) {
|
|
182
|
-
// Check if plurals are disabled
|
|
183
|
-
if (this.config.extract.disablePlurals) {
|
|
184
|
-
// When plurals are disabled, treat count as a regular option
|
|
185
|
-
// Still handle context normally
|
|
186
|
-
const contextValues = this.expressionResolver.resolvePossibleContextStringValues(contextExpression)
|
|
187
|
-
const contextSeparator = this.config.extract.contextSeparator ?? '_'
|
|
188
|
-
|
|
189
|
-
if (contextValues.length > 0) {
|
|
190
|
-
// For static context (string literal), only add context variants
|
|
191
|
-
if (contextExpression.type === 'StringLiteral') {
|
|
192
|
-
for (const context of contextValues) {
|
|
193
|
-
for (const extractedKey of extractedKeys) {
|
|
194
|
-
const contextKey = `${extractedKey.key}${contextSeparator}${context}`
|
|
195
|
-
this.pluginContext.addKey({
|
|
196
|
-
key: contextKey,
|
|
197
|
-
ns: extractedKey.ns,
|
|
198
|
-
defaultValue: extractedKey.defaultValue,
|
|
199
|
-
locations: extractedKey.locations
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
// For dynamic context, add both base and context variants
|
|
205
|
-
extractedKeys.forEach(extractedKey => {
|
|
206
|
-
this.pluginContext.addKey({
|
|
207
|
-
key: extractedKey.key,
|
|
208
|
-
ns: extractedKey.ns,
|
|
209
|
-
defaultValue: extractedKey.defaultValue,
|
|
210
|
-
locations: extractedKey.locations,
|
|
211
|
-
keyAcceptingContext: extractedKey.key
|
|
212
|
-
})
|
|
213
|
-
})
|
|
214
|
-
for (const context of contextValues) {
|
|
215
|
-
for (const extractedKey of extractedKeys) {
|
|
216
|
-
const contextKey = `${extractedKey.key}${contextSeparator}${context}`
|
|
217
|
-
this.pluginContext.addKey({
|
|
218
|
-
key: contextKey,
|
|
219
|
-
ns: extractedKey.ns,
|
|
220
|
-
defaultValue: extractedKey.defaultValue,
|
|
221
|
-
locations: extractedKey.locations
|
|
222
|
-
})
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
// Fallback to just base keys if context resolution fails
|
|
228
|
-
extractedKeys.forEach(extractedKey => {
|
|
229
|
-
this.pluginContext.addKey({
|
|
230
|
-
key: extractedKey.key,
|
|
231
|
-
ns: extractedKey.ns,
|
|
232
|
-
defaultValue: extractedKey.defaultValue,
|
|
233
|
-
locations: extractedKey.locations,
|
|
234
|
-
keyAcceptingContext: extractedKey.key
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
} else {
|
|
239
|
-
// Original plural handling logic when plurals are enabled
|
|
240
|
-
// Find isOrdinal prop on the <Trans> component
|
|
241
|
-
const ordinalAttr = node.opening.attributes?.find(
|
|
242
|
-
(attr) =>
|
|
243
|
-
attr.type === 'JSXAttribute' &&
|
|
244
|
-
attr.name.type === 'Identifier' &&
|
|
245
|
-
attr.name.value === 'ordinal'
|
|
246
|
-
)
|
|
247
|
-
const isOrdinal = !!ordinalAttr
|
|
248
|
-
|
|
249
|
-
const contextValues = this.expressionResolver.resolvePossibleContextStringValues(contextExpression)
|
|
250
|
-
const contextSeparator = this.config.extract.contextSeparator ?? '_'
|
|
251
|
-
|
|
252
|
-
// Generate all combinations of context and plural forms
|
|
253
|
-
if (contextValues.length > 0) {
|
|
254
|
-
// Generate base plural forms (no context) - these also accept context
|
|
255
|
-
extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode, undefined, extractedKey.locations, extractedKey.key))
|
|
256
|
-
|
|
257
|
-
// Generate context + plural combinations
|
|
258
|
-
for (const context of contextValues) {
|
|
259
|
-
for (const extractedKey of extractedKeys) {
|
|
260
|
-
const contextKey = `${extractedKey.key}${contextSeparator}${context}`
|
|
261
|
-
// The base key that accepts context is extractedKey.key (without the context suffix)
|
|
262
|
-
this.generatePluralKeysForTrans(contextKey, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode, extractedKey.explicitDefault, extractedKey.locations, extractedKey.key)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
// Fallback to just plural forms if context resolution fails
|
|
267
|
-
extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode, extractedKey.explicitDefault, extractedKey.locations))
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
} else if (contextExpression) {
|
|
271
|
-
const contextValues = this.expressionResolver.resolvePossibleContextStringValues(contextExpression)
|
|
272
|
-
const contextSeparator = this.config.extract.contextSeparator ?? '_'
|
|
273
|
-
|
|
274
|
-
if (contextValues.length > 0) {
|
|
275
|
-
// Add context variants
|
|
276
|
-
for (const context of contextValues) {
|
|
277
|
-
for (const { key, ns, defaultValue, locations } of extractedKeys) {
|
|
278
|
-
this.pluginContext.addKey({
|
|
279
|
-
key: `${key}${contextSeparator}${context}`,
|
|
280
|
-
ns,
|
|
281
|
-
defaultValue,
|
|
282
|
-
locations,
|
|
283
|
-
})
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// Only add the base key as a fallback if the context is dynamic (i.e., not a simple string).
|
|
287
|
-
if (contextExpression.type !== 'StringLiteral') {
|
|
288
|
-
extractedKeys.forEach(extractedKey => {
|
|
289
|
-
this.pluginContext.addKey({
|
|
290
|
-
key: extractedKey.key,
|
|
291
|
-
ns: extractedKey.ns,
|
|
292
|
-
defaultValue: extractedKey.defaultValue,
|
|
293
|
-
locations: extractedKey.locations,
|
|
294
|
-
keyAcceptingContext: extractedKey.key
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
// If no context values were resolved, just add base keys
|
|
300
|
-
extractedKeys.forEach(extractedKey => {
|
|
301
|
-
this.pluginContext.addKey({
|
|
302
|
-
key: extractedKey.key,
|
|
303
|
-
ns: extractedKey.ns,
|
|
304
|
-
defaultValue: extractedKey.defaultValue,
|
|
305
|
-
locations: extractedKey.locations,
|
|
306
|
-
keyAcceptingContext: extractedKey.key
|
|
307
|
-
})
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
|
-
} else if (hasCount) {
|
|
311
|
-
// Check if plurals are disabled
|
|
312
|
-
if (this.config.extract.disablePlurals) {
|
|
313
|
-
// When plurals are disabled, just add the base keys (no plural forms)
|
|
314
|
-
extractedKeys.forEach(extractedKey => {
|
|
315
|
-
this.pluginContext.addKey({
|
|
316
|
-
key: extractedKey.key,
|
|
317
|
-
ns: extractedKey.ns,
|
|
318
|
-
defaultValue: extractedKey.defaultValue,
|
|
319
|
-
locations: extractedKey.locations
|
|
320
|
-
})
|
|
321
|
-
})
|
|
322
|
-
} else {
|
|
323
|
-
// Original plural handling logic when plurals are enabled
|
|
324
|
-
// Find isOrdinal prop on the <Trans> component
|
|
325
|
-
const ordinalAttr = node.opening.attributes?.find(
|
|
326
|
-
(attr) =>
|
|
327
|
-
attr.type === 'JSXAttribute' &&
|
|
328
|
-
attr.name.type === 'Identifier' &&
|
|
329
|
-
attr.name.value === 'ordinal'
|
|
330
|
-
)
|
|
331
|
-
const isOrdinal = !!ordinalAttr
|
|
332
|
-
|
|
333
|
-
extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode, extractedKey.explicitDefault, extractedKey.locations))
|
|
334
|
-
}
|
|
335
|
-
} else {
|
|
336
|
-
// No count or context - just add the base keys
|
|
337
|
-
extractedKeys.forEach(extractedKey => {
|
|
338
|
-
this.pluginContext.addKey({
|
|
339
|
-
key: extractedKey.key,
|
|
340
|
-
ns: extractedKey.ns,
|
|
341
|
-
defaultValue: extractedKey.defaultValue,
|
|
342
|
-
locations: extractedKey.locations
|
|
343
|
-
})
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Generates plural keys for Trans components, with support for tOptions plural defaults.
|
|
352
|
-
*
|
|
353
|
-
* @param key - Base key name for pluralization
|
|
354
|
-
* @param defaultValue - Default value for the keys
|
|
355
|
-
* @param ns - Namespace for the keys
|
|
356
|
-
* @param isOrdinal - Whether to generate ordinal plural forms
|
|
357
|
-
* @param optionsNode - Optional tOptions object expression for plural-specific defaults
|
|
358
|
-
* @param explicitDefaultFromSource - Whether the default was explicitly provided
|
|
359
|
-
* @param locations - Source location information for this key
|
|
360
|
-
* @param keyAcceptingContext - The base key that accepts context (if this is a context variant)
|
|
361
|
-
*/
|
|
362
|
-
private generatePluralKeysForTrans (
|
|
363
|
-
key: string,
|
|
364
|
-
defaultValue: string | undefined,
|
|
365
|
-
ns: string | false | undefined,
|
|
366
|
-
isOrdinal: boolean,
|
|
367
|
-
optionsNode?: ObjectExpression,
|
|
368
|
-
explicitDefaultFromSource?: boolean,
|
|
369
|
-
locations?: Array<{ file: string, line?: number, column?: number }>,
|
|
370
|
-
keyAcceptingContext?: string
|
|
371
|
-
): void {
|
|
372
|
-
try {
|
|
373
|
-
const type = isOrdinal ? 'ordinal' : 'cardinal'
|
|
374
|
-
const pluralCategories = new Intl.PluralRules(this.config.extract?.primaryLanguage, { type }).resolvedOptions().pluralCategories
|
|
375
|
-
const pluralSeparator = this.config.extract.pluralSeparator ?? '_'
|
|
376
|
-
|
|
377
|
-
// Get plural-specific default values from tOptions if available
|
|
378
|
-
let otherDefault: string | undefined
|
|
379
|
-
let ordinalOtherDefault: string | undefined
|
|
380
|
-
|
|
381
|
-
if (optionsNode) {
|
|
382
|
-
otherDefault = getObjectPropValue(optionsNode, `defaultValue${pluralSeparator}other`) as string | undefined
|
|
383
|
-
ordinalOtherDefault = getObjectPropValue(optionsNode, `defaultValue${pluralSeparator}ordinal${pluralSeparator}other`) as string | undefined
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Special-case single-"other" languages: generate base key (or context variant) instead of key_other
|
|
387
|
-
if (pluralCategories.length === 1 && pluralCategories[0] === 'other') {
|
|
388
|
-
// Determine final default for the base/other form
|
|
389
|
-
const specificDefault = optionsNode ? getObjectPropValue(optionsNode, `defaultValue${pluralSeparator}other`) as string | undefined : undefined
|
|
390
|
-
const finalDefault = typeof specificDefault === 'string' ? specificDefault : (typeof defaultValue === 'string' ? defaultValue : key)
|
|
391
|
-
|
|
392
|
-
// add base key (no suffix)
|
|
393
|
-
this.pluginContext.addKey({
|
|
394
|
-
key,
|
|
395
|
-
ns,
|
|
396
|
-
defaultValue: finalDefault,
|
|
397
|
-
hasCount: true,
|
|
398
|
-
isOrdinal,
|
|
399
|
-
explicitDefault: Boolean(explicitDefaultFromSource || typeof specificDefault === 'string' || typeof otherDefault === 'string'),
|
|
400
|
-
locations,
|
|
401
|
-
keyAcceptingContext
|
|
402
|
-
})
|
|
403
|
-
return
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
for (const category of pluralCategories) {
|
|
407
|
-
// Look for the most specific default value (e.g., defaultValue_ordinal_one)
|
|
408
|
-
const specificDefaultKey = isOrdinal ? `defaultValue${pluralSeparator}ordinal${pluralSeparator}${category}` : `defaultValue${pluralSeparator}${category}`
|
|
409
|
-
const specificDefault = optionsNode ? getObjectPropValue(optionsNode, specificDefaultKey) as string | undefined : undefined
|
|
410
|
-
|
|
411
|
-
// Determine the final default value using a clear fallback chain
|
|
412
|
-
let finalDefaultValue: string | undefined
|
|
413
|
-
if (typeof specificDefault === 'string') {
|
|
414
|
-
// 1. Use the most specific default if it exists (e.g., defaultValue_one)
|
|
415
|
-
finalDefaultValue = specificDefault
|
|
416
|
-
} else if (category === 'one' && typeof defaultValue === 'string') {
|
|
417
|
-
// 2. SPECIAL CASE: The 'one' category falls back to the main default value (children content)
|
|
418
|
-
finalDefaultValue = defaultValue
|
|
419
|
-
} else if (isOrdinal && typeof ordinalOtherDefault === 'string') {
|
|
420
|
-
// 3a. Other ordinal categories fall back to 'defaultValue_ordinal_other'
|
|
421
|
-
finalDefaultValue = ordinalOtherDefault
|
|
422
|
-
} else if (!isOrdinal && typeof otherDefault === 'string') {
|
|
423
|
-
// 3b. Other cardinal categories fall back to 'defaultValue_other'
|
|
424
|
-
finalDefaultValue = otherDefault
|
|
425
|
-
} else if (typeof defaultValue === 'string') {
|
|
426
|
-
// 4. If no '_other' is found, all categories can fall back to the main default value
|
|
427
|
-
finalDefaultValue = defaultValue
|
|
428
|
-
} else {
|
|
429
|
-
// 5. Final fallback to the base key itself
|
|
430
|
-
finalDefaultValue = key
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const finalKey = isOrdinal
|
|
434
|
-
? `${key}${pluralSeparator}ordinal${pluralSeparator}${category}`
|
|
435
|
-
: `${key}${pluralSeparator}${category}`
|
|
436
|
-
|
|
437
|
-
this.pluginContext.addKey({
|
|
438
|
-
key: finalKey,
|
|
439
|
-
ns,
|
|
440
|
-
defaultValue: finalDefaultValue,
|
|
441
|
-
hasCount: true,
|
|
442
|
-
isOrdinal,
|
|
443
|
-
// Only treat plural/context variant as explicit when:
|
|
444
|
-
// - the extractor indicated the default was explicit on the source element
|
|
445
|
-
// - OR a plural-specific default was provided in tOptions (specificDefault/otherDefault)
|
|
446
|
-
explicitDefault: Boolean(explicitDefaultFromSource || typeof specificDefault === 'string' || typeof otherDefault === 'string'),
|
|
447
|
-
locations,
|
|
448
|
-
// Pass through the base key that accepts context (if any)
|
|
449
|
-
keyAcceptingContext
|
|
450
|
-
})
|
|
451
|
-
}
|
|
452
|
-
} catch (e) {
|
|
453
|
-
// Fallback to a simple key if Intl API fails
|
|
454
|
-
this.pluginContext.addKey({
|
|
455
|
-
key,
|
|
456
|
-
ns,
|
|
457
|
-
defaultValue,
|
|
458
|
-
locations
|
|
459
|
-
})
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Extracts element name from JSX opening tag.
|
|
465
|
-
*
|
|
466
|
-
* Handles both simple identifiers and member expressions:
|
|
467
|
-
* - `<Trans>` → 'Trans'
|
|
468
|
-
* - `<React.Trans>` → 'React.Trans'
|
|
469
|
-
*
|
|
470
|
-
* @param node - JSX element node
|
|
471
|
-
* @returns Element name or undefined if not extractable
|
|
472
|
-
*/
|
|
473
|
-
private getElementName (node: JSXElement): string | undefined {
|
|
474
|
-
if (node.opening.name.type === 'Identifier') {
|
|
475
|
-
return node.opening.name.value
|
|
476
|
-
} else if (node.opening.name.type === 'JSXMemberExpression') {
|
|
477
|
-
let curr: any = node.opening.name
|
|
478
|
-
const names: string[] = []
|
|
479
|
-
while (curr.type === 'JSXMemberExpression') {
|
|
480
|
-
if (curr.property.type === 'Identifier') names.unshift(curr.property.value)
|
|
481
|
-
curr = curr.object
|
|
482
|
-
}
|
|
483
|
-
if (curr.type === 'Identifier') names.unshift(curr.value)
|
|
484
|
-
return names.join('.')
|
|
485
|
-
}
|
|
486
|
-
return undefined
|
|
487
|
-
}
|
|
488
|
-
}
|