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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@swalha1999/translate

v0.4.0

Published

AI-powered translation for user-generated content with intelligent caching

Readme

@swalha1999/translate

AI-powered translation for user-generated content with intelligent caching. Supports multiple AI providers (OpenAI, Anthropic, Google Gemini, Mistral, Groq) through a unified interface.

Features

  • Multi-provider support - OpenAI, Anthropic, Google Gemini, Mistral, Groq
  • Intelligent caching - Hash-based and resource-based caching
  • Manual overrides - Override AI translations for specific resources
  • RTL support - Built-in RTL language detection
  • Batch translation - Translate multiple texts efficiently
  • Object translation - Type-safe translation of object fields in one line
  • Language detection - Auto-detect source language
  • Database adapters - Memory, Drizzle, Prisma
  • Analytics - Custom analytics callback for tracking translations

Installation

# Core package
npm install @swalha1999/translate

# With Drizzle adapter
npm install @swalha1999/translate @swalha1999/translate-adapter-drizzle drizzle-orm

# With Prisma adapter
npm install @swalha1999/translate @swalha1999/translate-adapter-prisma @prisma/client

Quick Start

import { createTranslate, createMemoryAdapter, openai } from '@swalha1999/translate'

const translate = createTranslate({
  adapter: createMemoryAdapter(),
  model: openai('gpt-4o-mini'),
  languages: ['en', 'ar', 'he', 'ru'] as const,
})

// Translate text
const result = await translate.text({
  text: 'Hello, world!',
  to: 'ar',
})
console.log(result.text) // مرحبا بالعالم

// Batch translate
const results = await translate.batch({
  texts: ['Hello', 'Goodbye'],
  to: 'he',
})

// Detect language
const detected = await translate.detectLanguage('مرحبا')
console.log(detected.language) // 'ar'

// Translate object fields (type-safe!)
const todo = { id: '1', title: 'Buy groceries', description: 'Milk and eggs' }
const translated = await translate.object(todo, {
  fields: ['title', 'description'],
  to: 'ar',
  resourceType: 'todo',       // optional: enables resource-based caching
  resourceIdField: 'id',      // optional: which field contains the ID
})
// { id: '1', title: 'شراء البقالة', description: 'حليب وبيض' }

// Translate array of objects
const todos = [
  { id: '1', title: 'Buy groceries', done: false },
  { id: '2', title: 'Call mom', done: true },
]
const translatedTodos = await translate.objects(todos, {
  fields: ['title'],
  to: 'he',
  context: 'task management app',
  resourceType: 'todo',
  resourceIdField: 'id',
})

AI Providers

All providers are re-exported from the main package:

import { openai, anthropic, google, mistral, groq } from '@swalha1999/translate'

// OpenAI
const t1 = createTranslate({
  model: openai('gpt-4o-mini'),
  // ...
})

// Anthropic Claude
const t2 = createTranslate({
  model: anthropic('claude-3-haiku-20240307'),
  // ...
})

// Google Gemini
const t3 = createTranslate({
  model: google('gemini-1.5-flash'),
  // ...
})

// Mistral
const t4 = createTranslate({
  model: mistral('mistral-small-latest'),
  // ...
})

// Groq (fast inference)
const t5 = createTranslate({
  model: groq('llama-3.1-70b-versatile'),
  // ...
})

Set API keys via environment variables:

OPENAI_API_KEY=sk-xxx
ANTHROPIC_API_KEY=sk-ant-xxx
GOOGLE_GENERATIVE_AI_API_KEY=xxx
MISTRAL_API_KEY=xxx
GROQ_API_KEY=gsk_xxx

Database Adapters

Drizzle Adapter

npm install @swalha1999/translate-adapter-drizzle drizzle-orm

1. Add the schema to your Drizzle config:

You can import the pre-built schema:

// schema.ts
import { translationCache } from '@swalha1999/translate-adapter-drizzle/schema'

export { translationCache }

Or define it yourself (PostgreSQL):

// schema.ts
import { pgTable, text, timestamp, boolean, index } from 'drizzle-orm/pg-core'

