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

@next-box/i18n

v0.0.13

Published

A type-safe content resolver with content path auto-complete and value preview.

Readme

@next-box/i18n

A type-safe content resolver with content path auto-complete, value preview, and markdown support.

npm version License: MIT

To use this library you must be able/willing to define your content in typescript and markdown files.

Installation

# terminal
npm add @next-box/i18n

Usage

Create content files

Content needs to be fed into the library's createI18n init function in the form of a serializable object. Each language for which you have content should live in its own file and be structured the same way and have a content named export.

Below is a simplistic example of how to define a content file that would be compatible with createI18n.

// ./content.ts
export const content = {
  pages: {
    homepage: {
      documentTitle: 'This is the homepage',
    },
  },
} as const; // This is important!

If you were to support multiple languages, your content files might look like the examples below.

// ./content/fr/index.ts
export const content = {
  pages: {
    homepage: {
      documentTitle: 'Voici la page d’accueil'
    },
  },
} as const; // This is important!
// ./content/en/index.ts
export const content = {
  pages: {
    homepage: {
      documentTitle: 'This is the homepage'
    },
  },
} as const; // This is important!
// ./.env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NEXT_PUBLIC_LANGUAGE_CODE: 'en' | 'fr';
  }
}
// ./content/index.ts
const { NEXT_PUBLIC_LANGUAGE_CODE } = process.env;
let language: typeof import('./en/index.ts') | typeof import('./fr/index.ts');

switch (NEXT_PUBLIC_LANGUAGE_CODE) {
  case 'en': {
    language = await import('./en/index.ts');
    break;
  }

  case 'fr': {
    language = await import('./fr/index.ts');
    break;
  }
}

export const { content } = language;

Read the typescript docs on it for more details on as const.

Create an instance of the i18n

createI18n takes the content object as an argument and returns an object with the t and tt content resolver functions as properties. You can then use those functions to look up content by its path from the root of the object.

If you pass the above content into createI18n, you can use the t and tt functions to retrieve the documentTitle like in the example below.

import { createI18n } from '@next-box/i18n';
import { content } from './content.ts';

const { tt } = createI18n(content);
// Path passed into `tt` is type-checked against the object passed into `createI18n`
// Hover over `documentTitle` or `tt` and get value preview.
// This would be "This is the homepage" for the single language example.
// This would be "Films populaires" | "Popular Movies" for the multi-language example.
const documentTitle = tt('pages.homepage.documentTitle');
// Do something with `documentTitle`

The t function is an untyped content lookup. The function takes a string and returns the value that path resolves to in the i18n object passed into createI18n. If the function resolves no value, then it returns undefined.

The tt function, on the other hand, is a typed content lookup. The function takes a string that is type-checked against all the possible paths in the i18n object passed into createI18n. The value that path resolves to can be previewed through IDE's inbuilt Typescript type preview. If the function resolves no value, then it throws an error.

Templating

The t and tt functions support content with template variables. You can define template variables in content with double curley braces either side of a camelcase variable name, i.e. Welcome back, {{fullName}}!.

Then, when you want to retrieve that piece of content, you do so along with a second argument of key/value pairs used to populate the template variables like in the example below.

import { createI18n } from '@next-box/i18n';

const content = {
  en: {
    greeting: 'Welcome back, {{fullName}}!',
  },
} as const;

const { tt } = createI18n(content);
const greeting = tt('en.greeting', { fullName: 'Joe Bloggs' });
console.log(greeting); // Will log 'Welcome back, Joe Bloggs!'

Error handling

There are two types of errors to help guard against mistakes in retrieving content, build-time typescript errors and runtime javascript exceptions. The former prevents a developer from entering an invalid path into the tt function, while the latter provides a backstop in case the initial type errors were ignored.

There is no error handling with the t function. It is indented to be much looser in terms of what a developer can enter and what happens when the function cannot resolve the path; it just returns undefined.

Using markdown files

For blocks of content that consist of multiple paragraphs or different types of content, it can be easier to define these in markdown files. The library supports markdown files through the md macro.

Macros are a mechanism for running JavaScript functions at bundle-time. The value returned from these functions are directly inlined into your bundle.

Below is an example of a content object that includes markdown.

import { md } from '@next-box/i18n/macros' with { type: 'macro' }; // This is important!

const content = {
  en: {
    contactUs: md('./markdown/contactUs.md'),
  },
} as const;

All the functions exported from the '/macros' sub-path are intended to be imported with the with { type: 'macro' } import attribute in conjunction with the use of the unplugin-macros plugin or a compiler/bundler that supports the macros import attribute such as Bun or Parcel.

By default, the path you pass into the md macro is relative to the current working directory. You can change this via the .i18n-macro.config.json config file. Create one in your project root and add a markdownDir property with a relative path from the current working directory to the directory where your markdown files live.

{
  "markdownDir": "./src/content/markdown"
}

The markdownDir path supports a template variable NEXT_PUBLIC_LANGUAGE_CODE, which is populated by setting an environment variable with the same name. You can use this if you support multiple languages. Please note, the multi-language content directory structure must be the same for this approach to work correctly.

{
  "markdownDir": "./src/content/{{NEXT_PUBLIC_LANGUAGE_CODE}}/markdown"
}

Markdown to JSX

The md macro is designed to be used with the Md markdown render component and I18nProvider context provider. Md transforms markdown into React components, while I18nProvider provides a way to share a component mapper and markdown-to-jsx options between instances of Md.

Under the hood, the library uses the markdown-to-jsx npm package to transform markdown into JSX. See the package's documentation for more information on its configuration options. Our componentMapper prop maps to the markdown-to-jsx overrides option.

The contrived example below illustrates how to use the md macro, I18nProvider, and Md component together.

import { md } from '@next-box/i18n/macros' with { type: 'macro' };
import { I18nProvider, Md } from '@next-box/i18n';
import { componentMapper } from './componentMapper.ts';

export const Component = () => {
  return (
    <I18nProvider componentMapper={componentMapper}>
      <Md>{md('content.path.to.markdown.file')}</Md>
    </I18nProvider>
  );
};

Changelog

Check out the features, fixes and more that go into each major, minor and patch version.

License

@next-box/i18n is MIT Licensed.