npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@speranto/speranto

v0.4.3

Published

A quick and simple machine translation tool for i18n in webapps. Translate JSON, JS/TS, Markdown files and database content using OpenAI, Mistral, or Ollama.

Readme

Speranto

A quick and simple machine translation tool for i18n in web apps. Named after Esperanto, the universal European language, Speranto helps you translate content across multiple languages.

The published CLI runs on Node.js 22.13 or newer.

Installation

npm install @speranto/speranto
# or
yarn add @speranto/speranto
# or
pnpm add @speranto/speranto

Configuration types are also available on JSR:

npx jsr add @speranto/speranto
# or
deno add jsr:@speranto/speranto
import type { Config } from '@speranto/speranto'

Agent documentation

The npm package installs current Speranto guidance for coding agents into the consuming project:

.agents/
└── speranto/
    ├── guide.md
    └── manifest.json

docs/agent-guide.md in the Speranto repository is the canonical source. During installation and package upgrades, the postinstall hook copies that guide to .agents/speranto/guide.md when Speranto is a direct dependency. It also adds a small managed reference to the project's existing AGENTS.md, CLAUDE.md, or .claude/CLAUDE.md file. When none of those files exist, it creates a minimal AGENTS.md. Existing content outside the following markers is never replaced:

<!-- speranto-agent-docs:start -->

## Speranto

When working with localization, Speranto configuration, or translated content, read and follow
@.agents/speranto/guide.md.

<!-- speranto-agent-docs:end -->

In a workspace, the hook verifies the dependency against the package where Speranto was installed, then places the documentation at the nearest workspace root. It recognizes pnpm-workspace.yaml and the package.json workspaces formats used by npm, Yarn, Bun, and workspace-based build tools. Previously managed files in the package directory are removed during this migration without deleting user-authored content. Set SPERANTO_PROJECT_ROOT to override the documentation destination explicitly.

If CLAUDE.md already imports AGENTS.md using Claude's @AGENTS.md syntax, only AGENTS.md is updated. Repeated installation is idempotent, and updating the package replaces the managed guide with the canonical guide from the installed version.

Current package managers generally block unapproved dependency lifecycle scripts. The install still succeeds, but the agent guide is not synchronized until the hook is approved or the setup command is run directly.

| Package manager | Default behavior | Approve and run the hook | | --------------- | -------------------------------------------- | --------------------------------------------------------------------------------------- | | npm | Reports and skips unapproved install scripts | npm install-scripts approve @speranto/speranto, then npm rebuild @speranto/speranto | | pnpm | Reports dependencies awaiting build approval | pnpm approve-builds @speranto/speranto | | Bun | Blocks untrusted dependency lifecycle scripts | bun pm trust @speranto/speranto | | Yarn 4.14+ | Disables third-party postinstall scripts | Set dependenciesMeta["@speranto/speranto"].built to true, then run yarn install |

For Yarn, add the approval to the consuming project's top-level package.json:

{
  "dependenciesMeta": {
    "@speranto/speranto": {
      "built": true
    }
  }
}

npm pins script approval to the currently installed package version by default, so an upgrade may require approval again. pnpm and Yarn store package-level approval in the consuming project. Older package-manager versions or projects with a broader script policy may run the hook without an approval step.

Regardless of package-manager policy, install or repair the documentation directly with:

speranto setup-agents

This command performs the same synchronization as the postinstall hook and remains available when the hook was blocked. In CI, run it explicitly or use --check to fail when committed agent documentation is stale.

Check whether the installed copy is current, or remove all Speranto-managed references and files:

speranto setup-agents --check
speranto setup-agents --remove

Set SPERANTO_SKIP_AGENT_DOCS=1 to disable automatic installation. The generated .agents/speranto/guide.md and its manifest may be committed so agents can use them before dependencies are installed. Do not edit the generated guide directly; update project-specific instructions in AGENTS.md or CLAUDE.md instead.

Development

Install pnpm using the current official Node.js installer, then install dependencies, type-check, test, and build:

