Skip to content

feat: lazy per-locale loading, frozen-lock check command, locale persistence - #4

Merged
Peyton-Spencer merged 2 commits into
mainfrom
feat/lazy-locales-check
Jul 19, 2026
Merged

feat: lazy per-locale loading, frozen-lock check command, locale persistence#4
Peyton-Spencer merged 2 commits into
mainfrom
feat/lazy-locales-check

Conversation

@Peyton-Spencer

Copy link
Copy Markdown
Contributor

Implements per-locale lazy loading, a zero-AI CI freshness gate, locale persistence, and shipped virtual-module types.

What's new

Lazy per-locale virtual modules (src/vite.ts)

  • virtual:solid-translate is unchanged (eager, back-compat).
  • virtual:solid-translate/locale/<locale> — one module per locale exporting that locale's dict as default. Each becomes its own chunk via dynamic import (locale ids validated against [A-Za-z0-9_-]+ so no path traversal).
  • virtual:solid-translate/lazy — exports the manifest:
    {
      sourceLocale: string;
      locales: string[]; // source + targets, deduped
      loaders: Record<string, () => Promise<Record<string, string>>>;
    }
  • HMR now invalidates the per-locale module for the changed JSON file alongside the eager module.

Runtime: lazy-aware TranslationProvider (src/index.ts, src/context.ts)

  • translations accepts the eager Record<locale, dict> or the lazy manifest (TranslationsInput).
  • Lazy mode: target dicts load via loader into a signal on init/setLocale; while pending (or on loader failure) t()/<T> return source text — never throw, never suspend. Loaded dicts are cached; loaders run once per locale. availableLocales() derives from manifest.locales; sourceLocale defaults from the manifest.

Locale persistence

  • persistLocale?: boolean | { key?: string } (default false). Initial locale = valid localStorage value, else auto-detect; setLocale writes through. Explicit locale prop always wins. All storage access guarded for SSR/no-localStorage.

solid-translate check — CI freshness primitive (src/cli.ts)

  • Runs extraction only: no AI provider, no writes, no ai package needed (the ai import is now deferred into the translate path).
  • Compares extracted keys + content hashes + context hints against .solid-translate.lock, and verifies every target locale file contains every key.
  • Exit 0 = fresh; exit 1 = stale with a per-locale report of missing/changed/orphaned keys. --json for machine-readable output.

GitHub Action (action.yml)

  • command: check supported; new stale output ("true"/"false"); new fail-on-stale input (default "true") — stale check fails the step unless softened. Exit codes >1 (crashes) always fail.
  • README CI/CD section gains two recipes: a PR gate running check, and translate-on-main + PR via peter-evans/create-pull-request.

Shipped virtual-module types

  • New virtual.d.ts published via solid-translate/virtual (exports + files): declares all three virtual specifiers. Consumers use /// <reference types="solid-translate/virtual" /> or a tsconfig types entry instead of hand-writing declarations.

Bug fix (pre-existing, blocking)

  • src/cli.ts had its own shebang and tsup's banner adds one, so dist/cli.js shipped with a doubled shebang and failed under Node for every command. Removed the source shebang (the banner provides it) — required for check to actually run in CI via npx.

Tests

bun test: 67 pass, 0 fail (was 44) — new suites:

  • tests/check.test.ts — fixture projects in temp dirs: fresh lock → exit 0; edited source string / new string / deleted string / changed context / missing target key / missing target file → exit 1 with the right report buckets.
  • tests/lazy-provider.test.ts — source renders immediately; locale switch falls back to source while pending then swaps after the loader resolves; caching; loader rejection never throws; eager mode unchanged.
  • tests/persist-locale.test.ts — default-off, valid/invalid persisted values, write-through, custom key, explicit-prop precedence.
  • tests/vite-virtual.test.ts — eager/lazy/per-locale module generation, missing-file empty dict, path-unsafe locale rejection.

bun run build and bunx tsc --noEmit are clean. Deliberately avoids refactoring the translate/lock-write logic and extract.ts internals (two PRs in flight there).

🤖 Generated with Claude Code

Peyton Spencer and others added 2 commits July 19, 2026 16:05
…istence

- virtual:solid-translate/lazy + virtual:solid-translate/locale/<locale>
  virtual modules: each locale dictionary becomes its own code-split chunk
  via dynamic import; the eager virtual:solid-translate module is unchanged
- TranslationProvider accepts either the eager translations record or the
  lazy manifest; lazy dictionaries load asynchronously with graceful
  source-language fallback (never throws, never suspends) and are cached
- persistLocale prop (default off) persists the active locale to
  localStorage with SSR/no-storage guards
- solid-translate check: zero-AI CI freshness primitive comparing extracted
  keys+hashes+contexts against .solid-translate.lock and verifying every
  target locale file contains every key; --json for machine output;
  exit 0 fresh / 1 stale
- action.yml: command: check with stale output and fail-on-stale input
- ship virtual.d.ts via solid-translate/virtual so consumers stop
  hand-writing virtual module declarations
- defer the ai import in the CLI so ai-free commands (extract, check) run
  without the ai package installed
- fix doubled shebang in dist/cli.js that made the built CLI fail on Node

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Peyton-Spencer
Peyton-Spencer force-pushed the feat/lazy-locales-check branch from a989d69 to 4a2c4c9 Compare July 19, 2026 20:06
@Peyton-Spencer
Peyton-Spencer merged commit 452fc48 into main Jul 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant