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

@ooneex/translation

v1.2.0

Published

Internationalization framework with locale management, translation key resolution, and pluralization support for multi-language applications

Downloads

802

Readme

@ooneex/translation

Internationalization framework with locale management, translation key resolution, and pluralization support for multi-language applications.

Browser Bun TypeScript MIT License

Features

Multi-Language Support - 31 supported locales including Arabic, Chinese, French, Spanish, and more

Type-Safe Locales - LocaleType union type and LocaleInfoType with region data

Locale Constants - Readonly locales array of all supported locale codes

Error Handling - Custom TranslationException for missing or invalid translations

Cross-Platform - Works in Browser, Node.js, Bun, and Deno

Installation

bun add @ooneex/translation

Usage

Basic Usage

import { trans } from '@ooneex/translation';

// Using dictionary with string keys
const dictionary = {
  greeting: {
    en: 'Hello, World!',
    fr: 'Bonjour, le monde!',
    es: 'Hola, mundo!'
  }
};

// Get English translation (default)
const english = trans('greeting', { dict: dictionary });
console.log(english); // "Hello, World!"

// Get French translation
const french = trans('greeting', {
  lang: 'fr',
  dict: dictionary
});
console.log(french); // "Bonjour, le monde!"

// Using direct translation objects
const directTranslation = {
  en: 'Welcome',
  fr: 'Bienvenue',
  es: 'Bienvenido'
};

const welcome = trans(directTranslation, { lang: 'fr' });
console.log(welcome); // "Bienvenue"

Parameter Interpolation

import { trans } from '@ooneex/translation';

const dictionary = {
  welcome: {
    en: 'Welcome, {{ name }}!',
    fr: 'Bienvenue, {{ name }}!',
    es: '¡Bienvenido, {{ name }}!'
  },
  userInfo: {
    en: 'User {{ name }} has {{ count }} points',
    fr: 'L\'utilisateur {{ name }} a {{ count }} points'
  }
};

// Single parameter
const greeting = trans('welcome', {
  lang: 'en',
  dict: dictionary,
  params: { name: 'John' }
});
console.log(greeting); // "Welcome, John!"

// Multiple parameters
const info = trans('userInfo', {
  lang: 'en',
  dict: dictionary,
  params: { name: 'Alice', count: 150 }
});
console.log(info); // "User Alice has 150 points"

// Different parameter types
const stats = trans('stats', {
  dict: {
    stats: {
      en: 'Active: {{ active }}, Score: {{ score }}, ID: {{ id }}'
    }
  },
  params: {
    active: true,
    score: 95.5,
    id: 12345
  }
});
console.log(stats); // "Active: true, Score: 95.5, ID: 12345"

Pluralization

import { trans } from '@ooneex/translation';

const dictionary = {
  item: {
    en: '{{ count }} item',
    fr: '{{ count }} élément'
  },
  item_plural: {
    en: '{{ count }} items',
    fr: '{{ count }} éléments'
  },
  message: {
    en: '{{ count }} message',
    fr: '{{ count }} message'
  },
  message_plural: {
    en: '{{ count }} messages',
    fr: '{{ count }} messages'
  },
  message_zero: {
    en: 'No messages',
    fr: 'Aucun message'
  }
};

// Singular form (count = 1)
const singular = trans('item', {
  lang: 'en',
  dict: dictionary,
  count: 1
});
console.log(singular); // "1 item"

// Plural form (count > 1)
const plural = trans('item', {
  lang: 'en',
  dict: dictionary,
  count: 5
});
console.log(plural); // "5 items"

// Zero form (count = 0, when available)
const zero = trans('message', {
  lang: 'en',
  dict: dictionary,
  count: 0
});
console.log(zero); // "No messages"

// Zero form fallback to plural (when zero form not available)
const zeroFallback = trans('item', {
  lang: 'en',
  dict: dictionary,
  count: 0
});
console.log(zeroFallback); // "0 items"

Nested Keys

import { trans } from '@ooneex/translation';

const dictionary = {
  user: {
    profile: {
      name: {
        en: 'Full Name',
        fr: 'Nom complet',
        es: 'Nombre completo'
      },
      email: {
        en: 'Email Address',
        fr: 'Adresse e-mail'
      }
    }
  },
  app: {
    navigation: {
      home: {
        en: 'Home',
        fr: 'Accueil'
      },
      about: {
        en: 'About',
        fr: 'À propos'
      }
    }
  }
};

// Access nested keys with dot notation
const profileName = trans('user.profile.name', {
  lang: 'fr',
  dict: dictionary
});
console.log(profileName); // "Nom complet"

