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

restringjs

v0.2.3

Published

Live string editor for React — edit text in-context, then bake changes into code

Downloads

96

Readme

restringjs

Live string editor for React. Edit text in-context, then bake changes into code.

npm version npm bundle size CI License: MIT TypeScript PRs Welcome

restringjs gives your team a sidebar where they can tweak every user-facing string in your app, see changes instantly, then permanently bake those edits into your source files with a single CLI command. No CMS. No runtime overhead in production.

Why

You have a marketing team that wants to change "Get Started" to "Start Free Trial." Today that's a Jira ticket, a PR, a deploy. With restringjs, they open the sidebar, make the edit, and a developer runs npx restringjs bake to commit it. Or you skip the middleman entirely and bake it yourself.

The key insight: most string changes don't need a CMS. They need a good workflow for getting text changes into source code.

Features

  • Live editing sidebar with search, filtering, and section grouping
  • Zero bytes in production - the sidebar tree-shakes completely when disabled
  • Bake & eject - AST transforms apply overrides directly into source files, preserving formatting, comments, and quote style. Handles nested objects, arrays, and as const
  • Full CLI toolkit - bake, diff, validate, export, import, clear with --prefix support for bridging DB key formats
  • ICU MessageFormat - syntax validation, variable chips, plural grouping with locale-aware labels
  • i18next support - auto-detects {{variable}} and $t() patterns
  • Visual highlight mode - overlay registered DOM elements, click to jump to the editor
  • RTL-aware - inputs auto-detect text direction
  • Rich text - opt-in HTML/Markdown preservation per field
  • Pluggable storage - memory, localStorage, and REST adapters included (or write your own)
  • Server-side rendering - Next.js App Router and Pages Router helpers
  • TypeScript-first - full types, no any leakage

Demo

See all features in action without setting up a project:

git clone https://github.com/maki-q/restringjs.git
cd restringjs
pnpm install
pnpm demo

Five pages covering basic usage, FAQ sections, i18n (ICU + i18next), rich text editing, and visual highlight mode.

Quick Start

npm install restringjs

1. Wrap your app

import { RestringProvider, RestringSidebar } from 'restringjs';
import { createLocalStorageAdapter } from 'restringjs/adapters';

const adapter = createLocalStorageAdapter();

function App() {
  return (
    <RestringProvider enabled={process.env.NODE_ENV === 'development'} adapter={adapter}>
      <YourApp />
      <RestringSidebar />
    </RestringProvider>
  );
}

2. Register strings

import { useRestring } from 'restringjs';

function Hero() {
  const title = useRestring({
    path: 'hero.title',
    defaultValue: 'Welcome to our app',
    section: 'marketing',
  });

  const subtitle = useRestring({
    path: 'hero.subtitle',
    defaultValue: 'The best way to manage your strings',
    section: 'marketing',
  });

  return (
    <section>
      <h1>{title}</h1>
      <p>{subtitle}</p>
    </section>
  );
}

3. Bake changes into code

npx restringjs bake "src/**/*.tsx"

Your source files now contain the edited strings. No runtime overhead. No adapter needed in production.

Visual Highlight Mode

Overlay registered DOM elements so you can see exactly which strings are editable:

import { RestringProvider, RestringSidebar, RestringHighlight } from 'restringjs';

<RestringProvider enabled adapter={adapter}>
  <YourApp />
  <RestringHighlight />
  <RestringSidebar />
</RestringProvider>

Click any highlighted element to jump to its field in the sidebar. Overlays track scroll and resize automatically.

Configure the highlight color via the provider:

<RestringProvider
  enabled
  adapter={adapter}
  defaultHighlightMode={true}
  highlightColor="#ff6b6b"
>

ICU MessageFormat

restringjs understands ICU syntax out of the box:

const greeting = useRestring({
  path: 'greeting',
  defaultValue: 'Hello {name}, you have {count, plural, one {# message} other {# messages}}',
  format: 'icu',
});

The sidebar shows variable chips, validates syntax in real time, and groups plural forms with locale-aware labels.

i18next Support

const welcome = useRestring({
  path: 'welcome',
  defaultValue: 'Welcome {{userName}}! See $t(features.title) for details.',
  format: 'i18next',
});

Format detection is automatic. You can omit format and let restringjs figure it out.

Sections

