i18next-cli 1.34.0 → 1.35.0

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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/cli.js +271 -1
  3. package/dist/cjs/config.js +211 -1
  4. package/dist/cjs/extractor/core/ast-visitors.js +372 -1
  5. package/dist/cjs/extractor/core/extractor.js +245 -1
  6. package/dist/cjs/extractor/core/key-finder.js +132 -1
  7. package/dist/cjs/extractor/core/translation-manager.js +745 -1
  8. package/dist/cjs/extractor/parsers/ast-utils.js +85 -1
  9. package/dist/cjs/extractor/parsers/call-expression-handler.js +942 -1
  10. package/dist/cjs/extractor/parsers/comment-parser.js +375 -1
  11. package/dist/cjs/extractor/parsers/expression-resolver.js +362 -1
  12. package/dist/cjs/extractor/parsers/jsx-handler.js +492 -1
  13. package/dist/cjs/extractor/parsers/jsx-parser.js +355 -1
  14. package/dist/cjs/extractor/parsers/scope-manager.js +408 -1
  15. package/dist/cjs/extractor/plugin-manager.js +106 -1
  16. package/dist/cjs/heuristic-config.js +99 -1
  17. package/dist/cjs/index.js +28 -1
  18. package/dist/cjs/init.js +174 -1
  19. package/dist/cjs/linter.js +431 -1
  20. package/dist/cjs/locize.js +269 -1
  21. package/dist/cjs/migrator.js +196 -1
  22. package/dist/cjs/rename-key.js +354 -1
  23. package/dist/cjs/status.js +336 -1
  24. package/dist/cjs/syncer.js +120 -1
  25. package/dist/cjs/types-generator.js +165 -1
  26. package/dist/cjs/utils/default-value.js +43 -1
  27. package/dist/cjs/utils/file-utils.js +136 -1
  28. package/dist/cjs/utils/funnel-msg-tracker.js +75 -1
  29. package/dist/cjs/utils/logger.js +36 -1
  30. package/dist/cjs/utils/nested-object.js +124 -1
  31. package/dist/cjs/utils/validation.js +71 -1
  32. package/dist/esm/cli.js +269 -1
  33. package/dist/esm/config.js +206 -1
  34. package/dist/esm/extractor/core/ast-visitors.js +370 -1
  35. package/dist/esm/extractor/core/extractor.js +241 -1
  36. package/dist/esm/extractor/core/key-finder.js +130 -1
  37. package/dist/esm/extractor/core/translation-manager.js +743 -1
  38. package/dist/esm/extractor/parsers/ast-utils.js +80 -1
  39. package/dist/esm/extractor/parsers/call-expression-handler.js +940 -1
  40. package/dist/esm/extractor/parsers/comment-parser.js +373 -1
  41. package/dist/esm/extractor/parsers/expression-resolver.js +360 -1
  42. package/dist/esm/extractor/parsers/jsx-handler.js +490 -1
  43. package/dist/esm/extractor/parsers/jsx-parser.js +334 -1
  44. package/dist/esm/extractor/parsers/scope-manager.js +406 -1
  45. package/dist/esm/extractor/plugin-manager.js +103 -1
  46. package/dist/esm/heuristic-config.js +97 -1
  47. package/dist/esm/index.js +11 -1
  48. package/dist/esm/init.js +172 -1
  49. package/dist/esm/linter.js +425 -1
  50. package/dist/esm/locize.js +265 -1
  51. package/dist/esm/migrator.js +194 -1
  52. package/dist/esm/rename-key.js +352 -1
  53. package/dist/esm/status.js +334 -1
  54. package/dist/esm/syncer.js +118 -1
  55. package/dist/esm/types-generator.js +163 -1
  56. package/dist/esm/utils/default-value.js +41 -1
  57. package/dist/esm/utils/file-utils.js +131 -1
  58. package/dist/esm/utils/funnel-msg-tracker.js +72 -1
  59. package/dist/esm/utils/logger.js +34 -1
  60. package/dist/esm/utils/nested-object.js +120 -1
  61. package/dist/esm/utils/validation.js +68 -1
  62. package/package.json +2 -2
  63. package/types/extractor/core/ast-visitors.d.ts.map +1 -1
  64. package/types/extractor/parsers/call-expression-handler.d.ts +3 -2
  65. package/types/extractor/parsers/call-expression-handler.d.ts.map +1 -1
  66. package/types/locize.d.ts.map +1 -1
