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

@valfuse-node/localization

v0.3.0

Published

Localization generation engine — compiler, CLI, validator and runtime for @valfuse-node

Readme

@valfuse-node/localization

Localization generation engine — YAML/JSON source files → type-safe TypeScript manifest → browser-safe interpolation runtime. CLI compiler + validator + coverage reporter + watch mode. Zero runtime dependencies in the browser.

npm install @valfuse-node/localization

Requires Node.js ≥ 20 for the CLI / compiler. The runtime is browser-safe and tree-shakable.


Table of Contents


Three-Layer Architecture

┌────────────────────────────────────────────────────────────────────┐
│  Source files (YAML / JSON)   →   in: assets/localizations/       │
└────────────────────────────────────────────────────────────────────┘
                                ↓ valfuse-localization generate
┌────────────────────────────────────────────────────────────────────┐
│  Generated artifacts          →   out: src/assets/localizations/  │
│    • localization.ts          (framework-specific entry point)    │
│    • localization.types.ts    (TypeScript types for all keys)     │
│    • localization.manifest.json (browser-loadable runtime data)   │
└────────────────────────────────────────────────────────────────────┘
                                ↓
┌────────────────────────────────────────────────────────────────────┐
│  Runtime (browser)            →   interpolate, lookupMessage, …   │
└────────────────────────────────────────────────────────────────────┘

The compiler runs at build time; the runtime is shipped to the browser. There is no overlap — the runtime never imports Node-only APIs.


Quick Start

# 1. Scaffold a sample module
npx valfuse-localization init

# 2. Compile to TypeScript + JSON manifest
npx valfuse-localization generate

# 3. (optional) Watch mode for development
npx valfuse-localization generate --watch

The default scaffold creates assets/localizations/common/en.json and assets/localizations/common/id.json.


Source File Format (YAML / JSON)

Each module is a folder under input_dir. Each locale is a file inside the module folder, named <locale>.json (or .yaml).

assets/localizations/
├── common/
│   ├── en.json
│   ├── id.json
│   └── ja.json
└── auth/
    ├── en.json
    └── id.json

Leaf string (most common)

{
  "@@module": "common",
  "@@locale": "en",
  "app": {
    "title": "My App",
    "tagline": "Form validation that doesn't get in your way"
  },
  "errors": {
    "required": "This field is required",
    "min_length": "Must be at least {min} characters"
  }
}

The two reserved keys at the root are:

| Key | Purpose | |---|---| | @@module | Module name (must match the folder name) | | @@locale | Locale code (en, id, ja, …) |

Flat vs. nested

Both produce the same generated keys. The compiler flattens nested objects using dot-paths.

{ "auth": { "login": { "title": "Log in" } } }

Generates key: common.auth.login.title (with namespace_prefix: module — the default) or auth.login.title (with namespace_prefix: none).

Placeholders

Use {name} syntax. Identifiers match [a-zA-Z_][a-zA-Z0-9_]*.

{ "welcome": "Hello, {name}! You have {count} new messages." }

Interpolate at runtime with interpolate("Hello, {name}!", { name: "Alfin" })"Hello, Alfin!".

Missing placeholders keep their original {token} so missing values are visible during development.


Structured Variants (Plural / Gender / Context)

Mark a leaf as structured with one of the three reserved keys: @plural, @gender, @context.

@plural — pick a variant by quantity

{
  "items": {
    "count": {
      "@plural": {
        "zero":  "No items",
        "one":   "1 item",
        "other": "{count} items"
      }
    }
  }
}
import { plural } from "@valfuse-node/localization/runtime";
plural("common.items.count", 0);   // → "No items"
plural("common.items.count", 1);   // → "1 item"
plural("common.items.count", 5);   // → "5 items"

{count} is auto-injected as a placeholder for plural branches.

@gender — pick a variant by gender

{
  "profile": {
    "follower_count": {
      "@gender": {
        "male":   "{count} male followers",
        "female": "{count} female followers",
        "other":  "{count} followers"
      }
    }
  }
}
import { gender } from "@valfuse-node/localization/runtime";
gender("common.profile.follower_count", "male",   { count: 3 }); // → "3 male followers"
gender("common.profile.follower_count", "female", { count: 3 }); // → "3 female followers"

@context — pick a variant by free-form context

{
  "word": {
    "open": {
      "@context": {
        "verb":   "Open the file",
        "adjective": "The file is open"
      }
    }
  }
}
import { context } from "@valfuse-node/localization/runtime";
context("common.word.open", "verb");      // → "Open the file"
context("common.word.open", "adjective"); // → "The file is open"

Resolution priority

When a leaf has both an @plural and metadata placeholders, the variant branches are selected first; placeholders interpolate into the chosen branch.


Inline Metadata

Attach developer-only metadata to a leaf using sibling keys. Metadata is never emitted to the runtime — it lives only in the source for tooling.

{
  "welcome": {
    "@description": "Greeting shown on the home page hero",
    "@example":     "Hello, Alfin!",
    "@placeholders": {
      "name": "User's first name"
    },
    "value": "Hello, {name}!"
  }
}