const homeNav = trans('app.navigation.home', {
  lang: 'en',
  dict: dictionary
});
console.log(homeNav); // "Home"

Advanced Usage

import { trans, locales, TranslationException } from '@ooneex/translation';

// Check supported locales
console.log(locales); // ['ar', 'bg', 'cs', 'da', 'de', 'el', 'en', ...]

// Error handling
try {
  const result = trans('nonexistent.key', {
    dict: {},
    lang: 'en'
  });
} catch (error) {
  if (error instanceof TranslationException) {
    console.log('Translation not found:', error.message);
  }
}

// Complex real-world example
const appDictionary = {
  notifications: {
    unread: {
      en: 'You have {{ count }} unread notification',
      fr: 'Vous avez {{ count }} notification non lue'
    },
    unread_plural: {
      en: 'You have {{ count }} unread notifications',
      fr: 'Vous avez {{ count }} notifications non lues'
    },
    unread_zero: {
      en: 'No unread notifications',
      fr: 'Aucune notification non lue'
    }
  },
  validation: {
    required: {
      en: 'The {{ field }} field is required',
      fr: 'Le champ {{ field }} est requis'
    },
    minLength: {
      en: 'The {{ field }} must be at least {{ min }} characters',
      fr: 'Le {{ field }} doit contenir au moins {{ min }} caractères'
    }
  }
};

// Notification with pluralization
const notification = trans('notifications.unread', {
  lang: 'en',
  dict: appDictionary,
  count: 3
});
console.log(notification); // "You have 3 unread notifications"

// Form validation with parameters
const validation = trans('validation.minLength', {
  lang: 'fr',
  dict: appDictionary,
  params: { field: 'mot de passe', min: 8 }
});
console.log(validation); // "Le mot de passe doit contenir au moins 8 caractères"

API Reference

trans<T extends string>(key, options?): T

The main translation function that handles all translation scenarios.

Parameters:

  • key: string | Record<LocaleType, string> - Translation key (dot notation supported) or direct translation object
  • options?: Object - Translation options
    • lang?: LocaleType - Target language (defaults to 'en')
    • params?: Record<string, boolean | number | bigint | string> - Parameters for interpolation
    • dict?: Record<string, unknown> - Translation dictionary (required when using string keys)
    • count?: number - Count for pluralization

Returns: Translated string

Example:

// String key with dictionary
trans('greeting', { lang: 'fr', dict: dictionary });

// Direct translation object
trans({ en: 'Hello', fr: 'Bonjour' }, { lang: 'fr' });

// With parameters
trans('welcome', {
  dict: dictionary,
  params: { name: 'John' }
});

// With pluralization
trans('item', {
  dict: dictionary,
  count: 5
});

Types

LocaleType

TypeScript type representing all supported locale codes.

Supported locales:

type LocaleType = 'ar' | 'bg' | 'cs' | 'da' | 'de' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fi' | 'fr' | 'hu' | 'hy' | 'it' | 'ja' | 'ko' | 'lt' | 'nl' | 'no' | 'pl' | 'pt' | 'ro' | 'ru' | 'sk' | 'sv' | 'th' | 'uk' | 'zh' | 'zh-tw';

LocaleInfoType

Type for locale information including region data.

type LocaleInfoType = {
  code: LocaleType;
  region: string | null;
};

Constants

locales

Array of all supported locale codes.

const locales: readonly LocaleType[];

Exceptions

TranslationException

Custom exception thrown when translations are not found or invalid.

Properties:

  • Extends base Exception class
  • HTTP status: 404 (Not Found)
  • Includes translation key and context information

Example:

try {
  trans('missing.key', { dict: {} });
} catch (error) {
  if (error instanceof TranslationException) {
    console.log(error.message); // "Translation key 'missing.key' not found"
  }
}

Translation Dictionary Structure

Basic Structure

const dictionary = {
  keyName: {
    en: 'English translation',
    fr: 'French translation',
    es: 'Spanish translation'
    // ... other locales
  }
};

Nested Structure

const dictionary = {
  section: {
    subsection: {
      keyName: {
        en: 'English translation',
        fr: 'French translation'
      }
    }
  }
};

Pluralization Structure

const dictionary = {
  // Singular form (count = 1)
  item: {
    en: '{{ count }} item',
    fr: '{{ count }} élément'
  },
  // Plural form (count != 1, except 0 if zero form exists)
  item_plural: {
    en: '{{ count }} items',
    fr: '{{ count }} éléments'
  },
  // Zero form (count = 0, optional)
  item_zero: {
    en: 'No items',
    fr: 'Aucun élément'
  }
};

