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

@devalxui/kova-translate

v0.1.0

Published

Free, dead-simple i18n. <K> tags + JSON dictionaries + URL-based locale detection. No AI, no API keys, no paid services. Works with React, Next.js, and plain HTML.

Downloads

141

Readme

@devalxui/kova-translate

Free, dead-simple i18n. Wrap text in <K> tags, run one command, ship every language. No AI, no API keys, no paid services.

npm install @devalxui/kova-translate
npx kova-translate init

Table of contents


Why kova-translate

Most i18n libraries make you do all the work — extract strings into JSON, write translation keys, wire up a provider, then pay $20/mo to a translation service to actually fill the JSON.

kova-translate flips it. You write English. It translates the rest, automatically, for free.

  • Wrap any text in <K>...</K> (React) or <k>...</k> (HTML).
  • Run npx kova-translate sync once and every locale's JSON is filled in for you.
  • Or pass autoTranslate: true and skip JSON files entirely — translations happen at runtime, cached forever in localStorage.
  • Variable placeholders ({name}, {count}) are protected and survive every translation pass.
  • A built-in setup wizard (kova-translate init) writes the config, adds npm scripts, and prints copy-pasteable code for your framework.

It works with React, Next.js, Vue, Svelte, vanilla HTML, or anything else.


The two modes

You pick one or both — the wizard asks you which during setup.

Build-time

Run npx kova-translate sync whenever you add or change <K> tags. It walks your source, extracts every string, and fills locales/de.json, locales/fr.json, etc. via the free Google Translate endpoint. You ship those JSON files with your app.

  • Pros: zero runtime overhead, works offline, deterministic, you can hand-tune any translation by editing the JSON.
  • Cons: re-run the CLI when text changes.

Runtime

Pass autoTranslate: true to the provider/init call and don't ship any JSON. When a <K> tag's text isn't yet known in the active locale, kova-translate quietly fetches a translation from MyMemory's free public endpoint, caches it forever in localStorage, and updates the DOM.

  • Pros: zero setup, write once in English, auto-translate forever. Subsequent visits hit cache, no network.
  • Cons: first paint per-string requires a network round-trip; relies on a free public service (5k chars/day per IP, 50k with email).

Hybrid (the default)

Use both. Ship JSON for your hot paths (landing page, billing copy) and let autoTranslate: true handle long-tail strings. Static dictionary takes priority; runtime fills in anything missing.


60-second quick start

# 1. install
npm install @devalxui/kova-translate

# 2. set up — interactive wizard, picks framework + mode + locales
npx kova-translate init

# 3. write code with <K> tags
#    (the wizard prints the snippet for your framework)

# 4. translate everything
npm run translate

That's it. You now have locales/de.json, locales/fr.json, etc. fully populated.


The setup wizard

npx kova-translate init

It asks:

  1. Source / default locale — usually en.
  2. Target locales — comma-separated 2-letter ISO codes: de, fr, es, ja, pt, it.
  3. Source code directory — usually ./src or ./app.
  4. Locale JSON output directory — usually ./locales or ./public/locales.
  5. Translation mode — build-time, runtime, or hybrid.
  6. Framework — React, Next.js, vanilla, Vue, Svelte, or other.
  7. Run initial scan + auto-translate now? — fills the JSON immediately.

Then it:

  • writes kova-translate.config.json (so future commands don't need flags)
  • adds npm run translate and npm run translate:auto to your package.json
  • runs the initial scan + translation for you
  • prints a tailor-made setup snippet for your framework

Re-run anytime to change settings; it offers your existing config as defaults.


Using it — React

// app/providers.tsx
import { TranslateProvider, K, useTranslate } from "@devalxui/kova-translate/react";
import en from "./locales/en.json";
import de from "./locales/de.json";
import fr from "./locales/fr.json";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <TranslateProvider
      locales={["en", "de", "fr"]}
      defaultLocale="en"
      translations={{ en, de, fr }}
      autoTranslate         // optional — fills missing keys at runtime
    >
      {children}
    </TranslateProvider>
  );
}
// any component
import { K, useTranslate } from "@devalxui/kova-translate/react";

