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

@dropshot/i18n-inline-editor

v0.2.0

Published

Framework-neutral React editor for messages rendered on a page

Readme

@dropshot/i18n-inline-editor

A framework-neutral React editor for reviewing messages directly on a rendered screen during development. It identifies the supplied messages on the page, lets a user edit their translations, validates message placeholders, and reports the resulting changes through callbacks.

npm install @dropshot/i18n-inline-editor

Import the component and its stylesheet where the editor is rendered:

import {InlineMessageEditor} from '@dropshot/i18n-inline-editor';
import '@dropshot/i18n-inline-editor/styles.css';

<InlineMessageEditor
  messages={messages}
  locale={locale}
  sourceLocale={sourceLocale}
  onPreview={setPreviewChanges}
/>

Each message has an id, source, and translation. onPreview receives the pending changes after each edit, so the consumer can show them on the page.

The editor shows a single control: a small globe button. Pressing it turns editing on and off, and its colour reports the state — grey when off, blue when on, amber once an edit is pending. There is no panel, and nothing is persisted; this release exists to see edited copy on the running screen.

Consumer responsibilities

The consumer owns everything outside the screen: use onPreview to apply the pending changes. The component neither reads nor writes a catalog, API, or storage layer, and it does not save.

The consumer also owns environment gating. Render the editor only in the environments, routes, and access conditions where development editing is allowed. The package does not select environments or provide authorization.

Automatic IDs for Next.js and next-intl

Next.js 16 applications using next-intl 4 message extraction can add exact message IDs to rendered DOM elements during local development. Wrap the config returned by createNextIntlPlugin:

import {withI18nInlineEditor} from '@dropshot/i18n-inline-editor/next';
import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin({
  experimental: {
    extract: {},
    messages: {
      path: './messages',
      locales: ['ko', 'en'],
      sourceLocale: 'ko',
    },
  },
});

// The app decides which environments may edit copy, and passes the same value to the provider.
export const inlineEditorEnabled =
  process.env.NODE_ENV === 'development' && process.env.I18N_INLINE_EDITOR === 'true';

export default withI18nInlineEditor(withNextIntl(nextConfig), {enabled: inlineEditorEnabled});

The package never reads the environment. Which environments, routes, and people may edit copy is the app's policy, and only the app knows it. Pass the same value to the build wrapper and to the provider — if they disagree, the editor appears on a screen whose messages carry no build IDs.

The adapter runs immediately after next-intl's extraction transform and reuses its generated message ID. It marks unambiguous intrinsic JSX usages with data-i18n-id; translated aria-label, title, alt, and placeholder attributes receive independent attribute markers. Indirect values, custom component props, and elements containing multiple direct messages remain available to the editor's text fallback instead of receiving a potentially incorrect ID.

The editor reports exact, fallback, and unlinked counts. If the adapter is missing or its supported Next.js/next-intl integration changes, it reports that the build plugin is inactive instead of silently claiming exact matches. The config adapter is a no-op unless both development gates are enabled.

Wiring a next-intl app in one step

@dropshot/i18n-inline-editor/next-intl bundles the runtime wiring so an application does not reimplement catalog flattening, preview state, saving, or editor copy. Wrap the layout children with it in place of NextIntlClientProvider:

import {I18nInlineEditorProvider} from '@dropshot/i18n-inline-editor/next-intl';

export default async function RootLayout({children}: {children: ReactNode}) {
  return (
    <html lang={await getLocale()}>
      <body>
        <I18nInlineEditorProvider enabled={inlineEditorEnabled} sourceLocale="ko">
          {children}
        </I18nInlineEditorProvider>
      </body>
    </html>
  );
}

The server entry reads both catalogs with getMessages and imports the client editor only when enabled is true, so a production build never pulls the editor into its client bundle. Interface copy defaults to Korean; pass labels to change it.

Pass locales to place every locale side by side. Without it the editor shows the source text and the translation for the screen being viewed — which are the same value while viewing the source language, leaving nothing to compare. Only the locale on screen is editable: the page was not rendered in the other locales, so an edit there could not be seen. Already-read catalogs are not fetched again.

exclude is optional. It drops top-level keys that did not come from the extracted catalog — formatting values and other hand-written settings merged into the same message tree. Leaving them in is harmless: they are never rendered, so no frame is drawn for them. Use it to keep values that are not translated copy out of the editable set.

Two behaviours follow from how next-intl works, and are worth stating before the editor is handed to anyone:

  • Only messages rendered by client components update live. Preview replaces the messages held by the client provider, and a message rendered in a server component is already fixed in the server output, so it keeps its original text until the page is requested again.
  • One generated ID can appear in several namespaces. Extraction derives the ID from the source text alone, and the DOM marker carries only that ID, so identical Korean copy in two namespaces is one editable entry and saving updates every place it appears. Keys that share a name but hold different source text split the other way: their identity is the path, so index.title and metadata.title stay separate entries and are matched by rendered text rather than a marker.

Requires next-intl as a peer dependency. The getRequestConfig callback must honour the locale argument, otherwise the source catalog cannot be read alongside the translated one.

Customization

Provide labels to replace the built-in interface text, formatDiagnostic to replace validation diagnostics, trigger to replace the globe inside the button, and localeLabel or sourceLocaleLabel to control locale names shown in the editor.

Match diagnostics go to the console when the editor opens: how many messages were linked by build ID, how many by rendered text, and how many the build plugin marked but could not link. They are there for whoever wires the editor up, not for whoever edits the copy.

License

MIT