Group related fields in the sidebar:

import { useRegisterSection, useRestring } from 'restringjs';

function PricingPage() {
  useRegisterSection({
    id: 'pricing',
    label: 'Pricing Page',
    order: 2,
    description: 'All pricing-related copy',
  });

  const headline = useRestring({
    path: 'pricing.headline',
    defaultValue: 'Simple, transparent pricing',
    section: 'pricing',
  });

  return <h1>{headline}</h1>;
}

Adapters

import {
  createMemoryAdapter,
  createLocalStorageAdapter,
  createRestAdapter,
} from 'restringjs/adapters';

// Ephemeral (lost on refresh)
const memory = createMemoryAdapter();

// Persists in browser localStorage
const local = createLocalStorageAdapter('my-app:overrides');

// Persists to your API
const rest = createRestAdapter('https://api.example.com/overrides', {
  headers: { Authorization: 'Bearer ...' },
});

Custom adapter

Implement three async methods:

import type { RestringAdapter } from 'restringjs';

const myAdapter: RestringAdapter = {
  async load() {
    // Return Record<string, string> of field path -> override value
    const res = await fetch('/api/overrides');
    return res.json();
  },
  async save(overrides) {
    await fetch('/api/overrides', {
      method: 'PUT',
      body: JSON.stringify(overrides),
    });
  },
  async clear() {
    await fetch('/api/overrides', { method: 'DELETE' });
  },
};

Server-Side Rendering

Next.js App Router

import { createServerApply } from 'restringjs/server';

const apply = createServerApply(async () => {
  // Load overrides from your database, cookie, API, etc.
  return { 'hero.title': 'Server-rendered override' };
});

export default async function Page() {
  const strings = await apply({
    hero: { title: 'Default title', subtitle: 'Default subtitle' },
  });

  return <h1>{strings.hero.title}</h1>;
}

Next.js Pages Router

import { withRestringOverrides, serverApply } from 'restringjs/server';

export const getServerSideProps = async () => {
  const { restringOverrides } = await withRestringOverrides(() => loadOverrides())();
  const strings = serverApply(defaultStrings, restringOverrides);
  return { props: { strings } };
};

CLI

Every command supports --help for full usage details.

bake - Write overrides into source files

AST transform via ts-morph. Preserves formatting, comments, quote style, and code structure. Handles nested objects, arrays, as const, and deeply nested paths.

# Bake overrides into source files
npx restringjs bake "src/**/*.tsx"

# Dry run - preview what would change without writing
npx restringjs bake "src/**/*.tsx" --dry-run

# Custom overrides file (default: .restringjs-overrides.json)
npx restringjs bake "src/**/*.tsx" --overrides=my-overrides.json

# Bridge DB key format to source variable names
# If your DB stores "home.title" but source has `const strings = { home: { title: "..." } }`:
npx restringjs bake "src/**/*.ts" --prefix=strings

Bake reports unmatched override keys to stderr so you know if any overrides didn't find a matching path in source.

diff - Compare source strings against overrides

npx restringjs diff "src/**/*.ts" --overrides=overrides.json
npx restringjs diff "src/**/*.ts" --overrides=overrides.json --prefix=strings

Prints a colored table showing path, original value, and override value for each changed field.

validate - Check for stale or empty overrides

npx restringjs validate "src/**/*.ts" --overrides=overrides.json
npx restringjs validate "src/**/*.ts" --overrides=overrides.json --prefix=strings

Flags stale keys (overrides referencing paths that no longer exist in source) and empty values. Exits with code 1 if any issues found - useful in CI.

export - Extract all strings from source

# Print to stdout
npx restringjs export "src/**/*.ts"

# Write to file
npx restringjs export "src/**/*.ts" --output=strings.json

# Strip variable prefix from keys
npx restringjs export "src/**/*.ts" --prefix=strings

Walks source files and extracts every string literal with its dot-path. Use this to bootstrap an overrides file or audit what's editable.

import - Merge overrides into a file

npx restringjs import --overrides=new-overrides.json
npx restringjs import --overrides=new-overrides.json --target=.restringjs-overrides.json

Reads a JSON override map and merges it into the target file (default .restringjs-overrides.json).

clear - Reset override file

npx restringjs clear
npx restringjs clear --target=.restringjs-overrides.json

Writes {} to the target file.