export function Hero() {
  const { t, locale, setLocale } = useTranslate();
  return (
    <section>
      <h1><K>Welcome to Kova</K></h1>
      <p><K vars={{ name: "Alex" }}>{`Hello {name}`}</K></p>

      {/* same thing without the JSX wrapper: */}
      <h2>{t("Earn $500 per signup")}</h2>

      <select value={locale} onChange={(e) => setLocale(e.target.value)}>
        <option value="en">English</option>
        <option value="de">Deutsch</option>
        <option value="fr">Français</option>
      </select>
    </section>
  );
}

<TranslateProvider> props

| Prop | Type | Default | Notes | |------------------|--------------------------------------------|--------------------------|-------| | locales | string[] | required | All supported locales. | | defaultLocale | string | required | Source language code. | | translations | Record<string, Record<string, string>> | {} | Static dictionaries. Optional with autoTranslate. | | locale | string | auto-detected | Force a specific locale. | | autoDetect | boolean | true | Read locale from URL path's first segment. | | autoTranslate | boolean | false | Fetch missing keys from MyMemory at runtime, cache in localStorage. | | providers | TranslateProvider[] | BROWSER_PROVIDERS | Override the engine chain. | | cacheKey | string | "kova-translate-cache" | localStorage key. |

<K> props

| Prop | Type | Default | Notes | |-------------|-------------------------------------|------------|-------| | children | string | required | Source text. Use {name} for placeholders. | | vars | Record<string, string \| number> | undefined| Values for placeholders. | | as | keyof JSX.IntrinsicElements | Fragment | Wrap output in this tag. | | className | string | undefined| Applied when as is set. |

useTranslate()

const { t, plural, locale, setLocale } = useTranslate();
t("Welcome to Kova");                                // "Willkommen bei Kova"
t("Hello {name}", { name: "Alex" });                 // "Hallo Alex"
plural(5, { one: "1 cat", other: "{count} cats" }); // "5 Katzen"
setLocale("fr");                                     // switch language

Using it — Next.js

Use the React adapter inside app/layout.tsx, then optionally wire up middleware to auto-prefix the URL with the active locale.

// app/layout.tsx
"use client";
import { TranslateProvider } from "@devalxui/kova-translate/react";
import en from "../locales/en.json";
import de from "../locales/de.json";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <TranslateProvider
          locales={["en", "de"]}
          defaultLocale="en"
          translations={{ en, de }}
          autoTranslate
        >
          {children}
        </TranslateProvider>
      </body>
    </html>
  );
}
// middleware.ts
import { NextResponse } from "next/server";
import { createLocaleMiddleware } from "@devalxui/kova-translate/next";

export const middleware = createLocaleMiddleware(NextResponse, {
  locales: ["en", "de", "fr"],
  defaultLocale: "en",
  strategy: "redirect",       // "redirect" | "rewrite" | "passthrough"
  detectFromHeader: true,     // honor Accept-Language on first hit
});

export const config = {
  matcher: ["/((?!api|_next|.*\\..*).*)"],
};

The three middleware strategies:

  • passthrough — leave the URL alone (default). The provider still detects the active locale from the URL when present.
  • rewrite — internally rewrite /about to /{detected}/about. URL stays clean.
  • redirect — 302 to /{detected}/about. URL changes visibly.

Using it — plain HTML / Vue / Svelte / anything

The vanilla adapter scans the DOM for <k> tags and translates them in place. It works in any environment with a DOM — Vue components, Svelte components, raw HTML, anything that ends up rendering elements.

<h1><k>Welcome to Kova</k></h1>
<p><k>Hello {name}</k></p>
<button><k>Sign up</k></button>
<input data-k-placeholder="Search the docs">

<script type="module">
  import { init, setLocale, setVars } from "https://esm.sh/@devalxui/kova-translate/vanilla";

  init({
    locales: ["en", "de", "fr", "es", "ja"],
    defaultLocale: "en",
    autoDetect: true,
    autoTranslate: true,           // optional — fetches missing strings
    vars: { name: "Alex" },
    // translations: { en, de, ... } // optional with autoTranslate
  });
</script>

Drop-in <script> tag (no bundler)

If you don't want a build step or ES modules at all, use the global build from unpkg/jsdelivr:

<script src="https://unpkg.com/@devalxui/kova-translate/dist/kova-translate.global.js"></script>
<script>
  KovaTranslate.init({
    locales: ["en", "de", "fr"],
    defaultLocale: "en",
    autoTranslate: true,
  });

  // KovaTranslate.setLocale("de"), .setVars(...), .plural(...), etc.
</script>

Bundle is ~7.5 kB minified.