Pluralization Rules

  1. count = 1: Uses the base key (singular form)
  2. count = 0: Uses key_zero if available, otherwise falls back to key_plural
  3. count > 1 or count < 0: Uses key_plural
  4. Fallback: If plural forms don't exist, falls back to singular form

Fallback Strategy

  1. Language fallback: If requested language not available, falls back to English ('en')
  2. Key fallback: If translation key not found, returns the key itself
  3. Pluralization fallback: If plural forms don't exist, uses singular form
  4. Empty translation: Throws TranslationException for empty translations

Best Practices

1. Organize Dictionary Structure

// Good: Organized by feature/section
const dictionary = {
  auth: {
    login: { en: 'Log In', fr: 'Se connecter' },
    logout: { en: 'Log Out', fr: 'Se déconnecter' }
  },
  navigation: {
    home: { en: 'Home', fr: 'Accueil' },
    about: { en: 'About', fr: 'À propos' }
  }
};

2. Use Consistent Parameter Names

// Good: Consistent naming
const dictionary = {
  welcome: { en: 'Welcome, {{ username }}!' },
  goodbye: { en: 'Goodbye, {{ username }}!' }
};

3. Handle Pluralization Properly

// Good: Complete pluralization support
const dictionary = {
  notification: { en: '{{ count }} notification' },
  notification_plural: { en: '{{ count }} notifications' },
  notification_zero: { en: 'No notifications' }
};

4. Provide Fallbacks

// Good: Always include English translations
const dictionary = {
  greeting: {
    en: 'Hello',
    fr: 'Bonjour',
    es: 'Hola'
    // en is always available as fallback
  }
};

Error Handling

The package throws TranslationException in the following cases:

  • Translation key not found in dictionary
  • Nested key path doesn't exist
  • Translation value is empty
  • String key used without dictionary
try {
  const result = trans('missing.key', { dict: dictionary });
} catch (error) {
  if (error instanceof TranslationException) {
    // Handle translation error
    console.warn('Translation missing:', error.message);
    // Provide fallback or default value
    return 'Fallback text';
  }
}

Performance Considerations

  • Dictionary Caching: Cache translation dictionaries to avoid repeated parsing
  • Lazy Loading: Load only required translations for better performance
  • Nested Key Optimization: Avoid deeply nested structures when possible
  • Parameter Validation: Validate parameters before passing to avoid runtime errors

Real-world Examples

E-commerce Application

const ecommerceDict = {
  product: {
    price: {
      en: 'Price: ${{ amount }}',
      fr: 'Prix : {{ amount }} $'
    },
    availability: {
      en: '{{ count }} in stock',
      fr: '{{ count }} en stock'
    },
    availability_zero: {
      en: 'Out of stock',
      fr: 'Rupture de stock'
    }
  },
  cart: {
    item: {
      en: '{{ count }} item',
      fr: '{{ count }} article'
    },
    item_plural: {
      en: '{{ count }} items',
      fr: '{{ count }} articles'
    },
    total: {
      en: 'Total: ${{ amount }}',
      fr: 'Total : {{ amount }} $'
    }
  }
};

// Usage
const price = trans('product.price', {
  dict: ecommerceDict,
  lang: 'en',
  params: { amount: '29.99' }
});

const cartItems = trans('cart.item', {
  dict: ecommerceDict,
  lang: 'fr',
  count: 3
});

User Dashboard

const dashboardDict = {
  dashboard: {
    welcome: {
      en: 'Welcome back, {{ username }}!',
      fr: 'Bon retour, {{ username }} !'
    },
    stats: {
      unread: {
        en: 'You have {{ count }} unread message',
        fr: 'Vous avez {{ count }} message non lu'
      },
      unread_plural: {
        en: 'You have {{ count }} unread messages',
        fr: 'Vous avez {{ count }} messages non lus'
      },
      unread_zero: {
        en: 'No unread messages',
        fr: 'Aucun message non lu'
      }
    }
  }
};

// Usage
const welcome = trans('dashboard.welcome', {
  dict: dashboardDict,
  lang: 'fr',
  params: { username: 'Marie' }
});

const messages = trans('dashboard.stats.unread', {
  dict: dashboardDict,
  lang: 'en',
  count: 0
});

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

  1. Clone the repository
  2. Install dependencies: bun install
  3. Run tests: bun run test
  4. Build the project: bun run build

Guidelines

  • Write tests for new features
  • Follow the existing code style
  • Update documentation for API changes
  • Ensure all tests pass before submitting PR
  • Add new locales to the supported list when needed

Made with ❤️ by the Ooneex team