# @fluenti/cli

> CLI tool for the Fluenti i18n framework. Extracts translatable messages from Vue SFC and TSX/JSX source files, compiles PO/JSON catalogs to optimized ES modules with tree-shaking support, shows translation progress stats, and provides AI-powered translation via Claude Code or Codex CLI.

- Package: `@fluenti/cli`
- Binary: `fluenti`
- Docs: https://fluenti.dev
- Repository: https://github.com/usefluenti/fluenti/tree/main/packages/cli
- License: MIT

## Installation

```bash
pnpm add -D @fluenti/cli
```

## Configuration

The CLI loads config from `fluenti.config.ts`, `fluenti.config.js`, or `fluenti.config.mjs` in the project root. Config files are loaded via `jiti` for TypeScript support.

```ts
// fluenti.config.ts
export default {
  sourceLocale: 'en',
  locales: ['en', 'ja', 'zh-CN'],
  catalogDir: './locales',
  format: 'po' as const,
  include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],
  compileOutDir: './src/locales/compiled',
}
```

### Config Options (FluentiBuildConfig)

| Property | Type | Default | Description |
|---|---|---|---|
| `sourceLocale` | `string` | `'en'` | Source language code used as the base for extraction |
| `locales` | `string[]` | `['en']` | All supported locale codes |
| `catalogDir` | `string` | `'./locales'` | Directory where PO/JSON catalog files are stored |
| `format` | `'po' \| 'json'` | `'po'` | Catalog file format |
| `include` | `string[]` | `['./src/**/*.{vue,tsx,jsx,ts,js}']` | Glob patterns for source files to scan |
| `exclude` | `string[]` | `undefined` | Glob patterns to exclude from scanning |
| `compileOutDir` | `string` | `'./locales/compiled'` | Output directory for compiled JS/TS modules |
| `devWarnings` | `boolean` | `undefined` | Enable development-mode warnings |
| `fallbackChain` | `Record<string, Locale[]>` | `undefined` | Per-locale or wildcard fallback chains |
| `splitting` | `'dynamic' \| 'static' \| false` | `undefined` | Code splitting strategy |
| `defaultBuildLocale` | `string` | `undefined` | Default locale for static build strategy |
| `plugins` | `readonly FluentiPlugin[]` | `undefined` | Plugins that hook into the extract and compile pipelines |

All commands accept `--config path` to specify a custom config file path.

## fluenti extract

Scan source files for translatable messages and update catalog files.

```bash
fluenti extract [--config path]
```

### Extraction Sources

**Vue SFC files (`.vue`):**
- `v-t` directive: `<div v-t>Hello</div>`
- `v-t` with explicit ID: `<div v-t:greeting>Hello</div>`
- `v-t.plural` modifier: pipe-separated plural forms converted to ICU plural syntax
- `<Trans>` component: `message` prop or rich text children
- `<Plural>` component: category props (`zero`, `one`, `two`, `few`, `many`, `other`)
- `<Select>` component: direct case props or `options`
- direct-import `t` from `@fluenti/vue`
- runtime `t` bindings from `useI18n()`
- Template expressions: `{{ t('message.id') }}`

**TSX/JSX files:**
- Tagged templates: `` t`Hello ${name}` `` extracts as `Hello {name}`
- Function calls: `t('Hello')` extracts the string literal
- `<Trans>` component with `message` prop or children
- `<Plural>` component with category props
- `<Select>` component with direct case props or `options`
- direct-import `t` from framework packages
- runtime `t` bindings from `useI18n()`

### Catalog Update Behavior

- New messages: added to the catalog with origin information (file path, line number)
- Existing messages: preserved with translations intact, origin updated
- Removed messages: marked as obsolete (not deleted, so translations are not lost)
- Reports per locale: `N added, N unchanged, N obsolete`

### Catalog File Structure

Catalogs are stored as `{catalogDir}/{locale}.po` or `{catalogDir}/{locale}.json` depending on the `format` setting.

## fluenti compile

Compile PO/JSON catalogs to optimized ES modules.

```bash
fluenti compile [--config path]
```

Generates JavaScript files with tree-shakeable named exports per message. Each export has an `@__PURE__` annotation so bundlers can eliminate unused messages from the production bundle. A `export default { ... }` re-export maps message ID keys to the named exports for default-import compatibility.

```js
// locales/compiled/en.js
/* @__PURE__ */ export const _a1b2c3 = 'Hello, world!'
/* @__PURE__ */ export const _d4e5f6 = (v) => `Hello, ${v.name}!`

export default {
  'Hello, world!': _a1b2c3,
  'Hello, {name}!': _d4e5f6,
}
```

Also generates an index module with locale list and lazy loaders:

```js
// locales/compiled/index.js
export const locales = ['en', 'ja', 'zh-CN']
export const loaders = {
  'en': () => import('./en.js'),
  'ja': () => import('./ja.js'),
  'zh-CN': () => import('./zh-CN.js'),
}
```

All locales share the same set of export names (union of all message IDs). Missing translations export an empty string, ensuring consistent imports across locales.