Vanilla API

import {
  init,           // initialize and start scanning
  setLocale,      // change active locale, re-translate all <k> tags
  setVars,        // merge new variable values, re-translate
  t,              // translate a single string outside the DOM
  getLocale,      // current active locale or null
  clearCache,     // wipe the localStorage cache
  destroy,        // stop the MutationObserver
} from "@devalxui/kova-translate/vanilla";

init() accepts:

| Option | Type | Default | Notes | |------------------|-------------------------------------------|--------------------------|-------| | locales | string[] | required | | | defaultLocale | string | required | | | translations | Record<string, Record<string, string>> | {} | Optional with autoTranslate. | | locale | string | auto-detected | | | autoDetect | boolean | true | First URL path segment. | | autoTranslate | boolean | false | Runtime auto-translate via MyMemory. | | tag | string | "k" | Custom tag name. | | vars | Record<string, string \| number> | {} | | | providers | TranslateProvider[] | BROWSER_PROVIDERS | | | cacheKey | string | "kova-translate-cache" | |

Vue

<script setup>
  import { onMounted } from "vue";
  import { init } from "@devalxui/kova-translate/vanilla";

  onMounted(() => {
    init({
      locales: ["en", "de"],
      defaultLocale: "en",
      autoTranslate: true,
    });
  });
</script>

<template>
  <h1><k>Welcome to Kova</k></h1>
</template>

Svelte

<script>
  import { onMount } from "svelte";
  import { init } from "@devalxui/kova-translate/vanilla";

  onMount(() => {
    init({
      locales: ["en", "de"],
      defaultLocale: "en",
      autoTranslate: true,
    });
  });
</script>

<h1><k>Welcome to Kova</k></h1>

Using it — Node / framework-agnostic core

For server-side rendering, scripts, or any non-DOM context, use the core directly.

import {
  translate,
  interpolate,
  detectLocaleFromPath,
  stripLocaleFromPath,
} from "@devalxui/kova-translate";

const locale = detectLocaleFromPath("/de/about", ["en", "de"], "en");   // "de"
const text = translate("Hello {name}", locale, dictionaries, "en", { name: "Alex" });

For programmatic auto-translation (CI scripts, custom tooling):

import { autoTranslate, NODE_PROVIDERS } from "@devalxui/kova-translate/engine";

const value = await autoTranslate("Welcome to Kova", "en", "ja", NODE_PROVIDERS);
//   -> "Kovaへようこそ"

Variable interpolation

Use curly braces in your source text:

<K vars={{ name: "Alex", count: 3 }}>
  {`Hi {name}, you have {count} new referrals.`}
</K>

Why the backtick template literal? React interprets {name} inside JSX as an expression. The template literal makes it a literal string with curly braces.

The CLI extracts these placeholders, sends the masked text (__KT0__, you have __KT1__ new referrals.) to the translator, and restores them after. The translation engine never sees your placeholder names — they're protected end-to-end.

In vanilla HTML (no JSX), you just write the curlies directly:

<k>Hi {name}, you have {count} new referrals.</k>

Plurals

Per-locale plural rules are handled with the browser's built-in Intl.PluralRules. Every CLDR category is supported: zero, one, two, few, many, other. Each form is its own translation key, so the scanner picks them all up.

React

import { Plural, useTranslate } from "@devalxui/kova-translate/react";

// component form
<Plural
  count={count}
  one="1 referral"
  other="{count} referrals"
/>

// hook form
const { plural } = useTranslate();
plural(count, {
  one: "1 unread message",
  other: "{count} unread messages",
});

Vanilla / anywhere

import { plural } from "@devalxui/kova-translate/vanilla";

plural(count, {
  one: "1 cat",
  other: "{count} cats",
});

Languages with multiple plural forms

Russian, Arabic, Polish, etc. — just pass every form your translation needs:

<Plural
  count={count}
  one="1 элемент"
  few="{count} элемента"
  many="{count} элементов"
  other="{count} элементов"
/>

{count} is automatically merged into vars; pass extra vars if you need them.

The CLI scanner extracts every form from <Plural one="..." other="..." /> props and from plural(count, { one: "...", other: "..." }) calls, treating each as a regular translation key.


HTML inside translated strings

By default <K> is text-only — safer and stops surprises. Opt in with dangerouslyAllowHtml when you want inline tags inside the translated string:

