eloqnt/studiov0.5

Docs

eloqnt/studio is an AI-based toolchain that helps you ship high-quality translations. Its primary entry point is the command line interface, which is configured through local project files.

Project files

All relevant configuration is kept in a .eloqnt folder at your project root:

.eloqnt/
config.ts
styleguide.md
styleguide.es.md

.eloqnt/config.{ts,mts,js,mjs}

Keeps your configuration.

.eloqnt/styleguide.md

(optional)

A global styleguide with rules that apply to every locale (plain markdown).

.eloqnt/styleguide.<locale>.md

(optional)

A per-locale styleguide (e.g. styleguide.es.md) that's layered on top of the global one and takes precedence for that locale.

Configuration

defineConfig describes your project: where source code and messages live, and optionally which model to translate with.

.eloqnt/config.ts

import {defineConfig} from '@eloqnt/cli';
export default defineConfig({
srcPath: './src',
messages: {
path: './messages',
locales: 'infer',
sourceLocale: 'en',
format: 'json'
}
});

srcPath

string | Array<string> (optional)

Relative path(s) to the source code that uses the messages (optional, next-intl only).

Enables source code analysis that feeds AST-level lint rules (orphan-message and undefined-key), as well as contextually enriched translations.

In a monorepo, pass an array of relative source roots, including sibling or installed packages (see also the next-intl docs).

messages

object

pathstring | Array<string>

Relative path(s) to the directories containing messages. To support custom paths for messages for frameworks other than next-intl, you can use {locale} and {namespace} placeholders.

locales'infer' | Array<string>

Locales to translate between, either an explicit array or 'infer' to detect them.

sourceLocalestring

The primary locale that serves as the source for translations.

formatMessageFormat

How catalogs are stored: either the built-in 'po' (recommended), 'json' or a custom format.

model

LanguageModel (optional)

If you're on the "Bring your own model" plan, you can configure a custom provider and model that the AI SDK supports. By doing this, all inference will run through the provided model and no messages or source code will ever touch our backend.

Note that you may need to set environment parameters like ANTHROPIC_API_KEY.

.eloqnt/config.ts

import {anthropic} from '@ai-sdk/anthropic';
import {defineConfig} from '@eloqnt/cli';
export default defineConfig({
// Pick any model supported by the AI SDK
model: anthropic('claude-opus-4-8'),
// ...
});

Using a coding agent subscription (Claude Code, Codex, …)

If you already subscribe to a coding agent, 3rd-party providers let you use it as your model (examples: Claude Code, Codex). No separate API key needed.

.eloqnt/config.ts

import {claudeCode} from 'ai-sdk-provider-claude-code';
import {defineConfig} from '@eloqnt/cli';
export default defineConfig({
model: claudeCode('opus'),
// ...
});

Note: These providers are community-maintained, and whether your subscription allows this kind of use is up to the vendor. That can change at any time, so check that it's in line with the plan you're using.

Styleguides

A styleguide is free-form markdown that steers aspects like tone, terminology, and consistency. You write what is specific to your project, and eloqnt/engine layers on translation best practices automatically.

styleguide.md

Applies to every locale and contains overall guidance.

.eloqnt/styleguide.md

# Styleguide
## Audience
A few sentences about who your users are and how your site/app serves them.
## Voice and tone
- Friendly and concise.
- Address the user as "you", and prefer active voice.
## Do not translate
- eloqnt
- API key

Styleguides are self-contained. eloqnt/engine doesn't follow local or external links inside them, so any guidance the engine needs must live in the file itself.

If you want to share styleguides across packages and apps—for example in a monorepo—you can symlink them where relevant.

styleguide.<locale>.md

Layers on top of the global styleguide and takes precedence wherever the two conflict.

.eloqnt/styleguide.es.md

# `es` styleguide
## Tone
- Use informal "tú", never formal "usted".
- Use straight quotation marks (`"…"`), not Spanish guillemets.
## Glossary
- "dashboard": "panel"

Command-line interface

Install @eloqnt/cli and run commands from your project root.

eloqnt lint

Statically analyze your source code and messages for common problems.

Errors fail the command with a non-zero exit code, while warnings don’t. Pass --strict if you want to bail on warnings as well.

missing-translationA message in the source locale has no translation in a target locale.
superfluous-keyA target locale defines a key that the source locale doesn't.
inconsistent-argsA translation's ICU arguments don't match the source message.
duplicate-idA message id is defined in more than one messages folder (when messages.path lists several).
orphan-messageA catalog message is never referenced in your source code (next-intl only).
undefined-keyYour source code references a key that no catalog defines (next-intl only).

eloqnt review

Requires a plan