export const translationCache = pgTable('translation_cache', {
  id: text('id').primaryKey(),
  sourceText: text('source_text').notNull(),
  sourceLanguage: text('source_language').notNull(),
  targetLanguage: text('target_language').notNull(),
  translatedText: text('translated_text').notNull(),
  resourceType: text('resource_type'),
  resourceId: text('resource_id'),
  field: text('field'),
  isManualOverride: boolean('is_manual_override').notNull().default(false),
  provider: text('provider').notNull(),
  model: text('model'),
  createdAt: timestamp('created_at').notNull().defaultNow(),
  updatedAt: timestamp('updated_at').notNull().defaultNow(),
  lastUsedAt: timestamp('last_used_at').notNull().defaultNow(),
}, (table) => ({
  targetLangIdx: index('tc_target_lang_idx').on(table.targetLanguage),
  resourceIdx: index('tc_resource_idx').on(table.resourceType, table.resourceId, table.field),
  manualIdx: index('tc_manual_idx').on(table.isManualOverride),
}))

2. Run migrations to create the table:

npx drizzle-kit generate
npx drizzle-kit migrate

3. Use the adapter:

import { createTranslate, openai } from '@swalha1999/translate'
import { createDrizzleAdapter } from '@swalha1999/translate-adapter-drizzle'
import { translationCache } from './schema'
import { drizzle } from 'drizzle-orm/node-postgres'

const db = drizzle(process.env.DATABASE_URL!)

const translate = createTranslate({
  adapter: createDrizzleAdapter({ db, table: translationCache }),
  model: openai('gpt-4o-mini'),
  languages: ['en', 'ar', 'he', 'ru'] as const,
})

Prisma Adapter

npm install @swalha1999/translate-adapter-prisma @prisma/client

1. Add the model to your Prisma schema:

// schema.prisma
model TranslationCache {
  id              String   @id
  sourceText      String
  sourceLanguage  String
  targetLanguage  String
  translatedText  String
  resourceType    String?
  resourceId      String?
  field           String?
  isManualOverride Boolean @default(false)
  provider        String
  model           String?
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  lastUsedAt      DateTime @default(now())

  @@index([targetLanguage])
  @@index([resourceType, resourceId, field])
  @@index([isManualOverride])
  @@map("translation_cache")
}

2. Run migrations:

npx prisma migrate dev

3. Use the adapter:

import { createTranslate, openai } from '@swalha1999/translate'
import { createPrismaAdapter } from '@swalha1999/translate-adapter-prisma'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

const translate = createTranslate({
  adapter: createPrismaAdapter({ prisma }),
  model: openai('gpt-4o-mini'),
  languages: ['en', 'ar', 'he', 'ru'] as const,
})

API Reference

createTranslate(config)

Creates a translate instance.

interface TranslateConfig {
  adapter: CacheAdapter
  model: LanguageModel
  languages?: readonly string[]
  temperature?: number // default: 0.3
  verbose?: boolean // log LLM input/output
  onAnalytics?: (event: AnalyticsEvent) => void | Promise<void>
}

translate.text(params)

Translate a single text.

const result = await translate.text({
  text: 'Hello',
  to: 'ar',
  from: 'en', // optional, auto-detected if not provided
  context: 'greeting', // optional, helps with ambiguous terms
  resourceType: 'post', // optional, for resource-based caching
  resourceId: '123',
  field: 'title',
})

// Result
{
  text: 'مرحبا',
  from: 'en',
  to: 'ar',
  cached: false,
  isManualOverride: false
}

translate.batch(params)

Translate multiple texts.

const results = await translate.batch({
  texts: ['Hello', 'Goodbye'],
  to: 'he',
  from: 'en',
  context: 'greetings',
})

translate.object(item, params)

Translate specific fields of an object. Type-safe - only accepts fields with string values.

const todo = {
  id: '123',
  title: 'Buy groceries',
  description: 'Milk, eggs, and bread',
  priority: 5,
}

const translated = await translate.object(todo, {
  fields: ['title', 'description'], // Only string fields allowed
  to: 'ar',
  from: 'en',              // optional
  context: 'task management', // optional
  resourceType: 'todo',    // optional: enables resource-based caching
  resourceIdField: 'id',   // optional: field containing the resource ID
})

// Result: { id: '123', title: 'شراء البقالة', description: '...', priority: 5 }

Error handling: On translation failure, logs to console and returns the original object unchanged.

translate.objects(items, params)

Translate specific fields across an array of objects. Batches all translations efficiently.

