Skip to content

@akashjs/i18n API

createI18n(config)

Create an i18n instance with signal-based locale switching.

ts
function createI18n(config: I18nConfig): I18n;
ts
import { createI18n } from '@akashjs/i18n';

const i18n = createI18n({
  defaultLocale: 'en',
  messages: {
    en: { hello: 'Hello' },
    es: { hello: 'Hola' },
  },
});

I18nConfig

ts
interface I18nConfig {
  /** Default locale on startup */
  defaultLocale: string;

  /** Static messages keyed by locale */
  messages?: LocaleMessages;

  /** Lazy-load messages for a locale on demand */
  loadMessages?: (locale: string) => Promise<Messages>;

  /** Fallback locale when a key is missing in the current locale */
  fallbackLocale?: string;

  /** Custom pluralization rules keyed by locale */
  pluralRules?: Record<string, (count: number) => string>;
}

Messages types

ts
/** Nested or flat message object */
type Messages = Record<string, string | Messages>;

/** All messages keyed by locale */
type LocaleMessages = Record<string, Messages>;

I18n

The object returned by createI18n().

ts
interface I18n {
  t: (key: string, params?: Record<string, string | number>) => string;
  locale: ReadonlySignal<string>;
  setLocale: (locale: string) => Promise<void>;
  te: (key: string) => boolean;
  availableLocales: () => string[];
}

I18n.t(key, params?)

Translate a key with optional interpolation parameters.

ts
function t(key: string, params?: Record<string, string | number>): string;
ts
i18n.t('greeting');                    // 'Hello'
i18n.t('welcome', { name: 'Alice' }); // 'Welcome, Alice!'

Nested keys use dot notation:

ts
i18n.t('nav.home');  // Looks up messages.nav.home

Returns the key itself if no translation is found:

ts
i18n.t('missing.key'); // 'missing.key'

Pluralization

When params includes a count property, t() resolves a plural form. Define plural variants as sub-keys:

ts
// messages: { items: { one: '{count} item', other: '{count} items' } }
i18n.t('items', { count: 1 });  // '1 item'
i18n.t('items', { count: 5 });  // '5 items'

The plural form is determined by the active locale's plural rule. The resolved sub-key (e.g. items.one) is looked up, then interpolated with all params.

I18n.te(key)

Check if a translation key exists in the current locale or fallback locale.

ts
function te(key: string): boolean;
ts
i18n.te('greeting');     // true
i18n.te('nonexistent');  // false

I18n.setLocale(locale)

Set the active locale. If messages for the locale are not loaded and a loadMessages function was provided, it is called first.

ts
function setLocale(locale: string): Promise<void>;
ts
await i18n.setLocale('es');
i18n.t('greeting'); // Now returns the Spanish translation

Messages are cached after the first load. Switching back to a previously loaded locale does not re-fetch.

I18n.locale

A readonly signal containing the current locale string. Reactive — reading it inside a computed or render function tracks it as a dependency.

ts
const locale: ReadonlySignal<string>;
ts
i18n.locale(); // 'en'
await i18n.setLocale('fr');
i18n.locale(); // 'fr'

I18n.availableLocales()

Returns an array of all locale codes that have loaded messages.

ts
function availableLocales(): string[];
ts
i18n.availableLocales(); // ['en', 'es']

Pluralization Rules

Built-in rules follow CLDR conventions for these locales:

LocaleForms
enone (count === 1), other
esone (count === 1), other
frone (count <= 1), other
arzero, one, two, few (3-10), many (11-99), other

Provide custom rules via I18nConfig.pluralRules:

ts
const i18n = createI18n({
  defaultLocale: 'pl',
  pluralRules: {
    pl: (count) => {
      const mod10 = count % 10;
      const mod100 = count % 100;
      if (count === 1) return 'one';
      if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return 'few';
      return 'many';
    },
  },
  messages: {
    pl: {
      files: {
        one: '{count} plik',
        few: '{count} pliki',
        many: '{count} plików',
      },
    },
  },
});

A plural rule function receives the count number and returns the sub-key string ('one', 'other', 'few', etc.). If no rule exists for the current locale, the English rule is used as a fallback.

Released under the MIT License.