npx get-pnpm
pnpm install
pnpm exec tsc --noEmit
pnpm test
pnpm build

pnpm test starts the PostgreSQL 16 test container and runs the complete suite with LLM_API_KEY=test. Use pnpm test:unit to run the suite without PostgreSQL.

Agent-facing documentation is maintained in docs/agent-guide.md. Update it in the same change as CLI flags, configuration, generated-file behavior, or recommended translation workflows that affect coding agents.

Versioning

Keep package.json as the source of truth. The sync step updates shared package metadata in jsr.json (name, version, license, description) while leaving JSR-specific fields like exports and publish intact:

pnpm bump:version patch
pnpm bump:version minor
pnpm bump:version major

You can also create prereleases or set an explicit version:

pnpm bump:version prerelease beta
pnpm bump:version 1.0.0

If package.json was edited manually, resync jsr.json with:

pnpm sync:version

Usage

Run Speranto from your workspace directory:

speranto

Configuration

Creating a configuration file is strongly recommended. Speranto looks for a configuration file in your workspace:

  • speranto.config.ts (TypeScript, recommended)
  • speranto.config.js (JavaScript)

Example Configuration

// speranto.config.ts
import type { Config } from '@speranto/speranto'

const config: Config = {
  model: 'gpt-4o-mini',
  sourceLang: 'en',
  targetLangs: ['es', 'fr', 'de', 'it'],
  provider: 'openai',
  apiKey: process.env.OPENAI_API_KEY,
  files: {
    sourceDir: './content',
    targetDir: './content/[lang]',
    useLangCodeAsFilename: false,
    maxStringsPerGroup: 200,
  },
}

export default config
// speranto.config.js
const config = {
  model: 'mistral-large-latest',
  sourceLang: 'en',
  targetLangs: ['nl'],
  provider: 'mistral',
  apiKey: process.env.MISTRAL_API_KEY,
  files: {
    sourceDir: './i18n/languages',
    targetDir: './i18n/languages',
    useLangCodeAsFilename: true,
  },
}

export default config

Configuration Options

| Option | Type | Description | | ----------------- | ---------- | ----------------------------------------------------------------------------- | | model | string | The AI model to use for translation | | sourceLang | string | Source language code (e.g., 'en' for English) | | targetLangs | string[] | Array of target language codes | | provider | string | LLM provider: 'openai', 'ollama', 'mistral', or any OpenAI-compatible | | apiKey | string | API key for the LLM provider | | baseUrl | string | Provider base URL (overrides the provider default) | | ollama | object | Ollama model lifecycle and inference settings (see below) | | concurrency | number | Global LLM call limit across languages and sources (default: 5, local: 1) | | timeout | number | Request timeout in milliseconds (default: 600000 / 10 minutes) | | verbose | boolean | Print the resolved configuration with secrets redacted | | retranslate | boolean | Force retranslation of all values, even if already translated | | init | boolean | Build state from existing translations without translating | | dryRun | boolean | Report pending work and approximate source tokens without making changes | | instructionsDir | string | Directory containing language-specific instruction files (see below) |

File Translation Options (files)

| Option | Type | Description | | ----------------------- | ---------- | ------------------------------------------------------------------- | | sourceDir | string | Directory containing source files | | targetDir | string | Output directory pattern (use [lang] as placeholder) | | useLangCodeAsFilename | boolean | Use language code as filename (e.g., en.json → es.json) | | maxStringsPerGroup | number | Maximum strings per translation batch (helps with large files) | | excludeKeys | string[] | Field names to exclude from translation (matched against leaf keys) |

Speranto keeps file translation state in a sidecar .speranto/ directory so it can use hash-based change detection. That lets it skip unchanged files quickly and only retranslate changed groups/chunks on later runs.

Translation failures are reported as errors and cause a non-zero CLI exit. Speranto validates group responses before updating output or sidecar state, so a failed file is not partially written. Other files that completed successfully are committed before the command exits.

Target languages, files, and database tables are prepared concurrently. All LLM requests pass through one global FIFO queue, so increasing parallelism never multiplies concurrency by the number of languages. Local Ollama and localhost endpoints default to one active request; set concurrency explicitly when the local server supports continuous batching.

