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
package/src/locize.ts DELETED
@@ -1,263 +0,0 @@
1
- import { execa } from 'execa'
2
- import chalk from 'chalk'
3
- import ora from 'ora'
4
- import inquirer from 'inquirer'
5
- import { resolve, sep } from 'node:path'
6
- import type { I18nextToolkitConfig } from './types'
7
-
8
- /**
9
- * Verifies that the locize-cli tool is installed and accessible.
10
- *
11
- * @throws Exits the process with error code 1 if locize-cli is not found
12
- *
13
- * @example
14
- * ```typescript
15
- * await checkLocizeCliExists()
16
- * // Continues execution if locize-cli is available
17
- * // Otherwise exits with installation instructions
18
- * ```
19
- */
20
- async function checkLocizeCliExists (): Promise<void> {
21
- try {
22
- await execa('locize', ['--version'])
23
- } catch (error: any) {
24
- if (error.code === 'ENOENT') {
25
- console.error(chalk.red('Error: `locize-cli` command not found.'))
26
- console.log(chalk.yellow('Please install it globally to use the locize integration:'))
27
- console.log(chalk.cyan('npm install -g locize-cli'))
28
- process.exit(1)
29
- }
30
- }
31
- }
32
-
33
- /**
34
- * Interactive setup wizard for configuring Locize credentials.
35
- *
36
- * This function guides users through setting up their Locize integration when
37
- * configuration is missing or invalid. It:
38
- * 1. Prompts for Project ID, API Key, and version
39
- * 2. Validates required fields
40
- * 3. Temporarily sets credentials for the current run
41
- * 4. Provides security recommendations for storing credentials
42
- * 5. Shows code examples for proper configuration
43
- *
44
- * @param config - Configuration object to update with new credentials
45
- * @returns Promise resolving to the Locize configuration or undefined if setup was cancelled
46
- *
47
- * @example
48
- * ```typescript
49
- * const locizeConfig = await interactiveCredentialSetup(config)
50
- * if (locizeConfig) {
51
- * // Proceed with sync using the new credentials
52
- * }
53
- * ```
54
- */
55
- async function interactiveCredentialSetup (config: I18nextToolkitConfig): Promise<{ projectId?: string, apiKey?: string, version?: string } | undefined> {
56
- console.log(chalk.yellow('\nLocize configuration is missing or invalid. Let\'s set it up!'))
57
-
58
- const answers = await inquirer.prompt([
59
- {
60
- type: 'input',
61
- name: 'projectId',
62
- message: 'What is your locize Project ID? (Find this in your project settings on www.locize.app)',
63
- validate: input => !!input || 'Project ID cannot be empty.',
64
- },
65
- {
66
- type: 'password',
67
- name: 'apiKey',
68
- message: 'What is your locize API key? (Create or use one in your project settings > "API Keys")',
69
- validate: input => !!input || 'API Key cannot be empty.',
70
- },
71
- {
72
- type: 'input',
73
- name: 'version',
74
- message: 'What version do you want to sync with?',
75
- default: 'latest',
76
- },
77
- ])
78
-
79
- if (!answers.projectId) {
80
- console.error(chalk.red('Project ID is required to continue.'))
81
- return undefined
82
- }
83
-
84
- const { save } = await inquirer.prompt([{
85
- type: 'confirm',
86
- name: 'save',
87
- message: 'Would you like to see how to save these credentials for future use?',
88
- default: true,
89
- }])
90
-
91
- if (save) {
92
- const envSnippet = `
93
- # Add this to your .env file (and ensure .env is in your .gitignore!)
94
- LOCIZE_API_KEY=${answers.apiKey}
95
- `
96
- const configSnippet = `
97
- // Add this to your i18next.config.ts file
98
- locize: {
99
- projectId: '${answers.projectId}',
100
- // For security, apiKey is best set via an environment variable
101
- apiKey: process.env.LOCIZE_API_KEY,
102
- version: '${answers.version}',
103
- },`
104
-
105
- console.log(chalk.cyan('\nGreat! For the best security, we recommend using environment variables for your API key.'))
106
- console.log(chalk.bold('\nRecommended approach (.env file):'))
107
- console.log(chalk.green(envSnippet))
108
- console.log(chalk.bold('Then, in your i18next.config.ts:'))
109
- console.log(chalk.green(configSnippet))
110
- }
111
-
112
- return {
113
- projectId: answers.projectId,
114
- apiKey: answers.apiKey,
115
- version: answers.version,
116
- }
117
- }
118
-
119
- /**
120
- * Helper function to build the array of arguments for the execa call.
121
- * This ensures the logic is consistent for both the initial run and the retry.
122
- */
123
- function buildArgs (command: string, config: I18nextToolkitConfig, cliOptions: any): string[] {
124
- const { locize: locizeConfig = {}, extract } = config
125
- const { projectId, apiKey, version } = locizeConfig
126
-
127
- const commandArgs: string[] = [command]
128
-
129
- if (projectId) commandArgs.push('--project-id', projectId)
130
- if (apiKey) commandArgs.push('--api-key', apiKey)
131
- if (version) commandArgs.push('--ver', version)
132
- // TODO: there might be more configurable locize-cli options in future
133
-
134
- // Pass-through options from the CLI
135
- if (command === 'sync') {
136
- const updateValues = cliOptions.updateValues ?? locizeConfig.updateValues
137
- if (updateValues) commandArgs.push('--update-values', 'true')
138
- const srcLngOnly = cliOptions.srcLngOnly ?? locizeConfig.sourceLanguageOnly
139
- if (srcLngOnly) commandArgs.push('--reference-language-only', 'true')
140
- const compareMtime = cliOptions.compareMtime ?? locizeConfig.compareModificationTime
141
- if (compareMtime) commandArgs.push('--compare-modification-time', 'true')
142
- const dryRun = cliOptions.dryRun ?? locizeConfig.dryRun
143
- if (dryRun) commandArgs.push('--dry', 'true')
144
- }
145
-
146
- // Derive a sensible base path for locize from the configured output.
147
- // If output is a string template we can strip the language placeholder.
148
- // If output is a function we cannot reliably infer the base; fall back to cwd.
149
- let basePath: string
150
- try {
151
- if (typeof extract.output === 'string') {
152
- const outputNormalized = extract.output.replace(/\\/g, '/')
153
- const baseCandidate = outputNormalized.includes('/{{language}}/')
154
- ? outputNormalized.split('/{{language}}/')[0]
155
- : outputNormalized.replace('{{language}}', '')
156
- const baseCandidateWithSep = baseCandidate.split('/').join(sep)
157
- basePath = resolve(process.cwd(), baseCandidateWithSep)
158
- } else if (typeof extract.output === 'function') {
159
- // Try calling the function with the primary language to get an example path,
160
- // then strip the language folder if present. If that fails, fallback to cwd.
161
- try {
162
- const sample = extract.output(config.extract.primaryLanguage || 'en')
163
- const sampleNormalized = String(sample).replace(/\\/g, '/')
164
- const baseCandidate = sampleNormalized.includes('/' + (config.extract.primaryLanguage || 'en') + '/')
165
- ? sampleNormalized.split('/' + (config.extract.primaryLanguage || 'en') + '/')[0]
166
- : sampleNormalized.replace(config.extract.primaryLanguage || 'en', '')
167
- basePath = resolve(process.cwd(), baseCandidate.split('/').join(sep))
168
- } catch {
169
- basePath = resolve(process.cwd(), '.')
170
- }
171
- } else {
172
- basePath = resolve(process.cwd(), '.')
173
- }
174
- } catch {
175
- basePath = resolve(process.cwd(), '.')
176
- }
177
-
178
- commandArgs.push('--path', basePath)
179
-
180
- return commandArgs
181
- }
182
-
183
- /**
184
- * Executes a locize-cli command with proper error handling and credential management.
185
- *
186
- * This is the core function that:
187
- * 1. Validates that locize-cli is installed
188
- * 2. Builds command arguments from configuration and CLI options
189
- * 3. Executes the locize command with proper credential handling
190
- * 4. Provides interactive credential setup on authentication errors
191
- * 5. Handles retries with new credentials
192
- * 6. Reports success or failure with appropriate exit codes
193
- *
194
- * @param command - The locize command to execute ('sync', 'download', or 'migrate')
195
- * @param config - The toolkit configuration with locize settings
196
- * @param cliOptions - Additional options passed from CLI arguments
197
- *
198
- * @example
199
- * ```typescript
200
- * // Sync local files to Locize
201
- * await runLocizeCommand('sync', config, { updateValues: true })
202
- *
203
- * // Download translations from Locize
204
- * await runLocizeCommand('download', config)
205
- *
206
- * // Migrate local files to a new Locize project
207
- * await runLocizeCommand('migrate', config)
208
- * ```
209
- */
210
- async function runLocizeCommand (command: 'sync' | 'download' | 'migrate', config: I18nextToolkitConfig, cliOptions: any = {}) {
211
- await checkLocizeCliExists()
212
-
213
- const spinner = ora(`Running 'locize ${command}'...\n`).start()
214
-
215
- let effectiveConfig = config
216
-
217
- try {
218
- // 1. First attempt
219
- const initialArgs = buildArgs(command, effectiveConfig, cliOptions)
220
- console.log(chalk.cyan(`\nRunning 'locize ${initialArgs.join(' ')}'...`))
221
- const result = await execa('locize', initialArgs, { stdio: 'pipe' })
222
-
223
- spinner.succeed(chalk.green(`'locize ${command}' completed successfully.`))
224
- if (result?.stdout) console.log(result.stdout) // Print captured output on success
225
- } catch (error: any) {
226
- const stderr = error.stderr || ''
227
- if (stderr.includes('missing required argument')) {
228
- // 2. Auth failure, trigger interactive setup
229
- const newCredentials = await interactiveCredentialSetup(effectiveConfig)
230
- if (newCredentials) {
231
- effectiveConfig = { ...effectiveConfig, locize: newCredentials }
232
-
233
- spinner.start('Retrying with new credentials...')
234
- try {
235
- // 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
236
- const retryArgs = buildArgs(command, effectiveConfig, cliOptions)
237
- console.log(chalk.cyan(`\nRunning 'locize ${retryArgs.join(' ')}'...`))
238
- const result = await execa('locize', retryArgs, { stdio: 'pipe' })
239
-
240
- spinner.succeed(chalk.green('Retry successful!'))
241
- if (result?.stdout) console.log(result.stdout)
242
- } catch (retryError: any) {
243
- spinner.fail(chalk.red('Error during retry.'))
244
- console.error(retryError.stderr || retryError.message)
245
- process.exit(1)
246
- }
247
- } else {
248
- spinner.fail('Operation cancelled.')
249
- process.exit(1) // User aborted the prompt
250
- }
251
- } else {
252
- // Handle other errors
253
- spinner.fail(chalk.red(`Error executing 'locize ${command}'.`))
254
- console.error(stderr || error.message)
255
- process.exit(1)
256
- }
257
- }
258
- console.log(chalk.green(`\n✅ 'locize ${command}' completed successfully.`))
259
- }
260
-
261
- export const runLocizeSync = (config: I18nextToolkitConfig, cliOptions?: any) => runLocizeCommand('sync', config, cliOptions)
262
- export const runLocizeDownload = (config: I18nextToolkitConfig, cliOptions?: any) => runLocizeCommand('download', config, cliOptions)
263
- export const runLocizeMigrate = (config: I18nextToolkitConfig, cliOptions?: any) => runLocizeCommand('migrate', config, cliOptions)
package/src/migrator.ts DELETED
@@ -1,208 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import { writeFile, access } from 'node:fs/promises'
3
- import { pathToFileURL } from 'node:url'
4
- import { createJiti } from 'jiti'
5
- import { getTsConfigAliases } from './config'
6
-
7
- /**
8
- * Path where the new configuration file will be created
9
- */
10
- const newConfigPath = resolve(process.cwd(), 'i18next.config.ts')
11
-
12
- /**
13
- * List of possible new configuration file names that would prevent migration
14
- */
15
- const POSSIBLE_NEW_CONFIGS = [
16
- 'i18next.config.ts',
17
- 'i18next.config.js',
18
- 'i18next.config.mjs',
19
- 'i18next.config.cjs',
20
- ]
21
-
22
- /**
23
- * List of supported legacy configuration file extensions
24
- */
25
- const LEGACY_CONFIG_EXTENSIONS = ['.js', '.mjs', '.cjs', '.ts']
26
-
27
- /**
28
- * Helper function to find a legacy config file with various extensions
29
- */
30
- async function findLegacyConfigFile (basePath: string): Promise<string | null> {
31
- // If the provided path already has an extension, use it directly
32
- if (LEGACY_CONFIG_EXTENSIONS.some(ext => basePath.endsWith(ext))) {
33
- try {
34
- await access(basePath)
35
- return basePath
36
- } catch {
37
- return null
38
- }
39
- }
40
-
41
- // Try different extensions
42
- for (const ext of LEGACY_CONFIG_EXTENSIONS) {
43
- const fullPath = `${basePath}${ext}`
44
- try {
45
- await access(fullPath)
46
- return fullPath
47
- } catch {
48
- // Continue to next extension
49
- }
50
- }
51
-
52
- return null
53
- }
54
-
55
- /**
56
- * Loads a legacy config file using the appropriate loader (jiti for TS, dynamic import for JS/MJS/CJS)
57
- */
58
- async function loadLegacyConfig (configPath: string): Promise<any> {
59
- try {
60
- let config: any
61
-
62
- // Use jiti for TypeScript files, native import for JavaScript
63
- if (configPath.endsWith('.ts')) {
64
- const aliases = await getTsConfigAliases()
65
- const jiti = createJiti(process.cwd(), {
66
- alias: aliases,
67
- interopDefault: false,
68
- })
69
-
70
- const configModule = await jiti.import(configPath, { default: true })
71
- config = configModule
72
- } else {
73
- const configUrl = pathToFileURL(configPath).href
74
- const configModule = await import(`${configUrl}?t=${Date.now()}`)
75
- config = configModule.default
76
- }
77
-
78
- return config
79
- } catch (error) {
80
- console.error(`Error loading legacy config from ${configPath}:`, error)
81
- return null
82
- }
83
- }
84
-
85
- /**
86
- * Migrates a legacy i18next-parser configuration file to the new
87
- * i18next-cli configuration format.
88
- *
89
- * This function:
90
- * 1. Checks if a legacy config file exists (supports .js, .mjs, .cjs, .ts)
91
- * 2. Prevents migration if any new config file already exists
92
- * 3. Dynamically imports the old configuration using appropriate loader
93
- * 4. Maps old configuration properties to new format:
94
- * - `$LOCALE` → `{{language}}`
95
- * - `$NAMESPACE` → `{{namespace}}`
96
- * - Maps lexer functions and components
97
- * - Creates sensible defaults for new features
98
- * 5. Generates a new TypeScript configuration file
99
- * 6. Provides warnings for deprecated features
100
- *
101
- * @param customConfigPath - Optional custom path to the legacy config file
102
- *
103
- * @example
104
- * ```bash
105
- * # Migrate default config
106
- * npx i18next-cli migrate-config
107
- *
108
- * # Migrate custom config with extension
109
- * npx i18next-cli migrate-config i18next-parser.config.mjs
110
- *
111
- * # Migrate custom config without extension (will try .js, .mjs, .cjs, .ts)
112
- * npx i18next-cli migrate-config my-custom-config
113
- * ```
114
- */
115
- export async function runMigrator (customConfigPath?: string) {
116
- let oldConfigPath: string | null
117
-
118
- if (customConfigPath) {
119
- oldConfigPath = await findLegacyConfigFile(resolve(process.cwd(), customConfigPath))
120
- if (!oldConfigPath) {
121
- console.log(`No legacy config file found at or near: ${customConfigPath}`)
122
- console.log('Tried extensions: .js, .mjs, .cjs, .ts')
123
- return
124
- }
125
- } else {
126
- // Default behavior: look for i18next-parser.config.* files
127
- oldConfigPath = await findLegacyConfigFile(resolve(process.cwd(), 'i18next-parser.config'))
128
- if (!oldConfigPath) {
129
- console.log('No i18next-parser.config.* found. Nothing to migrate.')
130
- console.log('Tried: i18next-parser.config.js, .mjs, .cjs, .ts')
131
- return
132
- }
133
- }
134
-
135
- console.log(`Attempting to migrate legacy config from: ${oldConfigPath}...`)
136
-
137
- // Check if ANY new config file already exists
138
- for (const configFile of POSSIBLE_NEW_CONFIGS) {
139
- try {
140
- const fullPath = resolve(process.cwd(), configFile)
141
- await access(fullPath)
142
- console.warn(`Warning: A new configuration file already exists at "${configFile}". Migration skipped to avoid overwriting.`)
143
- return
144
- } catch (e) {
145
- // File doesn't exist, which is good
146
- }
147
- }
148
-
149
- // Load the legacy config using the appropriate loader
150
- const oldConfig = await loadLegacyConfig(oldConfigPath)
151
-
152
- if (!oldConfig) {
153
- console.error('Could not read the legacy config file.')
154
- return
155
- }
156
-
157
- // --- Start Migration Logic ---
158
- const newConfig = {
159
- locales: oldConfig.locales || ['en'],
160
- extract: {
161
- input: oldConfig.input || 'src/**/*.{js,jsx,ts,tsx}',
162
- output: (oldConfig.output || 'locales/$LOCALE/$NAMESPACE.json')
163
- .replace('$LOCALE', '{{language}}')
164
- .replace('$NAMESPACE', '{{namespace}}'),
165
- defaultNS: oldConfig.defaultNamespace || 'translation',
166
- keySeparator: oldConfig.keySeparator,
167
- nsSeparator: oldConfig.namespaceSeparator,
168
- contextSeparator: oldConfig.contextSeparator,
169
- // A simple mapping for functions
170
- functions: oldConfig.lexers?.js?.functions || ['t', '*.t'],
171
- transComponents: oldConfig.lexers?.js?.components || ['Trans'],
172
- },
173
- types: {
174
- input: ['locales/{{language}}/{{namespace}}.json'], // Sensible default
175
- output: 'src/types/i18next.d.ts', // Sensible default
176
- },
177
- }
178
-
179
- // Make the migration smarter: if 't' is a function, also add the '*.t' wildcard
180
- // to provide better out-of-the-box support for common patterns like `i18n.t`.
181
- if (newConfig.extract.functions.includes('t') && !newConfig.extract.functions.includes('*.t')) {
182
- newConfig.extract.functions.push('*.t')
183
- }
184
- // --- End Migration Logic ---
185
-
186
- // Generate the new file content as a string
187
- const newConfigFileContent = `
188
- import { defineConfig } from 'i18next-cli';
189
-
190
- export default defineConfig(${JSON.stringify(newConfig, null, 2)});
191
- `
192
-
193
- await writeFile(newConfigPath, newConfigFileContent.trim())
194
-
195
- console.log('✅ Success! Migration complete.')
196
- console.log(`New configuration file created at: ${newConfigPath}`)
197
- console.warn('\nPlease review the generated file and adjust paths for "types.input" if necessary.')
198
-
199
- // Warning for deprecated features
200
- if (oldConfig.keepRemoved) {
201
- console.warn('Warning: The "keepRemoved" option is deprecated. Consider using the "preservePatterns" feature for dynamic keys.')
202
- }
203
-
204
- // Warning for compatibilityJSON v3
205
- if (oldConfig.i18nextOptions?.compatibilityJSON === 'v3') {
206
- console.warn('Warning: compatibilityJSON "v3" is not supported in i18next-cli. Only i18next v4 format is supported.')
207
- }
208
- }