npm package: @i18n-auto/core
Description: Zero-config, auto-translating internationalization for Node.js
Author: Muhammad Zubair Asim
Latest version: 0.1.1

# @i18n-auto/core

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.

**How it works:**

1. You write `i18nTranslate("Hello world", { key: "greeting", locale: "ur" })` in your code
2. The CLI scans your source files using AST parsing, extracts all translation calls
3. The CLI batch-translates extracted strings via Google Translate
4. Locale JSON files are written to disk
5. At runtime, `i18nTranslate()` is a synchronous lookup from pre-loaded JSON — no API calls, no async

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Runtime API](#runtime-api)
- [CLI Usage](#cli-usage)
- [How It Works](#how-it-works)
- [Translation Providers](#translation-providers)
- [Project Structure](#project-structure)
- [Contributing](#contributing)
- [License](#license)

## Installation

```bash
npm install @i18n-auto/core
```

### Prerequisites

- Node.js >= 18
- A Google Cloud API key with the Translation API enabled

## Quick Start

### 1. Create a configuration file

Create a file (e.g., `src/i18n.ts`) that initializes the library:

```typescript
import { initI18n } from '@i18n-auto/core';

initI18n({
  provider: 'google',
  apiKey: process.env.GOOGLE_TRANSLATE_API_KEY,
  defaultLocale: 'en',
  cachePath: './locales',
  locales: ['ur', 'fr', 'de'],
  sourcePath: './src',
});
```

### 2. Use translations in your code

```typescript
import { i18nTranslate } from '@i18n-auto/core';

// Synchronous — reads from pre-loaded JSON, no API calls at runtime
const welcome = i18nTranslate("Welcome to our platform!", {
  key: "welcome_msg",
  locale: "ur",
});
// → "ہمارے پلیٹ فارم میں خوش آمدید!"

const error = i18nTranslate("Invalid email address", {
  key: "err.invalid_email",
  locale: "fr",
});
// → "Adresse e-mail invalide"

// Dynamic placeholders with params
const greeting = i18nTranslate("Hello {{name}}, you have {{count}} messages", {
  key: "user.greeting",
  locale: "ur",
  params: { name: "Zubair", count: 5 },
});
// → "ہیلو Zubair، آپ کے پاس 5 پیغامات ہیں"
```

### 3. Run the CLI to generate translations

```bash
GOOGLE_TRANSLATE_API_KEY=your-key npx i18n-auto translate --config ./src/i18n.ts
```

This scans your source files, extracts all `i18nTranslate()` calls, translates new strings, and writes locale JSON files:

```
locales/
├── ur.json
├── fr.json
└── de.json
```

### 4. Deploy

The generated JSON files are read synchronously at startup. No API calls happen at runtime. Your application stays fast.

## Configuration

All configuration is defined in the `initI18n()` call. The CLI reads this same config file, so there is a single source of truth.

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `provider` | `string` | `'google'` | Translation provider to use |
| `apiKey` | `string` | — | API key for the translation provider. Use `process.env.YOUR_KEY` |
| `defaultLocale` | `string` | `'en'` | Source language of your text strings |
| `cachePath` | `string` | `'./locales'` | Directory where locale JSON files are stored |
| `locales` | `string[]` | `[]` | Target locales to translate into (e.g., `['ur', 'fr', 'de']`) |
| `sourcePath` | `string` | `'./src'` | Directory to scan for `i18nTranslate()` calls |

### Environment Variables

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.

```typescript
// The CLI detects that the env var name is GOOGLE_TRANSLATE_API_KEY
// and reads process.env.GOOGLE_TRANSLATE_API_KEY when running
initI18n({
  apiKey: process.env.GOOGLE_TRANSLATE_API_KEY,
  // ...
});
```

## Runtime API

### `initI18n(options: I18nAutoOptions): void`

Initializes the i18n runtime. Synchronously loads all locale JSON files from `cachePath` into memory. Call this once at application startup, before any `i18nTranslate()` calls.

```typescript
import { initI18n } from '@i18n-auto/core';

initI18n({
  defaultLocale: 'en',
  cachePath: './locales',
});
```

### `i18nTranslate(text: string, options: TranslateOptions): string`

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.

If the translation is not found, returns the original `text` as a fallback. Never throws.

```typescript
import { i18nTranslate } from '@i18n-auto/core';

const msg = i18nTranslate("Hello", { key: "greeting", locale: "ur" });
```

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `text` | `string` | The source text. Returned as fallback if no translation exists |
| `options.key` | `string` | The translation key (must match across all locales) |
| `options.locale` | `string` | The target locale (e.g., `'ur'`, `'fr'`) |
| `options.params` | `Record<string, string \| number>` | Optional. Dynamic values to interpolate into `{{placeholder}}` patterns |
| `options.lock` | `boolean` | Optional. If `true`, the CLI will not auto-translate this key. You provide the translation manually |

**Interpolation example:**

```typescript
i18nTranslate("Hello {{name}}, you have {{count}} new messages", {
  key: "inbox.greeting",
  locale: "ur",
  params: { name: "Zubair", count: 5 },
});
// → "ہیلو Zubair، آپ کے پاس 5 نئے پیغامات ہیں"
```

If a `{{placeholder}}` has no matching key in `params`, it is left unchanged in the output.

### `reloadTranslations(): void`

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.

```typescript
import { reloadTranslations } from '@i18n-auto/core';

reloadTranslations();
```

### `getTranslationStats(): Record<string, number>`

Returns the number of loaded translations per locale. Useful for health checks and debugging.

```typescript
import { getTranslationStats } from '@i18n-auto/core';

console.log(getTranslationStats());
// { ur: 42, fr: 42, de: 40 }
```

## CLI Usage

The CLI reads your `initI18n()` config file, scans source files, and generates translations.

### `translate`

```bash
npx i18n-auto translate --config <path>
```

| Flag | Required | Default | Description |
|------|----------|---------|-------------|
| `--config <path>` | Yes | — | Path to the file containing the `initI18n()` call |
| `--force` | No | `false` | Re-translate all strings, ignoring cache and change detection |

**What it does:**

1. Parses the `--config` file to read `provider`, `locales`, `cachePath`, and `sourcePath`
2. Globs all `.ts`, `.tsx`, `.js`, `.jsx` files in the source directory
3. AST-parses each file and extracts all `i18nTranslate()` calls
4. For each target locale:
   - Identifies **new** keys (not yet in locale JSON)
   - Identifies **changed** keys (source text modified since last run, detected via hash comparison)
   - Batch-translates only new and changed strings via the configured provider
5. Writes updated locale JSON files and updates `.meta.json` with source text hashes

**Example output:**

```
[i18n-auto] Scanning 24 files...
[i18n-auto] Found 15 translation entries
[i18n-auto] ur: Translating 3 new strings...
[i18n-auto] ur: Re-translating 1 changed strings...
[i18n-auto] ur: Translated 4 strings
[i18n-auto] fr: Translating 3 new strings...
[i18n-auto] fr: Re-translating 1 changed strings...
[i18n-auto] fr: Translated 4 strings
[i18n-auto] Done! Translated 8 strings for 3 locale(s)
```

**Force mode** re-translates everything, useful when you want to refresh all translations (e.g., after switching providers):

```bash
npx i18n-auto translate --config ./src/i18n.ts --force
```

### `stats`

```bash
npx i18n-auto stats --config <path>
```

Shows translation progress per locale:

```
[i18n-auto] Total translation keys found: 15

  ur: 15/15 translated ✓
  fr: 15/15 translated ✓
  de: 12/15 translated (3 missing)
```

## How It Works

### Architecture

```
@i18n-auto/core
│
├── Runtime (imported in your app)
│   ├── initI18n()         — loads locale JSON into memory at startup
│   ├── i18nTranslate()    — synchronous key lookup, returns string
│   ├── reloadTranslations() — re-reads JSON from disk
│   └── getTranslationStats() — returns per-locale counts
│
├── CLI (runs before deploy)
│   ├── AST Scanner        — parses source files, extracts i18nTranslate() calls
│   ├── Provider           — batch translates via Google Translate API
│   └── File Cache         — reads/writes locale JSON files
│
└── Locale Files (generated output)
    ├── ur.json
    ├── fr.json
    └── de.json
```

### Locale File Format

Each locale gets a flat JSON file at `{cachePath}/{locale}.json`:

```json
{
  "welcome_msg": "ہمارے پلیٹ فارم میں خوش آمدید!",
  "err.invalid_email": "غلط ای میل پتہ",
  "auth.login_success": "آپ کامیابی سے لاگ ان ہو گئے ہیں"
}
```

Simple key-value pairs. No nesting, no metadata. Human-readable and easy to review in pull requests.

### AST Extraction

The CLI uses `@babel/parser` and `@babel/traverse` to find translation calls. It only processes files that:

1. Import `i18nTranslate` from `@i18n-auto/core`
2. Call `i18nTranslate()` with a string literal as the first argument and an object with a `key` property as the second

Calls with dynamic values (variables instead of string literals) are skipped with a warning.

### Manual Translations (Lock)

Sometimes machine translation isn't good enough — you want to provide your own translation for specific keys. Use the `lock` option:

```typescript
i18nTranslate("Hello friend", {
  key: "greeting",
  locale: "ur",
  lock: true,
});
```

**What happens when the CLI runs:**

1. The CLI detects `lock: true` on this key
2. It adds the key to the locale JSON with an empty string `""`
3. It marks the key as `"locked"` in `.meta.json`
4. It **never** auto-translates or overwrites this key — even with `--force`

```json
// locales/ur.json (after CLI runs)
{
  "welcome_msg": "ہمارے پلیٹ فارم میں خوش آمدید!",
  "greeting": ""
}
```

You then manually edit `locales/ur.json` and fill in your translation:

```json
{
  "welcome_msg": "ہمارے پلیٹ فارم میں خوش آمدید!",
  "greeting": "خوش آمدید دوست!"
}
```

Future CLI runs will never touch this key. Your manual translation is safe.

Until you fill in the translation, the runtime returns the original English text as a fallback (empty strings are treated as missing).

### Dynamic Placeholders

Use `{{placeholder}}` syntax for dynamic values like usernames, counts, or dates:

```typescript
i18nTranslate("Hello {{name}}, your order #{{orderId}} is ready", {
  key: "order.ready",
  locale: "fr",
  params: { name: "Zubair", orderId: 1234 },
});
// → "Bonjour Zubair, votre commande #1234 est prête"
```

**How placeholders are protected during translation:**

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.

```
Source:      "Hello {{name}}, welcome to {{platform}}"
Sent to API: "Hello <x0>, welcome to <x1>"
API returns: "مرحبا <x0>، <x1> میں خوش آمدید"
Restored:    "مرحبا {{name}}، {{platform}} میں خوش آمدید"
```

At runtime, `i18nTranslate()` replaces `{{placeholder}}` with actual values from the `params` option.

### Incremental Translation and Change Detection

The CLI uses two mechanisms to minimize unnecessary API calls:

**New key detection:** If a key doesn't exist in the locale JSON, it's treated as new and translated.

**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.

```
locales/
├── ur.json          # Translated strings
├── fr.json
├── de.json
└── .meta.json       # Source text hashes (auto-generated)
```

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`.

**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.

## Translation Providers

### Google Translate (default)

Uses the [Google Cloud Translation API v2](https://cloud.google.com/translate/docs/reference/rest/v2/translations/translate).

**Setup:**

1. Create a project in [Google Cloud Console](https://console.cloud.google.com/)
2. Enable the Cloud Translation API
3. Create an API key under APIs & Services > Credentials
4. Set the environment variable:

```bash
export GOOGLE_TRANSLATE_API_KEY=your-api-key-here
```

**Batch limits:** The provider automatically chunks requests to stay within the 128-string-per-request API limit.

### Adding a Custom Provider

Implement the `TranslationProvider` interface:

```typescript
interface TranslationProvider {
  translateBatch(texts: string[], from: string, to: string): Promise<string[]>;
}
```

## Project Structure

```
src/
├── index.ts              # Library entry — exports runtime functions and types
├── cli.ts                # CLI entry — commander setup, orchestration
├── i18n-auto.ts          # Runtime: initI18n, i18nTranslate, reload, stats
├── interfaces/
│   ├── index.ts
│   ├── i18n-auto-options.interface.ts
│   ├── translate-options.interface.ts
│   └── translation-provider.interface.ts
├── scanner/
│   ├── index.ts
│   └── extractor.ts      # AST parsing + extraction
├── providers/
│   ├── index.ts
│   ├── google.provider.ts
│   └── provider.factory.ts
└── cache/
    ├── index.ts
    └── file-cache.ts      # Read/write locale JSON
```

## Typical Workflow

```bash
# Development: write code with i18nTranslate() calls
# ↓
# Before deploy: generate translations
GOOGLE_TRANSLATE_API_KEY=your-key npx i18n-auto translate --config ./src/i18n.ts

# Check progress
npx i18n-auto stats --config ./src/i18n.ts

# Commit generated locale files
git add locales/
git commit -m "chore: update translations"
```

## Contributing

This project uses [conventional commits](https://www.conventionalcommits.org/). All commit messages are validated by commitlint via a git hook.

```bash
# Valid commit messages
git commit -m "feat: add DeepL provider support"
git commit -m "fix: handle empty translation response"
git commit -m "docs: update API reference"

# Run tests
npm test

# Type check
npm run typecheck

# Build
npm run build
```

### Commit Types

| Type | When to use |
|------|-------------|
| `feat` | New feature |
| `fix` | Bug fix |
| `docs` | Documentation only |
| `chore` | Maintenance, dependencies |
| `refactor` | Code change that neither fixes a bug nor adds a feature |
| `test` | Adding or updating tests |

## License

[MIT](LICENSE)