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

@xtatistix/autotranslate

v0.2.10

Published

Attribute-based DOM auto-translation for web apps

Readme

auto-translate

DOM-driven auto-translation for web apps. Pairs input fields via data attributes, detects empty targets on blur, and batch-translates through pluggable providers (DeepL, Google, LLM). Includes built-in free fallback providers (MyMemory -> Glosbe, no API keys) for resilient translation. Framework-agnostic — works with Angular, React, Vue, or vanilla JS.

How it works

Annotate your inputs with data-at-key and data-at-locale. When a user fills in one language and leaves the field, auto-translate detects the empty pair and fills it.

<label>Turkish</label>
<input data-at-key="edu" data-at-locale="tr" value="İlkokul" />

<label>English</label>
<input data-at-key="edu" data-at-locale="en" value="" />
<!-- ↑ auto-filled with "Primary School" on blur -->

No wrapper components. No framework bindings. Just attributes.

Install

npm install @istabot/autotranslate

Quick start

import { AutoTranslate } from '@istabot/autotranslate';

AutoTranslate.init({
  providers: {
    short: [{ type: 'deepl', apiKey: 'YOUR_KEY' }],
    long:  [{ type: 'llm', apiKey: 'YOUR_KEY', model: 'gpt-4o-mini' }]
  },
  sourceLocale: 'tr',
  targetLocales: ['en'],
});

That's it. Every data-at-key pair in the DOM is now live.

You can also omit providers completely. In that case, auto-translate uses built-in free fallback providers (mymemory -> glosbe).

Production setup (no API keys in the browser)

AutoTranslate.init({
  providers: {
    short: [{ type: 'proxy', endpoint: '/api/translate' }],
    long:  [{ type: 'proxy', endpoint: '/api/translate' }]
  },
  sourceLocale: 'tr',
  targetLocales: ['en'],
});

Your backend receives { texts, from, to, context } and returns { translations: string[] }.

Data attributes

| Attribute | Required | Description | |---|---|---| | data-at-key | yes | Groups inputs that represent the same translatable content | | data-at-locale | yes | BCP 47 locale code (tr, en, de, ar, etc.) | | data-at-context | no | Domain hint for better translations (e.g., "medical", "statistics") | | data-at-maxlength | no | Truncate translation to N characters |

Configuration

AutoTranslate.init({
  // --- Providers (optional) ---
  providers: {
    short: [{ type: 'deepl', apiKey: '...' }],
    long:  [{ type: 'llm', apiKey: '...', model: 'gpt-4o-mini' }]
  },
  // If omitted, built-in free fallback providers are used.

  // --- Routing ---
  threshold: 50,              // characters — above this → long provider

  // --- Locales ---
  sourceLocale: 'tr',
  targetLocales: ['en'],

  // --- Trigger ---
  trigger: 'blur',            // 'blur' | 'manual'
  enableGuard: () => true,    // return false → package does nothing

  // --- Batching ---
  batch: {
    debounceMs: 300,
    maxSize: 50,
  },

  // --- Behavior ---
  overwrite: false,           // true → re-translate even when target has value
  root: document,             // narrow the scope to a specific container
  context: 'statistics',      // global domain hint for LLM translations
});

Events

const at = AutoTranslate.init({ ... });

at.on('translated', (results) => {
  console.log(results);
  // [{ key: 'edu', locale: 'en', value: 'Primary School' }]
});

at.on('error', (err) => {
  console.error(err.key, err.message);
});

at.on('before-send', (batch) => { /* inspect or log */ });
at.on('skipped', (reason) => { /* enable guard off, target not empty, etc. */ });

Manual control

at.translateAll();   // translate all empty targets now
at.scan();           // re-scan DOM after dynamic content changes
at.update({          // patch one or multiple config fields without re-init
  sourceLocale: 'de',
  targetLocales: ['en', 'fr'],
});
at.destroy();        // remove all listeners and observers

Trigger direction

  • Translation is one-way from sourceLocale to target locales.
  • Blurring a target-locale field never triggers translation.
  • In blur mode, only fields that match sourceLocale can trigger translation.

Dynamic DOM

auto-translate detects new inputs added after init (Angular *ngFor, React lists, vanilla JS) via MutationObserver. Enable it explicitly:

AutoTranslate.init({
  observe: true,
  root: document.querySelector('.editor-panel'),
  // ...
});

Providers

