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.
Files changed (42) hide show
  1. package/dist/cjs/cli.js +1 -1
  2. package/dist/cjs/extractor/parsers/expression-resolver.js +1 -1
  3. package/dist/esm/cli.js +1 -1
  4. package/dist/esm/extractor/parsers/expression-resolver.js +1 -1
  5. package/package.json +6 -6
  6. package/types/cli.d.ts +3 -1
  7. package/types/cli.d.ts.map +1 -1
  8. package/types/extractor/parsers/expression-resolver.d.ts.map +1 -1
  9. package/CHANGELOG.md +0 -595
  10. package/src/cli.ts +0 -283
  11. package/src/config.ts +0 -215
  12. package/src/extractor/core/ast-visitors.ts +0 -259
  13. package/src/extractor/core/extractor.ts +0 -250
  14. package/src/extractor/core/key-finder.ts +0 -142
  15. package/src/extractor/core/translation-manager.ts +0 -750
  16. package/src/extractor/index.ts +0 -7
  17. package/src/extractor/parsers/ast-utils.ts +0 -87
  18. package/src/extractor/parsers/call-expression-handler.ts +0 -793
  19. package/src/extractor/parsers/comment-parser.ts +0 -424
  20. package/src/extractor/parsers/expression-resolver.ts +0 -353
  21. package/src/extractor/parsers/jsx-handler.ts +0 -488
  22. package/src/extractor/parsers/jsx-parser.ts +0 -1463
  23. package/src/extractor/parsers/scope-manager.ts +0 -445
  24. package/src/extractor/plugin-manager.ts +0 -116
  25. package/src/extractor.ts +0 -15
  26. package/src/heuristic-config.ts +0 -92
  27. package/src/index.ts +0 -22
  28. package/src/init.ts +0 -175
  29. package/src/linter.ts +0 -345
  30. package/src/locize.ts +0 -263
  31. package/src/migrator.ts +0 -208
  32. package/src/rename-key.ts +0 -398
  33. package/src/status.ts +0 -380
  34. package/src/syncer.ts +0 -133
  35. package/src/types-generator.ts +0 -139
  36. package/src/types.ts +0 -577
  37. package/src/utils/default-value.ts +0 -45
  38. package/src/utils/file-utils.ts +0 -167
  39. package/src/utils/funnel-msg-tracker.ts +0 -84
  40. package/src/utils/logger.ts +0 -36
  41. package/src/utils/nested-object.ts +0 -135
  42. package/src/utils/validation.ts +0 -72
