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

dev-lang-natural

v0.1.0

Published

Checks whether translated UI strings sound natural to a native speaker, not just grammatically correct. Uses an LLM (BYOK) as a native-speaker judge at CI/build time.

Readme

dev-lang-natural

Check whether your translated UI strings sound natural to a native speaker — not just grammatically correct.

Existing i18n linters catch missing keys, unused keys, and broken placeholders. None of them tell you that a translation reads like a robot wrote it. That gap is the whole reason this package exists.

EN     : "Shopping made easy"      (a landing-page tagline)
ROBOTIC: "खरीदारी बनाया आसान"       ← wrong verb form, robotic word order
NATURAL: "खरीदारी आसान बनाई गई"     ← what a native actually writes

Or the sneakier kind — grammatically perfect but wrong meaning:

EN     : "Save changes"           (a button)
WRONG  : "Änderungen sparen"       ← German "sparen" = save MONEY, not save data
NATURAL: "Änderungen speichern"    ← the correct verb

It runs at CI / build time (not runtime), uses your own LLM key (BYOK — Gemini, OpenAI, or Anthropic), and caches results so unchanged strings never cost you twice.


How it works

You can't judge Hindi / Arabic / Spanish yourself, so the engine makes an LLM role-play as a native speaker judge. For every string it does two things:

  1. Back-translation — hides the English, asks the LLM to translate the target back to English, and compares meaning. Catches drift.
  2. Naturalness rating — the LLM acts as a monolingual native speaker, scores 0–10 how natural it reads, flags machine-translation smell, and suggests the natural version.

Before any of that, placeholders like {name} and {count} are verified deterministically — a dropped placeholder crashes your app and matters more than tone.


Install

npm install -D dev-lang-natural

Configure

Create dev-lang-natural.config.js in your project root:

export default {
  localesDir: "./messages",
  sourceLocale: "en",
  targetLocales: ["hi", "ar", "es"],
  provider: "gemini",        // gemini | openai | anthropic
  threshold: 7,              // fail below this naturalness score
  context: {
    "auth.submit": "button on login form",
    "cart.checkout": "primary call-to-action button",
  },
};

The API key comes from the environment, never the config file:

export GEMINI_API_KEY=...      # or OPENAI_API_KEY / ANTHROPIC_API_KEY

Run

npx dev-lang-natural              # check every target locale
npx dev-lang-natural --locale hi  # check just one
npx dev-lang-natural --no-cache   # re-judge everything
npx dev-lang-natural --json       # machine-readable output for CI

Example output

Every flagged string tells you which file, which key, the score, what's wrong, and the fix:

  HI  messages/hi.json  ✗ 2/11 need attention

    [3/10] tagline — messages/hi.json  unnatural
      source:   Shopping made easy
      current:  खरीदारी बनाया आसान
      why:      wrong verb form; robotic word order
      fix:      खरीदारी आसान बनाई गई

    [3/10] cart.itemCount — messages/hi.json  broken placeholder
      source:   You have {count} items in your cart
      current:  आपकी टोकरी में सामान हैं
      missing:  {count} (placeholder dropped — will break the UI)
      means:    "There are items in your cart." (drifted from source)
      fix:      आपकी टोकरी में {count} आइटम हैं

  11 checked  ·  9 natural  ·  2 flagged  ·  9 cached · 2 judged

Exit codes (CI-friendly)

| Code | Meaning | |------|---------| | 0 | all translations read naturally | | 1 | one or more flagged (fails the build) | | 2 | usage / configuration error |


Programmatic API

The whole package is plumbing around one function, judge():

import { createProvider, judgeOne } from "dev-lang-natural";

const provider = createProvider("gemini");

const result = await judgeOne(provider, {
  key: "tagline",
  locale: "hi",
  source: "Shopping made easy",
  candidate: "खरीदारी बनाया आसान",
  context: "marketing tagline on the landing page",
});

console.log(result.score);       // 3
console.log(result.pass);        // false
console.log(result.suggestion);  // "खरीदारी आसान बनाई गई"

Or run the full pipeline yourself:

import { loadConfig, run, renderReport } from "dev-lang-natural";

const config = await loadConfig();
const summary = await run(config);
console.log(renderReport(summary, config.threshold));
process.exit(summary.ok ? 0 : 1);

Providers

| Provider | Env var | Default model | |-------------|---------------------|----------------------------| | gemini | GEMINI_API_KEY | gemini-2.0-flash | | openai | OPENAI_API_KEY | gpt-4o-mini | | anthropic | ANTHROPIC_API_KEY | claude-haiku-4-5 |

Override the model per project:

export default {
  // ...
  provider: "gemini",
  providerOptions: { model: "gemini-2.5-flash", temperature: 0 },
};

Adding a new provider is one small file implementing complete(system, user) — see src/providers/.


Caching

Verdicts are hashed on source + candidate + context + model and stored in .dev-lang-natural-cache.json. Each run only sends changed strings to the LLM, so CI stays cheap. Add the cache file to .gitignore (or commit it to share verdicts across the team — your call).


Scope for v1

  • ✅ Naturalness scoring + native-speaker suggestions
  • ✅ Back-translation meaning-drift detection
  • ✅ Placeholder verification ({name}, {{name}}, %s, :name)
  • ✅ Missing / extra key reporting
  • ✅ Caching + pluggable providers + CI exit codes
  • ⏭️ --fix (write suggestions back) — planned v2
  • ⏭️ Dev-mode React overlay — planned v3
  • ❌ RTL layout for Arabic — a separate concern, out of scope

License

MIT