@zamdevio/i18nprune/core
The programmatic heart of i18nprune — battle-tested primitives used by the CLI itself.
API tiers (semver intent)
| Tier | Meaning |
|---|---|
| Stable | Documented for integrations; breaking changes ship on a major bump. |
| Advanced | Supported and typed, but sharper edges (heuristics, ordering, or TTY-only helpers) — read the section notes before upgrading blindly. |
Stable (initial classification): resolveContext, clearContextCache, scanProjectLiteralKeyUsage, computeMissingLiteralKeys, computeMissingLiteralKeysFromResolvedKeys, resolvedLiteralKeysInProject, readJsonFile, collectStringLeaves, scanSources, isPreservePath, buildKeyReferenceContext, resolveReferenceConfig, scanProjectDynamicKeySites, CLI JSON output types (ValidateJsonOutput, CleanupJsonOutput, MissingJsonOutput, SyncJsonOutput), config types (I18nPruneConfig, Policies), Context, ResolvedPaths, ProjectLiteralKeyUsage.
Advanced: lower-level observation pipeline (scanProjectKeyObservations, literalKeyUsageFromObservations, resolvedKeysFromObservations, scanKeyObservations, exactLiteralKeys), per-file dynamic analysis helpers (findDynamicKeySites, analyzeDynamicKeysFromSourceText), template rebuild utilities (tryRebuildTemplateKeyFromConsts, tryResolveTemplatePrefixBeforeUnknown), and interactive helpers (canAsk, promptApprovedRemovalKeys, groupKeysByTopSegment) — use only when you need the same hooks as the CLI.
defineConfig is also re-exported from core for convenience; prefer @zamdevio/i18nprune/config when authoring config files.
Flat vs namespaced imports (both supported)
We fully support both styles for the same symbols:
-
Flat —
import { resolveContext, scanProjectLiteralKeyUsage } from '@zamdevio/i18nprune/core'
Fine for small scripts, snippets, and existing code. Not deprecated. -
Namespaced —
import { context, extractor } from '@zamdevio/i18nprune/core'
Recommended for new integrations and larger tools: clearer grouping, easier discovery in the IDE, and a stable mental model that mirrorscore/domains.
Recommendation: prefer namespaces for new code; keep flat where it already works or reads shorter. There is no plan to remove flat exports before an explicit major version with a migration story. For CLI machine-readable output, see JSON output (--json).
Namespaced imports
import { context, extractor, validate, files } from '@zamdevio/i18nprune/core';
const ctx = context.resolveContext();
const usage = extractor.scanProjectLiteralKeyUsage(ctx);
const raw = files.readJsonFile(ctx.paths.sourceLocale);
const missing = validate.computeMissingLiteralKeys(ctx, raw);Namespaces: context, extractor, dynamic, json, ask, preserve, reference, validate, scanner, files, result (envelope helpers + RESULT_API_VERSION).
Structured CLI JSON: global --json — see JSON output (--json).
When to use
Use this when building:
- Custom CI scripts
- Pre-commit hooks
- Bulk migration tools
- IDE extensions
- Internal devops tooling
Core Functions
Context
import { resolveContext, clearContextCache } from '@zamdevio/i18nprune/core';
const ctx = resolveContext(); // uses process.cwd()
const ctx2 = resolveContext('/path/to/project');
clearContextCache(); // for tests or long-running processesHeadless envelopes (no process.exit)
tryResolveContext returns a Result (no throw). runValidate, runConfig, runMissing, runSync, runCleanupCheck, runDoctor, runQuality, runReview, runLanguages, runGenerate (async), runReport (async) return the same CliJsonEnvelope the CLI uses for global --json (including issues[]). stringifyEnvelope serializes like stdout.
import {
tryResolveContext,
runValidate,
runConfig,
stringifyEnvelope,
ISSUE_VALIDATE_MISSING_LITERAL_KEYS,
} from '@zamdevio/i18nprune/core';
const res = tryResolveContext('/path/to/project');
if (!res.ok) {
console.error(res.issues);
} else {
const v = runValidate(res.data);
console.log(v.ok, v.issues, v.data.missing);
console.log(stringifyEnvelope(runConfig(res.data), true));
}generate / report: see JSON: programmatic — both have run* helpers; report still uses --format for the on-disk artifact.
buildConfigSnapshot (and thus config --json / runConfig) sets cliVersion from CLI_VERSION in packages/cli/src/constants/cli.ts (kept in sync with packages/cli/package.json).
run* vs commands/<name>/run.ts: the command file owns argv, resolveContext, human tables, logger, report file, exit code. run* in core/*/jsonEnvelope.ts builds the same domain outcome the JSON branch needs: gather data, attach issues[], wrap buildCliJsonEnvelope. The human branch often recomputes similar work (e.g. quality) because presentation (tables, colors, truncation) is separate; refactoring to one shared “compute then render” core is possible but not required for correctness.
See JSON: programmatic and issue codes.
Key Extraction & Scanning
import { scanProjectLiteralKeyUsage, scanProjectDynamicKeySites } from '@zamdevio/i18nprune/core';
const usage = scanProjectLiteralKeyUsage(ctx);
const literalKeys = usage.resolvedKeys;
const dynamicSites = scanProjectDynamicKeySites(ctx);Missing literal keys (same as validate)
Uses per-file template + const resolution (same as keySites), not a single merged-source const map.
Typed CLI JSON payloads (import from @zamdevio/i18nprune/core): ValidateJsonOutput, CleanupJsonOutput, MissingJsonOutput — for validate, cleanup --json, and missing --json respectively.
import {
computeMissingLiteralKeys,
resolvedLiteralKeysInProject,
readJsonFile,
resolveContext,
} from '@zamdevio/i18nprune/core';
const ctx = resolveContext();
const raw = readJsonFile(ctx.paths.sourceLocale);
const missing = computeMissingLiteralKeys(ctx, raw); // dotted paths in code but not in that JSON
const allLiterals = resolvedLiteralKeysInProject(ctx); // Set of resolved keys across srcTo scan the project once (e.g. validate-style pipelines), reuse the same observations for missing keys and usage:
import {
computeMissingLiteralKeysFromResolvedKeys,
literalKeyUsageFromObservations,
readJsonFile,
resolveContext,
scanProjectKeyObservations,
} from '@zamdevio/i18nprune/core';
const ctx = resolveContext();
const observations = scanProjectKeyObservations(ctx);
const usage = literalKeyUsageFromObservations(observations);
const raw = readJsonFile(ctx.paths.sourceLocale);
const missing = computeMissingLiteralKeysFromResolvedKeys(raw, usage.resolvedKeys);Template rebuild and partial prefix
import {
tryRebuildTemplateKeyFromConsts,
tryResolveTemplatePrefixBeforeUnknown,
} from '@zamdevio/i18nprune/core';
tryRebuildTemplateKeyFromConsts('a.${NS}.b', { NS: 'section' }); // => 'a.section.b' or null
tryResolveTemplatePrefixBeforeUnknown('a.b.${id}.x', {}); // => 'a.b' when id is unknownscanProjectDynamicKeySites uses the same rules: fully rebuilt templates are omitted from dynamic results; otherwise resolvedPrefix may be set. See docs/architecture/decisions/005-dynamic-key-rebuild-and-prefix.md.
Interactive cleanup helpers (--ask)
import {
canAsk,
promptApprovedRemovalKeys,
groupKeysByTopSegment,
} from '@zamdevio/i18nprune/core';
import type { PromptRemovalKeysMode } from '@zamdevio/i18nprune/core';
// Same primitives the `cleanup --ask` command uses; call only when canAsk() is true.JSON Utilities
import { collectStringLeaves, readJsonFile } from '@zamdevio/i18nprune/core';
const sourceData = readJsonFile(ctx.paths.sourceLocale);
const allLeaves = collectStringLeaves(sourceData);Safety & Design
- Same resolution logic as the CLI (no drift)
- Pure functions where possible
- No side effects except
readJsonFile - Full TypeScript support
Heuristic extraction limits
Literal keys, key-site observations, and dynamic sites use pattern matching and per-file const maps, not a full TS program analysis. Indirect calls (e.g. const fn = t) and cross-file constants may not match CLI expectations.
Read Detection limits in docs/regex/ for an honest scope statement, then extraction and key-sites-and-dynamic for details.