@@ -1,424 +0,0 @@
1
- import type { PluginContext, I18nextToolkitConfig } from '../../types'
2
-
3
- /**
4
- * Extracts translation keys from comments in source code using regex patterns.
5
- * Supports extraction from single-line (//) and multi-line comments.
6
- *
7
- * @param code - The source code to analyze
8
- * @param pluginContext - Context object with helper methods to add found keys
9
- * @param config - Configuration object containing extraction settings
10
- * @param scopeResolver - Function to resolve scope information for variables (optional)
11
- *
12
- * @example
13
- * ```typescript
14
- * const code = `
15
- * // t('user.name', 'User Name')
16
- * /* t('app.title', { defaultValue: 'My App', ns: 'common' }) *\/
17
- * `
18
- *
19
- * const context = createPluginContext(allKeys)
20
- * extractKeysFromComments(code, context, config, scopeResolver)
21
- * // Extracts: user.name and app.title with their respective settings
22
- * ```
23
- */
24
- export function extractKeysFromComments (
25
- code: string,
26
- pluginContext: PluginContext,
27
- config: I18nextToolkitConfig,
28
- scopeResolver?: (varName: string) => { defaultNs?: string; keyPrefix?: string } | undefined
29
- ): void {
30
- // Hardcode the function name to 't' to prevent parsing other functions like 'test()'.
31
- const functionNameToFind = 't'
32
-
33
- // Use a reliable word boundary (\b) to match 't(...)' but not 'http.get(...)'.
34
- const keyRegex = new RegExp(`\\b${functionNameToFind}\\s*\\(\\s*(['"])([^'"]+)\\1`, 'g')
35
-
36
- // Prepare preservePatterns for filtering
37
- const rawPreservePatterns = config.extract.preservePatterns || []
38
- const preservePatterns = rawPreservePatterns.map(globToRegex)
39
- const nsSeparator = config.extract.nsSeparator ?? ':'
40
-
41
- const matchesPreserve = (key: string, ns?: string) => {
42
- // 1) regex-style matches (existing behavior)
43
- if (preservePatterns.some(re => re.test(key))) return true
44
- // 2) namespace:* style patterns => preserve entire namespace
45
- for (const rp of rawPreservePatterns) {
46
- if (typeof rp !== 'string') continue
47
- if (rp.endsWith(`${nsSeparator}*`)) {
48
- const nsPrefix = (typeof nsSeparator === 'string' && nsSeparator.length > 0)
49
- ? rp.slice(0, -(nsSeparator.length + 1))
50
- : rp.slice(0, -1)
51
- // support '*' as a wildcard namespace
52
- if (nsPrefix === '*' || (ns && nsPrefix === ns)) return true
53
- }
54
- }
55
- return false
56
- }
57
-
58
- const commentTexts = collectCommentTexts(code)
59
-
60
- for (const text of commentTexts) {
61
- let match: RegExpExecArray | null
62
- while ((match = keyRegex.exec(text)) !== null) {
63
- let key = match[2]
64
-
65
- // Validate that the key is not empty or whitespace-only
66
- if (!key || key.trim() === '') {
67
- continue // Skip empty keys
68
- }
69
-
70
- // We'll check preservePatterns after namespace resolution below
71
-
72
- let ns: string | false | undefined
73
- const remainder = text.slice(match.index + match[0].length)
74
-
75
- const defaultValue = parseDefaultValueFromComment(remainder)
76
- const context = parseContextFromComment(remainder)
77
- const count = parseCountFromComment(remainder)
78
- const ordinal = parseOrdinalFromComment(remainder)
79
-
80
- // Check if key ends with _ordinal suffix (like in ast-visitors)
81
- let isOrdinalByKey = false
82
- const pluralSeparator = config.extract.pluralSeparator ?? '_'
83
- if (key.endsWith(`${pluralSeparator}ordinal`)) {
84
- isOrdinalByKey = true
85
- // Normalize the key by stripping the suffix
86
- key = key.slice(0, -(pluralSeparator.length + 7)) // Remove "_ordinal"
87
-
88
- // Validate that the key is still not empty after normalization
89
- if (!key || key.trim() === '') {
90
- continue // Skip keys that become empty after normalization
91
- }
92
-
93
- // Re-check preservePatterns after key normalization (will check namespace-aware helper)
94
- if (matchesPreserve(key, ns as string | undefined)) {
95
- continue // Skip normalized keys that match preserve patterns
96
- }
97
- }
98
-
99
- const isOrdinal = ordinal === true || isOrdinalByKey
100
-
101
- // 1. Check for namespace in options object first (e.g., { ns: 'common' })
102
- ns = parseNsFromComment(remainder)
103
-
104
- // 2. If not in options, check for separator in key (e.g., 'common:button.save')
105
- const nsSeparator = config.extract.nsSeparator ?? ':'
106
- if (!ns && nsSeparator && key.includes(nsSeparator)) {
107
- const parts = key.split(nsSeparator)
108
- ns = parts.shift()
109
- key = parts.join(nsSeparator)
110
-
111
- // Validate that the key didn't become empty after namespace removal
112
- if (!key || key.trim() === '') {
113
- continue // Skip keys that become empty after namespace removal
114
- }
115
-
116
- // Re-check preservePatterns after namespace processing (namespace-aware)
117
- if (matchesPreserve(key, ns as string | undefined)) {
118
- continue // Skip processed keys that match preserve patterns
119
- }
120
- }
121
-
122
- // 3. If no explicit namespace found, try to resolve from scope
123
- // This allows commented t() calls to inherit namespace from useTranslation scope
124
- if (!ns && scopeResolver) {
125
- const scopeInfo = scopeResolver('t')
126
- if (scopeInfo?.defaultNs) {
127
- ns = scopeInfo.defaultNs
128
- }
129
- }
130
-
131
- // Final preserve check for keys without prior namespace normalization
132
- if (matchesPreserve(key, ns as string | undefined)) {
133
- continue
134
- }
135
-
136
- // 4. Final fallback to configured default namespace
137
- if (!ns) ns = config.extract.defaultNS
138
-
139
- // 5. Handle context and count combinations based on disablePlurals setting
140
- if (config.extract.disablePlurals) {
141
- // When plurals are disabled, ignore count for key generation
142
- if (context) {
143
- // Only generate context variants (no base key when context is static)
144
- pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key })
145
- } else {
146
- // Simple key (ignore count)
147
- pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key })
148
- }
149
- } else {
150
- // Original plural handling logic when plurals are enabled
151
- if (context && count) {
152
- // Generate context+plural combinations
153
- generateContextPluralKeys(key, defaultValue ?? key, ns, context, pluginContext, config, isOrdinal)
154
-
155
- // Only generate base plural forms if generateBasePluralForms is not disabled
156
- const shouldGenerateBaseForms = config.extract?.generateBasePluralForms !== false
157
- if (shouldGenerateBaseForms) {
158
- generatePluralKeys(key, defaultValue ?? key, ns, pluginContext, config, isOrdinal)
159
- }
160
- } else if (context) {
161
- // Just context variants
162
- pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key })
163
- pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key })
164
- } else if (count) {
165
- // Just plural variants
166
- generatePluralKeys(key, defaultValue ?? key, ns, pluginContext, config, isOrdinal)
167
- } else {
168
- // Simple key
169
- pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key })
170
- }
171
- }
172
- }
173
- }
174
- }
175
-
176
- /**
177
- * Generates plural keys for a given base key
178
- */
179
- function generatePluralKeys (
180
- key: string,
181
- defaultValue: string,
182
- ns: string | false | undefined,
183
- pluginContext: PluginContext,
184
- config: I18nextToolkitConfig,
185
- isOrdinal = false
186
- ): void {
187
- try {
188
- const type = isOrdinal ? 'ordinal' : 'cardinal'
189
-
190
- // Generate plural forms for ALL target languages to ensure we have all necessary keys
191
- const allPluralCategories = new Set<string>()
192
-
193
- for (const locale of config.locales) {
194
- try {
195
- const pluralRules = new Intl.PluralRules(locale, { type })
196
- const categories = pluralRules.resolvedOptions().pluralCategories
197
- categories.forEach(cat => allPluralCategories.add(cat))
198
- } catch (e) {
199
- // If a locale is invalid, fall back to English rules
200
- const englishRules = new Intl.PluralRules('en', { type })
201
- const categories = englishRules.resolvedOptions().pluralCategories
202
- categories.forEach(cat => allPluralCategories.add(cat))
203
- }
204
- }
205
-
206
- const pluralCategories = Array.from(allPluralCategories).sort()
207
- const pluralSeparator = config.extract.pluralSeparator ?? '_'
208
-
209
- // If the only plural category is "other", prefer emitting the base key instead of "key_other"
210
- if (pluralCategories.length === 1 && pluralCategories[0] === 'other') {
211
- // Emit base key only
212
- pluginContext.addKey({
213
- key,
214
- ns,
215
- defaultValue,
216
- hasCount: true
217
- })
218
- return
219
- }
220
-
221
- // Generate keys for each plural category
222
- for (const category of pluralCategories) {
223
- const finalKey = isOrdinal
224
- ? `${key}${pluralSeparator}ordinal${pluralSeparator}${category}`
225
- : `${key}${pluralSeparator}${category}`
226
-
227
- pluginContext.addKey({
228
- key: finalKey,
229
- ns,
230
- defaultValue,
231
- hasCount: true,
232
- isOrdinal
233
- })
234
- }
235
- } catch (e) {
236
- // Fallback if Intl API fails
237
- pluginContext.addKey({ key, ns, defaultValue })
238
- }
239
- }
240
-
241
- /**
242
- * Generates context + plural combination keys
243
- */
244
- function generateContextPluralKeys (
245
- key: string,
246
- defaultValue: string,
247
- ns: string | false | undefined,
248
- context: string,
249
- pluginContext: PluginContext,
250
- config: I18nextToolkitConfig,
251
- isOrdinal = false
252
- ): void {
253
- try {
254
- const type = isOrdinal ? 'ordinal' : 'cardinal'
255
-
256
- // Generate plural forms for ALL target languages to ensure we have all necessary keys
257
- const allPluralCategories = new Set<string>()
258
-
259
- for (const locale of config.locales) {
260
- try {
261
- const pluralRules = new Intl.PluralRules(locale, { type })
262
- const categories = pluralRules.resolvedOptions().pluralCategories
263
- categories.forEach(cat => allPluralCategories.add(cat))
264
- } catch (e) {
265
- // If a locale is invalid, fall back to English rules
266
- const englishRules = new Intl.PluralRules(config.extract.primaryLanguage || 'en', { type })
267
- const categories = englishRules.resolvedOptions().pluralCategories
268
- categories.forEach(cat => allPluralCategories.add(cat))
269
- }
270
- }
271
-
272
- const pluralCategories = Array.from(allPluralCategories).sort()
273
- const pluralSeparator = config.extract.pluralSeparator ?? '_'
274
-
275
- // Generate keys for each context + plural combination
276
- for (const category of pluralCategories) {
277
- const finalKey = isOrdinal
278
- ? `${key}_${context}${pluralSeparator}ordinal${pluralSeparator}${category}`
279
- : `${key}_${context}${pluralSeparator}${category}`
280
-
281
- pluginContext.addKey({
282
- key: finalKey,
283
- ns,
284
- defaultValue,
285
- hasCount: true,
286
- isOrdinal
287
- })
288
- }
289
- } catch (e) {
290
- // Fallback if Intl API fails
291
- pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue })
292
- }
293
- }
294
-
295
- /**
296
- * Parses default value from the remainder of a comment after a translation function call.
297
- * Supports both string literals and object syntax with defaultValue property.
298
- *
299
- * @param remainder - The remaining text after the translation key
300
- * @returns The parsed default value or undefined if none found
301
- *
302
- * @internal
303
- */
304
- function parseDefaultValueFromComment (remainder: string): string | undefined {
305
- // Simple string default: , 'VALUE' or , "VALUE"
306
- const dvString = /^\s*,\s*(['"])(.*?)\1/.exec(remainder)
307
- if (dvString) return dvString[2]
308
-
309
- // Object with defaultValue: , { defaultValue: 'VALUE', ... }
310
- const dvObj = /^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(remainder)
311
- if (dvObj) return dvObj[2]
312
-
313
- return undefined
314
- }
315
-
316
- /**
317
- * Parses namespace from the remainder of a comment after a translation function call.
318
- * Looks for namespace specified in options object syntax.
319
- *
320
- * @param remainder - The remaining text after the translation key
321
- * @returns The parsed namespace or undefined if none found
322
- *
323
- * @internal
324
- */
325
- function parseNsFromComment (remainder: string): string | undefined {
326
- // Look for ns in an options object, e.g., { ns: 'common' }
327
- const nsObj = /^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(remainder)
328
- if (nsObj) return nsObj[2]
329
-
330
- return undefined
331
- }
332
-
333
- /**
334
- * Collects all comment texts from source code, both single-line and multi-line.
335
- * Deduplicates comments to avoid processing the same text multiple times.
336
- *
337
- * @param src - The source code to extract comments from
338
- * @returns Array of unique comment text content
339
- *
340
- * @internal
341
- */
342
- function collectCommentTexts (src: string): string[] {
343
- const texts: string[] = []
344
- const seen = new Set<string>()
345
-
346
- const commentRegex = /\/\/(.*)|\/\*([\s\S]*?)\*\//g
347
- let cmatch: RegExpExecArray | null
348
- while ((cmatch = commentRegex.exec(src)) !== null) {
349
- const content = cmatch[1] ?? cmatch[2]
350
- const s = content.trim()
351
- if (s && !seen.has(s)) {
352
- seen.add(s)
353
- texts.push(s)
354
- }
355
- }
356
-
357
- return texts
358
- }
359
-
360
- /**
361
- * Parses context from the remainder of a comment after a translation function call.
362
- * Looks for context specified in options object syntax.
363
- *
364
- * @param remainder - The remaining text after the translation key
365
- * @returns The parsed context value or undefined if none found
366
- *
367
- * @internal
368
- */
369
- function parseContextFromComment (remainder: string): string | undefined {
370
- // Look for context in an options object, e.g., { context: 'male' }
371
- const contextObj = /^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(remainder)
372
- if (contextObj) return contextObj[2]
373
-
374
- return undefined
375
- }
376
-
377
- /**
378
- * Parses count from the remainder of a comment after a translation function call.
379
- * Looks for count specified in options object syntax.
380
- *
381
- * @param remainder - The remaining text after the translation key
382
- * @returns The parsed count value or undefined if none found
383
- *
384
- * @internal
385
- */
386
- function parseCountFromComment (remainder: string): number | undefined {
387
- // Look for count in an options object, e.g., { count: 1 }
388
- const countObj = /^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(remainder)
389
- if (countObj) return parseInt(countObj[1], 10)
390
-
391
- return undefined
392
- }
393
-
394
- /**
395
- * Parses ordinal flag from the remainder of a comment after a translation function call.
396
- * Looks for ordinal specified in options object syntax.
397
- *
398
- * @param remainder - The remaining text after the translation key
399
- * @returns The parsed ordinal value or undefined if none found
400
- *
401
- * @internal
402
- */
403
- function parseOrdinalFromComment (remainder: string): boolean | undefined {
404
- // Look for ordinal in an options object, e.g., { ordinal: true }
405
- const ordinalObj = /^\s*,\s*\{[^}]*ordinal\s*:\s*(true|false)/.exec(remainder)
406
- if (ordinalObj) return ordinalObj[1] === 'true'
407
-
408
- return undefined
409
- }
410
-
411
- /**
412
- * Converts a glob pattern to a regular expression.
413
- * Supports basic glob patterns with * wildcards.
414
- *
415
- * @param glob - The glob pattern to convert
416
- * @returns A RegExp that matches the glob pattern
417
- *
418
- * @internal
419
- */
420
- function globToRegex (glob: string): RegExp {
421
- const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
422
- const regexString = `^${escaped.replace(/\*/g, '.*')}$`
423
- return new RegExp(regexString)
424
- }