Before translation starts, Speranto reports the complete plan: file and table targets, pending semantic translation jobs, cached work, and database rows. Interactive terminals show overall and per-language progress plus the currently active file groups or database rows. Redirected and CI output uses stable planning, periodic progress, language completion, rate-limit, and final summary lines instead of terminal redraws.

Ollama

Ollama runs translations locally without an API key. Install and start Ollama, then configure an instruction-following model:

const config: Config = {
  provider: 'ollama',
  model: 'gemma3:4b',
  sourceLang: 'en',
  targetLangs: ['nl'],
  instructionsDir: './instructions',
  ollama: {
    autoPull: false,
    keepAlive: '10m',
    contextLength: 8192,
    temperature: 0.2,
  },
  files: {
    sourceDir: './content',
    targetDir: './content/[lang]',
  },
}

Speranto checks that the Ollama server is reachable and that the configured model is installed. When autoPull is false (the default), a missing model produces the exact ollama pull command to run. Set it to true to let Speranto download a missing model automatically. JSON translation groups use Ollama's native JSON output mode.

| Ollama option | Type | Description | | --------------- | ------------------ | --------------------------------------------------------- | | autoPull | boolean | Download a missing model automatically (default: false) | | keepAlive | string \| number | How long Ollama keeps the model loaded | | contextLength | number | Context window passed as Ollama's num_ctx | | temperature | number | Sampling temperature passed to Ollama |

Use baseUrl for a remote server or a Docker hostname, for example http://ollama:11434. Set apiKey only when an authenticated proxy or hosted Ollama endpoint requires it.

Language-Specific Instructions

You can provide custom translation instructions for each target language by creating Markdown files in an instructions directory:

instructions/
├── es.md    # Spanish-specific instructions
├── fr.md    # French-specific instructions
└── nl.md    # Dutch-specific instructions

Then reference it in your config:

const config: Config = {
  // ...
  instructionsDir: './instructions',
}

Example instruction file (instructions/nl.md):

# Instructions for Dutch Translation

- Use informal "je/jij" instead of formal "u"
- Keep technical terms in English when commonly used
- Use short, direct sentences

Command Line Options

You can override configuration with command line flags:

# Specify a custom config file
speranto --config ./custom-config.js

# Override specific options
speranto --model gpt-4o-mini --source-lang en --target-langs es,fr,de

# Use a custom OpenAI-compatible provider
speranto --provider custom --base-url https://my-llm.example.com/v1

# Force retranslation of all values
speranto --retranslate

# Build state from existing translations without translating
speranto --init

# Preview pending work and approximate source-token usage
speranto --dry-run

# All available options
speranto \
  -c, --config <path>              # Path to config file (auto-detects .ts or .js when omitted)
  -m, --model <model>              # Model to use for translation
  -s, --source-lang <lang>         # Source language code
  -l, --target-langs <langs>       # Target language codes (comma-separated)
  -p, --provider <provider>        # LLM provider (openai, ollama, mistral, or any OpenAI-compatible)
  -k, --api-key <key>              # API key for LLM provider
  -b, --base-url <url>             # Base URL for OpenAI-compatible API
  -i, --instructions-dir <path>    # Directory containing language instruction files
  -n, --concurrency <number>       # Global LLM call limit (default 5, local 1)
  -v, --verbose                    # Enable verbose output for debugging
  -r, --retranslate                # Force retranslation of all values
  --init                           # Build state from existing translations
  --dry-run                        # Report pending work without translating or writing

Database Translation

Speranto can also translate content stored in database tables. This is useful for CMS systems or applications that store translatable content in a database.

Simply add a database section to your config file alongside or instead of files:

// speranto.config.ts
import type { Config } from '@speranto/speranto'

