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

@objectstack/service-i18n

v6.9.0

Published

I18n Service for ObjectStack — implements II18nService with file-based locale loading

Readme

@objectstack/service-i18n

I18n Service for ObjectStack — implements II18nService with file-based locale loading and translation management.

Features

  • Multi-Language Support: Manage translations for unlimited languages
  • File-Based Locales: Load translations from JSON/YAML files
  • Namespace Support: Organize translations by domain (e.g., common, errors, ui)
  • Interpolation: Dynamic variable replacement in translations
  • Pluralization: Language-specific plural rules
  • Fallback Chain: Graceful fallback from dialect → base language → default
  • Type-Safe: TypeScript support with type-safe translation keys
  • Hot Reload: Reload translations without restarting (development)

Installation

pnpm add @objectstack/service-i18n

Basic Usage

import { defineStack } from '@objectstack/spec';
import { ServiceI18n } from '@objectstack/service-i18n';

const stack = defineStack({
  services: [
    ServiceI18n.configure({
      defaultLocale: 'en-US',
      supportedLocales: ['en-US', 'es-ES', 'fr-FR', 'de-DE'],
      loadPath: './locales/{{lng}}/{{ns}}.json',
    }),
  ],
});

Configuration

interface I18nServiceConfig {
  /** Default locale (e.g., 'en-US') */
  defaultLocale: string;

  /** List of supported locales */
  supportedLocales: string[];

  /** Path template for locale files */
  loadPath: string;

  /** Fallback locale when translation is missing */
  fallbackLocale?: string;

  /** Enable hot reload in development */
  hotReload?: boolean;
}

Directory Structure

locales/
├── en-US/
│   ├── common.json
│   ├── errors.json
│   └── ui.json
├── es-ES/
│   ├── common.json
│   ├── errors.json
│   └── ui.json
└── fr-FR/
    ├── common.json
    ├── errors.json
    └── ui.json

Example locales/en-US/common.json:

{
  "welcome": "Welcome to ObjectStack",
  "greeting": "Hello, {{name}}!",
  "item_count": "You have {{count}} item",
  "item_count_plural": "You have {{count}} items",
  "save_button": "Save",
  "cancel_button": "Cancel"
}

Service API

// Get i18n service
const i18n = kernel.getService<II18nService>('i18n');

Basic Translation

// Simple translation
const text = await i18n.t('common:welcome');
// "Welcome to ObjectStack"

// With interpolation
const greeting = await i18n.t('common:greeting', { name: 'Alice' });
// "Hello, Alice!"

// With pluralization
const count1 = await i18n.t('common:item_count', { count: 1 });
// "You have 1 item"

const count5 = await i18n.t('common:item_count', { count: 5 });
// "You have 5 items"

Change Locale

// Set locale for current context
await i18n.setLocale('es-ES');

// Get current locale
const locale = i18n.getLocale();
// "es-ES"

// Translate in specific locale (without changing context)
const text = await i18n.t('common:welcome', { locale: 'fr-FR' });

Namespaces

// Load translation from 'errors' namespace
const errorMsg = await i18n.t('errors:not_found');

// Load multiple namespaces
await i18n.loadNamespaces(['common', 'ui', 'errors']);

// Check if namespace is loaded
const isLoaded = i18n.isNamespaceLoaded('common');

Locale Management

// Get all supported locales
const locales = i18n.getSupportedLocales();
// ['en-US', 'es-ES', 'fr-FR', 'de-DE']

// Check if locale is supported
const isSupported = i18n.isLocaleSupported('ja-JP');
// false

// Get locale metadata
const metadata = i18n.getLocaleMetadata('en-US');
// {
//   name: 'English (United States)',
//   nativeName: 'English (United States)',
//   direction: 'ltr',
//   pluralRules: 'en'
// }

Advanced Features

Nested Keys

{
  "user": {
    "profile": {
      "title": "User Profile",
      "edit": "Edit Profile"
    }
  }
}
await i18n.t('common:user.profile.title');
// "User Profile"

Arrays

