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
package/src/utils/file-utils.ts
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile, access } from 'node:fs/promises'
|
|
2
|
-
import { dirname, extname, resolve, normalize } from 'node:path'
|
|
3
|
-
import { createJiti } from 'jiti'
|
|
4
|
-
import type { I18nextToolkitConfig } from '../types'
|
|
5
|
-
import { getTsConfigAliases } from '../config'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Ensures that the directory for a given file path exists.
|
|
9
|
-
* Creates all necessary parent directories recursively if they don't exist.
|
|
10
|
-
*
|
|
11
|
-
* @param filePath - The file path for which to ensure the directory exists
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* await ensureDirectoryExists('/path/to/nested/file.json')
|
|
16
|
-
* // Creates /path/to/nested/ directory if it doesn't exist
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
export async function ensureDirectoryExists (filePath: string): Promise<void> {
|
|
20
|
-
const dir = dirname(filePath)
|
|
21
|
-
await mkdir(dir, { recursive: true })
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Reads a file asynchronously and returns its content as a UTF-8 string.
|
|
26
|
-
*
|
|
27
|
-
* @param filePath - The path to the file to read
|
|
28
|
-
* @returns Promise resolving to the file content as a string
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* const content = await readFileAsync('./config.json')
|
|
33
|
-
* const config = JSON.parse(content)
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export async function readFileAsync (filePath: string): Promise<string> {
|
|
37
|
-
return await readFile(filePath, 'utf-8')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Writes data to a file asynchronously.
|
|
42
|
-
*
|
|
43
|
-
* @param filePath - The path where to write the file
|
|
44
|
-
* @param data - The string data to write to the file
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```typescript
|
|
48
|
-
* const jsonData = JSON.stringify({ key: 'value' }, null, 2)
|
|
49
|
-
* await writeFileAsync('./output.json', jsonData)
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
export async function writeFileAsync (filePath: string, data: string): Promise<void> {
|
|
53
|
-
await writeFile(filePath, data)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Resolve an output template (string or function) into an actual path string.
|
|
58
|
-
*
|
|
59
|
-
* - If `outputTemplate` is a function, call it with (language, namespace)
|
|
60
|
-
* - If it's a string, replace placeholders:
|
|
61
|
-
* - {{language}} or {{lng}} -> language
|
|
62
|
-
* - {{namespace}} -> namespace (or removed if namespace is undefined)
|
|
63
|
-
* - Normalizes duplicate slashes and returns a platform-correct path.
|
|
64
|
-
*/
|
|
65
|
-
export function getOutputPath (
|
|
66
|
-
outputTemplate: string | ((language: string, namespace?: string) => string) | undefined,
|
|
67
|
-
language: string,
|
|
68
|
-
namespace?: string
|
|
69
|
-
): string {
|
|
70
|
-
if (!outputTemplate) {
|
|
71
|
-
// Fallback to a sensible default
|
|
72
|
-
return normalize(`locales/${language}/${namespace ?? 'translation'}.json`)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (typeof outputTemplate === 'function') {
|
|
76
|
-
try {
|
|
77
|
-
const result = String(outputTemplate(language, namespace))
|
|
78
|
-
return normalize(result.replace(/\/\/+/g, '/'))
|
|
79
|
-
} catch {
|
|
80
|
-
// If user function throws, fallback to default path
|
|
81
|
-
return normalize(`locales/${language}/${namespace ?? 'translation'}.json`)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// It's a string template
|
|
86
|
-
let out = String(outputTemplate)
|
|
87
|
-
out = out.replace(/\{\{language\}\}|\{\{lng\}\}/g, language)
|
|
88
|
-
|
|
89
|
-
if (namespace !== undefined && namespace !== null) {
|
|
90
|
-
out = out.replace(/\{\{namespace\}\}/g, namespace)
|
|
91
|
-
} else {
|
|
92
|
-
// remove any occurrences of /{{namespace}} or {{namespace}} (keeping surrounding slashes tidy)
|
|
93
|
-
out = out.replace(/\/?\{\{namespace\}\}/g, '')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// collapse duplicate slashes and normalize to platform-specific separators
|
|
97
|
-
out = out.replace(/\/\/+/g, '/')
|
|
98
|
-
return normalize(out)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Dynamically loads a translation file, supporting .json, .js, and .ts formats.
|
|
103
|
-
* @param filePath - The path to the translation file.
|
|
104
|
-
* @returns The parsed content of the file, or null if not found or failed to parse.
|
|
105
|
-
*/
|
|
106
|
-
export async function loadTranslationFile (filePath: string): Promise<Record<string, any> | null> {
|
|
107
|
-
const fullPath = resolve(process.cwd(), filePath)
|
|
108
|
-
try {
|
|
109
|
-
await access(fullPath)
|
|
110
|
-
} catch {
|
|
111
|
-
return null // File doesn't exist
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const ext = extname(fullPath).toLowerCase()
|
|
116
|
-
|
|
117
|
-
if (ext === '.json') {
|
|
118
|
-
const content = await readFile(fullPath, 'utf-8')
|
|
119
|
-
return JSON.parse(content)
|
|
120
|
-
} else if (ext === '.ts' || ext === '.js') {
|
|
121
|
-
// Load TypeScript path aliases for proper module resolution
|
|
122
|
-
const aliases = await getTsConfigAliases()
|
|
123
|
-
|
|
124
|
-
const jiti = createJiti(process.cwd(), {
|
|
125
|
-
alias: aliases,
|
|
126
|
-
interopDefault: true,
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const module = await jiti.import(fullPath, { default: true }) as unknown
|
|
130
|
-
return module as Record<string, any> | null
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return null // Unsupported file type
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.warn(`Could not parse translation file ${filePath}:`, error)
|
|
136
|
-
return null
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Serializes a translation object into a string based on the desired format.
|
|
142
|
-
* @param data - The translation data object.
|
|
143
|
-
* @param format - The desired output format ('json', 'js-esm', etc.).
|
|
144
|
-
* @param indentation - The number of spaces for indentation.
|
|
145
|
-
* @returns The serialized file content as a string.
|
|
146
|
-
*/
|
|
147
|
-
export function serializeTranslationFile (
|
|
148
|
-
data: Record<string, any>,
|
|
149
|
-
format: I18nextToolkitConfig['extract']['outputFormat'] = 'json',
|
|
150
|
-
indentation: number | string = 2
|
|
151
|
-
): string {
|
|
152
|
-
const jsonString = JSON.stringify(data, null, indentation)
|
|
153
|
-
|
|
154
|
-
switch (format) {
|
|
155
|
-
case 'js':
|
|
156
|
-
case 'js-esm':
|
|
157
|
-
return `export default ${jsonString};\n`
|
|
158
|
-
case 'js-cjs':
|
|
159
|
-
return `module.exports = ${jsonString};\n`
|
|
160
|
-
case 'ts':
|
|
161
|
-
// Using `as const` provides better type inference for TypeScript users
|
|
162
|
-
return `export default ${jsonString} as const;\n`
|
|
163
|
-
case 'json':
|
|
164
|
-
default:
|
|
165
|
-
return `${jsonString}\n`
|
|
166
|
-
}
|
|
167
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path'
|
|
2
|
-
import { tmpdir } from 'node:os'
|
|
3
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* In-memory cache to track which funnel messages have been shown in the current session.
|
|
7
|
-
*/
|
|
8
|
-
const hasLocizeFunnelBeenShown: Record<string, boolean> = {}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Path to the persistent file that stores the last time each funnel message was shown.
|
|
12
|
-
* Stored in the OS temporary directory to persist across CLI sessions.
|
|
13
|
-
*/
|
|
14
|
-
const LAST_FUNNEL_FILE = join(tmpdir(), 'i18next-cli-last-funnel-message-shown.json') // Store in OS temp dir
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Cooldown period in milliseconds before a funnel message can be shown again.
|
|
18
|
-
* Currently set to 24 hours.
|
|
19
|
-
*/
|
|
20
|
-
const TIP_COOLDOWN_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Determines whether a funnel message should be shown to the user.
|
|
24
|
-
*
|
|
25
|
-
* A funnel message will not be shown if:
|
|
26
|
-
* - It has already been shown in the current session (in-memory cache)
|
|
27
|
-
* - It was shown within the last 24 hours (persistent file cache)
|
|
28
|
-
*
|
|
29
|
-
* @param funnelMessage - The unique identifier for the funnel message
|
|
30
|
-
* @returns Promise that resolves to true if the message should be shown, false otherwise
|
|
31
|
-
*/
|
|
32
|
-
export async function shouldShowFunnel (funnelMessage: string): Promise<boolean> {
|
|
33
|
-
if (hasLocizeFunnelBeenShown[funnelMessage]) return false
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const content = await readFile(LAST_FUNNEL_FILE, 'utf-8')
|
|
37
|
-
const cnt: Record<string, number> = JSON.parse(content)
|
|
38
|
-
if (Date.now() - (cnt[funnelMessage] || 0) < TIP_COOLDOWN_MS) {
|
|
39
|
-
return false // Less than 24 hours since last shown
|
|
40
|
-
}
|
|
41
|
-
} catch (e) {
|
|
42
|
-
// File doesn't exist or is invalid, assume it's okay to show the tip
|
|
43
|
-
}
|
|
44
|
-
return true
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Records that a funnel message has been shown to the user.
|
|
49
|
-
*
|
|
50
|
-
* Updates both the in-memory cache and the persistent file cache with the current timestamp.
|
|
51
|
-
* This prevents the message from being shown again within the cooldown period.
|
|
52
|
-
*
|
|
53
|
-
* @param funnelMessage - The unique identifier for the funnel message that was shown
|
|
54
|
-
* @returns Promise that resolves when the record has been updated
|
|
55
|
-
*/
|
|
56
|
-
export async function recordFunnelShown (funnelMessage: string): Promise<void> {
|
|
57
|
-
try {
|
|
58
|
-
hasLocizeFunnelBeenShown[funnelMessage] = true
|
|
59
|
-
let data: Record<string, number> = {}
|
|
60
|
-
try {
|
|
61
|
-
const existing = await readFile(LAST_FUNNEL_FILE, 'utf-8')
|
|
62
|
-
data = JSON.parse(existing) as Record<string, number>
|
|
63
|
-
} catch (err) {
|
|
64
|
-
// ignore, we'll create a new file
|
|
65
|
-
}
|
|
66
|
-
data[funnelMessage] = Date.now()
|
|
67
|
-
await writeFile(LAST_FUNNEL_FILE, JSON.stringify(data))
|
|
68
|
-
} catch (e) {
|
|
69
|
-
// Ignore errors here, it's just a best-effort cache
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Resets the in-memory cache for a specific funnel message.
|
|
75
|
-
*
|
|
76
|
-
* This function is intended for testing purposes only. It clears the session cache
|
|
77
|
-
* but does not affect the persistent file cache.
|
|
78
|
-
*
|
|
79
|
-
* @param funnelMessage - The unique identifier for the funnel message to reset
|
|
80
|
-
*/
|
|
81
|
-
// just for the tests
|
|
82
|
-
export function reset (funnelMessage: string) {
|
|
83
|
-
delete hasLocizeFunnelBeenShown[funnelMessage]
|
|
84
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Logger } from '../types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Default console-based logger implementation for the i18next toolkit.
|
|
5
|
-
* Provides basic logging functionality with different severity levels.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* const logger = new ConsoleLogger()
|
|
10
|
-
* logger.info('Extraction started')
|
|
11
|
-
* logger.warn('Deprecated configuration option used')
|
|
12
|
-
* logger.error('Failed to parse file')
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export class ConsoleLogger implements Logger {
|
|
16
|
-
/**
|
|
17
|
-
* Logs an informational message to the console.
|
|
18
|
-
*
|
|
19
|
-
* @param message - The message to log
|
|
20
|
-
*/
|
|
21
|
-
info (message: string): void { console.log(message) }
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Logs a warning message to the console.
|
|
25
|
-
*
|
|
26
|
-
* @param message - The warning message to log
|
|
27
|
-
*/
|
|
28
|
-
warn (message: string): void { console.warn(message) }
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Logs an error message to the console.
|
|
32
|
-
*
|
|
33
|
-
* @param message - The error message to log
|
|
34
|
-
*/
|
|
35
|
-
error (message: string): void { console.error(message) }
|
|
36
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sets a nested value in an object using a key path and separator.
|
|
3
|
-
* Creates intermediate objects as needed.
|
|
4
|
-
*
|
|
5
|
-
* @param obj - The target object to modify
|
|
6
|
-
* @param path - The key path (e.g., 'user.profile.name')
|
|
7
|
-
* @param value - The value to set
|
|
8
|
-
* @param keySeparator - The separator to use for splitting the path, or false for flat keys
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* const obj = {}
|
|
13
|
-
* setNestedValue(obj, 'user.profile.name', 'John', '.')
|
|
14
|
-
* // Result: { user: { profile: { name: 'John' } } }
|
|
15
|
-
*
|
|
16
|
-
* // With flat keys
|
|
17
|
-
* setNestedValue(obj, 'user.name', 'Jane', false)
|
|
18
|
-
* // Result: { 'user.name': 'Jane' }
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function setNestedValue (
|
|
22
|
-
obj: Record<string, any>,
|
|
23
|
-
path: string,
|
|
24
|
-
value: any,
|
|
25
|
-
keySeparator: string | false
|
|
26
|
-
): void {
|
|
27
|
-
if (keySeparator === false) {
|
|
28
|
-
obj[path] = value
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const keys = path.split(keySeparator)
|
|
33
|
-
let current = obj
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < keys.length; i++) {
|
|
36
|
-
const key = keys[i]
|
|
37
|
-
const isLastKey = i === keys.length - 1
|
|
38
|
-
|
|
39
|
-
if (isLastKey) {
|
|
40
|
-
// We've reached the end of the path, set the value.
|
|
41
|
-
current[key] = value
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const nextLevel = current[key]
|
|
46
|
-
|
|
47
|
-
// Check for a conflict: the path requires an object, but a primitive exists.
|
|
48
|
-
if (nextLevel !== undefined && (typeof nextLevel !== 'object' || nextLevel === null)) {
|
|
49
|
-
// Conflict detected. The parent path is already a leaf node.
|
|
50
|
-
// We must set the entire original path as a flat key on the root object.
|
|
51
|
-
obj[path] = value
|
|
52
|
-
return // Stop processing to prevent overwriting the parent.
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// If the path doesn't exist, create an empty object to continue.
|
|
56
|
-
if (nextLevel === undefined) {
|
|
57
|
-
current[key] = {}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
current = current[key]
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Retrieves a nested value from an object using a key path and separator.
|
|
66
|
-
*
|
|
67
|
-
* @param obj - The object to search in
|
|
68
|
-
* @param path - The key path (e.g., 'user.profile.name')
|
|
69
|
-
* @param keySeparator - The separator to use for splitting the path, or false for flat keys
|
|
70
|
-
* @returns The found value or undefined if not found
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```typescript
|
|
74
|
-
* const obj = { user: { profile: { name: 'John' } } }
|
|
75
|
-
* const name = getNestedValue(obj, 'user.profile.name', '.')
|
|
76
|
-
* // Returns: 'John'
|
|
77
|
-
*
|
|
78
|
-
* // With flat keys
|
|
79
|
-
* const flatObj = { 'user.name': 'Jane' }
|
|
80
|
-
* const name = getNestedValue(flatObj, 'user.name', false)
|
|
81
|
-
* // Returns: 'Jane'
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export function getNestedValue (
|
|
85
|
-
obj: Record<string, any>,
|
|
86
|
-
path: string,
|
|
87
|
-
keySeparator: string | false
|
|
88
|
-
): any {
|
|
89
|
-
if (keySeparator === false) {
|
|
90
|
-
return obj[path]
|
|
91
|
-
}
|
|
92
|
-
return path.split(keySeparator).reduce((acc, key) => acc && acc[key], obj)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Extracts all nested keys from an object, optionally with a prefix.
|
|
97
|
-
* Recursively traverses the object structure to build a flat list of key paths.
|
|
98
|
-
*
|
|
99
|
-
* @param obj - The object to extract keys from
|
|
100
|
-
* @param keySeparator - The separator to use for joining keys, or false for flat keys
|
|
101
|
-
* @param prefix - Optional prefix to prepend to all keys
|
|
102
|
-
* @returns Array of all nested key paths
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```typescript
|
|
106
|
-
* const obj = {
|
|
107
|
-
* user: {
|
|
108
|
-
* profile: { name: 'John', age: 30 },
|
|
109
|
-
* settings: { theme: 'dark' }
|
|
110
|
-
* }
|
|
111
|
-
* }
|
|
112
|
-
*
|
|
113
|
-
* const keys = getNestedKeys(obj, '.')
|
|
114
|
-
* // Returns: ['user.profile.name', 'user.profile.age', 'user.settings.theme']
|
|
115
|
-
*
|
|
116
|
-
* // With flat keys
|
|
117
|
-
* const flatObj = { 'user.name': 'Jane', 'user.age': 25 }
|
|
118
|
-
* const flatKeys = getNestedKeys(flatObj, false)
|
|
119
|
-
* // Returns: ['user.name', 'user.age']
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
export function getNestedKeys (obj: object, keySeparator: string | false, prefix = ''): string[] {
|
|
123
|
-
if (keySeparator === false) {
|
|
124
|
-
return Object.keys(obj)
|
|
125
|
-
}
|
|
126
|
-
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
127
|
-
const newKey = prefix ? `${prefix}${keySeparator}${key}` : key
|
|
128
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
129
|
-
acc.push(...getNestedKeys(value, keySeparator, newKey))
|
|
130
|
-
} else {
|
|
131
|
-
acc.push(newKey)
|
|
132
|
-
}
|
|
133
|
-
return acc
|
|
134
|
-
}, [] as string[])
|
|
135
|
-
}
|
package/src/utils/validation.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { I18nextToolkitConfig } from '../types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validates the extractor configuration to ensure required fields are present and properly formatted.
|
|
5
|
-
*
|
|
6
|
-
* This function performs the following validations:
|
|
7
|
-
* - Ensures extract.input is specified and non-empty
|
|
8
|
-
* - Ensures extract.output is specified
|
|
9
|
-
* - Ensures locales array is specified and non-empty
|
|
10
|
-
* - Ensures extract.output contains the required {{language}} placeholder
|
|
11
|
-
*
|
|
12
|
-
* @param config - The i18next toolkit configuration object to validate
|
|
13
|
-
*
|
|
14
|
-
* @throws {ExtractorError} When any validation rule fails
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* try {
|
|
19
|
-
* validateExtractorConfig(config)
|
|
20
|
-
* console.log('Configuration is valid')
|
|
21
|
-
* } catch (error) {
|
|
22
|
-
* console.error('Invalid configuration:', error.message)
|
|
23
|
-
* }
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export function validateExtractorConfig (config: I18nextToolkitConfig): void {
|
|
27
|
-
if (!config.extract.input?.length) {
|
|
28
|
-
throw new ExtractorError('extract.input must be specified and non-empty')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!config.extract.output) {
|
|
32
|
-
throw new ExtractorError('extract.output must be specified')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!config.locales?.length) {
|
|
36
|
-
throw new ExtractorError('locales must be specified and non-empty')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// If output is a function, we accept it (user is responsible for producing a valid path).
|
|
40
|
-
if (typeof config.extract.output === 'string') {
|
|
41
|
-
if (!config.extract.output.includes('{{language}}') && !config.extract.output.includes('{{lng}}')) {
|
|
42
|
-
throw new ExtractorError('extract.output must contain {{language}} placeholder')
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Custom error class for extraction-related errors.
|
|
49
|
-
* Provides additional context like file path and underlying cause.
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```typescript
|
|
53
|
-
* throw new ExtractorError('Failed to parse file', 'src/component.tsx', syntaxError)
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export class ExtractorError extends Error {
|
|
57
|
-
/**
|
|
58
|
-
* Creates a new ExtractorError with optional file context and cause.
|
|
59
|
-
*
|
|
60
|
-
* @param message - The error message
|
|
61
|
-
* @param file - Optional file path where the error occurred
|
|
62
|
-
* @param cause - Optional underlying error that caused this error
|
|
63
|
-
*/
|
|
64
|
-
constructor (
|
|
65
|
-
message: string,
|
|
66
|
-
public readonly file?: string,
|
|
67
|
-
public readonly cause?: Error
|
|
68
|
-
) {
|
|
69
|
-
super(file ? `${message} in file ${file}` : message)
|
|
70
|
-
this.name = 'ExtractorError'
|
|
71
|
-
}
|
|
72
|
-
}
|