@swalha1999/translate
v0.4.0
Published
AI-powered translation for user-generated content with intelligent caching
Maintainers
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/clientQuick 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_xxxDatabase Adapters
Drizzle Adapter
npm install @swalha1999/translate-adapter-drizzle drizzle-orm1. 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 migrate3. 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/client1. 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 dev3. 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
resourceTypeandresourceIdField
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') // falsetranslate.clearCache(language?)
Clear cached translations.
await translate.clearCache() // Clear all
await translate.clearCache('he') // Clear specific languagetranslate.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
