@i18n-auto/core 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License Copyright (c) 2026 Muhammad Zubair Asim
2
+
3
+ Permission is hereby
4
+ granted, free of charge, to any person obtaining a copy of this software and
5
+ associated documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,496 @@
1
+ # @i18n-auto/core
2
+
3
+ Automatic internationalization for Node.js and TypeScript backends. Write your code in English, run one CLI command, and get production-ready locale files for any language.
4
+
5
+ **How it works:**
6
+
7
+ 1. You write `i18nTranslate("Hello world", { key: "greeting", locale: "ur" })` in your code
8
+ 2. The CLI scans your source files using AST parsing, extracts all translation calls
9
+ 3. The CLI batch-translates extracted strings via Google Translate
10
+ 4. Locale JSON files are written to disk
11
+ 5. At runtime, `i18nTranslate()` is a synchronous lookup from pre-loaded JSON — no API calls, no async
12
+
13
+ ## Table of Contents
14
+
15
+ - [Installation](#installation)
16
+ - [Quick Start](#quick-start)
17
+ - [Configuration](#configuration)
18
+ - [Runtime API](#runtime-api)
19
+ - [CLI Usage](#cli-usage)
20
+ - [How It Works](#how-it-works)
21
+ - [Translation Providers](#translation-providers)
22
+ - [Project Structure](#project-structure)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @i18n-auto/core
30
+ ```
31
+
32
+ ### Prerequisites
33
+
34
+ - Node.js >= 18
35
+ - A Google Cloud API key with the Translation API enabled
36
+
37
+ ## Quick Start
38
+
39
+ ### 1. Create a configuration file
40
+
41
+ Create a file (e.g., `src/i18n.ts`) that initializes the library:
42
+
43
+ ```typescript
44
+ import { initI18n } from '@i18n-auto/core';
45
+
46
+ initI18n({
47
+ provider: 'google',
48
+ apiKey: process.env.GOOGLE_TRANSLATE_API_KEY,
49
+ defaultLocale: 'en',
50
+ cachePath: './locales',
51
+ locales: ['ur', 'fr', 'de'],
52
+ sourcePath: './src',
53
+ });
54
+ ```
55
+
56
+ ### 2. Use translations in your code
57
+
58
+ ```typescript
59
+ import { i18nTranslate } from '@i18n-auto/core';
60
+
61
+ // Synchronous — reads from pre-loaded JSON, no API calls at runtime
62
+ const welcome = i18nTranslate("Welcome to our platform!", {
63
+ key: "welcome_msg",
64
+ locale: "ur",
65
+ });
66
+ // → "ہمارے پلیٹ فارم میں خوش آمدید!"
67
+
68
+ const error = i18nTranslate("Invalid email address", {
69
+ key: "err.invalid_email",
70
+ locale: "fr",
71
+ });
72
+ // → "Adresse e-mail invalide"
73
+
74
+ // Dynamic placeholders with params
75
+ const greeting = i18nTranslate("Hello {{name}}, you have {{count}} messages", {
76
+ key: "user.greeting",
77
+ locale: "ur",
78
+ params: { name: "Zubair", count: 5 },
79
+ });
80
+ // → "ہیلو Zubair، آپ کے پاس 5 پیغامات ہیں"
81
+ ```
82
+
83
+ ### 3. Run the CLI to generate translations
84
+
85
+ ```bash
86
+ GOOGLE_TRANSLATE_API_KEY=your-key npx i18n-auto translate --config ./src/i18n.ts
87
+ ```
88
+
89
+ This scans your source files, extracts all `i18nTranslate()` calls, translates new strings, and writes locale JSON files:
90
+
91
+ ```
92
+ locales/
93
+ ├── ur.json
94
+ ├── fr.json
95
+ └── de.json
96
+ ```
97
+
98
+ ### 4. Deploy
99
+
100
+ The generated JSON files are read synchronously at startup. No API calls happen at runtime. Your application stays fast.
101
+
102
+ ## Configuration
103
+
104
+ All configuration is defined in the `initI18n()` call. The CLI reads this same config file, so there is a single source of truth.
105
+
106
+ | Option | Type | Default | Description |
107
+ |--------|------|---------|-------------|
108
+ | `provider` | `string` | `'google'` | Translation provider to use |
109
+ | `apiKey` | `string` | — | API key for the translation provider. Use `process.env.YOUR_KEY` |
110
+ | `defaultLocale` | `string` | `'en'` | Source language of your text strings |
111
+ | `cachePath` | `string` | `'./locales'` | Directory where locale JSON files are stored |
112
+ | `locales` | `string[]` | `[]` | Target locales to translate into (e.g., `['ur', 'fr', 'de']`) |
113
+ | `sourcePath` | `string` | `'./src'` | Directory to scan for `i18nTranslate()` calls |
114
+
115
+ ### Environment Variables
116
+
117
+ The `apiKey` field must reference an environment variable using `process.env.VARIABLE_NAME`. The CLI reads the variable name from the AST and resolves it at runtime. This keeps secrets out of your source code.
118
+
119
+ ```typescript
120
+ // The CLI detects that the env var name is GOOGLE_TRANSLATE_API_KEY
121
+ // and reads process.env.GOOGLE_TRANSLATE_API_KEY when running
122
+ initI18n({
123
+ apiKey: process.env.GOOGLE_TRANSLATE_API_KEY,
124
+ // ...
125
+ });
126
+ ```
127
+
128
+ ## Runtime API
129
+
130
+ ### `initI18n(options: I18nAutoOptions): void`
131
+
132
+ Initializes the i18n runtime. Synchronously loads all locale JSON files from `cachePath` into memory. Call this once at application startup, before any `i18nTranslate()` calls.
133
+
134
+ ```typescript
135
+ import { initI18n } from '@i18n-auto/core';
136
+
137
+ initI18n({
138
+ defaultLocale: 'en',
139
+ cachePath: './locales',
140
+ });
141
+ ```
142
+
143
+ ### `i18nTranslate(text: string, options: TranslateOptions): string`
144
+
145
+ Returns the translated string for the given key and locale. This is a **synchronous** function — it performs a simple object lookup against the pre-loaded JSON data.
146
+
147
+ If the translation is not found, returns the original `text` as a fallback. Never throws.
148
+
149
+ ```typescript
150
+ import { i18nTranslate } from '@i18n-auto/core';
151
+
152
+ const msg = i18nTranslate("Hello", { key: "greeting", locale: "ur" });
153
+ ```
154
+
155
+ **Parameters:**
156
+
157
+ | Parameter | Type | Description |
158
+ |-----------|------|-------------|
159
+ | `text` | `string` | The source text. Returned as fallback if no translation exists |
160
+ | `options.key` | `string` | The translation key (must match across all locales) |
161
+ | `options.locale` | `string` | The target locale (e.g., `'ur'`, `'fr'`) |
162
+ | `options.params` | `Record<string, string \| number>` | Optional. Dynamic values to interpolate into `{{placeholder}}` patterns |
163
+ | `options.lock` | `boolean` | Optional. If `true`, the CLI will not auto-translate this key. You provide the translation manually |
164
+
165
+ **Interpolation example:**
166
+
167
+ ```typescript
168
+ i18nTranslate("Hello {{name}}, you have {{count}} new messages", {
169
+ key: "inbox.greeting",
170
+ locale: "ur",
171
+ params: { name: "Zubair", count: 5 },
172
+ });
173
+ // → "ہیلو Zubair، آپ کے پاس 5 نئے پیغامات ہیں"
174
+ ```
175
+
176
+ If a `{{placeholder}}` has no matching key in `params`, it is left unchanged in the output.
177
+
178
+ ### `reloadTranslations(): void`
179
+
180
+ Reloads all locale JSON files from disk. Call this after running the CLI if your application is long-running and you want to pick up new translations without restarting.
181
+
182
+ ```typescript
183
+ import { reloadTranslations } from '@i18n-auto/core';
184
+
185
+ reloadTranslations();
186
+ ```
187
+
188
+ ### `getTranslationStats(): Record<string, number>`
189
+
190
+ Returns the number of loaded translations per locale. Useful for health checks and debugging.
191
+
192
+ ```typescript
193
+ import { getTranslationStats } from '@i18n-auto/core';
194
+
195
+ console.log(getTranslationStats());
196
+ // { ur: 42, fr: 42, de: 40 }
197
+ ```
198
+
199
+ ## CLI Usage
200
+
201
+ The CLI reads your `initI18n()` config file, scans source files, and generates translations.
202
+
203
+ ### `translate`
204
+
205
+ ```bash
206
+ npx i18n-auto translate --config <path>
207
+ ```
208
+
209
+ | Flag | Required | Default | Description |
210
+ |------|----------|---------|-------------|
211
+ | `--config <path>` | Yes | — | Path to the file containing the `initI18n()` call |
212
+ | `--force` | No | `false` | Re-translate all strings, ignoring cache and change detection |
213
+
214
+ **What it does:**
215
+
216
+ 1. Parses the `--config` file to read `provider`, `locales`, `cachePath`, and `sourcePath`
217
+ 2. Globs all `.ts`, `.tsx`, `.js`, `.jsx` files in the source directory
218
+ 3. AST-parses each file and extracts all `i18nTranslate()` calls
219
+ 4. For each target locale:
220
+ - Identifies **new** keys (not yet in locale JSON)
221
+ - Identifies **changed** keys (source text modified since last run, detected via hash comparison)
222
+ - Batch-translates only new and changed strings via the configured provider
223
+ 5. Writes updated locale JSON files and updates `.meta.json` with source text hashes
224
+
225
+ **Example output:**
226
+
227
+ ```
228
+ [i18n-auto] Scanning 24 files...
229
+ [i18n-auto] Found 15 translation entries
230
+ [i18n-auto] ur: Translating 3 new strings...
231
+ [i18n-auto] ur: Re-translating 1 changed strings...
232
+ [i18n-auto] ur: Translated 4 strings
233
+ [i18n-auto] fr: Translating 3 new strings...
234
+ [i18n-auto] fr: Re-translating 1 changed strings...
235
+ [i18n-auto] fr: Translated 4 strings
236
+ [i18n-auto] Done! Translated 8 strings for 3 locale(s)
237
+ ```
238
+
239
+ **Force mode** re-translates everything, useful when you want to refresh all translations (e.g., after switching providers):
240
+
241
+ ```bash
242
+ npx i18n-auto translate --config ./src/i18n.ts --force
243
+ ```
244
+
245
+ ### `stats`
246
+
247
+ ```bash
248
+ npx i18n-auto stats --config <path>
249
+ ```
250
+
251
+ Shows translation progress per locale:
252
+
253
+ ```
254
+ [i18n-auto] Total translation keys found: 15
255
+
256
+ ur: 15/15 translated ✓
257
+ fr: 15/15 translated ✓
258
+ de: 12/15 translated (3 missing)
259
+ ```
260
+
261
+ ## How It Works
262
+
263
+ ### Architecture
264
+
265
+ ```
266
+ @i18n-auto/core
267
+
268
+ ├── Runtime (imported in your app)
269
+ │ ├── initI18n() — loads locale JSON into memory at startup
270
+ │ ├── i18nTranslate() — synchronous key lookup, returns string
271
+ │ ├── reloadTranslations() — re-reads JSON from disk
272
+ │ └── getTranslationStats() — returns per-locale counts
273
+
274
+ ├── CLI (runs before deploy)
275
+ │ ├── AST Scanner — parses source files, extracts i18nTranslate() calls
276
+ │ ├── Provider — batch translates via Google Translate API
277
+ │ └── File Cache — reads/writes locale JSON files
278
+
279
+ └── Locale Files (generated output)
280
+ ├── ur.json
281
+ ├── fr.json
282
+ └── de.json
283
+ ```
284
+
285
+ ### Locale File Format
286
+
287
+ Each locale gets a flat JSON file at `{cachePath}/{locale}.json`:
288
+
289
+ ```json
290
+ {
291
+ "welcome_msg": "ہمارے پلیٹ فارم میں خوش آمدید!",
292
+ "err.invalid_email": "غلط ای میل پتہ",
293
+ "auth.login_success": "آپ کامیابی سے لاگ ان ہو گئے ہیں"
294
+ }
295
+ ```
296
+
297
+ Simple key-value pairs. No nesting, no metadata. Human-readable and easy to review in pull requests.
298
+
299
+ ### AST Extraction
300
+
301
+ The CLI uses `@babel/parser` and `@babel/traverse` to find translation calls. It only processes files that:
302
+
303
+ 1. Import `i18nTranslate` from `@i18n-auto/core`
304
+ 2. Call `i18nTranslate()` with a string literal as the first argument and an object with a `key` property as the second
305
+
306
+ Calls with dynamic values (variables instead of string literals) are skipped with a warning.
307
+
308
+ ### Manual Translations (Lock)
309
+
310
+ Sometimes machine translation isn't good enough — you want to provide your own translation for specific keys. Use the `lock` option:
311
+
312
+ ```typescript
313
+ i18nTranslate("Hello friend", {
314
+ key: "greeting",
315
+ locale: "ur",
316
+ lock: true,
317
+ });
318
+ ```
319
+
320
+ **What happens when the CLI runs:**
321
+
322
+ 1. The CLI detects `lock: true` on this key
323
+ 2. It adds the key to the locale JSON with an empty string `""`
324
+ 3. It marks the key as `"locked"` in `.meta.json`
325
+ 4. It **never** auto-translates or overwrites this key — even with `--force`
326
+
327
+ ```json
328
+ // locales/ur.json (after CLI runs)
329
+ {
330
+ "welcome_msg": "ہمارے پلیٹ فارم میں خوش آمدید!",
331
+ "greeting": ""
332
+ }
333
+ ```
334
+
335
+ You then manually edit `locales/ur.json` and fill in your translation:
336
+
337
+ ```json
338
+ {
339
+ "welcome_msg": "ہمارے پلیٹ فارم میں خوش آمدید!",
340
+ "greeting": "خوش آمدید دوست!"
341
+ }
342
+ ```
343
+
344
+ Future CLI runs will never touch this key. Your manual translation is safe.
345
+
346
+ Until you fill in the translation, the runtime returns the original English text as a fallback (empty strings are treated as missing).
347
+
348
+ ### Dynamic Placeholders
349
+
350
+ Use `{{placeholder}}` syntax for dynamic values like usernames, counts, or dates:
351
+
352
+ ```typescript
353
+ i18nTranslate("Hello {{name}}, your order #{{orderId}} is ready", {
354
+ key: "order.ready",
355
+ locale: "fr",
356
+ params: { name: "Zubair", orderId: 1234 },
357
+ });
358
+ // → "Bonjour Zubair, votre commande #1234 est prête"
359
+ ```
360
+
361
+ **How placeholders are protected during translation:**
362
+
363
+ The CLI automatically detects `{{placeholder}}` patterns and replaces them with XML tokens (`<x0>`, `<x1>`, etc.) before sending text to the translation API. Google Translate preserves XML/HTML tags, so the placeholders survive translation intact. After receiving the translated text, the tokens are restored back to `{{placeholder}}` format.
364
+
365
+ ```
366
+ Source: "Hello {{name}}, welcome to {{platform}}"
367
+ Sent to API: "Hello <x0>, welcome to <x1>"
368
+ API returns: "مرحبا <x0>، <x1> میں خوش آمدید"
369
+ Restored: "مرحبا {{name}}، {{platform}} میں خوش آمدید"
370
+ ```
371
+
372
+ At runtime, `i18nTranslate()` replaces `{{placeholder}}` with actual values from the `params` option.
373
+
374
+ ### Incremental Translation and Change Detection
375
+
376
+ The CLI uses two mechanisms to minimize unnecessary API calls:
377
+
378
+ **New key detection:** If a key doesn't exist in the locale JSON, it's treated as new and translated.
379
+
380
+ **Change detection:** The CLI maintains a `.meta.json` file in the cache directory that stores a SHA-256 hash of each source text. When you modify the source text for an existing key (e.g., fixing a typo), the CLI detects the hash mismatch and re-translates that key across all locales.
381
+
382
+ ```
383
+ locales/
384
+ ├── ur.json # Translated strings
385
+ ├── fr.json
386
+ ├── de.json
387
+ └── .meta.json # Source text hashes (auto-generated)
388
+ ```
389
+
390
+ The `.meta.json` file is auto-generated and managed by the CLI. You can choose to commit it to version control (recommended for teams) or add it to `.gitignore`.
391
+
392
+ **Force mode:** Use `--force` to bypass all caching and re-translate every string. This is useful when switching translation providers or when you want a fresh set of translations.
393
+
394
+ ## Translation Providers
395
+
396
+ ### Google Translate (default)
397
+
398
+ Uses the [Google Cloud Translation API v2](https://cloud.google.com/translate/docs/reference/rest/v2/translations/translate).
399
+
400
+ **Setup:**
401
+
402
+ 1. Create a project in [Google Cloud Console](https://console.cloud.google.com/)
403
+ 2. Enable the Cloud Translation API
404
+ 3. Create an API key under APIs & Services > Credentials
405
+ 4. Set the environment variable:
406
+
407
+ ```bash
408
+ export GOOGLE_TRANSLATE_API_KEY=your-api-key-here
409
+ ```
410
+
411
+ **Batch limits:** The provider automatically chunks requests to stay within the 128-string-per-request API limit.
412
+
413
+ ### Adding a Custom Provider
414
+
415
+ Implement the `TranslationProvider` interface:
416
+
417
+ ```typescript
418
+ interface TranslationProvider {
419
+ translateBatch(texts: string[], from: string, to: string): Promise<string[]>;
420
+ }
421
+ ```
422
+
423
+ ## Project Structure
424
+
425
+ ```
426
+ src/
427
+ ├── index.ts # Library entry — exports runtime functions and types
428
+ ├── cli.ts # CLI entry — commander setup, orchestration
429
+ ├── i18n-auto.ts # Runtime: initI18n, i18nTranslate, reload, stats
430
+ ├── interfaces/
431
+ │ ├── index.ts
432
+ │ ├── i18n-auto-options.interface.ts
433
+ │ ├── translate-options.interface.ts
434
+ │ └── translation-provider.interface.ts
435
+ ├── scanner/
436
+ │ ├── index.ts
437
+ │ └── extractor.ts # AST parsing + extraction
438
+ ├── providers/
439
+ │ ├── index.ts
440
+ │ ├── google.provider.ts
441
+ │ └── provider.factory.ts
442
+ └── cache/
443
+ ├── index.ts
444
+ └── file-cache.ts # Read/write locale JSON
445
+ ```
446
+
447
+ ## Typical Workflow
448
+
449
+ ```bash
450
+ # Development: write code with i18nTranslate() calls
451
+ # ↓
452
+ # Before deploy: generate translations
453
+ GOOGLE_TRANSLATE_API_KEY=your-key npx i18n-auto translate --config ./src/i18n.ts
454
+
455
+ # Check progress
456
+ npx i18n-auto stats --config ./src/i18n.ts
457
+
458
+ # Commit generated locale files
459
+ git add locales/
460
+ git commit -m "chore: update translations"
461
+ ```
462
+
463
+ ## Contributing
464
+
465
+ This project uses [conventional commits](https://www.conventionalcommits.org/). All commit messages are validated by commitlint via a git hook.
466
+
467
+ ```bash
468
+ # Valid commit messages
469
+ git commit -m "feat: add DeepL provider support"
470
+ git commit -m "fix: handle empty translation response"
471
+ git commit -m "docs: update API reference"
472
+
473
+ # Run tests
474
+ npm test
475
+
476
+ # Type check
477
+ npm run typecheck
478
+
479
+ # Build
480
+ npm run build
481
+ ```
482
+
483
+ ### Commit Types
484
+
485
+ | Type | When to use |
486
+ |------|-------------|
487
+ | `feat` | New feature |
488
+ | `fix` | Bug fix |
489
+ | `docs` | Documentation only |
490
+ | `chore` | Maintenance, dependencies |
491
+ | `refactor` | Code change that neither fixes a bug nor adds a feature |
492
+ | `test` | Adding or updating tests |
493
+
494
+ ## License
495
+
496
+ [MIT](LICENSE)