Review source strings for errors and fix them in place (defaults to strings with missing translations). When using srcPath, changes will sync to your source code as well.

By default, source strings are only reviewed for objective language errors (spelling and grammar), as well as glossary consistency based on your global styleguide. Intentional wording is protected from unwanted corrections.

If you want to enforce additional rules like typographic preferences, you can mention them in styleguide.<sourceLocale>.md. It's recommended to restrict yourself to mechanical rules in this file and avoid tonality instructions that could cause the review command to rewrite source strings unexpectedly.

--idstring(Re-)review these message ids (comma-separated)

eloqnt translate

Requires a plan

Translate strings to target locales (defaults to missing ones).

--idstring(Re-)translate these message ids (comma-separated)
--localestringRestrict to these target locales (comma-separated)

eloqnt login

Authenticates the CLI by storing an auth token locally.

Your login stays active as long as you keep using the CLI. After 14 days without activity, you will be signed out. Run eloqnt login again to reconnect.

For usage in CI, you can set up an API key and provide it as ELOQNT_TOKEN in your environment to authenticate without the interactive login.

eloqnt logout

Logs out of eloqnt studio by removing a local auth token.

eloqnt whoami

Shows the logged-in user account.

eloqnt crowdin

Coming soon

Sync your translations with Crowdin.

Global flags

Accepted by every command.

--configstringPath to the eloqnt config file (defaults to .eloqnt/config.{ts,mts,js,mjs})
--jsonbooleanOutput the result as JSON

Guides

Walkthroughs for advanced setups.

GitHub Actions

By authenticating the CLI with an API key, you can automatically invoke it as part of your continuous integration workflow (e.g. on GitHub Actions).

Here's an example workflow that:

  1. Checks for lint issues on every push
  2. Translates any missing strings once you switch from "Draft" to "Ready for review"

.github/workflows/i18n.yml

name: i18n
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
i18n:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- run: npm ci
# Issues should be fixed also in draft mode
- run: npx eloqnt lint
# Once ready for review, translate missing strings
- if: ${{ !github.event.pull_request.draft }}
env:
ELOQNT_TOKEN: ${{ secrets.ELOQNT_TOKEN }}
run: |
npx eloqnt review
npx eloqnt translate
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR_ID+$GITHUB_ACTOR@users.noreply.github.com"
git add -A
git diff --quiet --cached || (git commit -m "Translate missing strings" && git push)

Note that commits pushed with the default GITHUB_TOKEN don't run workflows. To have workflows run automatically on pushed commits, create a fine-grained personal access token with read and write access, and pass it to the checkout step:

.github/workflows/i18n.yml

# ...
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
token: ${{ secrets.GH_TOKEN }}
# ...

Custom formats

Beyond the built-in json and po formats, you can configure custom formats. These can handle special cases, like for example storing message descriptions in JSON files:

messages/en.json

{
"HomePage.title": {
"message": "Welcome, {name}!",
"description": ["Greeting for the user when the app starts."]
}
}

To configure a custom format, you need to specify a codec along with an extension.

The codec can be created via defineCodec at an arbitrary place in your project:

.eloqnt/StructuredJSONCodec.ts

import {defineCodec} from '@eloqnt/cli';
type Entry = {message: string; description?: Array<string>};
export default defineCodec(() => ({
decode(content) {
const data = JSON.parse(content) as Record<string, Entry>;
return Object.entries(data).map(([id, entry]) => ({
id,
message: entry.message,
description: entry.description ?? [],
references: []
}));
},
encode(messages) {
const data: Record<string, Entry> = {};
for (const message of messages) {
data[message.id] = {
message: message.message,
description: message.description
};
}
return JSON.stringify(data, null, 2);
}
}));

Then, reference it in your configuration along with an extension:

.eloqnt/config.ts

import {defineConfig} from '@eloqnt/cli';
export default defineConfig({
messages: {
format: {
codec: './.eloqnt/StructuredJSONCodec.ts',
extension: '.json'
},
// ...
}
});

Custom paths for messages

By default, messages.path is a directory that holds one file per locale.

For example, path: './messages' corresponds to:

  • ./messages/en.json
  • ./messages/es.json
  • etc.

To support custom folder structures (typically for frameworks other than next-intl), you can use the following placeholders:

  • {locale}: Resolves to e.g. en, es, etc.
  • {namespace}: Optionally split messages per namespace (the first part of a message id)

For example:

.eloqnt/config.ts

import {defineConfig} from '@eloqnt/cli';
export default defineConfig({
messages: {
path: './locales/{locale}/{namespace}',
locales: 'infer',
sourceLocale: 'en',
format: 'json'
}
});

Will match:

  • ./messages/en/auth.json
  • ./messages/es/auth.json
  • etc.