| Key | Type | Purpose | |---|---|---| | @description | string | Human-readable description for translators / docs | | @example | string | Example of a fully-interpolated message | | @placeholders | Record<string, string> | Per-placeholder documentation | | @custom | Record<string, unknown> | Any custom tooling metadata (use only when validation.allow_custom_metadata: true) |

Reserved keys (cannot be used as message names): @@module, @@locale, @plural, @gender, @context, @description, @example, @placeholders, @custom.


The valfuse-localization.yaml Config File

input_dir: assets/localizations        # where the YAML/JSON source files live
output_dir: src/assets/localizations   # where the generated TS/JSON go

framework: react                       # react | vue | nest
class_name: Localization               # name of the generated class/namespace

base_locale: en                        # default locale at runtime
fallback_locale: en                    # fallback when a key is missing
strict: true                           # throw on errors (vs. just warn)

# Prepend the module folder name to every key:
#   "module" (default) → common.auth.login.title
#   "none"             → auth.login.title
namespace_prefix: module

generated:
  runtime_entry_file: localization.ts
  runtime_types_file: localization.types.ts
  runtime_manifest_file: localization.manifest.json

validation:
  max_depth: 10                        # reject nested objects deeper than this
  require_key_parity: true             # every key must exist in every locale
  require_placeholder_parity: true     # placeholder set must match across locales
  require_structured_parity: true      # @plural/@gender/@context variants must match
  allow_custom_metadata: true          # allow @custom blocks
  validate_path_metadata_consistency: true  # metadata must match across locales

reporting:
  output_dir: src/assets/localizations/reports
  coverage_format: json                # json | html

CLI Commands

All commands read valfuse-localization.yaml from the current working directory. Pass { cwd: "..." } programmatically.

npx valfuse-localization init

Scaffolds assets/localizations/common/{en,id}.json with sample content. Use it to bootstrap a new project.

npx valfuse-localization init
# → ✅ Initialized localization files in <cwd>/assets/localizations/common

npx valfuse-localization generate

Compiles source files → localization.ts, localization.types.ts, localization.manifest.json, plus coverage report.

npx valfuse-localization generate
npx valfuse-localization generate --watch    # re-run on file changes

Errors are printed to stderr. With strict: true, the process exits non-zero on errors.

npx valfuse-localization validate

Compiles + runs the validator pipeline + prints diagnostics. Does not write any output files.

npx valfuse-localization validate

npx valfuse-localization coverage [--format json|html] [--output <file>]

Builds and writes a coverage report (per-locale fill rate, per-key fill matrix).

npx valfuse-localization coverage                       # → coverage.json
npx valfuse-localization coverage --format html         # → coverage.html
npx valfuse-localization coverage --output ./cov.json   # → ./cov.json

npx valfuse-localization clean

Removes the generated output directory.

npx valfuse-localization clean
# → ✅ Localization output cleaned.

Generated Outputs

localization.ts — the framework-specific entry point

Exposes a singleton with the active locale, a t() lookup function, and the underlying manifest. The exact shape depends on framework: react | vue | nest in the config.

// For framework: react
import { Localization, localization } from "./assets/localizations/localization";
// `localization` is the runtime manifest + t() helper
// `Localization` is the class for the configured framework

localization.types.ts — type-safe keys

Auto-generates a TranslationKey union from the compiled set of keys. Use it to type-safe your lookups.

import type { TranslationKey } from "./assets/localizations/localization.types";
const key: TranslationKey = "common.errors.required"; // ✓
const bad: TranslationKey = "common.erros.requird";   // ✗ type error

localization.manifest.json — the browser-loadable payload

{
  "base_locale": "en",
  "fallback_locale": "en",
  "locales": ["en", "id"],
  "entries": [
    { "key": "common.app.title", "placeholders": [] },
    { "key": "common.welcome", "placeholders": ["name"] }
  ],
  "messages": {
    "en": { "common.app.title": "My App", "common.welcome": "Hello, {name}!" },
    "id": { "common.app.title": "Aplikasi Saya", "common.welcome": "Halo, {name}!" }
  }
}

Structured variants are stored as JSON-encoded strings — use parseStructuredVariants() to decode them.


Browser Runtime API

Import from @valfuse-node/localization/runtime (or @valfuse-node/core). The runtime is zero-dependency, browser-safe, tree-shakable.

import {
  interpolate,
  lookupMessage,
  pickContextVariant,
  pickGenderVariant,
  pickPluralVariant,
  pickStructuredContextVariant,
  pickStructuredGenderVariant,
  pickStructuredPluralVariant,
  parseStructuredVariants,
} from "@valfuse-node/localization/runtime";

interpolate(template, params)

interpolate("Hello, {name}!", { name: "Alfin" });
// → "Hello, Alfin!"

interpolate("Hello, {name}!", {});         // missing → kept as-is → "Hello, {name}!"
interpolate("Total: {price}", { price: 0 }); // 0 is a real value → "Total: 0"

Placeholder identifiers match [a-zA-Z_][a-zA-Z0-9_]*.

