locai 2.6.2 → 2.7.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 (131) hide show
  1. package/README.md +23 -25
  2. package/dist/cli/helpers.d.ts +34 -2
  3. package/dist/cli/helpers.js +96 -37
  4. package/dist/cli.js +40 -39
  5. package/dist/commands/review.d.ts +33 -8
  6. package/dist/commands/review.js +89 -22
  7. package/dist/commands/translator.d.ts +4 -2
  8. package/dist/commands/translator.js +1 -1
  9. package/dist/config/index.d.ts +53 -66
  10. package/dist/config/index.js +2 -1
  11. package/dist/config/setup.d.ts +3 -2
  12. package/dist/config/setup.js +5 -3
  13. package/dist/core/adapters/android-adapter.d.ts +3 -2
  14. package/dist/core/adapters/android-adapter.js +12 -13
  15. package/dist/core/adapters/base-adapter.d.ts +13 -2
  16. package/dist/core/adapters/ios-adapter.d.ts +3 -2
  17. package/dist/core/adapters/json-adapter.d.ts +4 -3
  18. package/dist/core/adapters/json-adapter.js +1 -3
  19. package/dist/core/adapters/po-adapter.d.ts +3 -2
  20. package/dist/core/adapters/po-adapter.js +4 -5
  21. package/dist/core/adapters/properties-adapter.d.ts +3 -2
  22. package/dist/core/adapters/properties-adapter.js +2 -4
  23. package/dist/core/adapters/yaml-adapter.d.ts +4 -3
  24. package/dist/core/adapters/yaml-adapter.js +1 -3
  25. package/dist/core/context-processor.d.ts +21 -6
  26. package/dist/core/context-processor.js +2 -7
  27. package/dist/core/detection/framework-detector.js +1 -1
  28. package/dist/core/detection/structure-detector.js +1 -1
  29. package/dist/core/fallback-provider.d.ts +3 -4
  30. package/dist/core/fallback-provider.js +15 -21
  31. package/dist/core/orchestrator.d.ts +98 -26
  32. package/dist/core/orchestrator.js +56 -116
  33. package/dist/core/pipeline/Pipeline.d.ts +2 -2
  34. package/dist/core/pipeline/Pipeline.js +1 -3
  35. package/dist/core/pipeline/context.d.ts +69 -13
  36. package/dist/core/pipeline/context.js +1 -7
  37. package/dist/core/pipeline/steps/CacheReadStep.d.ts +10 -7
  38. package/dist/core/pipeline/steps/CacheReadStep.js +1 -12
  39. package/dist/core/pipeline/steps/CacheWriteStep.d.ts +2 -2
  40. package/dist/core/pipeline/steps/CacheWriteStep.js +1 -8
  41. package/dist/core/pipeline/steps/ConfidenceCheckStep.d.ts +5 -0
  42. package/dist/core/pipeline/steps/ConfidenceCheckStep.js +24 -44
  43. package/dist/core/pipeline/steps/ContextEnrichmentStep.js +3 -14
  44. package/dist/core/pipeline/steps/QualityCheckStep.d.ts +1 -1
  45. package/dist/core/pipeline/steps/QualityCheckStep.js +0 -2
  46. package/dist/core/pipeline/steps/ToneCheckStep.js +1 -1
  47. package/dist/core/pipeline/steps/TranslationStep.d.ts +13 -3
  48. package/dist/core/pipeline/steps/TranslationStep.js +21 -23
  49. package/dist/core/pipeline/steps/VectorCacheReadStep.d.ts +9 -1
  50. package/dist/core/pipeline/steps/VectorCacheReadStep.js +3 -5
  51. package/dist/core/pipeline/steps/VectorCacheWriteStep.d.ts +2 -1
  52. package/dist/core/pipeline/steps/VectorCacheWriteStep.js +2 -1
  53. package/dist/core/provider-factory.d.ts +15 -9
  54. package/dist/core/provider-factory.js +42 -32
  55. package/dist/providers/anthropic.d.ts +5 -4
  56. package/dist/providers/anthropic.js +3 -2
  57. package/dist/providers/api-types.d.ts +74 -0
  58. package/dist/providers/api-types.js +5 -0
  59. package/dist/providers/base-provider.d.ts +27 -31
  60. package/dist/providers/base-provider.js +22 -46
  61. package/dist/providers/dashscope.js +0 -2
  62. package/dist/providers/deepseek.js +0 -1
  63. package/dist/providers/gemini.js +0 -1
  64. package/dist/providers/openai.d.ts +3 -3
  65. package/dist/providers/openai.js +0 -1
  66. package/dist/providers/xai.js +0 -1
  67. package/dist/services/embedding-provider.js +4 -2
  68. package/dist/services/language-processor.d.ts +5 -3
  69. package/dist/services/language-processor.js +38 -43
  70. package/dist/services/source-analyzer.d.ts +24 -0
  71. package/dist/services/source-analyzer.js +161 -68
  72. package/dist/services/translation-service.d.ts +31 -13
  73. package/dist/services/translation-service.js +116 -71
  74. package/dist/services/vector-store.d.ts +15 -2
  75. package/dist/services/vector-store.js +14 -10
  76. package/dist/types/index.d.ts +31 -33
  77. package/dist/utils/ai-context-analyzer.d.ts +12 -3
  78. package/dist/utils/ai-context-analyzer.js +18 -10
  79. package/dist/utils/confidence-scorer.d.ts +16 -19
  80. package/dist/utils/confidence-scorer.js +36 -125
  81. package/dist/utils/error-helper.d.ts +27 -6
  82. package/dist/utils/error-helper.js +10 -9
  83. package/dist/utils/file-manager.d.ts +8 -7
  84. package/dist/utils/file-manager.js +22 -23
  85. package/dist/utils/glossary-manager.d.ts +22 -3
  86. package/dist/utils/glossary-manager.js +47 -28
  87. package/dist/utils/graceful-shutdown.d.ts +7 -1
  88. package/dist/utils/graceful-shutdown.js +1 -2
  89. package/dist/utils/input-validator.d.ts +53 -66
  90. package/dist/utils/input-validator.js +100 -151
  91. package/dist/utils/locale-paths.d.ts +7 -0
  92. package/dist/utils/locale-paths.js +16 -0
  93. package/dist/utils/logger.d.ts +8 -8
  94. package/dist/utils/logger.js +7 -15
  95. package/dist/utils/object-transformer.d.ts +20 -5
  96. package/dist/utils/object-transformer.js +20 -28
  97. package/dist/utils/progress-tracker.js +2 -3
  98. package/dist/utils/prompt-templates.d.ts +45 -5
  99. package/dist/utils/prompt-templates.js +61 -5
  100. package/dist/utils/quality/base-checker.d.ts +40 -18
  101. package/dist/utils/quality/base-checker.js +1 -2
  102. package/dist/utils/quality/code-block-checker.d.ts +13 -5
  103. package/dist/utils/quality/index.d.ts +2 -1
  104. package/dist/utils/quality/index.js +8 -27
  105. package/dist/utils/quality/length-checker.d.ts +35 -5
  106. package/dist/utils/quality/length-checker.js +2 -3
  107. package/dist/utils/quality/markdown-checker.d.ts +14 -7
  108. package/dist/utils/quality/special-characters-checker.d.ts +18 -1
  109. package/dist/utils/quality/special-characters-checker.js +0 -1
  110. package/dist/utils/quality/style-guide-checker.d.ts +1 -1
  111. package/dist/utils/quality/style-guide-checker.js +0 -2
  112. package/dist/utils/quality/tone-verifier.js +1 -1
  113. package/dist/utils/rate-limiter.d.ts +17 -1
  114. package/dist/utils/rate-limiter.js +12 -11
  115. package/dist/utils/retry-helper.d.ts +21 -3
  116. package/dist/utils/retry-helper.js +11 -3
  117. package/dist/utils/review-queue.d.ts +45 -0
  118. package/dist/utils/review-queue.js +53 -0
  119. package/dist/utils/state-manager.d.ts +12 -3
  120. package/dist/utils/state-manager.js +32 -7
  121. package/dist/utils/statistics-manager.d.ts +11 -1
  122. package/dist/utils/statistics-manager.js +8 -0
  123. package/dist/utils/ui-manager.d.ts +2 -1
  124. package/dist/utils/ui-manager.js +15 -1
  125. package/package.json +14 -9
  126. package/dist/commands/interactive-review.d.ts +0 -9
  127. package/dist/commands/interactive-review.js +0 -109
  128. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.d.ts +0 -1
  129. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +0 -100
  130. package/dist/utils/quality/__tests__/tone-verifier.test.d.ts +0 -1
  131. package/dist/utils/quality/__tests__/tone-verifier.test.js +0 -91