<K dangerouslyAllowHtml>
  {`Welcome to <strong>Kova</strong>, the last referral toolkit you'll need.`}
</K>

The string is translated as a whole (Google/MyMemory preserve HTML tags), then rendered via dangerouslySetInnerHTML. Only use this with trusted source text — never user input.

In vanilla, set data-html on the tag:

<k data-html>Welcome to <strong>Kova</strong>!</k>

The runtime swaps innerHTML instead of textContent. Source text is still captured before init, so re-translations apply correctly.

For richer mixed content (a <strong> from a component, not literal HTML), prefer composition with multiple <K> tags:

<>
  <K>Welcome to</K> <strong><K>Kova</K></strong>{". "}
  <K>The last referral toolkit.</K>
</>

The CLI

npx kova-translate <command> [options]

Commands

| Command | What it does | |--------------------------|--------------| | init | Interactive setup wizard. Run this first. | | sync (default) | Scans source code, then auto-translates every locale. | | scan | Scans only — extracts keys into JSON files (no translation). | | auto | Auto-translates only — assumes JSON files already exist. | | help | Prints help. |

Options

| Option | Notes | |---------------------|-------| | <src> (positional)| Source directory to scan. Default: ./src (or config). | | --out <dir> | Output directory for locale JSON. Default: ./locales. | | --locales <list> | Comma-separated target locales. | | --default <code> | Source/default locale. | | --force | Re-translate even non-empty entries. | | --delay <ms> | Delay between API calls. Default: 80. |

What the scanner extracts

| Pattern | Example | |----------------------|--------------------------------------| | React <K> element | <K>Welcome to Kova</K> | | <K> with vars | <K vars={{ name }}>{Hi {name}}</K> | | HTML <k> element | <k>Welcome to Kova</k> | | Function call t() | t("Sign in") or t('Sign up') |

It walks .ts, .tsx, .js, .jsx, .mjs, .cjs, .html, .vue, .svelte files, skipping node_modules, .next, dist, etc.


The runtime cache

When autoTranslate: true is on, every translated string is cached in localStorage under the key kova-translate-cache:

{
  "de": {
    "Welcome to Kova": "Willkommen bei Kova",
    "Sign up": "Registrieren"
  },
  "ja": {
    "Welcome to Kova": "Kovaへようこそ"
  }
}
  • Cache survives page reloads — no re-fetches.
  • Hand-edit it in DevTools to override any auto-translation.
  • Clear it with clearCache() (vanilla) or by deleting the localStorage entry.
  • Customize the storage key with cacheKey: "my-app-i18n".

The static translations prop always wins over the cache, so you can override any auto-translation by adding it to your JSON file.


How translation works under the hood

Two free, no-API-key engines:

  1. Google Translate (unofficial endpoint) — used in Node CLI. Same endpoint the Google Translate widget hits. No auth, but rate-limits at high volume. Best quality.
  2. MyMemory (api.mymemory.translated.net) — used in browsers. CORS-enabled. Free up to 5k chars/day per IP, 50k/day with email registered.

The engine tries them in order (autoTranslate() falls back if the first fails). You can supply your own provider chain — anything implementing { name, translate(text, source, target) } works:

import { autoTranslate, googleProvider, createMyMemoryProvider } from "@devalxui/kova-translate/engine";

const myMemory = createMyMemoryProvider({ email: "[email protected]" }); // 50k/day quota

const value = await autoTranslate("Welcome", "en", "de", [googleProvider, myMemory]);

Placeholder protection

Before sending to a provider, every {name} is replaced with __KT0__, __KT1__, etc. After the translation comes back, those tokens are restored verbatim. Translators never see your variable names.


Configuration file

kova-translate.config.json lives in your project root. The wizard creates it; the CLI reads it.

{
  "src": "./src",
  "out": "./public/locales",
  "defaultLocale": "en",
  "locales": ["en", "de", "fr", "es", "ja"],
  "framework": "next",
  "mode": "hybrid",
  "delayMs": 80
}

CLI flags always override config values. The framework and mode fields are reference for the wizard only — the CLI doesn't act on them.


Recipes

Add a new language to an existing project

# edit kova-translate.config.json — add "ja" to "locales"
npm run translate
# only the missing entries get translated

Re-translate everything (e.g. you fixed your source copy)

npx kova-translate sync --force

Translate only specific locales

npx kova-translate auto --locales de,fr --default en