{
  "days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
const days = await i18n.t('common:days', { returnObjects: true });
// ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

Context-Based Translations

{
  "friend": "A friend",
  "friend_male": "A boyfriend",
  "friend_female": "A girlfriend"
}
await i18n.t('common:friend', { context: 'male' });
// "A boyfriend"

await i18n.t('common:friend', { context: 'female' });
// "A girlfriend"

Formatting

// Date formatting
const formatted = await i18n.formatDate(new Date(), {
  locale: 'es-ES',
  format: 'long',
});
// "15 de enero de 2024"

// Number formatting
const price = await i18n.formatNumber(1234.56, {
  style: 'currency',
  currency: 'EUR',
  locale: 'fr-FR',
});
// "1 234,56 €"

// Relative time
const relative = await i18n.formatRelative(new Date('2024-01-01'), {
  locale: 'en-US',
});
// "3 months ago"

Dynamic Loading

// Add a new locale dynamically
await i18n.addLocale('ja-JP', {
  loadPath: './locales/ja-JP/{{ns}}.json',
});

// Remove a locale
await i18n.removeLocale('ja-JP');

// Reload translations (useful in development)
await i18n.reload();

Integration with Metadata

Translate metadata labels automatically:

import { ObjectSchema, Field } from '@objectstack/spec/data';

const contact = ObjectSchema.create({
  name: 'contact',
  label: 'i18n:objects.contact.label', // References translation key
  fields: {
    name: Field.text({
      label: 'i18n:fields.contact.name',
    }),
  },
});

// Translation file: locales/en-US/metadata.json
{
  "objects": {
    "contact": {
      "label": "Contact",
      "label_plural": "Contacts"
    }
  },
  "fields": {
    "contact": {
      "name": "Full Name"
    }
  }
}

REST API Endpoints

GET    /api/v1/i18n/locales              # Get supported locales
GET    /api/v1/i18n/translations/:locale # Get all translations for locale
POST   /api/v1/i18n/translate            # Translate keys (batch)

Client Integration

React Hook Example

import { useTranslation } from '@objectstack/client-react';

function MyComponent() {
  const { t, locale, setLocale } = useTranslation();

  return (
    <div>
      <h1>{t('common:welcome')}</h1>
      <button onClick={() => setLocale('es-ES')}>
        Español
      </button>
    </div>
  );
}

Best Practices

  1. Use Namespaces: Organize translations by domain (common, ui, errors, metadata)
  2. Consistent Keys: Use dot notation for nested keys (e.g., user.profile.title)
  3. Provide Context: Use context for gender, formality, or pluralization variants
  4. Fallback Values: Always provide fallback translations in default locale
  5. Avoid Hardcoding: Never hardcode user-facing text; use translation keys
  6. Professional Translation: Use professional translators for production
  7. Version Control: Store translation files in version control

Locale Coverage Detection

// Get coverage statistics
const coverage = await i18n.getCoverage();
// {
//   'en-US': { total: 245, missing: 0, percentage: 100 },
//   'es-ES': { total: 245, missing: 12, percentage: 95.1 },
//   'fr-FR': { total: 245, missing: 45, percentage: 81.6 }
// }

// Get missing keys for a locale
const missing = await i18n.getMissingKeys('es-ES');
// ['errors.validation.email', 'ui.dashboard.title', ...]

Performance Considerations

  • Lazy Loading: Namespaces are loaded on demand
  • Caching: Translations are cached in memory
  • Hot Reload: Only enable in development
  • Bundle Size: Load only required locales on client

Contract Implementation

Implements II18nService from @objectstack/spec/contracts:

interface II18nService {
  t(key: string, options?: TranslationOptions): Promise<string>;
  setLocale(locale: string): Promise<void>;
  getLocale(): string;
  getSupportedLocales(): string[];
  loadNamespaces(namespaces: string[]): Promise<void>;
  formatDate(date: Date, options?: FormatOptions): Promise<string>;
  formatNumber(value: number, options?: FormatOptions): Promise<string>;
}

License

Apache-2.0. See LICENSING.md.

See Also