| Provider | Best for | Batch support | API key required | |---|---|---|---| | deepl | Short labels, UI text | yes (array) | yes | | google | Short labels, UI text | yes (array) | yes | | llm | Long text, domain-specific terms | yes (JSON array prompt) | yes | | proxy | Production (keys stay on server) | yes | no (server-side) | | mymemory | Free fallback translation | no (single request per text) | no | | glosbe | Free fallback when MyMemory fails | no (single request per text) | no |

Silent fallback chain

You can pass a provider chain for automatic failover. The package retries with bounded backoff and silently moves to the next provider when one fails.

Built-in fallback behavior:

  • Your configured provider chain is tried first.
  • mymemory is automatically appended as free fallback.
  • glosbe is automatically appended after mymemory.
  • No API key is required for these built-in fallbacks.
AutoTranslate.init({
  providers: {
    short: [
      { type: 'google', apiKey: 'GOOGLE_KEY' },
      { type: 'deepl', apiKey: 'DEEPL_KEY' },
      { type: 'proxy', endpoint: '/api/translate' }
    ],
    long: [
      { type: 'llm', apiKey: 'OPENAI_KEY', model: 'gpt-4o-mini' },
      { type: 'proxy', endpoint: '/api/translate' }
    ]
  }
});

error is emitted only when all configured providers and built-in free fallbacks (mymemory, glosbe) fail.

Custom provider

import type { TranslationProvider } from '@istabot/autotranslate';

const myProvider: TranslationProvider = {
  translate: async (texts, from, to, context?) => {
    const res = await fetch('/my-endpoint', {
      method: 'POST',
      body: JSON.stringify({ texts, from, to, context }),
    });
    const data = await res.json();
    return data.translations; // string[], same order as input
  }
};

AutoTranslate.init({
  providers: {
    short: myProvider,
    long: myProvider,
  },
  // ...
});

Framework examples

Angular

// app.component.ts
import { AutoTranslate } from '@istabot/autotranslate';

ngOnInit() {
  this.at = AutoTranslate.init({
    root: this.editorRef.nativeElement,
    observe: true,
    providers: {
      short: [{ type: 'proxy', endpoint: '/api/translate' }],
      long: [{ type: 'proxy', endpoint: '/api/translate' }],
    },
    sourceLocale: 'tr',
    targetLocales: ['en'],
    enableGuard: () => this.syncLabels,
  });
}

onSyncLabelsChange(value: boolean) {
  this.syncLabels = value;
  this.at.update({
    enableGuard: () => this.syncLabels,
  });
}

ngOnDestroy() {
  this.at.destroy();
}

React

import { AutoTranslate } from '@istabot/autotranslate';

function VariableEditor() {
  const containerRef = useRef(null);
  const atRef = useRef(null);
  const [sync, setSync] = useState(false);

  useEffect(() => {
    const at = AutoTranslate.init({
      root: containerRef.current,
      observe: true,
      providers: {
        short: [{ type: 'proxy', endpoint: '/api/translate' }],
        long: [{ type: 'proxy', endpoint: '/api/translate' }],
      },
      sourceLocale: 'tr',
      targetLocales: ['en'],
      enableGuard: () => sync,
    });
    atRef.current = at;
    return () => at.destroy();
  }, []);

  useEffect(() => {
    atRef.current?.update({
      enableGuard: () => sync,
    });
  }, [sync]);

  return <div ref={containerRef}>...</div>;
}

Design decisions

Why data attributes instead of a component API? Framework-agnostic by design. A React hook or Angular directive can wrap auto-translate, but the core doesn't depend on any framework. This means it works in server-rendered HTML, CMS editors, legacy jQuery apps — anything with a DOM.

Why hybrid providers? Short labels like "İlkokul" → "Primary School" don't need an LLM. A translation API is faster, cheaper, and more deterministic. LLMs are reserved for longer text where context and domain knowledge matter.

Why blur, not keystroke? Translating on every keystroke wastes API calls on incomplete words. Blur means the user finished typing. The debounce window batches multiple fields changed in quick succession.

Why no overwrite by default? If a user manually typed an English label, auto-translate should not silently replace it. Overwrite is opt-in.

Bundle

  • Zero runtime dependencies
  • ESM + CJS + TypeScript declarations
  • Tree-shakeable — unused providers are excluded
  • Target: < 5KB gzipped (core only)

Current verification (local): dist/index.js gzipped size is below 5KB.

Release checklist

npm test
npm run test:coverage
npm run build
npm run size
npm run publish:github
npm run publish:npm

Requirements

  • Node 18+
  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • TypeScript 5+ (for type-checking only — JavaScript works fine)

License

MIT

Credits

Built by istabot.

Inspired by the real-world pain of manually translating hundreds of variable labels in statistical analysis software.