@@ -1 +1,43 @@
1
- "use strict";exports.resolveDefaultValue=function(t,e,r,u,n){if("function"==typeof t)try{return t(e,r,u,n||e)}catch(t){return""}return t||""};
1
+ 'use strict';
2
+
3
+ /**
4
+ * Resolves the default value for a missing key in secondary languages.
5
+ * Supports both string and function-based default values.
6
+ *
7
+ * @param defaultValue - The configured default value (string or function)
8
+ * @param key - The translation key
9
+ * @param namespace - The namespace for the key
10
+ * @param language - The target language
11
+ * @returns The resolved default value
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // String-based default value
16
+ * const result1 = resolveDefaultValue('[MISSING]', 'user.name', 'common', 'de', 'Alice')
17
+ * // Returns: '[MISSING]'
18
+ *
19
+ * // Function-based default value
20
+ * const defaultValueFn = (key, ns, lang) => `${lang.toUpperCase()}_${ns}_${key}`
21
+ * const result2 = resolveDefaultValue(defaultValueFn, 'user.name', 'common', 'de', 'Alice')
22
+ * // Returns: 'DE_common_user.name_Alice'
23
+ *
24
+ * // Error handling - function throws
25
+ * const errorFn = () => { throw new Error('Oops') }
26
+ * const result3 = resolveDefaultValue(errorFn, 'user.name', 'common', 'de', 'Alice')
27
+ * // Returns: '' (fallback to empty string)
28
+ * ```
29
+ */
30
+ function resolveDefaultValue(defaultValue, key, namespace, language, value) {
31
+ if (typeof defaultValue === 'function') {
32
+ try {
33
+ return defaultValue(key, namespace, language, value || key);
34
+ }
35
+ catch (error) {
36
+ // If the function throws an error, fall back to empty string
37
+ return '';
38
+ }
39
+ }
40
+ return defaultValue || '';
41
+ }
42
+
43
+ exports.resolveDefaultValue = resolveDefaultValue;
@@ -1 +1,136 @@
1
- "use strict";var e=require("node:fs/promises"),t=require("node:path"),r=require("jiti"),n=require("../config.js"),s=require("@croct/json5-parser");exports.getOutputPath=function(e,r,n){if(!e)return t.normalize(`locales/${r}/${n??"translation"}.json`);if("function"==typeof e)try{const s=String(e(r,n));return t.normalize(s.replace(/\/\/+/g,"/"))}catch{return t.normalize(`locales/${r}/${n??"translation"}.json`)}let s=String(e);return s=s.replace(/\{\{language\}\}|\{\{lng\}\}/g,r),s=null!=n?s.replace(/\{\{namespace\}\}/g,n):s.replace(/\/?\{\{namespace\}\}/g,""),s=s.replace(/\/\/+/g,"/"),t.normalize(s)},exports.loadRawJson5Content=async function(r){const n=t.resolve(process.cwd(),r);try{return await e.access(n),await e.readFile(n,"utf-8")}catch{return null}},exports.loadTranslationFile=async function(a){const o=t.resolve(process.cwd(),a);try{await e.access(o)}catch{return null}try{const a=t.extname(o).toLowerCase();if(".json5"===a){const t=await e.readFile(o,"utf-8");return s.JsonParser.parse(t,s.JsonObjectNode).toJSON()}if(".json"===a){const t=await e.readFile(o,"utf-8");return JSON.parse(t)}if(".ts"===a||".js"===a){const e=await n.getTsConfigAliases(),t=r.createJiti(process.cwd(),{alias:e,interopDefault:!0});return await t.import(o,{default:!0})}return null}catch(e){return console.warn(`Could not parse translation file ${a}:`,e),null}},exports.serializeTranslationFile=function(e,t="json",r=2,n){const a=JSON.stringify(e,null,r);switch(t){case"json5":if(n){const t=s.JsonParser.parse(n,s.JsonObjectNode);return t.update(e),t.toString({object:{indentationSize:Number(r)??2}})}return s.JsonParser.parse(a,s.JsonObjectNode).toString({object:{indentationSize:Number(r)??2}});case"js":case"js-esm":return`export default ${a};\n`;case"js-cjs":return`module.exports = ${a};\n`;case"ts":return`export default ${a} as const;\n`;default:return`${a}\n`}};
1
+ 'use strict';
2
+
3
+ var promises = require('node:fs/promises');
4
+ var node_path = require('node:path');
5
+ var jiti = require('jiti');
6
+ var config = require('../config.js');
7
+ var json5Parser = require('@croct/json5-parser');
8
+
9
+ /**
10
+ * Resolve an output template (string or function) into an actual path string.
11
+ *
12
+ * - If `outputTemplate` is a function, call it with (language, namespace)
13
+ * - If it's a string, replace placeholders:
14
+ * - {{language}} or {{lng}} -> language
15
+ * - {{namespace}} -> namespace (or removed if namespace is undefined)
16
+ * - Normalizes duplicate slashes and returns a platform-correct path.
17
+ */
18
+ function getOutputPath(outputTemplate, language, namespace) {
19
+ if (!outputTemplate) {
20
+ // Fallback to a sensible default
21
+ return node_path.normalize(`locales/${language}/${namespace ?? 'translation'}.json`);
22
+ }
23
+ if (typeof outputTemplate === 'function') {
24
+ try {
25
+ const result = String(outputTemplate(language, namespace));
26
+ return node_path.normalize(result.replace(/\/\/+/g, '/'));
27
+ }
28
+ catch {
29
+ // If user function throws, fallback to default path
30
+ return node_path.normalize(`locales/${language}/${namespace ?? 'translation'}.json`);
31
+ }
32
+ }
33
+ // It's a string template
34
+ let out = String(outputTemplate);
35
+ out = out.replace(/\{\{language\}\}|\{\{lng\}\}/g, language);
36
+ if (namespace !== undefined && namespace !== null) {
37
+ out = out.replace(/\{\{namespace\}\}/g, namespace);
38
+ }
39
+ else {
40
+ // remove any occurrences of /{{namespace}} or {{namespace}} (keeping surrounding slashes tidy)
41
+ out = out.replace(/\/?\{\{namespace\}\}/g, '');
42
+ }
43
+ // collapse duplicate slashes and normalize to platform-specific separators
44
+ out = out.replace(/\/\/+/g, '/');
45
+ return node_path.normalize(out);
46
+ }
47
+ /**
48
+ * Dynamically loads a translation file, supporting .json, .js, and .ts formats.
49
+ * @param filePath - The path to the translation file.
50
+ * @returns The parsed content of the file, or null if not found or failed to parse.
51
+ */
52
+ async function loadTranslationFile(filePath) {
53
+ const fullPath = node_path.resolve(process.cwd(), filePath);
54
+ try {
55
+ await promises.access(fullPath);
56
+ }
57
+ catch {
58
+ return null; // File doesn't exist
59
+ }
60
+ try {
61
+ const ext = node_path.extname(fullPath).toLowerCase();
62
+ if (ext === '.json5') {
63
+ const content = await promises.readFile(fullPath, 'utf-8');
64
+ // Parse as a JSON5 object node
65
+ const node = json5Parser.JsonParser.parse(content, json5Parser.JsonObjectNode);
66
+ return node.toJSON();
67
+ }
68
+ else if (ext === '.json') {
69
+ const content = await promises.readFile(fullPath, 'utf-8');
70
+ return JSON.parse(content);
71
+ }
72
+ else if (ext === '.ts' || ext === '.js') {
73
+ // Load TypeScript path aliases for proper module resolution
74
+ const aliases = await config.getTsConfigAliases();
75
+ const jiti$1 = jiti.createJiti(process.cwd(), {
76
+ alias: aliases,
77
+ interopDefault: true,
78
+ });
79
+ const module = await jiti$1.import(fullPath, { default: true });
80
+ return module;
81
+ }
82
+ return null; // Unsupported file type
83
+ }
84
+ catch (error) {
85
+ console.warn(`Could not parse translation file ${filePath}:`, error);
86
+ return null;
87
+ }
88
+ }
89
+ // Helper to load raw JSON5 content for preservation
90
+ async function loadRawJson5Content(filePath) {
91
+ const fullPath = node_path.resolve(process.cwd(), filePath);
92
+ try {
93
+ await promises.access(fullPath);
94
+ return await promises.readFile(fullPath, 'utf-8');
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Serializes a translation object into a string based on the desired format.
102
+ * For JSON5, preserves comments and formatting using JsonObjectNode.update().
103
+ */
104
+ function serializeTranslationFile(data, format = 'json', indentation = 2, rawContent // Pass raw content for JSON5 preservation
105
+ ) {
106
+ const jsonString = JSON.stringify(data, null, indentation);
107
+ switch (format) {
108
+ case 'json5': {
109
+ if (rawContent) {
110
+ // Parse the original JSON5 file, update it, and output as string
111
+ const node = json5Parser.JsonParser.parse(rawContent, json5Parser.JsonObjectNode);
112
+ node.update(data);
113
+ return node.toString({ object: { indentationSize: Number(indentation) ?? 2 } });
114
+ }
115
+ // Fallback: create a new node by parsing the generated JSON string and output as string
116
+ const node = json5Parser.JsonParser.parse(jsonString, json5Parser.JsonObjectNode);
117
+ return node.toString({ object: { indentationSize: Number(indentation) ?? 2 } });
118
+ }
119
+ case 'js':
120
+ case 'js-esm':
121
+ return `export default ${jsonString};\n`;
122
+ case 'js-cjs':
123
+ return `module.exports = ${jsonString};\n`;
124
+ case 'ts':
125
+ // Using `as const` provides better type inference for TypeScript users
126
+ return `export default ${jsonString} as const;\n`;
127
+ case 'json':
128
+ default:
129
+ return `${jsonString}\n`;
130
+ }
131
+ }
132
+
133
+ exports.getOutputPath = getOutputPath;
134
+ exports.loadRawJson5Content = loadRawJson5Content;
135
+ exports.loadTranslationFile = loadTranslationFile;
136
+ exports.serializeTranslationFile = serializeTranslationFile;
@@ -1 +1,75 @@
1
- "use strict";var e=require("node:path"),t=require("node:os"),n=require("node:fs/promises");const r={},i=e.join(t.tmpdir(),"i18next-cli-last-funnel-message-shown.json");exports.recordFunnelShown=async function(e){try{r[e]=!0;let t={};try{const e=await n.readFile(i,"utf-8");t=JSON.parse(e)}catch(e){}t[e]=Date.now(),await n.writeFile(i,JSON.stringify(t))}catch(e){}},exports.shouldShowFunnel=async function(e){if(r[e])return!1;try{const t=await n.readFile(i,"utf-8"),r=JSON.parse(t);if(Date.now()-(r[e]||0)<864e5)return!1}catch(e){}return!0};
1
+ 'use strict';
2
+
3
+ var node_path = require('node:path');
4
+ var node_os = require('node:os');
5
+ var promises = require('node:fs/promises');
6
+
7
+ /**
8
+ * In-memory cache to track which funnel messages have been shown in the current session.
9
+ */
10
+ const hasLocizeFunnelBeenShown = {};
11
+ /**
12
+ * Path to the persistent file that stores the last time each funnel message was shown.
13
+ * Stored in the OS temporary directory to persist across CLI sessions.
14
+ */
15
+ const LAST_FUNNEL_FILE = node_path.join(node_os.tmpdir(), 'i18next-cli-last-funnel-message-shown.json'); // Store in OS temp dir
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
+ * Determines whether a funnel message should be shown to the user.
23
+ *
24
+ * A funnel message will not be shown if:
25
+ * - It has already been shown in the current session (in-memory cache)
26
+ * - It was shown within the last 24 hours (persistent file cache)
27
+ *
28
+ * @param funnelMessage - The unique identifier for the funnel message
29
+ * @returns Promise that resolves to true if the message should be shown, false otherwise
30
+ */
31
+ async function shouldShowFunnel(funnelMessage) {
32
+ if (hasLocizeFunnelBeenShown[funnelMessage])
33
+ return false;
34
+ try {
35
+ const content = await promises.readFile(LAST_FUNNEL_FILE, 'utf-8');
36
+ const cnt = JSON.parse(content);
37
+ if (Date.now() - (cnt[funnelMessage] || 0) < TIP_COOLDOWN_MS) {
38
+ return false; // Less than 24 hours since last shown
39
+ }
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
+ * Records that a funnel message has been shown to the user.
48
+ *
49
+ * Updates both the in-memory cache and the persistent file cache with the current timestamp.
50
+ * This prevents the message from being shown again within the cooldown period.
51
+ *
52
+ * @param funnelMessage - The unique identifier for the funnel message that was shown
53
+ * @returns Promise that resolves when the record has been updated
54
+ */
55
+ async function recordFunnelShown(funnelMessage) {
56
+ try {
57
+ hasLocizeFunnelBeenShown[funnelMessage] = true;
58
+ let data = {};
59
+ try {
60
+ const existing = await promises.readFile(LAST_FUNNEL_FILE, 'utf-8');
61
+ data = JSON.parse(existing);
62
+ }
63
+ catch (err) {
64
+ // ignore, we'll create a new file
65
+ }
66
+ data[funnelMessage] = Date.now();
67
+ await promises.writeFile(LAST_FUNNEL_FILE, JSON.stringify(data));
68
+ }
69
+ catch (e) {
70
+ // Ignore errors here, it's just a best-effort cache
71
+ }
72
+ }
73
+
74
+ exports.recordFunnelShown = recordFunnelShown;
75
+ exports.shouldShowFunnel = shouldShowFunnel;
@@ -1 +1,36 @@
1
- "use strict";exports.ConsoleLogger=class{info(o){console.log(o)}warn(o){console.warn(o)}error(o){console.error(o)}};
1
+ 'use strict';
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
+ class ConsoleLogger {
16
+ /**
17
+ * Logs an informational message to the console.
18
+ *
19
+ * @param message - The message to log
20
+ */
21
+ info(message) { console.log(message); }
22
+ /**
23
+ * Logs a warning message to the console.
24
+ *
25
+ * @param message - The warning message to log
26
+ */
27
+ warn(message) { console.warn(message); }
28
+ /**
29
+ * Logs an error message to the console.
30
+ *
31
+ * @param message - The error message to log
32
+ */
33
+ error(message) { console.error(message); }
34
+ }
35
+
36
+ exports.ConsoleLogger = ConsoleLogger;
@@ -1 +1,124 @@
1
- "use strict";exports.getNestedKeys=function e(t,r,n=""){return!1===r?Object.keys(t):Object.entries(t).reduce((t,[s,o])=>{const u=n?`${n}${r}${s}`:s;return"object"!=typeof o||null===o||Array.isArray(o)?t.push(u):t.push(...e(o,r,u)),t},[])},exports.getNestedValue=function(e,t,r){return!1===r?e[t]:t.split(r).reduce((e,t)=>e&&e[t],e)},exports.setNestedValue=function(e,t,r,n){if(!1===n)return void(e[t]=r);const s=t.split(n);let o=e;for(let n=0;n<s.length;n++){const u=s[n];if(n===s.length-1)return void(o[u]=r);const i=o[u];if(void 0!==i&&("object"!=typeof i||null===i))return void(e[t]=r);void 0===i&&(o[u]={}),o=o[u]}};
1
+ 'use strict';
2
+
3
+ /**
4
+ * Sets a nested value in an object using a key path and separator.
5
+ * Creates intermediate objects as needed.
6
+ *
7
+ * @param obj - The target object to modify
8
+ * @param path - The key path (e.g., 'user.profile.name')
9
+ * @param value - The value to set
10
+ * @param keySeparator - The separator to use for splitting the path, or false for flat keys
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const obj = {}
15
+ * setNestedValue(obj, 'user.profile.name', 'John', '.')
16
+ * // Result: { user: { profile: { name: 'John' } } }
17
+ *
18
+ * // With flat keys
19
+ * setNestedValue(obj, 'user.name', 'Jane', false)
20
+ * // Result: { 'user.name': 'Jane' }
21
+ * ```
22
+ */
23
+ function setNestedValue(obj, path, value, keySeparator) {
24
+ if (keySeparator === false) {
25
+ obj[path] = value;
26
+ return;
27
+ }
28
+ const keys = path.split(keySeparator);
29
+ let current = obj;
30
+ for (let i = 0; i < keys.length; i++) {
31
+ const key = keys[i];
32
+ const isLastKey = i === keys.length - 1;
33
+ if (isLastKey) {
34
+ // We've reached the end of the path, set the value.
35
+ current[key] = value;
36
+ return;
37
+ }
38
+ const nextLevel = current[key];
39
+ // Check for a conflict: the path requires an object, but a primitive exists.
40
+ if (nextLevel !== undefined && (typeof nextLevel !== 'object' || nextLevel === null)) {
41
+ // Conflict detected. The parent path is already a leaf node.
42
+ // We must set the entire original path as a flat key on the root object.
43
+ obj[path] = value;
44
+ return; // Stop processing to prevent overwriting the parent.
45
+ }
46
+ // If the path doesn't exist, create an empty object to continue.
47
+ if (nextLevel === undefined) {
48
+ current[key] = {};
49
+ }
50
+ current = current[key];
51
+ }
52
+ }
53
+ /**
54
+ * Retrieves a nested value from an object using a key path and separator.
55
+ *
56
+ * @param obj - The object to search in
57
+ * @param path - The key path (e.g., 'user.profile.name')
58
+ * @param keySeparator - The separator to use for splitting the path, or false for flat keys
59
+ * @returns The found value or undefined if not found
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const obj = { user: { profile: { name: 'John' } } }
64
+ * const name = getNestedValue(obj, 'user.profile.name', '.')
65
+ * // Returns: 'John'
66
+ *
67
+ * // With flat keys
68
+ * const flatObj = { 'user.name': 'Jane' }
69
+ * const name = getNestedValue(flatObj, 'user.name', false)
70
+ * // Returns: 'Jane'
71
+ * ```
72
+ */
73
+ function getNestedValue(obj, path, keySeparator) {
74
+ if (keySeparator === false) {
75
+ return obj[path];
76
+ }
77
+ return path.split(keySeparator).reduce((acc, key) => acc && acc[key], obj);
78
+ }
79
+ /**
80
+ * Extracts all nested keys from an object, optionally with a prefix.
81
+ * Recursively traverses the object structure to build a flat list of key paths.
82
+ *
83
+ * @param obj - The object to extract keys from
84
+ * @param keySeparator - The separator to use for joining keys, or false for flat keys
85
+ * @param prefix - Optional prefix to prepend to all keys
86
+ * @returns Array of all nested key paths
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const obj = {
91
+ * user: {
92
+ * profile: { name: 'John', age: 30 },
93
+ * settings: { theme: 'dark' }
94
+ * }
95
+ * }
96
+ *
97
+ * const keys = getNestedKeys(obj, '.')
98
+ * // Returns: ['user.profile.name', 'user.profile.age', 'user.settings.theme']
99
+ *
100
+ * // With flat keys
101
+ * const flatObj = { 'user.name': 'Jane', 'user.age': 25 }
102
+ * const flatKeys = getNestedKeys(flatObj, false)
103
+ * // Returns: ['user.name', 'user.age']
104
+ * ```
105
+ */
106
+ function getNestedKeys(obj, keySeparator, prefix = '') {
107
+ if (keySeparator === false) {
108
+ return Object.keys(obj);
109
+ }
110
+ return Object.entries(obj).reduce((acc, [key, value]) => {
111
+ const newKey = prefix ? `${prefix}${keySeparator}${key}` : key;
112
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
113
+ acc.push(...getNestedKeys(value, keySeparator, newKey));
114
+ }
115
+ else {
116
+ acc.push(newKey);
117
+ }
118
+ return acc;
119
+ }, []);
120
+ }
121
+
122
+ exports.getNestedKeys = getNestedKeys;
123
+ exports.getNestedValue = getNestedValue;
124
+ exports.setNestedValue = setNestedValue;
@@ -1 +1,71 @@
1
- "use strict";class t extends Error{file;cause;constructor(t,e,r){super(e?`${t} in file ${e}`:t),this.file=e,this.cause=r,this.name="ExtractorError"}}exports.ExtractorError=t,exports.validateExtractorConfig=function(e){if(!e.extract.input?.length)throw new t("extract.input must be specified and non-empty");if(!e.extract.output)throw new t("extract.output must be specified");if(!e.locales?.length)throw new t("locales must be specified and non-empty");if("string"==typeof e.extract.output&&!e.extract.output.includes("{{language}}")&&!e.extract.output.includes("{{lng}}"))throw new t("extract.output must contain {{language}} placeholder")};
1
+ 'use strict';
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
+ function validateExtractorConfig(config) {
27
+ if (!config.extract.input?.length) {
28
+ throw new ExtractorError('extract.input must be specified and non-empty');
29
+ }
30
+ if (!config.extract.output) {
31
+ throw new ExtractorError('extract.output must be specified');
32
+ }
33
+ if (!config.locales?.length) {
34
+ throw new ExtractorError('locales must be specified and non-empty');
35
+ }
36
+ // If output is a function, we accept it (user is responsible for producing a valid path).
37
+ if (typeof config.extract.output === 'string') {
38
+ if (!config.extract.output.includes('{{language}}') && !config.extract.output.includes('{{lng}}')) {
39
+ throw new ExtractorError('extract.output must contain {{language}} placeholder');
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * Custom error class for extraction-related errors.
45
+ * Provides additional context like file path and underlying cause.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * throw new ExtractorError('Failed to parse file', 'src/component.tsx', syntaxError)
50
+ * ```
51
+ */
52
+ class ExtractorError extends Error {
53
+ file;
54
+ cause;
55
+ /**
56
+ * Creates a new ExtractorError with optional file context and cause.
57
+ *
58
+ * @param message - The error message
59
+ * @param file - Optional file path where the error occurred
60
+ * @param cause - Optional underlying error that caused this error
61
+ */
62
+ constructor(message, file, cause) {
63
+ super(file ? `${message} in file ${file}` : message);
64
+ this.file = file;
65
+ this.cause = cause;
66
+ this.name = 'ExtractorError';
67
+ }
68
+ }
69
+
70
+ exports.ExtractorError = ExtractorError;
71
+ exports.validateExtractorConfig = validateExtractorConfig;