0.0.5 • Published 7 months ago

@next-box/i18n v0.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
7 months ago

@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 {
    LANGUAGE_CODE: 'en' | 'fr';
  }
}
// ./content/index.ts
const { LANGUAGE_CODE } = process.env;
let language: typeof import('./en/index.ts') | typeof import('./fr/index.ts');

switch (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('./src/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.

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.

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.

0.0.5

7 months ago

0.0.4

7 months ago

0.0.3

7 months ago

0.0.2-alpha-3.0

7 months ago