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

v17.5.0

Published

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

Downloads

4,537

Readme

@objectstack/service-i18n

The shipped provider for the kernel's i18n service slot — a file-based II18nService implementation that also mounts the /api/v1/i18n/* routes.

Slot criticality: core (ServiceRequirementDef in @objectstack/spec/system).

Installation

pnpm add @objectstack/service-i18n

Usage

import { ObjectKernel } from '@objectstack/core';
import type { II18nService } from '@objectstack/spec/contracts';
import { I18nServicePlugin } from '@objectstack/service-i18n';

const kernel = new ObjectKernel();
await kernel.use(new I18nServicePlugin({
  defaultLocale: 'en',
  localesDir: './i18n',
  fallbackLocale: 'en',
}));
await kernel.bootstrap();

const i18n = kernel.getService<II18nService>('i18n');
i18n.t('objects.account.label', 'en');            // 'Account'
i18n.t('greeting', 'en', { name: 'Alice' });      // 'Hello, Alice!'

⚠️ t() is synchronous and takes the locale as its second positional argument — t(key, locale, params?). It is not await-able and there is no ambient "current locale" to set: every call names the locale it wants.

Plugin options

I18nServicePluginOptions has exactly five fields, all optional.

| Option | Type | Default | Purpose | |:---|:---|:---|:---| | defaultLocale | string | 'en' | Reported by getDefaultLocale(); used as the adapter's default. | | localesDir | string | none | Directory of {locale}.json files loaded at construction. | | fallbackLocale | string | none | Consulted when a key is missing in the requested locale; reported by getFallbackLocale(), which the REST metadata reads pass to the document translators as their fallback chain (#14882). | | registerRoutes | boolean | true | Register the REST routes at kernel:ready. | | basePath | string | '/api/v1/i18n' | Base path for those routes. |

With registerRoutes: false — or when no http-server service is present — the plugin logs a warning and the service stays available programmatically through kernel.getService('i18n').

Locale files

One JSON file per locale, named {locale}.json, in localesDir. Files may be flat or nested; keys resolve by dot notation. There is no per-namespace file layout and no {{lng}}/{{ns}} path template.

i18n/
├── en.json
├── zh-CN.json
└── ja-JP.json

i18n/en.json:

{
  "greeting": "Hello, {{name}}!",
  "objects": {
    "account": { "label": "Account" }
  }
}

Interpolation is {{paramName}} only, substituted from the third argument of t(). A parameter with no supplied value is left as the literal {{name}} placeholder. There is no pluralization, no context suffix resolution, no returnObjects, and no date / number / relative-time formatting in this package — use Intl for those.

Service API

II18nService (from @objectstack/spec/contracts) declares four required members plus optional ones; FileI18nAdapter implements the required four and four of the optional.

import type { II18nService } from '@objectstack/spec/contracts';

// required
//   t(key, locale, params?)               -> string   (the key itself when unresolved)
//   getTranslations(locale)               -> Record<string, unknown>
//   loadTranslations(locale, data)        -> void      (deep-merged into the locale)
//   getLocales()                          -> string[]
// optional, implemented here
//   getDefaultLocale() / setDefaultLocale(locale)
//   getFallbackLocale()                   -> string | undefined  (the locale t() consults second)
//   setSupportedLocales(locales | undefined)

t() returns the key itself when nothing resolves — it never throws and never returns undefined, so a missing translation surfaces as a visible key rather than an empty string.

loadTranslations deep-merges, so several plugins can each contribute keys under the same nested path (every platform plugin pushes its own bundle at kernel:ready).

Which locales are reported

getLocales() reports what is loaded, narrowed by the app's declared i18n.supportedLocales when the runtime injects them via setSupportedLocales. The narrowing rules are contractual:

  • absent / empty / not an array ⇒ no narrowing (every loaded locale is reported);
  • a declared locale with no loaded bundle is still reported (declared-but-unserved is visible rather than silently intersected away);
  • narrowing is applied at read time, never as a prune of what is stored — bundles keep arriving after the app plugin has run.

Runtime-authored translations

Translations authored in Studio persist as translation metadata. The plugin wires the shared core sync, which replaces the authored layer wholesale (clear-then-reload) at kernel:ready, on metadata:reloaded, and on translation protocol mutations — so a key deleted from an authored item stops resolving on the next sync, while the static bundle layer underneath is untouched.

REST API

Registered by this plugin directly on the http-server service. These three routes are the whole surface (shown at the default basePath):

GET    /api/v1/i18n/locales                     # available locales
GET    /api/v1/i18n/translations/:locale        # all translations for one locale
GET    /api/v1/i18n/labels/:object/:locale      # field labels for one object

⚠️ The locale is a path segment, not a ?locale= query parameter — the query dialect was a wire-level 404 against every serving surface and was retired. Each route is expressed by the SDK (i18n.getLocales, i18n.getTranslations, i18n.getFieldLabels); src/i18n-route-ledger.ts is the audited list, and a conformance test fails when a mounted route has no entry or an entry names a route that is no longer mounted.

Client integration

There is no useTranslation hook in this repo. On the client, @objectstack/client-react carries the active locale so requests send a matching Accept-Language, and translations are fetched through @objectstack/client:

import type { ObjectStackClient } from '@objectstack/client';
import { ObjectStackProvider, useObjectStackLocale } from '@objectstack/client-react';

function App({ client, language }: { client: ObjectStackClient; language: string }) {
  return (
    <ObjectStackProvider client={client} locale={language}>
      <Screen />
    </ObjectStackProvider>
  );
}

function Screen() {
  const locale = useObjectStackLocale();   // string | undefined
  return <span>{locale}</span>;
}
import { ObjectStackClient } from '@objectstack/client';

const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
const locales = await client.i18n.getLocales();
const bundle = await client.i18n.getTranslations('zh-CN');
const labels = await client.i18n.getFieldLabels('crm_account', 'zh-CN');

Exports

import { I18nServicePlugin, FileI18nAdapter } from '@objectstack/service-i18n';

Types: I18nServicePluginOptions, FileI18nAdapterOptions.

FileI18nAdapter is the implementation behind the plugin, exported for hosts that wire their own kernel integration. Beyond the contract it also exposes replaceAuthoredTranslations(byLocale), which the authored-translation sync uses.

License

Apache-2.0. See LICENSING.md.

See Also