@@ -1,4 +1,5 @@
1
- interface ReviewItem {
1
+ /** A queued translation as the review reads it: the fields of a ReviewQueueItem it uses. */
2
+ export interface ReviewItem {
2
3
  key: string;
3
4
  language: string;
4
5
  source: string;
@@ -16,10 +17,19 @@ interface ReviewItem {
16
17
  edited?: boolean;
17
18
  editedAt?: string;
18
19
  }
19
- interface ReviewCommandConfig {
20
+ export interface ReviewDecisions {
21
+ accepted: ReviewItem[];
22
+ edited: ReviewItem[];
23
+ rejected: ReviewItem[];
24
+ skipped: ReviewItem[];
25
+ }
26
+ export interface ReviewCommandConfig {
20
27
  localesDir: string;
21
28
  source: string;
22
- [key: string]: any;
29
+ }
30
+ /** Nested locale file content: leaves are translations, branches are key segments. */
31
+ export interface LocaleTree {
32
+ [key: string]: string | LocaleTree;
23
33
  }
24
34
  declare class ReviewCommand {
25
35
  private config;
@@ -35,6 +45,18 @@ declare class ReviewCommand {
35
45
  * @returns {boolean} - True if loaded successfully, else false.
36
46
  */
37
47
  loadReviewQueue(): boolean;
48
+ /**
49
+ * The locale file `locai translate` writes `language` to. As in
50
+ * FileManager.findLocaleFiles, a `<localesDir>/<source>/translation.json` source means the
51
+ * nested layout, anything else the flat `<localesDir>/<language>.json` one.
52
+ */
53
+ private localeFile;
54
+ /**
55
+ * Remove the reviewed items from the review queue: the rejected ones, and the accepted or
56
+ * edited ones whose translation is now in the locale file. Skipped items, items the session
57
+ * did not reach and items applyDecisions did not write stay queued.
58
+ */
59
+ private pruneReviewQueue;
38
60
  /**
39
61
  * Start interactive review session.
40
62
  */
@@ -44,7 +66,7 @@ declare class ReviewCommand {
44
66
  */
45
67
  private handleAction;
46
68
  /**
47
- * Save review decisions to disk and apply them.
69
+ * Save review decisions to disk, apply them and remove the reviewed items from the queue.
48
70
  */
49
71
  saveDecisions(): void;
50
72
  /**
@@ -52,12 +74,15 @@ declare class ReviewCommand {
52
74
  */
53
75
  applyDecisions(): void;
54
76
  /**
55
- * Set nested value in object using dot notation.
56
- * @param {any} obj - Object to modify.
77
+ * Set nested value in object using dot notation. Missing parents are created; a parent
78
+ * that already holds a non-object value (e.g. a string translation) is never overwritten,
79
+ * and neither is a leaf that already holds nested keys.
80
+ * @param {LocaleTree} obj - Object to modify.
57
81
  * @param {string} path - Dot notation path.
58
- * @param {any} value - Value to set.
82
+ * @param {string} value - Value to set.
83
+ * @returns {string | null} - Why the value could not be set, or null when it was set.
59
84
  */
60
- setNestedValue(obj: any, path: string, value: any): void;
85
+ setNestedValue(obj: LocaleTree, path: string, value: string): string | null;
61
86
  /**
62
87
  * Export review queue to JSON.
63
88
  * @param {string} format - Export format (json or csv).
@@ -2,6 +2,8 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import prompts from "prompts";
4
4
  import ConfidenceScorer from "../utils/confidence-scorer.js";
5
+ import { targetLocaleFile } from "../utils/locale-paths.js";
6
+ import { readReviewQueue, removeFromReviewQueue } from "../utils/review-queue.js";
5
7
  class ReviewCommand {
6
8
  config;
7
9
  reviewQueue;
@@ -25,22 +27,60 @@ class ReviewCommand {
25
27
  * @returns {boolean} - True if loaded successfully, else false.
26
28
  */
27
29
  loadReviewQueue() {
28
- const reviewFile = path.join(process.cwd(), ".localize-cache", "review-queue.json");
29
- if (!fs.existsSync(reviewFile)) {
30
- console.log("\nNo review queue found.");
31
- console.log(" Run translation with confidence scoring first: localize translate --min-confidence 0.8");
32
- return false;
33
- }
34
30
  try {
35
- const data = JSON.parse(fs.readFileSync(reviewFile, "utf8"));
36
- this.reviewQueue = data.items || [];
31
+ const items = readReviewQueue();
32
+ if (!items) {
33
+ console.log("\nNo review queue found.");
34
+ console.log(" Run a translation that saves one first: locai translate --save-review-queue");
35
+ return false;
36
+ }
37
+ this.reviewQueue = items;
37
38
  return true;
38
39
  }
39
40
  catch (error) {
40
- console.error(`Error loading review queue: ${error.message}`);
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ console.error(`Error loading review queue: ${message}`);
41
43
  return false;
42
44
  }
43
45
  }
46
+ /**
47
+ * The locale file `locai translate` writes `language` to. As in
48
+ * FileManager.findLocaleFiles, a `<localesDir>/<source>/translation.json` source means the
49
+ * nested layout, anything else the flat `<localesDir>/<language>.json` one.
50
+ */
51
+ localeFile(language) {
52
+ const { localesDir, source } = this.config;
53
+ const nestedSource = path.join(localesDir, source, "translation.json");
54
+ const sourceFile = fs.existsSync(nestedSource)
55
+ ? nestedSource
56
+ : path.join(localesDir, `${source}.json`);
57
+ return targetLocaleFile(sourceFile, language);
58
+ }
59
+ /**
60
+ * Remove the reviewed items from the review queue: the rejected ones, and the accepted or
61
+ * edited ones whose translation is now in the locale file. Skipped items, items the session
62
+ * did not reach and items applyDecisions did not write stay queued.
63
+ */
64
+ pruneReviewQueue() {
65
+ const localeTrees = new Map();
66
+ const isWritten = (item) => {
67
+ let tree = localeTrees.get(item.language);
68
+ if (!tree) {
69
+ const localeFile = this.localeFile(item.language);
70
+ tree = fs.existsSync(localeFile)
71
+ ? JSON.parse(fs.readFileSync(localeFile, "utf8"))
72
+ : {};
73
+ localeTrees.set(item.language, tree);
74
+ }
75
+ let value = tree;
76
+ for (const segment of item.key.split(".")) {
77
+ value = value !== null && typeof value === "object" ? value[segment] : undefined;
78
+ }
79
+ return value === item.translation;
80
+ };
81
+ const written = [...this.decisions.accepted, ...this.decisions.edited].filter(isWritten);
82
+ removeFromReviewQueue([...written, ...this.decisions.rejected]);
83
+ }
44
84
  /**
45
85
  * Start interactive review session.
46
86
  */
@@ -108,7 +148,7 @@ class ReviewCommand {
108
148
  }
109
149
  }
110
150
  /**
111
- * Save review decisions to disk and apply them.
151
+ * Save review decisions to disk, apply them and remove the reviewed items from the queue.
112
152
  */
113
153
  saveDecisions() {
114
154
  const cacheDir = path.join(process.cwd(), ".localize-cache");
@@ -128,8 +168,9 @@ class ReviewCommand {
128
168
  };
129
169
  fs.writeFileSync(decisionsFile, JSON.stringify(data, null, 2));
130
170
  console.log(`\nReview complete! Decisions saved to ${decisionsFile}`);
131
- // Apply accepted and edited translations
171
+ // Apply accepted and edited translations, then drop the reviewed items from the queue
132
172
  this.applyDecisions();
173
+ this.pruneReviewQueue();
133
174
  }
134
175
  /**
135
176
  * Apply accepted and edited translations to locale files.
@@ -148,38 +189,64 @@ class ReviewCommand {
148
189
  }
149
190
  byLanguage[item.language].push(item);
150
191
  });
192
+ let skipped = 0;
151
193
  Object.entries(byLanguage).forEach(([lang, items]) => {
152
- const localeFile = path.join(this.config.localesDir, `${lang}.json`);
153
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
+ const localeFile = this.localeFile(lang);
154
195
  let localeData = {};
155
196
  if (fs.existsSync(localeFile)) {
156
197
  localeData = JSON.parse(fs.readFileSync(localeFile, "utf8"));
157
198
  }
199
+ let applied = 0;
158
200
  items.forEach((item) => {
159
- this.setNestedValue(localeData, item.key, item.translation);
201
+ const conflict = this.setNestedValue(localeData, item.key, item.translation);
202
+ if (conflict) {
203
+ skipped++;
204
+ console.warn(` Skipped "${item.key}" for ${lang}: ${conflict}; the existing value was kept`);
205
+ }
206
+ else {
207
+ applied++;
208
+ }
160
209
  });
210
+ if (applied === 0)
211
+ return;
212
+ fs.mkdirSync(path.dirname(localeFile), { recursive: true });
161
213
  fs.writeFileSync(localeFile, JSON.stringify(localeData, null, 2) + "\n");
162
- console.log(` Updated ${lang}.json (${items.length} translations)`);
214
+ console.log(` Updated ${path.relative(process.cwd(), localeFile)} (${applied} translations)`);
163
215
  });
164
- console.log("\nAll approved translations applied!");
216
+ console.log(skipped === 0
217
+ ? "\nAll approved translations applied!"
218
+ : `\nApplied ${toApply.length - skipped} approved translations; skipped ${skipped} (see warnings above).`);
165
219
  }
166
220
  /**
167
- * Set nested value in object using dot notation.
168
- * @param {any} obj - Object to modify.
221
+ * Set nested value in object using dot notation. Missing parents are created; a parent
222
+ * that already holds a non-object value (e.g. a string translation) is never overwritten,
223
+ * and neither is a leaf that already holds nested keys.
224
+ * @param {LocaleTree} obj - Object to modify.
169
225
  * @param {string} path - Dot notation path.
170
- * @param {any} value - Value to set.
226
+ * @param {string} value - Value to set.
227
+ * @returns {string | null} - Why the value could not be set, or null when it was set.
171
228
  */
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
229
  setNestedValue(obj, path, value) {
174
230
  const keys = path.split(".");
175
231
  let current = obj;
176
232
  for (let i = 0; i < keys.length - 1; i++) {
177
- if (!current[keys[i]]) {
233
+ const next = current[keys[i]];
234
+ if (next === undefined) {
178
235
  current[keys[i]] = {};
179
236
  }
237
+ else if (next === null || typeof next !== "object" || Array.isArray(next)) {
238
+ const kind = next === null ? "null" : Array.isArray(next) ? "an array" : `a ${typeof next}`;
239
+ return `"${keys.slice(0, i + 1).join(".")}" already holds ${kind}, not nested keys`;
240
+ }
180
241
  current = current[keys[i]];
181
242
  }
182
- current[keys[keys.length - 1]] = value;
243
+ const leaf = keys[keys.length - 1];
244
+ const existing = current[leaf];
245
+ if (existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
246
+ return `"${path}" already holds nested keys, not a translation`;
247
+ }
248
+ current[leaf] = value;
249
+ return null;
183
250
  }
184
251
  /**
185
252
  * Export review queue to JSON.
@@ -1,4 +1,6 @@
1
+ import { FixSummary } from "../services/translation-service.js";
1
2
  import { GlobalStats, TranslationOptions } from "../types/index.js";
3
+ export type { FixSummary };
2
4
  /**
3
5
  * Main translator function to process source file and create translations.
4
6
  * @param {string} file - Source file path.
@@ -17,7 +19,7 @@ declare function findLocaleFiles(localesDir: string, sourceLang: string): Promis
17
19
  * Validate and fix existing translations that have length issues.
18
20
  * @param {string} file - File path to validate.
19
21
  * @param {TranslationOptions} options - Validation options.
20
- * @returns {Promise<void>}
22
+ * @returns {Promise<FixSummary>} - How many length issues were found and fixed.
21
23
  */
22
- declare function validateAndFixExistingTranslations(file: string, options: TranslationOptions): Promise<void>;
24
+ declare function validateAndFixExistingTranslations(file: string, options: TranslationOptions): Promise<FixSummary>;
23
25
  export { translateFile, findLocaleFiles, validateAndFixExistingTranslations };
@@ -21,7 +21,7 @@ async function findLocaleFiles(localesDir, sourceLang) {
21
21
  * Validate and fix existing translations that have length issues.
22
22
  * @param {string} file - File path to validate.
23
23
  * @param {TranslationOptions} options - Validation options.
24
- * @returns {Promise<void>}
24
+ * @returns {Promise<FixSummary>} - How many length issues were found and fixed.
25
25
  */
26
26
  async function validateAndFixExistingTranslations(file, options) {
27
27
  return await translationService.validateAndFixExistingTranslations(file, options);
@@ -1,6 +1,31 @@
1
- import { ConfigLayer } from "c12";
2
- import { OrchestratorOptions } from "../core/orchestrator.js";
3
- export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "context"> {
1
+ import type { OrchestratorOptions, ProviderRetryOptions } from "../core/orchestrator.js";
2
+ import type { ContextConfig } from "../core/context-processor.js";
3
+ import type { ProviderConfig } from "../providers/base-provider.js";
4
+ import type { SyncOptions } from "../types/index.js";
5
+ import type { LoggerConfig } from "../utils/logger.js";
6
+ /** The `context` section as written in a config file; {@link loadConfig} fills in the rest. */
7
+ export interface ContextConfigInput extends Partial<Omit<ContextConfig, "detection" | "fallback">> {
8
+ detection?: Partial<ContextConfig["detection"]>;
9
+ fallback?: Partial<ContextConfig["fallback"]>;
10
+ }
11
+ /** The `fileOperations` configuration section. */
12
+ export interface FileOperationsConfig {
13
+ /**
14
+ * File format to use (json, yaml, po, properties, etc.)
15
+ * @default "json"
16
+ */
17
+ format?: "json" | "yaml" | "po" | "properties" | "arb" | "auto";
18
+ /**
19
+ * File structure (flat, nested, namespaced)
20
+ * @default "flat"
21
+ */
22
+ fileStructure?: "flat" | "nested" | "namespaced" | "auto";
23
+ backupFiles?: boolean;
24
+ backupDir?: string;
25
+ atomic?: boolean;
26
+ createMissingDirs?: boolean;
27
+ }
28
+ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "context" | "sourceCodeAnalyzer"> {
4
29
  /**
5
30
  * Source language code (e.g. 'en')
6
31
  */
@@ -10,8 +35,9 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
10
35
  */
11
36
  targets?: string[];
12
37
  /**
13
- * Deep Context (Source Code Analysis)
14
- * Scans source code for context (component name, comments, props)
38
+ * Deep Context (Source Code Analysis): find where each key is used in the `.ts`/`.tsx` files
39
+ * under `src/`, `app/` and `components/`, and add the file, component, comments, JSX props and
40
+ * a code snippet of that usage to the translation prompt.
15
41
  * @default true
16
42
  */
17
43
  deepContext?: boolean;
@@ -42,16 +68,7 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
42
68
  /**
43
69
  * Synchronization options
44
70
  */
45
- syncOptions?: {
46
- enabled?: boolean;
47
- removeDeletedKeys?: boolean;
48
- retranslateModified?: boolean;
49
- stateTracking?: {
50
- enabled?: boolean;
51
- stateFileName?: string;
52
- stateDir?: string;
53
- };
54
- };
71
+ syncOptions?: SyncOptions;
55
72
  /**
56
73
  * Concurrency limit for parallel translations
57
74
  * @default 5
@@ -66,22 +83,7 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
66
83
  * Minimum confidence threshold (0-1)
67
84
  */
68
85
  minConfidence?: number;
69
- fileOperations?: {
70
- /**
71
- * File format to use (json, yaml, po, properties, etc.)
72
- * @default "json"
73
- */
74
- format?: "json" | "yaml" | "po" | "properties" | "arb" | "auto";
75
- /**
76
- * File structure (flat, nested, namespaced)
77
- * @default "flat"
78
- */
79
- fileStructure?: "flat" | "nested" | "namespaced" | "auto";
80
- backupFiles?: boolean;
81
- backupDir?: string;
82
- atomic?: boolean;
83
- createMissingDirs?: boolean;
84
- };
86
+ fileOperations?: FileOperationsConfig;
85
87
  useAi?: boolean;
86
88
  contextProvider?: string;
87
89
  contextThreshold?: number;
@@ -101,42 +103,27 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
101
103
  force?: boolean;
102
104
  verbose?: boolean;
103
105
  version?: string;
104
- rateLimiter?: {
105
- enabled?: boolean;
106
- queueStrategy?: string;
107
- queueTimeout?: number;
108
- adaptiveThrottling?: boolean;
109
- providerLimits?: any;
110
- };
111
- context?: {
112
- enabled?: boolean;
113
- useAI?: boolean;
114
- aiProvider?: string;
115
- minTextLength?: number;
116
- allowNewCategories?: boolean;
117
- debug?: boolean;
118
- analysisOptions?: {
119
- model?: string;
120
- temperature?: number;
121
- maxTokens?: number;
122
- cacheAnalysis?: boolean;
123
- };
124
- detection?: {
125
- threshold?: number;
126
- minConfidence?: number;
127
- };
128
- categories?: any;
129
- fallback?: any;
130
- };
131
- qualityChecks?: {
132
- enabled?: boolean;
133
- rules?: any;
134
- autoFix?: boolean;
135
- };
106
+ logging?: LoggerConfig;
107
+ context?: ContextConfigInput;
136
108
  command?: string;
137
109
  fixLength?: boolean;
138
110
  showDetailedStats?: boolean;
139
111
  }
112
+ /**
113
+ * The configuration returned by {@link loadConfig}: the user's settings merged over the
114
+ * defaults, so the defaulted settings are always present.
115
+ */
116
+ export interface ResolvedLocalizeConfig extends LocalizeConfig {
117
+ source: string;
118
+ targets: string[];
119
+ localesDir: string;
120
+ concurrencyLimit: number;
121
+ cacheEnabled: boolean;
122
+ retryOptions: ProviderRetryOptions;
123
+ context: ContextConfig;
124
+ apiConfig: Record<string, ProviderConfig>;
125
+ fileOperations: FileOperationsConfig;
126
+ }
140
127
  /**
141
128
  * Type-safe configuration helper
142
129
  */
@@ -145,8 +132,8 @@ export declare function defineConfig(config: LocalizeConfig): LocalizeConfig;
145
132
  * Load configuration using c12
146
133
  */
147
134
  export declare function loadConfig(cwd?: string): Promise<{
148
- config: LocalizeConfig;
135
+ config: ResolvedLocalizeConfig;
149
136
  configFile: string | undefined;
150
- layers: ConfigLayer<LocalizeConfig, import("c12").ConfigLayerMeta>[] | undefined;
137
+ layers: import("c12").ConfigLayer<LocalizeConfig, import("c12").ConfigLayerMeta>[] | undefined;
151
138
  }>;
152
- export declare const validateEnvironment: (config?: any) => void;
139
+ export declare const validateEnvironment: (config?: LocalizeConfig) => void;
@@ -45,7 +45,8 @@ export async function loadConfig(cwd = process.cwd()) {
45
45
  },
46
46
  });
47
47
  return {
48
- config: config || {},
48
+ // c12 merges the user's config over `defaults`, so the defaulted settings are always set.
49
+ config: (config || {}),
49
50
  configFile,
50
51
  layers,
51
52
  };
@@ -1,3 +1,4 @@
1
+ import type { LocalizeConfig } from "./index.js";
1
2
  /**
2
3
  * Load environment variables from .env files.
3
4
  * Prioritizes .env.local over .env.
@@ -5,6 +6,6 @@
5
6
  export declare const loadEnvironmentVariables: () => Promise<void>;
6
7
  /**
7
8
  * Configure global components (logger, rate limiter, etc.).
8
- * @param {any} config - Configuration object.
9
+ * @param config - Configuration object.
9
10
  */
10
- export declare const configureComponents: (config: any) => void;
11
+ export declare const configureComponents: (config: LocalizeConfig) => void;
@@ -32,8 +32,10 @@ export const loadEnvironmentVariables = async () => {
32
32
  }
33
33
  catch (error) {
34
34
  // ENOENT is expected when files don't exist - silently skip
35
- if (error.code !== "ENOENT") {
36
- console.warn(`Warning: Could not load ${envFile}: ${error.message}`);
35
+ const isMissingFile = error instanceof Error && "code" in error && error.code === "ENOENT";
36
+ if (!isMissingFile) {
37
+ const message = error instanceof Error ? error.message : String(error);
38
+ console.warn(`Warning: Could not load ${envFile}: ${message}`);
37
39
  }
38
40
  }
39
41
  }
@@ -44,7 +46,7 @@ export const loadEnvironmentVariables = async () => {
44
46
  };
45
47
  /**
46
48
  * Configure global components (logger, rate limiter, etc.).
47
- * @param {any} config - Configuration object.
49
+ * @param config - Configuration object.
48
50
  */
49
51
  export const configureComponents = (config) => {
50
52
  // Configure FileManager with config settings
@@ -1,3 +1,4 @@
1
+ import type { TranslationObject } from "../../utils/object-transformer.js";
1
2
  import { FileFormatAdapter } from "./base-adapter.js";
2
3
  /**
3
4
  * Adapter for Android XML resources files (strings.xml)
@@ -7,6 +8,6 @@ export declare class AndroidAdapter implements FileFormatAdapter {
7
8
  private parser;
8
9
  private builder;
9
10
  constructor();
10
- parse(content: string): Promise<Record<string, any>>;
11
- serialize(data: Record<string, any>): Promise<string>;
11
+ parse(content: string): Promise<TranslationObject>;
12
+ serialize(data: TranslationObject): Promise<string>;
12
13
  }
@@ -51,14 +51,12 @@ export class AndroidAdapter {
51
51
  return result;
52
52
  }
53
53
  catch (error) {
54
- throw new Error(`Failed to parse Android XML: ${error.message}`);
54
+ throw new Error(`Failed to parse Android XML: ${error instanceof Error ? error.message : String(error)}`);
55
55
  }
56
56
  }
57
57
  async serialize(data) {
58
- const resources = {
59
- string: [],
60
- "string-array": [],
61
- };
58
+ const stringNodes = [];
59
+ const stringArrayNodes = [];
62
60
  // Collect keys to handle arrays properly
63
61
  const arrays = {};
64
62
  const strings = {};
@@ -76,25 +74,26 @@ export class AndroidAdapter {
76
74
  }
77
75
  // Add strings
78
76
  for (const [key, value] of Object.entries(strings)) {
79
- resources.string.push({
77
+ stringNodes.push({
80
78
  "@_name": key,
81
79
  "#text": value,
82
80
  });
83
81
  }
84
82
  // Add arrays
85
83
  for (const [key, items] of Object.entries(arrays)) {
86
- resources["string-array"].push({
84
+ stringArrayNodes.push({
87
85
  "@_name": key,
88
86
  item: items.filter((i) => i !== undefined),
89
87
  });
90
88
  }
91
89
  // Sort by name for consistency
92
- resources.string.sort((a, b) => a["@_name"].localeCompare(b["@_name"]));
93
- resources["string-array"].sort((a, b) => a["@_name"].localeCompare(b["@_name"]));
94
- if (resources["string-array"].length === 0)
95
- delete resources["string-array"];
96
- if (resources.string.length === 0)
97
- delete resources.string;
90
+ stringNodes.sort((a, b) => a["@_name"].localeCompare(b["@_name"]));
91
+ stringArrayNodes.sort((a, b) => a["@_name"].localeCompare(b["@_name"]));
92
+ const resources = {};
93
+ if (stringNodes.length > 0)
94
+ resources.string = stringNodes;
95
+ if (stringArrayNodes.length > 0)
96
+ resources["string-array"] = stringArrayNodes;
98
97
  const xmlObj = {
99
98
  "?xml": {
100
99
  "@_version": "1.0",
@@ -1,3 +1,14 @@
1
+ import type { TranslationObject } from "../../utils/object-transformer.js";
2
+ /**
3
+ * Options accepted by {@link FileFormatAdapter.serialize}.
4
+ * Adapters ignore options that do not apply to their format.
5
+ */
6
+ export interface SerializeOptions {
7
+ /** Indentation width (JSON, YAML) */
8
+ indent?: number;
9
+ /** Request compact output where the format supports it */
10
+ compact?: boolean;
11
+ }
1
12
  /**
2
13
  * Interface for file format adapters
3
14
  */
@@ -10,11 +21,11 @@ export interface FileFormatAdapter {
10
21
  * Parse file content into a key-value object
11
22
  * @param content - Raw string content
12
23
  */
13
- parse(content: string): Promise<Record<string, any>>;
24
+ parse(content: string): Promise<TranslationObject>;
14
25
  /**
15
26
  * Serialize data object into string format
16
27
  * @param data - Key-value object
17
28
  * @param options - Serialization options
18
29
  */
19
- serialize(data: Record<string, any>, options?: any): Promise<string>;
30
+ serialize(data: TranslationObject, options?: SerializeOptions): Promise<string>;
20
31
  }
@@ -1,3 +1,4 @@
1
+ import type { TranslationObject } from "../../utils/object-transformer.js";
1
2
  import { FileFormatAdapter } from "./base-adapter.js";
2
3
  /**
3
4
  * Adapter for iOS .strings files
@@ -5,6 +6,6 @@ import { FileFormatAdapter } from "./base-adapter.js";
5
6
  */
6
7
  export declare class IosAdapter implements FileFormatAdapter {
7
8
  extensions: string[];
8
- parse(content: string): Promise<Record<string, any>>;
9
- serialize(data: Record<string, any>): Promise<string>;
9
+ parse(content: string): Promise<TranslationObject>;
10
+ serialize(data: TranslationObject): Promise<string>;
10
11
  }
@@ -1,9 +1,10 @@
1
- import { FileFormatAdapter } from "./base-adapter.js";
1
+ import type { TranslationObject } from "../../utils/object-transformer.js";
2
+ import { FileFormatAdapter, SerializeOptions } from "./base-adapter.js";
2
3
  /**
3
4
  * Adapter for JSON files
4
5
  */
5
6
  export declare class JsonAdapter implements FileFormatAdapter {
6
7
  extensions: string[];
7
- parse(content: string): Promise<Record<string, any>>;
8
- serialize(data: Record<string, any>, options?: any): Promise<string>;
8
+ parse(content: string): Promise<TranslationObject>;
9
+ serialize(data: TranslationObject, options?: SerializeOptions): Promise<string>;
9
10
  }
@@ -3,16 +3,14 @@
3
3
  */
4
4
  export class JsonAdapter {
5
5
  extensions = [".json", ".arb"];
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
6
  async parse(content) {
8
7
  try {
9
8
  return JSON.parse(content);
10
9
  }
11
10
  catch (error) {
12
- throw new Error(`JSON parse error: ${error.message}`);
11
+ throw new Error(`JSON parse error: ${error instanceof Error ? error.message : String(error)}`);
13
12
  }
14
13
  }
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
14
  async serialize(data, options = {}) {
17
15
  const indent = options.indent || 4;
18
16
  return JSON.stringify(data, null, indent);
@@ -1,9 +1,10 @@
1
+ import type { TranslationObject } from "../../utils/object-transformer.js";
1
2
  import { FileFormatAdapter } from "./base-adapter.js";
2
3
  /**
3
4
  * Adapter for Gettext PO files
4
5
  */
5
6
  export declare class PoAdapter implements FileFormatAdapter {
6
7
  extensions: string[];
7
- parse(content: string): Promise<Record<string, any>>;
8
- serialize(data: Record<string, any>, options?: any): Promise<string>;
8
+ parse(content: string): Promise<TranslationObject>;
9
+ serialize(data: TranslationObject): Promise<string>;
9
10
  }