lookupMessage(context, key)

const ctx: RuntimeContext = {
  locale: "id",
  fallbackLocale: "en",
  messages: manifest.messages,
};

lookupMessage(ctx, "common.app.title");
// → "Aplikasi Saya" (if present)
// → "My App"        (falls back to fallbackLocale)
// → "common.app.title" (returns the key if both miss — useful for dev visibility)

pickPluralVariant(variants, count) / pickGenderVariant(variants, value) / pickContextVariant(variants, context)

Low-level variant pickers for parsed variant maps:

pickPluralVariant({ one: "1 item", other: "{count} items" }, 5);
// → "{count} items"

parseStructuredVariants(raw) + pickStructured*

For runtime structured entries (e.g. when reading from manifest.messages directly):

const raw = manifest.messages.en["common.items.count"]; // JSON-encoded string
const variants = parseStructuredVariants(raw);
// → { zero: "No items", one: "1 item", other: "{count} items" }

pickStructuredPluralVariant(raw, 5);
// → "5 items"  (auto-injects {count} for plural)

For pickStructuredPluralVariant, count is automatically injected into the interpolation params.

RuntimeContext

interface RuntimeContext {
  locale: string;
  fallbackLocale: string;
  messages: Record<string, Record<string, string>>;
}

Validators

The compiler runs seven independent validators. Each can be turned on/off in config (validation.*).

| Validator | What it checks | Toggle | |---|---|---| | validateKeyParity | Every key exists in every locale | require_key_parity | | validatePlaceholderParity | Placeholder sets match across locales | require_placeholder_parity | | validateStructuredParity | @plural/@gender/@context variant keys match across locales | require_structured_parity | | validateMetadataUsage | Inline metadata is well-formed | always on | | validatePathConsistency | Metadata is consistent across locales for the same path | validate_path_metadata_consistency | | validateFlattenCollision | No two source paths flatten to the same key | always on | | validateMaxDepth | No nesting deeper than max_depth | max_depth |


Compiler Pipeline (Programmatic)

import {
  loadConfig,
  compileProject,
  normalizeProject,
  validateProject,
} from "@valfuse-node/localization";

// 1. Load + validate config
const config = await loadConfig("./");

// 2. Compile source → NormalizedProject
const compiled = await compileProject("./", config);
// compiled.diagnostics   → Diagnostic[]
// compiled.manifest       → RuntimeManifest (the JSON-payload shape)

// 3. Lower-level access (if you need to inspect normalized data)
const normalized = normalizeProject(config);
validateProject(normalized);

compileProject is what runGenerate calls internally — it loads source, normalizes, validates, and produces a RuntimeManifest plus the diagnostic report.


Watch Mode

runGenerate({ watch: true }) re-runs the full pipeline whenever a source file changes.

import { runGenerate } from "@valfuse-node/localization";

await runGenerate({ watch: true });
// → keeps the process alive; Ctrl+C cleanly shuts down

Used internally by the CLI's --watch flag. Re-emits all generated files on every change — make sure to add them to your .gitignore.


Type Reference

interface RuntimeManifest {
  base_locale: string;
  fallback_locale: string;
  locales: string[];
  entries: { key: string; placeholders: string[] }[];
  messages: Record<string, Record<string, string>>;
}

interface StructuredNode {
  type: "plural" | "gender" | "context";
  variants: Record<string, string>;
}

interface InlineMetadata {
  description?: string;
  example?: string;
  placeholders?: Record<string, string>;
  custom?: Record<string, unknown>;
}

type Framework = "react" | "vue" | "nest";
type NamespacePrefix = "module" | "none";

Development Usage

Add to your build pipeline

// package.json
{
  "scripts": {
    "i18n:generate": "valfuse-localization generate",
    "i18n:watch":    "valfuse-localization generate --watch",
    "i18n:check":    "valfuse-localization validate",
    "i18n:coverage": "valfuse-localization coverage --format html"
  }
}

Load the manifest in a browser app

// React adapter: <LocalizationProvider manifest={manifest} />
// (See @valfuse-node/react for full setup)

Use the runtime directly (without the React provider)

import { interpolate, lookupMessage, type RuntimeContext, type RuntimeManifest } from "@valfuse-node/localization";

const manifest: RuntimeManifest = await fetch("/loc/manifest.json").then((r) => r.json());
const ctx: RuntimeContext = {
  locale: "id",
  fallbackLocale: manifest.base_locale,
  messages: manifest.messages,
};

const greeting = interpolate(lookupMessage(ctx, "common.welcome"), { name: "Alfin" });
// → "Halo, Alfin!"  (or "Hello, Alfin!" if the key is missing in `id`)

Programmatic generation in a custom build

// scripts/build-i18n.ts
import { loadConfig, compileProject } from "@valfuse-node/localization";
import { writeFile } from "node:fs/promises";

const config = await loadConfig("./");
const compiled = await compileProject("./", config);

await writeFile("./public/manifest.json", JSON.stringify(compiled.manifest, null, 2));

License

MIT