i18next-cli 1.24.13 → 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.
Files changed (39) hide show
  1. package/dist/cjs/cli.js +1 -1
  2. package/dist/esm/cli.js +1 -1
  3. package/package.json +6 -6
  4. package/types/cli.d.ts +3 -1
  5. package/types/cli.d.ts.map +1 -1
  6. package/CHANGELOG.md +0 -599
  7. package/src/cli.ts +0 -283
  8. package/src/config.ts +0 -215
  9. package/src/extractor/core/ast-visitors.ts +0 -259
  10. package/src/extractor/core/extractor.ts +0 -250
  11. package/src/extractor/core/key-finder.ts +0 -142
  12. package/src/extractor/core/translation-manager.ts +0 -750
  13. package/src/extractor/index.ts +0 -7
  14. package/src/extractor/parsers/ast-utils.ts +0 -87
  15. package/src/extractor/parsers/call-expression-handler.ts +0 -793
  16. package/src/extractor/parsers/comment-parser.ts +0 -424
  17. package/src/extractor/parsers/expression-resolver.ts +0 -391
  18. package/src/extractor/parsers/jsx-handler.ts +0 -488
  19. package/src/extractor/parsers/jsx-parser.ts +0 -1463
  20. package/src/extractor/parsers/scope-manager.ts +0 -445
  21. package/src/extractor/plugin-manager.ts +0 -116
  22. package/src/extractor.ts +0 -15
  23. package/src/heuristic-config.ts +0 -92
  24. package/src/index.ts +0 -22
  25. package/src/init.ts +0 -175
  26. package/src/linter.ts +0 -345
  27. package/src/locize.ts +0 -263
  28. package/src/migrator.ts +0 -208
  29. package/src/rename-key.ts +0 -398
  30. package/src/status.ts +0 -380
  31. package/src/syncer.ts +0 -133
  32. package/src/types-generator.ts +0 -139
  33. package/src/types.ts +0 -577
  34. package/src/utils/default-value.ts +0 -45
  35. package/src/utils/file-utils.ts +0 -167
  36. package/src/utils/funnel-msg-tracker.ts +0 -84
  37. package/src/utils/logger.ts +0 -36
  38. package/src/utils/nested-object.ts +0 -135
  39. 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
- }