const todos = [
  { id: '1', title: 'Buy groceries', description: 'Milk and eggs' },
  { id: '2', title: 'Call mom', description: null },
  { id: '3', title: 'Exercise', description: 'Go for a run' },
]

const translated = await translate.objects(todos, {
  fields: ['title', 'description'],
  to: 'he',
  context: 'task management app',
  resourceType: 'todo',
  resourceIdField: 'id',   // Each item's ID is used for caching
})

Features:

  • Batches all text fields into a single translation call (or per-field when using resource caching)
  • Skips null/undefined/empty fields automatically
  • Returns original array on error (with console.error log)
  • Preserves object structure and non-translated fields
  • Resource-based caching with resourceType and resourceIdField

translate.setManual(params)

Set a manual translation override.

await translate.setManual({
  text: 'flat',
  translatedText: 'דירה',
  to: 'he',
  resourceType: 'property',
  resourceId: '123',
  field: 'type',
})

translate.clearManual(params)

Clear a manual override.

await translate.clearManual({
  resourceType: 'property',
  resourceId: '123',
  field: 'type',
  to: 'he',
})

translate.detectLanguage(text)

Detect the language of a text.

const result = await translate.detectLanguage('مرحبا')
// { language: 'ar', confidence: 0.9 }

translate.isRTL(language)

Check if a language is RTL.

translate.isRTL('ar') // true
translate.isRTL('en') // false

translate.clearCache(language?)

Clear cached translations.

await translate.clearCache() // Clear all
await translate.clearCache('he') // Clear specific language

translate.clearResourceCache(resourceType, resourceId)

Clear cache for a specific resource.

await translate.clearResourceCache('property', '123')

translate.getCacheStats()

Get cache statistics.

const stats = await translate.getCacheStats()
// {
//   totalEntries: 150,
//   byLanguage: { ar: 50, he: 100 },
//   manualOverrides: 5
// }

Analytics

Track translation events with a custom analytics callback. Useful for monitoring usage, debugging, and integrating with analytics services.

Setup

import { createTranslate, createMemoryAdapter, openai } from '@swalha1999/translate'
import type { AnalyticsEvent } from '@swalha1999/translate'

const translate = createTranslate({
  adapter: createMemoryAdapter(),
  model: openai('gpt-4o-mini'),
  onAnalytics: (event: AnalyticsEvent) => {
    console.log(`[${event.type}] ${event.duration}ms`)

    // Send to your analytics service
    myAnalytics.track('translation', event)
  }
})

Event Types

The onAnalytics callback receives events for:

| Type | Description | |------|-------------| | translation | Successful AI translation | | cache_hit | Translation served from cache | | detection | Language detection completed | | error | Translation or detection failed |

AnalyticsEvent Interface

interface AnalyticsEvent {
  type: 'translation' | 'detection' | 'cache_hit' | 'error'
  text: string                    // Original text
  translatedText?: string         // Translated result (if applicable)
  from?: SupportedLanguage        // Source language
  to?: SupportedLanguage          // Target language
  cached: boolean                 // Whether result was from cache
  duration: number                // Time in milliseconds
  provider?: string               // AI provider (e.g., 'openai')
  model?: string                  // Model used (e.g., 'gpt-4o-mini')
  error?: string                  // Error message (for error events)
  resourceType?: string           // Resource type if provided
  resourceId?: string             // Resource ID if provided
  field?: string                  // Field name if provided
}

Example: Logging with Context

const translate = createTranslate({
  adapter: createMemoryAdapter(),
  model: openai('gpt-4o-mini'),
  onAnalytics: (event) => {
    if (event.type === 'error') {
      console.error(`Translation failed: ${event.error}`, {
        text: event.text,
        to: event.to,
        duration: event.duration,
      })
    } else if (event.type === 'translation') {
      console.log(`Translated "${event.text}" to ${event.to} in ${event.duration}ms`)
    } else if (event.type === 'cache_hit') {
      console.log(`Cache hit for "${event.text}" (${event.duration}ms)`)
    }
  }
})

Async Callbacks

The callback can be async. Errors in the callback won't block translations:

const translate = createTranslate({
  adapter: createMemoryAdapter(),
  model: openai('gpt-4o-mini'),
  onAnalytics: async (event) => {
    await fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify(event),
    })
  }
})

License

MIT