Message ID hashing uses the FNV-1a algorithm for deterministic, short export names.

Output files: `{compileOutDir}/{locale}.js` + `{compileOutDir}/index.js`

## fluenti stats

Display a table showing translation progress per locale.

```bash
fluenti stats [--config path]
```

Output example:

```
  Locale  | Total | Translated | Progress
  --------+-------+------------+---------
  en      |    42 |         42 | 100.0%
  ja      |    42 |         38 | 90.5%
  zh-CN   |    42 |         20 | 47.6%
```

Obsolete entries are excluded from all counts. An entry is considered translated if it has a non-empty `translation` field.

## fluenti translate

AI-powered translation of untranslated messages using local CLI tools.

```bash
fluenti translate [--provider claude|codex] [--locale ja] [--batch-size 50] [--config path]
```

### Options

| Option | Type | Default | Description |
|---|---|---|---|
| `--provider` | `'claude' \| 'codex'` | `'claude'` | AI CLI tool to use for translation |
| `--locale` | `string` | All non-source locales | Translate only this specific locale |
| `--batch-size` | `number` (positive integer) | `50` | Number of messages per AI call |
| `--config` | `string` | Auto-detected | Path to config file |

### Providers

- **`claude`**: Invokes the Claude Code CLI via `claude -p "prompt"`. Requires `@anthropic-ai/claude-code` installed globally or in PATH.
- **`codex`**: Invokes the OpenAI Codex CLI via `codex -p "prompt" --full-auto`. Requires `@openai/codex` installed globally or in PATH.

### How It Works

1. Reads the catalog for each target locale
2. Identifies untranslated entries (entries with empty or missing `translation` field, excluding obsolete entries)
3. Batches untranslated messages by `--batch-size`
4. For each batch, sends a structured prompt to the AI CLI containing:
   - Source locale and target locale
   - Source messages as a JSON object `{ id: sourceText, ... }`
   - Rules: output valid JSON only, preserve ICU placeholders (`{name}`, `{count}`, `{gender}`), preserve HTML tags, no explanations
5. Parses the JSON response from the AI output
6. Writes successful translations back to the catalog file
7. Warns about any messages that were not translated in the response

### Translation Prompt Format

The prompt instructs the AI to:
- Translate from source locale to target locale
- Output ONLY valid JSON with the same keys and translated values
- Keep ICU MessageFormat placeholders unchanged (`{name}`, `{count}`, `{gender}`)
- Keep HTML tags unchanged
- Not add explanations or markdown formatting

### Error Handling

- If the CLI tool is not found (`ENOENT`), provides installation instructions
- If the AI response does not contain valid JSON, throws an error
- Individual missing translations within a batch are warned but do not fail the entire operation
- Max response buffer: 10MB per AI invocation

## Catalog Formats

### PO Format (Gettext)

File extension: `.po`. Compatible with Poedit, Crowdin, Weblate, Transifex.

```po
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"

#: src/App.vue:3
msgid "Hello"
msgstr "Bonjour"

#: src/App.vue:5
#, fuzzy
msgid "Goodbye"
msgstr ""
```

### JSON Format

File extension: `.json`. Simple key-value structure for programmatic editing.

```json
{
  "Hello": {
    "message": "Hello",
    "translation": "Bonjour",
    "origin": "src/App.vue:3"
  },
  "Goodbye": {
    "message": "Goodbye",
    "translation": "",
    "origin": "src/App.vue:5",
    "obsolete": false
  }
}
```

## Programmatic API

### translateCatalog(options)

The main translate function is exported for programmatic use:

```ts
import { translateCatalog } from '@fluenti/cli'
import type { AIProvider, TranslateOptions } from '@fluenti/cli'

const { catalog, translated } = await translateCatalog({
  provider: 'claude',       // 'claude' | 'codex'
  sourceLocale: 'en',
  targetLocale: 'ja',
  catalog: existingCatalog, // CatalogData object
  batchSize: 50,
})

console.log(`Translated ${translated} messages`)
```

**TranslateOptions**:

| Property | Type | Description |
|---|---|---|
| `provider` | `'claude' \| 'codex'` | AI provider to use |
| `sourceLocale` | `string` | Source language code |
| `targetLocale` | `string` | Target language code |
| `catalog` | `CatalogData` | Catalog data object with entries |
| `batchSize` | `number` | Messages per batch |

Returns `{ catalog: CatalogData, translated: number }`. The `catalog` is the same object passed in (mutated with translations), and `translated` is the count of newly translated messages.

## Typical Workflow

```bash
# 1. Extract messages from source code
fluenti extract

# 2. Translate using AI (translates all non-source locales)
fluenti translate --provider claude

# 3. Review and edit translations (optional, use Poedit or similar for PO files)

# 4. Compile to optimized JS modules with tree-shaking
fluenti compile

# 5. Check translation progress
fluenti stats
```

## Documentation

- Full docs: [fluenti.dev](https://fluenti.dev)
- Repository: [GitHub](https://github.com/usefluenti/fluenti)
