ai-l10n-sdk 1.5.1 → 1.6.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to the SDK package will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.0] - 2026-06-02
9
+
10
+ ### Added
11
+ - **`I18nProjectManager.extractLanguageCodeFromPath(sourceFilePath)`** — New public method that returns the detected source language code from the file path (e.g., `"en"` for `locales/en.json`) or `null` if the structure is unrecognised. Used internally by `AiTranslator` to auto-populate `sourceLanguageCode` on translation requests.
12
+ - **Extended `TranslationConfig`** with four new optional fields:
13
+ - `sourceLanguageCode?: string | null` — BCP-47 source language; auto-detected from the file path when not provided
14
+ - `generateGlossary?: boolean` — generates and saves a glossary for this language pair for future translations (default: `false`)
15
+ - `glossary?: GlossaryEntry[] | null` — override the active glossary: omit/`null` = use active, `[]` = disable, entries = replace for this request
16
+ - `terminology?: TerminologyEntry[]` — terms for consistent translation with disallowed synonyms
17
+ - **`GlossaryEntry` and `TerminologyEntry`** re-exported from `ai-l10n-core` for convenience
18
+
19
+ ### Changed
20
+ - Updated to use `ai-l10n-core@1.6.0`
21
+ - `AiTranslator.translate()` now auto-detects the source language code from the project structure and passes it to the API as `sourceLanguageCode`
22
+
8
23
  ## [1.5.1] - 2026-04-17
9
24
 
10
25
  ### Changed
package/README.md CHANGED
@@ -155,7 +155,7 @@ const result = await translator.translate({
155
155
  });
156
156
  ```
157
157
 
158
- For the full list of supported formats see the [API documentation](https://l10n.dev/ws/translate-i18n-files#supported-formats).
158
+ For the full list of supported formats see the [API documentation](https://api.l10n.dev/doc/#tag/ai-translation/post/v2/translate).
159
159
 
160
160
  ### Multiple Files
161
161
 
@@ -170,6 +170,64 @@ for (const file of files) {
170
170
  }
171
171
  ```
172
172
 
