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

@shubhamdeol/locale-ota-core

v1.0.0

Published

Pure functions: flatten, hash, diff, compose, delta, plural forms, marker validation, batching.

Readme

@shubhamdeol/locale-ota-core

The pure layer of locale-ota: flatten, hash, diff, compose, delta, plural forms, marker validation, batching and release resolution. No IO, no dependencies, no node: imports, so the same code runs on the server, in the CLI, in a browser and in React Native.

Status: phase 1, plus the expandReleaseKeys of phase 3b and the fallback chain of phase 4c. Version 0.0.0, unpublished.

Install

bun add @shubhamdeol/locale-ota-core

Public API

Everything below is exported from @shubhamdeol/locale-ota-core.

Keys

| Export | What it does | | --- | --- | | flatten(input, options?) | Flattens a resource file to Record<string, string>; arrays become items.0, numbers and booleans become strings, null and friends throw invalid_leaf. | | unflatten(flat, options?) | Rebuilds the nested object; a node whose children are 0..n-1 becomes an array. |

Hashing

| Export | What it does | | --- | --- | | hashValue(value) | Lowercase SHA-256 hex through crypto.subtle. | | buildKeySet(namespace, flat) | Hashes every value and returns KeySetEntry[] sorted by namespace then key. | | keySetHash(entries, options?) | The name of a release, decision D19: SHA-256 over the namespaces, keys and values, in buckets, as plan 5.8 defines it. Takes an injected digest and a pause. | | hashResources(resources, options?) | The same hash from a bundled file, per namespace, without per-value hashing: what a client computes. Flattens one subtree at a time and pauses every HASH_SLICE_KEYS keys and after every bucket. | | compareStrings(a, b) | Byte-wise string order, the order every sort in this package uses. |

Diff

| Export | What it does | | --- | --- | | diffKeySets(previous, next) | Returns { added, changed, removed }; a key changed when its source hash did. |

Compose and delta

| Export | What it does | | --- | --- | | compose(releaseKeys, rows, options) | The file a client sees: the newest row per release key at or below the cutoff with a matching source hash, nested per namespace. | | delta(releaseKeys, rows, options) | { set, remove } for a client that already holds a revision. | | composeChain(releaseKeys, rowsByLanguage, options) | The same file along a fallback chain: { resources, resolvedBy }. | | deltaChain(releaseKeys, rowsByLanguage, options) | { set, remove } along a fallback chain. | | groupRowsByLanguage(rows) | Groups a flat row list by its own language field, ready for the two chain functions. | | mergeResources(base, set, remove?, options?) | Applies one namespace of a delta to a nested object and returns a new one. |

The fallback chain

| Export | What it does | | --- | --- | | defaultParent(code) | The code without its last BCP 47 subtag: pt-BR gives pt, pt gives null. The kept prefix keeps its own casing. | | resolveChain({ requested, languages, sourceLanguage }) | The requested language, then its enabled ancestors. | | MAX_CHAIN_HOPS | 4: the most parent steps a chain takes, so a chain holds at most five codes. |

resolveChain stops before the source language, at a code it has already taken, at a code that is disabled or not configured, and after four hops. A requested language that is not configured, or is configured and disabled, has no chain at all: the list comes back empty and the server answers 404 language_not_enabled. The source language is the one exception, because it is served from its own source rows rather than from a chain: asking for it gives the one-code chain [requested]. Codes are matched without regard to case and come back spelled the way the languages rows spell them, so they can be handed straight to a row query.

composeChain expands the key set for chain[0] with expandReleaseKeys first, then takes for each key the first chain language with a row at or below the cutoff carrying the release source hash, and the newest such row. resolvedBy maps namespace + nsSeparator + flat key to the chain language that answered, so the dashboard can show that a pt-BR screen is reading a pt value. nsSeparator defaults to ":".

deltaChain takes as candidates the keys with any chain-language row in (from, cutoff] and resolves each twice. set holds the keys whose resolved value changed or appeared. remove holds the keys that resolved at from and do not resolve at the cutoff, and the keys whose only rows in the window carry a source hash the release has moved past: the English changed inside the release, so the value the client holds is stale. Rows are append-only, so the first of those two rules can only fire when the second does; it is written out because plan 5.3 step 5 names it.

compose and delta are the one-language cases of the two: every row given is a candidate whatever its language field says, and the key set is taken as it is rather than expanded.

Plurals

| Export | What it does | | --- | --- | | parsePluralKey(key) | { base, category, ordinal } for an i18next v4 suffix, else null. | | pluralKey(base, category, options?) | Builds base_one or base_ordinal_one. | | requiredPluralCategories(language, options?) | The categories Intl.PluralRules needs, in CLDR order; an unknown tag gets other alone. | | pluralWork(releaseKeys, language) | Groups plural keys by namespace and base, with the source forms and the target keys the language needs. | | expandReleaseKeys(releaseKeys, language) | The release key set plus the plural forms the language needs and the source does not ship, each under the source hash of the English form it is translated from. | | PLURAL_CATEGORIES | The CLDR order: zero, one, two, few, many, other. |

Markers and validation

| Export | What it does | | --- | --- | | extractMarkers(value, format?) | The interpolation markers of an i18next, icu or printf string, sorted and deduplicated. | | extractTags(value) | The HTML tag names, closing tags as /name, sorted, duplicates kept. | | validateTranslation(source, translation, options?) | { ok: true } or { ok: false, errors } with empty, marker_missing, marker_extra and tag_mismatch. |

Batching

| Export | What it does | | --- | --- | | batchWork(items, options?) | Splits a work list in order at 150 keys or 8 KB, an oversized item to its own batch. |

Errors

| Export | What it does | | --- | --- | | CoreError | The one error type: a code string and a details object. | | isCoreError(value) | Narrows an unknown value to CoreError. | | version | The published version of this package, "0.0.0" until it ships. |

Types

ResourceNode, ResourceValue, FlattenOptions, KeySetEntry, KeySetDiff, AddedKey, ChangedKey, RemovedKey, ReleaseKey, TranslationRow, TranslationOrigin, ComposedResources, ComposeOptions, DeltaOptions, DeltaResult, DeltaRemoval, ChainLanguage, ResolveChainInput, ChainComposeOptions, ChainComposeResult, ChainDeltaOptions, RowsByLanguage, RemovalKey, PluralCategory, ParsedPluralKey, PluralOptions, PluralGroup, PluralSourceForm, PluralTargetForm, MarkerFormat, ValidateOptions, ValidationError, ValidationResult, BatchOptions, ReleaseLike, SemverVersion.

Rules this package keeps

  • Zero dependencies, runtime and dev. Nothing from node:.
  • Every sort is byte-wise, so a composed file is the same bytes on every machine.
  • Hashing goes through globalThis.crypto.subtle, which exists in Node 20, Bun, browsers and React Native 0.76 or later.

Tests

bun test packages/core     # from the repository root
bun run typecheck          # from this folder
bun run build              # from this folder

test/fixtures/generate.ts builds a synthetic 4 MB English file of 40,000 keys from a seed, and nextVersion makes the version after it. Nothing is written to disk. src/perf.test.ts holds the phase 1 budgets: compose under 200 ms, diff under 500 ms.