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

@dualkit/convert

v0.1.0-beta.0

Published

Converts an English React or Next.js app to Arabic on a new git branch.

Readme

@dualkit/convert

Turns an English React or Next.js app into an Arabic one, on a new git branch.

It is the engine behind dualkit convert. Most people run it through the CLI:

npx dualkit@beta convert --mode bilingual --start "npm run dev" --port 3000

Convert makes no model calls. It rewrites the code it can rewrite safely, lists every English string for you or your agent to translate, and checks the result in a browser. Every Arabic string a model drafts is marked for a native speaker to check.

The two modes

| Mode | What you get | Branch | | --- | --- | --- | | bilingual | English and Arabic in one app, with a language switch. English stays exactly as it is. | dualkit/bilingual | | arabic | The whole app in Arabic and right to left, in place. There is no switch. | dualkit/arabic |

In a bilingual Next.js app the locale lives in the URL. English keeps its URLs, and Arabic pages sit under /ar. A bilingual Vite app keeps the locale in the dk-locale cookie and reads it before the first paint.

The two phases

Phase 1, dualkit convert --mode <mode>:

  1. Checks the repo. It needs a clean git working tree, and it refuses with a one-line reason if anything is uncommitted or the branch already exists.
  2. In bilingual mode, takes English screenshots of every page it can reach, at 390 and 1280 pixels wide, if you gave it a start command. Arabic mode has no English pages to compare, so it skips them.
  3. Makes the branch from your current commit. It never pushes and never touches your main branch.
  4. Rewrites the code: physical Tailwind classes and CSS to logical ones, dir="ltr" on phone, email and url fields, <bdi> around prices and other numbers inside text, mirrored arrow icons from known icon packages, and every English string moved into messages/en.json behind a translation hook.
  5. Wires in the translation library: next-intl for Next.js, react-i18next for Vite. It adds an Arabic font stylesheet scoped to Arabic pages.
  6. Writes dualkit-strings.json, one entry per string with its Arabic left empty, and commits. If you stop it or a step fails, it puts the repo back as it was.

Then you fill the Arabic in dualkit-strings.json. The dualkit-convert skill walks an agent through it.

Phase 2, dualkit convert --finish:

  1. Writes messages/ar.json from the filled ledger. It refuses while any string is still pending, unless you pass --force.
  2. Starts the app and compares each English page with its screenshot from phase 1. A page passes when under 0.1% of its pixels differ.
  3. Runs the 34 check rules on each Arabic page.
  4. Runs your tests, if the config names a test command.
  5. Writes dualkit-convert.md and commits. It exits 1 when an English page changed, an Arabic page has errors or the tests fail.

If a finish step itself fails, such as a start command that does not start, finish puts back the files it wrote and commits nothing. Your Arabic drafts in dualkit-strings.json stay as you left them.

What goes into git

Convert keeps its state in .dualkit/convert.json and a run summary in .dualkit/summary.json. Both are committed on the branch, so a fresh clone of the branch can run convert again or run finish. The branch .gitignore gets three lines that ignore the rest of .dualkit, such as screenshots:

.dualkit/*
!.dualkit/convert.json
!.dualkit/summary.json

The English screenshots are taken on your base branch before the convert branch exists, so convert also adds /.dualkit/* to the repo's .git/info/exclude. Your base branch stays clean and nothing is added to its files.

The config block

Convert reads a convert block from dualkit.config.json, or from the file --config names, which must be inside the app folder. The --start and --port flags are saved there for you.

{
  "convert": {
    "start": "npm run build && npm start",
    "port": 3000,
    "test": "npm test",
    "routes": ["/", "/pricing"],
    "params": { "/orders/[id]": "/orders/42" },
    "storageState": "auth.json"
  }
}

| Field | What it does | | --- | --- | | start | The command that starts your app. Without it there is no English baseline, and finish cannot run. A production build gives steadier screenshots than a dev server. | | port | The port the app listens on. | | test | Your test command. Finish runs it and puts the result in the report. | | routes | The pages to check in a Vite app. Convert cannot read client-side routes, so it checks only / unless you list them. Next.js routes are read from the app folder. | | params | A sample path for each dynamic route, such as /orders/[id]. Dynamic routes without one are listed in the report as not reached. | | storageState | A Playwright storage state file, for pages behind a sign-in. |

What it will not do

Convert flags these in the report instead of guessing:

  • Text that shares an element with other elements, such as Hello <b>{name}</b>, or a plural built in code. These need rich text or a plural message, by hand. Rich text is not converted in this version.
  • Text outside any component, such as a module-level constant or a Next.js metadata title, and the <title> in a Vite app's index.html.
  • A className built at run time, which it cannot read in full.
  • Classes with no logical form, inline SVG icons and files it cannot parse.
  • Dates and numbers formatted in the browser's own locale or in US dollars, and date pickers with their own week start. These are regional suggestions only.
  • Pages under /api and other paths the Next.js middleware skips. They stay English.

It refuses, with a one-line reason, when:

  • The app is not Next.js App Router or Vite React. The Pages Router, Create React App, Remix, Vue, Svelte and Angular are refused.
  • The app already has its own middleware or proxy file, or files with the names convert would write.
  • The root layout is a client component, has no <html> element, or takes its props in a form convert cannot add params to.

Known gaps

  • Convert does not place the language switch, because that would change the English look. It writes components/dualkit-language-switch.tsx and tells you to add it.
  • Next.js sends a visitor whose browser asks for Arabic from an English URL to the Arabic page. That is next-intl's locale detection, and convert leaves it on.

Use from code

import { convert, finish } from '@dualkit/convert'

Both take an io object for the browser, the shell and logging, so the CLI and tests can supply their own. See the exported ConvertIo and FinishIo types.

MIT licensed.