const config: Config = {
  model: 'gpt-4o-mini',
  sourceLang: 'en',
  targetLangs: ['es', 'fr', 'de'],
  provider: 'openai',
  apiKey: process.env.OPENAI_API_KEY,
  database: {
    type: 'postgres', // 'sqlite' or 'postgres'
    connection: process.env.DATABASE_URL!,
    tables: [
      {
        name: 'articles',
        columns: ['title', 'body', 'summary'],
        idColumn: 'id', // optional, defaults to 'id'
        langColumn: 'lang', // optional, use row language instead of global sourceLang
      },
      {
        name: 'products',
        columns: ['name', 'description'],
      },
    ],
    translationTableSuffix: '_translations', // optional, defaults to '_translations'
    concurrency: 10, // optional, number of concurrent translations
  },
}

export default config

SQLite Example

const config: Config = {
  // ... other options
  database: {
    type: 'sqlite',
    connection: './data/content.db',
    tables: [
      {
        name: 'posts',
        columns: ['title', 'content'],
      },
    ],
  },
}

Database Configuration Options (database)

| Option | Type | Description | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------- | | type | string | Database type: 'sqlite' or 'postgres' | | connection | string | Connection string (file path for SQLite, URL for PostgreSQL) | | tables | array | Array of tables to translate (see below) | | translationTableSuffix | string | Suffix for translation tables (default: '_translations') | | concurrency | number | Positive integer limiting active database row jobs (default: top-level value or 10); the global LLM limit still applies |

Table Configuration

| Option | Type | Description | | ------------ | ---------- | ---------------------------------------------------------------- | | name | string | Table name | | schema | string | Schema name (PostgreSQL only, default: 'public') | | columns | string[] | Array of column names to translate | | idColumn | string | Primary key column (default: 'id') | | langColumn | string | Optional source-language column for row-level language detection |

How It Works

For each source table, Speranto creates a translation table (e.g., articles_translations) with the following structure:

| Column | Description | | --------------------- | -------------------------------------------------------------------- | | id | Auto-incrementing primary key | | source_id | Reference to the source row | | lang | Language code for the stored row, including the base/source language | | source_lang | Source language used to generate this row | | row_source_hash | Hash of the current source content for fast skip checks | | field_source_hashes | JSON map of per-field hashes for partial retranslations | | <column> | Stored content for each specified column | | created_at | Timestamp of creation | | updated_at | Timestamp of last update |

The translation table is now the canonical read model for all languages. Speranto upserts the base/source language row into that table as well as translated rows, so consumers can query a single table regardless of language.

Database change detection is hash-based:

  • a row-level hash skips unchanged rows quickly
  • per-field hashes allow Speranto to retranslate only changed fields instead of the full row

If langColumn is configured, Speranto uses the row value as the source language for that record; otherwise it falls back to the global sourceLang.

Cleaning Up Database Translations

Speranto does not delete translation rows during normal translation runs. Use the dedicated cleanup command to remove rows for deleted source records and languages that are no longer the source language or one of the configured target languages:

speranto cleanup

The command displays the number of stale rows per table and asks for confirmation before deleting anything. Enter y or yes to approve. For non-interactive environments, pass --yes to approve the displayed cleanup automatically:

speranto cleanup --yes

Use --config <path> when the configuration is not in the current directory.

Database Test Commands

The full test script starts PostgreSQL and runs all tests:

pnpm test

To run the database suites individually:

LLM_API_KEY=test pnpm exec vitest run tests/database/sqlite.test.ts
pnpm docker:up
LLM_API_KEY=test pnpm exec vitest run tests/database/postgres.test.ts

Stop the PostgreSQL test container afterward with:

docker compose -p speranto -f tests/docker-compose.yml down

Combining Files and Database

You can translate both files and database content in a single run by including both files and database in your config:

const config: Config = {
  model: 'gpt-4o-mini',
  sourceLang: 'en',
  targetLangs: ['es', 'fr'],
  provider: 'openai',
  files: {
    sourceDir: './content',
    targetDir: './content/[lang]',
  },
  database: {
    type: 'postgres',
    connection: process.env.DATABASE_URL!,
    tables: [{ name: 'posts', columns: ['title', 'body'] }],
  },
}