173
+ ### Glossary & Terminology
174
+
175
+ #### Generate and Save a Glossary
176
+
177
+ When `generateGlossary` is enabled, a glossary is built from the source and translated target content and saved as the active glossary for this source/target language pair. It is then used automatically on future translations.
178
+
179
+ > **Balance note:** Balance is debited for the full source content upfront — even when `translateOnlyNewStrings` is `true`. When disabled (default), a temporary internal glossary is generated at no extra cost only for content that exceeds the AI chunk size.
180
+
181
+ ```typescript
182
+ const result = await translator.translate({
183
+ sourceFile: './locales/en.json',
184
+ targetLanguages: ['de', 'fr'],
185
+ generateGlossary: true,
186
+ });
187
+ ```
188
+
189
+ Manage your saved glossaries at [l10n.dev/ws/translation-glossary](https://l10n.dev/ws/translation-glossary).
190
+
191
+ #### Override the Glossary for a Single Request
192
+
193
+ Supply your own term mappings via `glossary`. Each `GlossaryEntry` maps a source term to the preferred target translation with an optional context note.
194
+
195
+ ```typescript
196
+ import { AiTranslator, GlossaryEntry } from 'ai-l10n-sdk';
197
+
198
+ const glossary: GlossaryEntry[] = [
199
+ { sourceTerm: 'Settings', targetTerm: 'Einstellungen' },
200
+ { sourceTerm: 'bank', targetTerm: 'Bank', context: 'financial institution' },
201
+ ];
202
+
203
+ const result = await translator.translate({
204
+ sourceFile: './locales/en.json',
205
+ targetLanguages: ['de'],
206
+ glossary,
207
+ });
208
+ ```
209
+
210
+ Pass an empty array (`glossary: []`) to disable the active glossary entirely for this request.
211
+
212
+ #### Terminology
213
+
214
+ Use `terminology` to enforce consistent term usage. Synonyms listed for each entry are replaced with the preferred `term`.
215
+
216
+ ```typescript
217
+ import { AiTranslator, TerminologyEntry } from 'ai-l10n-sdk';
218
+
219
+ const terminology: TerminologyEntry[] = [
220
+ { term: 'Settings', synonyms: ['Preferences', 'Options', 'Configuration'] },
221
+ { term: 'Dashboard' },
222
+ ];
223
+
224
+ const result = await translator.translate({
225
+ sourceFile: './locales/en.json',
226
+ targetLanguages: ['de', 'fr'],
227
+ terminology,
228
+ });
229
+ ```
230
+
173
231
  ## Core API
174
232
 
175
233
  `ILogger`, `ConsoleLogger`, `L10nTranslationService`, and all related types are part of the core library. See [ai-l10n-core](https://www.npmjs.com/package/ai-l10n-core) for full documentation. These are also re-exported from `ai-l10n-sdk` for convenience.
@@ -284,6 +342,32 @@ interface TranslationConfig {
284
342
  * Enable verbose logging (default: false)
285
343
  */
286
344
  verbose?: boolean;
345
+
346
+ /**
347
+ * BCP-47 code of the source language (e.g., "en", "en-US", "zh-Hans-CN").
348
+ * If not specified, auto-detected from the source file path.
349
+ */
350
+ sourceLanguageCode?: string | null;
351
+
352
+ /**
353
+ * When true, generates a glossary from source and translated target content and saves it as the
354
+ * active glossary for this language pair for future translations.
355
+ * Balance is debited for the full source content upfront — even when translateOnlyNewStrings is true.
356
+ * When false (default), an internal glossary is generated only for large content at no extra cost.
357
+ */
358
+ generateGlossary?: boolean;
359
+
360
+ /**
361
+ * Glossary entries to apply during translation.
362
+ * null/omitted = use active glossary, [] = disable glossary, entries = replace active for this request.
363
+ * Manage saved glossaries at https://l10n.dev/ws/translation-glossary
364
+ */
365
+ glossary?: GlossaryEntry[] | null;
366
+
367
+ /**
368
+ * A list of terms for consistent translations. Synonyms are replaced by the preferred term.
369
+ */
370
+ terminology?: TerminologyEntry[];
287
371
  }
288
372
  ```
289
373
 
@@ -330,6 +414,37 @@ interface TranslationOutput {
330
414
  }
331
415
  ```
332
416
 
417
+ #### GlossaryEntry
418
+
419
+ Maps a source term to a preferred target translation for use in `TranslationConfig.glossary`.
420
+
421
+ ```typescript
422
+ interface GlossaryEntry {
423
+ /** The term in the source language to be translated. Max length: 255. */
424
+ sourceTerm: string;
425
+ /** The preferred translation of the term in the target language. Max length: 255. */
426
+ targetTerm: string;
427
+ /**
428
+ * Optional context to clarify the meaning when the term is ambiguous. Max length: 500.
429
+ * Example: 'bank' could mean 'financial institution' or 'river bank'.
430
+ */
431
+ context?: string | null;
432
+ }
433
+ ```
434
+
435
+ #### TerminologyEntry
436
+
437
+ Specifies a preferred term and disallowed synonyms for use in `TranslationConfig.terminology`.
438
+
439
+ ```typescript
440
+ interface TerminologyEntry {
441
+ /** The preferred term to use in translations. */
442
+ term: string;
443
+ /** Synonyms that should be replaced by `term`. */
444
+ synonyms?: string[];
445
+ }
446
+ ```
447
+
333
448
  ### I18nProjectManager
334
449
 
335
450
  Utility class for managing i18n project structure detection, language code validation, and file path generation.
@@ -354,6 +469,25 @@ Creates an instance of I18nProjectManager.
354
469
 
355
470
  #### Methods
356
471
 
472
+ ##### `extractLanguageCodeFromPath(sourceFilePath: string): string | null`
473
+
474
+ Extracts the source language code from the file path using project structure detection.
475
+
476
+ **Parameters:**
477
+ - `sourceFilePath: string` - Path to the source file
478
+
479
+ **Returns:** `string | null` — The detected language code (e.g., `"en"`, `"en-US"`) or `null` if the structure is unrecognised
480
+
481
+ **Example:**
482
+ ```typescript
483
+ const manager = new I18nProjectManager();
484
+
485
+ manager.extractLanguageCodeFromPath('./locales/en.json'); // Returns: 'en'
486
+ manager.extractLanguageCodeFromPath('./locales/en/common.json'); // Returns: 'en'
487
+ manager.extractLanguageCodeFromPath('./lib/l10n/app_en_US.arb'); // Returns: 'en_US'
488
+ manager.extractLanguageCodeFromPath('./strings.json'); // Returns: null
489
+ ```
490
+
357
491
  ##### `detectLanguagesFromProject(sourceFilePath: string): string[]`
358
492
 
359
493
  Detects target language codes from the project structure by scanning directories and files.
@@ -441,6 +575,10 @@ manager.getUniqueFilePath('./output.json');
441
575
  // Returns: './output (2).json'
442
576
  ```
443
577
 
578
+ ### I18nProjectManager.extractLanguageCodeFromPath usage in AiTranslator
579
+
580
+ `AiTranslator` calls `extractLanguageCodeFromPath()` automatically on each translation to populate `sourceLanguageCode` in the API request. You can override this by setting `sourceLanguageCode` explicitly in `TranslationConfig`.
581
+
444
582
  ## License
445
583
 
446
584
  MIT
@@ -4,6 +4,11 @@ export declare class I18nProjectManager {
4
4
  constructor(logger?: ILogger);
5
5
  detectLanguagesFromProject(sourceFilePath: string): string[];
6
6
  generateTargetFilePath(sourceFilePath: string, targetLanguage: string): string;
7
+ /**
8
+ * Extracts the source language code from the file path using project structure detection.
9
+ * Returns the language code (e.g., `"en"`, `"en-US"`) or `null` if it cannot be determined.
10
+ */
11
+ extractLanguageCodeFromPath(sourceFilePath: string): string | null;
7
12
  getUniqueFilePath(filePath: string): string;
8
13
  private detectProjectStructure;
9
14
  }
@@ -164,6 +164,14 @@ class I18nProjectManager {
164
164
  }
165
165
  }
166
166
  }
167
+ /**
168
+ * Extracts the source language code from the file path using project structure detection.
169
+ * Returns the language code (e.g., `"en"`, `"en-US"`) or `null` if it cannot be determined.
170
+ */
171
+ extractLanguageCodeFromPath(sourceFilePath) {
172
+ const structureInfo = this.detectProjectStructure(sourceFilePath);
173
+ return structureInfo.sourceLanguage ?? null;
174
+ }
167
175
  getUniqueFilePath(filePath) {
168
176
  if (!fs.existsSync(filePath)) {
169
177
  return filePath;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ILogger } from "ai-l10n-core";
1
+ import { ILogger, GlossaryEntry, TerminologyEntry } from "ai-l10n-core";
2
2
  /**
3
3
  * Configuration options for translation
4
4
  */
@@ -53,6 +53,32 @@ export interface TranslationConfig {
53
53
  * Enable verbose logging (default: false)
54
54
  */
55
55
  verbose?: boolean;
56
+ /**
57
+ * BCP-47 code of the source language (e.g., "en", "en-US", "zh-Hans-CN").
58
+ * If not specified, auto-detected from the source file path.
59
+ */
60
+ sourceLanguageCode?: string | null;
61
+ /**
62
+ * When true, generates a glossary from source and translated target content and saves it as the
63
+ * active glossary for this language pair for future translations.
64
+ * Balance is debited for the full source content upfront — even when `translateOnlyNewStrings` is true.
65
+ * When false (default), an internal glossary is generated only for large content that exceeds the AI
66
+ * chunk size, at no extra cost. (default: false)
67
+ */
68
+ generateGlossary?: boolean;
69
+ /**
70
+ * Glossary entries to apply during translation.
71
+ * - `null` or omitted: use the active glossary for this language pair.
72
+ * - Empty array `[]`: disable glossary translation entirely.
73
+ * - One or more entries: replace the active glossary for this request.
74
+ * Manage saved glossaries at https://l10n.dev/ws/translation-glossary
75
+ */
76
+ glossary?: GlossaryEntry[] | null;
77
+ /**
78
+ * A list of terms to use for consistent translations.
79
+ * Synonyms listed per entry will be replaced by the preferred term.
80
+ */
81
+ terminology?: TerminologyEntry[];
56
82
  }
57
83
  /**
58
84
  * Result of a single translation operation
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ class AiTranslator {
74
74
  // Ensure API key is available
75
75
  const apiKey = await this.apiKeyManager.ensureApiKey(config.apiKey);
76
76
  // Determine target languages
77
+ let sourceLanguageCode = config.sourceLanguageCode;
77
78
  let targetLanguages = config.targetLanguages;
78
79
  if (!targetLanguages || targetLanguages.length === 0) {
79
80
  // Auto-detect from project structure
@@ -82,6 +83,12 @@ class AiTranslator {
82
83
  if (targetLanguages.length === 0) {
83
84
  throw new Error("No target languages found. Please specify targetLanguages in config or ensure your project has the proper structure (e.g., folders named with language codes or files named with language codes).");
84
85
  }
86
+ // Auto-detect source language code from file path if not provided
87
+ // If we can detect target languages from the project structure, we can also attempt to detect the source language code from the file path.
88
+ if (!sourceLanguageCode) {
89
+ sourceLanguageCode =
90
+ this.i18nProjectManager.extractLanguageCodeFromPath(sourceFilePath);
91
+ }
85
92
  this.logger.logInfo(`✨ Auto-detected target languages from project structure: ${targetLanguages.join(", ")}`);
86
93
  }
87
94
  // Validate language codes
@@ -100,6 +107,9 @@ class AiTranslator {
100
107
  const translateMetadata = config.translateMetadata ?? false;
101
108
  const saveFilteredStrings = config.saveFilteredStrings ?? true;
102
109
  const translateOnlyNewStrings = config.translateOnlyNewStrings ?? false;
110
+ const generateGlossary = config.generateGlossary ?? false;
111
+ const glossary = config.glossary;
112
+ const terminology = config.terminology;
103
113
  if (verbose) {
104
114
  this.logger.logInfo(`Configuration:
105
115
  - Use contractions: ${useContractions}
@@ -107,7 +117,11 @@ class AiTranslator {
107
117
  - Generate plural forms: ${generatePluralForms}
108
118
  - Translate metadata: ${translateMetadata}
109
119
  - Save filtered strings: ${saveFilteredStrings}
110
- - Translate only new strings: ${translateOnlyNewStrings}`);
120
+ - Translate only new strings: ${translateOnlyNewStrings}
121
+ - Source language: ${sourceLanguageCode ?? "auto-detect"}
122
+ - Generate glossary: ${generateGlossary}
123
+ - Glossary entries: ${glossary === undefined ? "use active" : glossary === null ? "use active" : glossary.length === 0 ? "disabled" : `${glossary.length} entries`}
124
+ - Terminology entries: ${terminology?.length ?? 0}`);
111
125
  }
112
126
  // Perform translations in parallel
113
127
  const totalLanguages = targetLanguages.length;
@@ -115,7 +129,7 @@ class AiTranslator {
115
129
  const targetFilePath = this.i18nProjectManager.generateTargetFilePath(sourceFilePath, targetLanguage);
116
130
  try {
117
131
  this.logger.logInfo(`\n🌐 Translating (${i + 1}/${totalLanguages}) to ${targetLanguage}...`);
118
- const result = await this.performTranslation(apiKey, sourceFilePath, targetLanguage, targetFilePath, translateOnlyNewStrings, useContractions, useShortening, generatePluralForms, translateMetadata, saveFilteredStrings, format, verbose);
132
+ const result = await this.performTranslation(apiKey, sourceFilePath, targetLanguage, targetFilePath, translateOnlyNewStrings, useContractions, useShortening, generatePluralForms, translateMetadata, saveFilteredStrings, format, verbose, sourceLanguageCode, generateGlossary, glossary, terminology);
119
133
  return result;
120
134
  }
121
135
  catch (error) {
@@ -172,7 +186,7 @@ class AiTranslator {
172
186
  };
173
187
  }
174
188
  }
175
- async performTranslation(apiKey, sourceFilePath, targetLanguage, targetFilePath, translateOnlyNewStrings, useContractions, useShortening, generatePluralForms, translateMetadata, saveFilteredStrings, format, verbose) {
189
+ async performTranslation(apiKey, sourceFilePath, targetLanguage, targetFilePath, translateOnlyNewStrings, useContractions, useShortening, generatePluralForms, translateMetadata, saveFilteredStrings, format, verbose, sourceLanguageCode, generateGlossary, glossary, terminology) {
176
190
  // Read source file
177
191
  const sourceContent = fs.readFileSync(sourceFilePath, "utf8");
178
192
  // Check if target file exists and read it if updating
@@ -189,6 +203,7 @@ class AiTranslator {
189
203
  const request = {
190
204
  sourceStrings: sourceContent,
191
205
  targetLanguageCode: normalizedLanguage,
206
+ sourceLanguageCode: sourceLanguageCode ?? null,
192
207
  useContractions,
193
208
  useShortening,
194
209
  generatePluralForms,
@@ -198,6 +213,9 @@ class AiTranslator {
198
213
  targetStrings,
199
214
  schema: format === "arb" ? ai_l10n_core_1.FileSchema.ARBFlutter : null,
200
215
  format,
216
+ generateGlossary,
217
+ glossary,
218
+ terminology,
201
219
  };
202
220
  // Call translation service
203
221
  const response = await this.translationService.translate(request, apiKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-l10n-sdk",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Lightweight SDK for AI-powered localization automation - programmatic API (no CLI)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "node": ">=14.0.0"
47
47
  },
48
48
  "dependencies": {
49
- "ai-l10n-core": "^1.5.1"
49
+ "ai-l10n-core": "^1.6.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/mocha": "^10.0.10",