The --prefix flag

Many apps store override keys without the source variable name (e.g. home.title in the database) while the AST resolver produces paths that include it (e.g. strings.home.title). The --prefix flag bridges this gap across all commands that work with both source files and overrides (bake, diff, validate, export).

API Reference

Hooks

| Hook | Description | |------|-------------| | useRestring(config) | Register a field, return its current value (override or default) | | useRegister(config) | Register a field, return [value, setValue] tuple | | useRegisterSection(config) | Register a sidebar section for grouping | | useFieldValue(path) | Read a field's current value without registering it | | useSnapshot() | Get the full store snapshot (fields, sections, overrides, dirty state) |

Components

| Component | Description | |-----------|-------------| | RestringProvider | Context provider. Set enabled to control sidebar availability. | | RestringSidebar | The editing sidebar UI. Search, filter, edit, save. | | RestringHighlight | Visual overlay mode. Renders borders on registered DOM elements. |

Provider Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | enabled | boolean | required | Whether editing mode is active | | adapter | RestringAdapter | memory | Storage adapter for persisting overrides | | defaultHighlightMode | boolean | true | Whether highlight overlays start enabled | | highlightColor | string | '#4a6cf7' | CSS color for highlight overlays and accents |

Utilities

| Function | Description | |----------|-------------| | applyOverrides(obj, overrides) | Apply overrides to an object immutably | | flattenObject(obj) | Flatten nested object to dot-path keys | | unflattenObject(flat) | Reverse of flatten | | detectFormat(value) | Auto-detect string format (icu, i18next, plain) | | createStore(options?) | Create a standalone store instance |

Server Exports (restringjs/server)

| Function | Description | |----------|-------------| | serverApply(strings, overrides) | Apply overrides server-side, returns new object | | createServerApply(loader) | Create a reusable apply function with an async override loader | | withRestringOverrides(loader) | Pages Router helper for getServerSideProps |

Field Config

interface FieldConfig {
  path: string;          // Dot-path key, e.g. "hero.title"
  defaultValue: string;  // Value before any overrides
  section?: string;      // Group in sidebar
  format?: 'icu' | 'i18next' | 'plain';
  richText?: boolean;    // Enable HTML/Markdown editing
  description?: string;  // Shown in sidebar
  locale?: string;       // e.g. 'en', 'fr'
}

How It Works

  1. Register strings with useRestring(). Each gets a unique dot-path key.
  2. Edit in the sidebar. Changes are stored via your chosen adapter (localStorage, REST, etc.).
  3. Bake with the CLI. ts-morph rewrites your source files, replacing default values with overrides.
  4. Eject if you want. Remove restringjs entirely and your strings are just hardcoded values. No lock-in.

The bake step uses AST transforms (not regex), so it preserves your formatting, comments, and code structure.

Configuration

Optional config file for CLI defaults:

// restringjs.config.ts
import { defineConfig } from 'restringjs/config';

export default defineConfig({
  sources: ['src/**/*.{ts,tsx}'],
  locale: 'en',
  format: 'icu',
  adapter: {
    type: 'rest',
    endpoint: 'https://api.example.com/overrides',
  },
});

Requirements

  • React 18+ (uses useSyncExternalStore)
  • TypeScript 5+ recommended
  • Node.js 18+ for CLI

Roadmap to 1.0

The following are planned before a stable 1.0 release:

  • Template literal support in bake - Currently, backtick strings (`Hello ${name}`) are gracefully skipped. Plain backtick strings without interpolation (NoSubstitutionTemplateLiteral) should be replaceable.
  • Full ICU MessageFormat validation - Current validation is brace-matching only. A proper AST parse would catch malformed plural/select syntax before save.
  • import/clear for non-file adapters - import and clear CLI commands only support file-based targets today. localStorage and REST adapter support is planned.
  • CHANGELOG - Proper release-by-release changelog (started in CHANGELOG.md, will be fleshed out with each release).

If any of these are blocking your use case, open an issue.

Contributing

git clone https://github.com/maki-q/restringjs.git
cd restringjs
pnpm install
pnpm check    # typecheck + lint
pnpm test     # run tests
pnpm demo     # start demo app

License

MIT - Quinton Maki


If you find this useful, consider buying me a coffee :coffee:

Report a Bug - Request a Feature - Discussions