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

@ulam/calamansi

v0.3.3

Published

Data-agnostic i18n, locale hooks, and logic utilities.

Readme

@ulam/calamansi

Data-agnostic i18n, hooks, and logic utilities. The sour layer of the ulam framework.

Named for the iconic Filipino sour citrus. Small, essential, full of character.

Purpose & Scope

What calamansi does:

  • Data-agnostic i18n with runtime locale switching (no rebuild needed)
  • String interpolation with variable substitution
  • Per-locale fallback chain (e.g., en-USen → default)
  • localStorage-backed preference persistence
  • Framework-agnostic vanilla core with framework adapters
  • Zero dependencies (not even polyfills)

What calamansi doesn't do:

  • Number formatting (use Intl.NumberFormat)
  • Date formatting (use Intl.DateTimeFormat)
  • Pluralization rules (bring your own logic or use Intl.PluralRules)
  • Translation file management or CI/CD pipeline
  • Build-time string extraction (all content lives in your app)
  • Server-side rendering pre-translation (you control the locale on server)

Who should use calamansi:

  • Apps that need runtime locale switching without reload
  • Projects with i18n data from APIs or CMS (not baked into bundle)
  • Vanilla JavaScript, React, Vue, or Angular projects needing i18n
  • Apps already using Intl APIs and wanting lightweight i18n on top
  • Teams wanting to avoid heavy i18n libraries for simple needs

The ulam Framework

Calamansi is one of six independent packages in the ulam framework. See docs/ARCHITECTURE.md for the complete framework structure and dependency graph.

Install

npm install @ulam/calamansi

Framework adapters are optional:

npm install @ulam/calamansi/react
npm install @ulam/calamansi/vue
npm install @ulam/calamansi/angular

Usage

Vanilla

The vanilla API works anywhere. No framework required.

import { initI18n, setLocale, getT } from '@ulam/calamansi'

initI18n({
  en: { hello: 'Hello, {name}' },
  tl: { hello: 'Kamusta, {name}' },
})

setLocale('tl')
const t = getT()
t('hello', { name: 'Mikey' }) // 'Kamusta, Mikey'

React

import { I18nProvider, useT } from '@ulam/calamansi/react'

// Mount once at app root
<I18nProvider locale="en">
  <App />
</I18nProvider>

// In components
function MyComponent() {
  const t = useT()
  return <p>{t('hello', { name: 'Mikey' })}</p>
}

useT() re-renders the component when the locale changes. I18nProvider calls setLocale() internally and is a thin wrapper around the vanilla call.

Vue

import { useT, usePref } from '@ulam/calamansi/vue'

// Inside setup()
const t = useT()           // reactive ref containing the translate function
t.value('hello', { name: 'Mikey' })

const { value: lang, set: setLang } = usePref('lang', 'en')
// lang is a readonly ref; setLang persists to localStorage and updates the ref

useT() returns a readonly ref that updates automatically when setLocale() is called anywhere in the app. There is no provider to mount; setLocale() notifies all subscribers globally.

usePref() returns { value, set } rather than a two-element array, matching Vue's conventional style for composables with a distinct setter.

Angular

import { I18nService, PrefService } from '@ulam/calamansi/angular'

When to use this vs. @angular/localize:

@angular/localize is a compile-time system. It extracts and bakes translated strings into the bundle at build time. It does not support runtime locale switching without a page reload or a rebuild. If that constraint works for your project, use @angular/localize.

Use calamansi when you need:

  • Runtime locale switching without a page reload
  • Locale data from an API or CMS (not baked into the bundle)
  • Shared locale logic between Angular and non-Angular code in the same project

Both can coexist. Use @angular/localize for static UI strings, and calamansi for dynamic or API-driven content.

@Component({ ... })
export class NavComponent {
  constructor(private i18n: I18nService) {}

  get label() {
    return this.i18n.t('nav.home')
  }

  switchToTagalog() {
    this.i18n.setLocale('tl')
  }
}

I18nService is providedIn: 'root'. It holds the translate function in an Angular signal so components using i18n.translateFn() in templates will update reactively when the locale changes.

PrefService exposes localStorage-backed preferences as Angular signals:

@Component({ ... })
export class SettingsComponent {
  constructor(private pref: PrefService) {}

  lang = this.pref.get('lang', 'en')  // Signal<string>

  setLang(locale: string) {
    this.pref.set('lang', locale)
  }
}

Supported features

  • Interpolation: t('hello', { name: 'Mikey' }) produces "Hello, Mikey"
  • Fallback: missing keys fall back to en, never show a raw key
  • No bundled translations: bring your own locale data; any { key: value } object works

Design principles

  • Data-agnostic: pass any { key: value } object; JSON files, API responses, CMS payloads all work
  • Thin: just lookup and interpolation; number/date formatting defers to native Intl APIs
  • Independently usable: no dependency on ube; any JS project can use calamansi i18n

Independence

Calamansi has no dependency on ube. Dependency flows one direction only:

calamansi ──► sawsawan (only cross-importer)

Neither ube nor sawsawan import from calamansi directly.

Subpath exports

| Import | Contents | | ------ | -------- | | @ulam/calamansi | Vanilla core: initI18n, setLocale, getT, getPref, setPref, isSignificantlyChanged | | @ulam/calamansi/react | I18nProvider, useT, usePref | | @ulam/calamansi/vue | useT, usePref, vanilla re-exports | | @ulam/calamansi/angular | I18nService, PrefService, vanilla re-exports |

See the root README for a complete framework support overview across all ulam packages.