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

@desolint/translation

v0.0.1

Published

Framework-agnostic i18next / react-i18next translation layer (client provider + useTranslation hook, server-side translator, locale helpers) for Desolint frontend projects.

Downloads

168

Readme

@desolint/translation

Framework-agnostic i18next / react-i18next translation layer for Desolint frontend projects — a client provider, a boilerplate-compatible useTranslation hook, a server-side translator, and locale helpers, with zero hard dependency on next or any app code.

Requirements

  • React 18 or newer, plus i18next 23+ and react-i18next 14+ (all peer dependencies)
  • npm 7 or newer — npm 7+ installs peer dependencies automatically

Install

npm install @desolint/translation i18next react-i18next i18next-resources-to-backend

react (>=18), i18next, react-i18next, and i18next-resources-to-backend are peer dependencies — the package reuses the copies your app already has and pulls in nothing else. No next, no nuqs, no UI library.

Neither installs peer dependencies automatically:

yarn add @desolint/translation react i18next react-i18next i18next-resources-to-backend
# or
pnpm add @desolint/translation react i18next react-i18next i18next-resources-to-backend

Why these are peers, not regular, dependencies: react-i18next keeps the active i18next instance in React context and module internals — two copies of react or i18next mean a component initialized against one and reading from the other, which silently returns keys instead of translations. Declaring them as peers makes npm reuse your app's copies.

What's in the box

Client entry — @desolint/translation

| Export | Purpose | | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | TranslationsProvider | Client provider — inits i18next for the active locale, hydrates from server resources, provides the instance + config to the tree. | | useTranslation() | {t, i18n, ready, ct, Trans} — namespaces come from the provider, so call sites take no arguments. | | ct({constant, t}) | Resolves a string \| (({t}) => …) constant. The hook / server translator return a t-bound ct({constant}). | | initializeTranslations({locale, config, resources?, instance?}) | The shared init routine (used by the provider and the server entry). | | setLocaleCookie, getCurrentLanguage, getLanguageOptions | Framework-agnostic locale helpers. | | DEFAULT_LOCALE_COOKIE_NAME, DEFAULT_LOCALE_COOKIE_MAX_AGE_DAYS | Cookie defaults. |

Server entry — @desolint/translation/server

| Export | Purpose | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | getTranslations({locale, config, resources?}) | Server-render init → {i18n, resources, t}. Pass resources to <TranslationsProvider>. | | getServerTranslator({config, locale?, resolveLocale?, resources?}) | {t, ct, i18n} for metadata / errors / anything outside the React tree. Replaces the boilerplate's translationUtilsValues(). | | mockEmptyT | Sentinel t for reading a constant object's raw value instead of a label. |

The I18nConfig object

The package holds no config of its own — no configure() call, no module singleton. Your app owns an I18nConfig (a stable module-level constant) and passes it into the provider and the server helpers:

import type {I18nConfig} from '@desolint/translation';

export const i18nConfig: I18nConfig = {
  locales: ['en', 'nl'],
  defaultLocale: 'en',
  namespaces: ['common', 'auth', 'dashboard'],
  // Required only when you don't hand the provider pre-loaded `resources`.
  // A template-string import lets the bundler code-split each namespace.
  loadResource: (language, namespace) =>
    import(`@/locales/${language}/${namespace}.json`),
};

Examples

Self-contained snippets live in examples/:

Provider

TranslationsProvider is a Client Component, and i18nConfig carries a loadResource function — so React can't pass the config across a Server → Client boundary. Wire it in from a thin 'use client' wrapper that imports the config directly (same shape as @desolint/react-query's provider wrapper):

// app/providers/TranslationsProvider.tsx
'use client';
import {TranslationsProvider} from '@desolint/translation';
import {i18nConfig} from '@/i18nConfig';