Hand-edit a translation

Open locales/de.json and edit the value. The CLI never overwrites non-empty entries unless you pass --force.

Override a single translation at runtime

The static translations prop wins over the runtime cache. Add an entry to de.json and it takes precedence over whatever MyMemory returned.

Translate a string outside JSX

const { t } = useTranslate();
const error = t("Could not save changes");
toast.error(error);

Translate plain string in a Node script

import { autoTranslate, NODE_PROVIDERS } from "@devalxui/kova-translate/engine";
const ja = await autoTranslate("Welcome", "en", "ja", NODE_PROVIDERS);

Custom tag name (vanilla)

init({
  locales: ["en", "de"],
  defaultLocale: "en",
  tag: "trans",          // <trans>Hello</trans>
});

Detect locale from a custom URL pattern

import { detectLocaleFromPath, stripLocaleFromPath } from "@devalxui/kova-translate";
const locale = detectLocaleFromPath("/de/about", ["en", "de"], "en");  // "de"
const path = stripLocaleFromPath("/de/about", ["en", "de"]);            // "/about"

Troubleshooting

<K> text shows up untranslated

  • Build mode: did you run npm run translate after adding the tag?
  • Runtime mode: open DevTools → Network. You should see a request to api.mymemory.translated.net. If not, autoTranslate: true isn't set.
  • Either: check that the active locale is correctly detected (useTranslate().locale).

Translations look weird / over-formal

Free providers (especially MyMemory) sometimes pick odd phrasings. Two fixes:

  1. Hand-edit the JSON — kova-translate respects manual edits forever.
  2. Use Google's endpoint via the CLI — it generally produces nicer output than MyMemory.

429 Too Many Requests

You hit MyMemory's daily quota. Options:

  • Add email to your provider: createMyMemoryProvider({ email: "[email protected]" }) raises the cap to 50k chars/day.
  • Switch to build-time mode and run the CLI once — Google's endpoint via Node has higher tolerance.

{name} shows literally instead of the value

  • Did you pass vars? <K vars={{ name: "Alex" }}>{Hello {name}}</K>
  • Are placeholder names spelled identically?

Next.js <K> warning about children type

<K> requires a string child. If you're rendering a string from a variable, use t() instead:

const { t } = useTranslate();
return <span>{t(message)}</span>;

CLI isn't finding my files

The scanner skips node_modules, .next, dist, build, .git, coverage, out. It walks .ts/.tsx/.js/.jsx/.mjs/.cjs/.html/.htm/.vue/.svelte. Pass an explicit src dir as the first argument: npx kova-translate sync ./app.


FAQ

Is this really free? No catch?

Yes. The engine uses two public endpoints — Google's unofficial widget endpoint and MyMemory — both of which work without API keys. There are rate limits, not paywalls. For small-to-medium apps, you'll never hit them.

What if Google changes the endpoint?

The engine has a fallback chain — if Google fails, MyMemory takes over. You can also plug in your own provider (LibreTranslate, DeepL, your own Argos instance) by implementing TranslateProvider.

Is the translation quality good?

For short UI strings: yes, generally on par with paid services. For long-form copy (paragraphs of marketing text): acceptable, but you may want to hand-edit the worst offenders. The JSON is just a file — fix anything you don't like.

Will the runtime mode slow down my page?

The first paint of any untranslated string takes one network round-trip (~100-300ms). After that, every subsequent load reads from localStorage with zero network. So: slow once, fast forever.

Does this work with SSR?

The static translations prop works in SSR fine. autoTranslate: true only kicks in client-side (it needs localStorage), so SSR will render source text and the client hydration will translate after mount. For best SSR results, use build-time mode.

Can I use this with a database / CMS?

Yes — you don't have to use the JSON files. Pass any object shaped as { [locale]: { [key]: string } } to the translations prop. Load from your DB, your CMS, your CDN — anywhere.

What about plurals?

Supported via <Plural> (React), useTranslate().plural(count, forms) (React), and the plural(count, forms) function exported by the vanilla adapter. Uses Intl.PluralRules so every locale's CLDR categories work — one, few, many, etc. See the Plurals section.

Can I translate HTML inside <K>?

Yes — pass dangerouslyAllowHtml on <K> (React) or data-html on <k> (HTML). The string is translated whole and rendered via innerHTML / dangerouslySetInnerHTML. Only use with trusted source text.


License

MIT © KOVA