export default function AppTranslationsProvider({children, locale, resources}) {
  return (
    <TranslationsProvider
      config={i18nConfig}
      locale={locale}
      resources={resources}
      fallback={<GlobalLoader />}
    >
      {children}
    </TranslationsProvider>
  );
}
// app/[locale]/layout.tsx  (Server Component)
import {getTranslations} from '@desolint/translation/server';
import {i18nConfig} from '@/i18nConfig';
import AppTranslationsProvider from '@/app/providers/TranslationsProvider';

export default async function LocaleLayout({children, params}) {
  const {locale} = await params;
  const {resources} = await getTranslations({locale, config: i18nConfig});

  return (
    <AppTranslationsProvider locale={locale} resources={resources}>
      {children}
    </AppTranslationsProvider>
  );
}

The server passes only serializable props (locale, resources). resources is optional — omit it and the provider lazy-loads namespaces through config.loadResource (client-only apps that render the provider directly can skip the wrapper). fallback (default null) renders while init is in flight.

useTranslation

'use client';
import {useTranslation} from '@desolint/translation';

function ProfileCard({name}: {name: string}) {
  const {t, ct, Trans} = useTranslation();

  return (
    <>
      <h2>{t('common:profile.title')}</h2>
      <p>{t('common:profile.greeting', {name})}</p>
      <Trans i18nKey='dashboard:welcome' values={{name}} />
    </>
  );
}

ct resolves a constant that may be a literal or a lazy translation — handy for shared constant objects:

const NAV = {label: ({t}) => t('dashboard:nav.settings'), href: '/settings'};
// at the call site:
ct({constant: NAV.label});

Server-side translation

import {getServerTranslator} from '@desolint/translation/server';
import {i18nConfig} from './i18nConfig';

export async function generateMetadata() {
  const {t} = await getServerTranslator({
    config: i18nConfig,
    // You resolve the request locale — e.g. a cookie via next/headers.
    resolveLocale: () => cookies().get('NEXTJS_LOCALE')?.value,
  });

  return {title: t('metadata:home.title')};
}

API

Client (@desolint/translation)

  • TranslationsProvider — props {children, locale, config, resources?, fallback?}
  • useTranslation(){t, i18n, ready, ct, Trans}
  • ct({constant, t})
  • initializeTranslations({locale, config, resources?, instance?}){i18n, resources, t}
  • setLocaleCookie({language, cookieName?, maxAgeDays?, path?})
  • getCurrentLanguage({currentLocale, languages})LanguageOption | null
  • getLanguageOptions({languages})LanguageOption[]
  • DEFAULT_LOCALE_COOKIE_NAME ('NEXTJS_LOCALE'), DEFAULT_LOCALE_COOKIE_MAX_AGE_DAYS (30)
  • TranslationConfigContext, useTranslationConfig()

Server (@desolint/translation/server)

  • getTranslations({locale, config, resources?}){i18n, resources, t}
  • getServerTranslator({config, locale?, resolveLocale?, resources?}){t, ct, i18n}
  • mockEmptyT()

Full type definitions ship in dist/index.d.ts and dist/server.d.ts.

What stays in your app

The Next.js- and UI-coupled pieces are deliberately not in the package:

  • I18nConfig / namespace list — your config, injected.
  • useLocaleRouter — a thin wrapper over next/navigation + i18n.language; keep it app-side.
  • The language switcher UI — build it on getLanguageOptions / setLocaleCookie / i18n.changeLanguage (see the example) with your own component library.

Development

npm install        # install dependencies (Husky wires up git hooks)
npm run build      # tsc + tsc-alias → dist/ (CJS + .d.ts)
npm run type-check # tsc --noEmit, source + examples/
npm test           # vitest
npm run lint       # eslint

TranslationsProvider is the only 'use client' module; the build keeps the directive on line 1 of dist/providers/TranslationsProvider.js (verified after every build). A pre-commit hook runs lint-staged; the same type-check / lint / test / build runs in CI on every push and PR.

Consumed locally via a file:../package-translation dependency until published — run npm run build after changing source, then npm install in the consuming app to refresh its copy.


License

MIT © Desolint — see LICENSE.

Free to use, modify and redistribute, commercially or otherwise. Provided "as is", without warranty or liability of any kind.