0.0.1 • Published 12 months ago

@nanokit/i18n v0.0.1

Weekly downloads
-
License
MIT
Repository
-
Last release
12 months ago

Introduction

@nanokit/i18n lets you write translations with

  • variables: "My name is {{name}}"
  • pluralization: "{{count}} (apple|apples)"
  • custom transforms on variables: "Good luck with your {{count:ordinal}} try".

For many use cases, this is all you need.

The variable types are automatically inferred to make your translations fully typesafe:

const english = {
  introduceMyself: `"My name is {{name}}"`,
};

const t = createTranslator(english);

t.introduceMyself({ name: "John" });
//                ^? { name: string }
// outputs: "My name is John"

Getting started

  • Install the package:
# npm
npm install @nanokit/i18n

# yarn
yarn add @nanokit/i18n

# pnpm
pnpm add @nanokit/i18n
  • Create a file for a particular language
// languages/en.ts

export const modifiers = {
  ... // examples below
}

export const translation = {
  ... // examples below
} as const
  • and get access to your typesafe translations with the usual t thing:
// translation.ts

import { createTranslator } from "@nanokit/i18n";
import { translation, modifiers } from "./languages/en";

export const t = createTranslator(translation, modifiers);

// example usage below

Note: Ideally, languages should be automatically detected and lazy loaded as needed. This is currently achieved with the React adapter (see below).

Examples

Write an object for a specific language like this:

// languages/en.ts

...

export const translation = {
  general: {
    hello: "Hello!",
    introduce: "My name is {{name}}",
    introduceSomeoneElse: "{{gender:pronoun_possessive}} name is {{name}}",
  },
  things: {
    apples: "Here (is|are) {{count}} (apple|apples)",
    deleteItems: "Delete {{count:numeral}} (item|items)",
  },
} as const;

and you can use your translations like this:

import { t } from "./translation";

t.general.hello();
// outputs: "Hello!"

t.general.introduce({ name: "John" });
//                  ^? { name: string }
// outputs: "My name is John"

t.general.introduceSomeoneElse({ gender: "female", name: "Jane" });
//                             ^? { gender: "male" | "female" | "neutral", name: string }
// outputs: "her name is Jane"

t.general.introduceSomeoneElse({ gender: "male", name: "John" });
// outputs: "his name is John"

t.things.apples({ count: 1 });
// outputs: "Here is 1 apple"

t.things.apples({ count: 2 });
// outputs: "Here are 2 apples"

t.things.deleteItems({ count: 1 });
// outputs: "Delete one item"

t.things.deleteItems({ count: 2 });
// outputs: "Delete two items"

Custom modifiers

What's this {{variable:something}} pattern all about? The tag :something represents a fully customizable modifier that give you infinite flexibility without bloating your translation strings with custom logic.

Simply define your modifier functions for each language:

// languages/en.ts

const modifiers = {
  pronoun_possessive: (gender: "male" | "female" | "neutral") => {
    return { male: "his", female: "her", neutral: "their" }[gender];
  };
}

...

Now, if you write {{gender:pronoun_possessive}} in your translation string, the gender variable automatically expects the value "male" | "female" | "neutral", and it will output "his", "her", or "their".

Multiple modifiers

You can add multiple modifiers for each variable, e.g. {{gender:pronoun_possessive:capitalize}}.

React adapter

Use @nanokit/i18n with React and lazy loading:

// translation-react.ts

import { translation, modifiers } from "./languages/en.ts";

// Define your general language model from the primary language.
// you can use this type for other languages to make sure
// they have the same keys, variables, and modifiers.
type LanguageModel = {
  translation: typeof translation;
  modifiers: typeof modifiers;
};

// Create provider component and hook
export const { TranslationProvider, useTranslation } = initI18n({
  // specify accessible languages:
  languages: ["en", "de", "fr", "es"],

  // create a loader for lazy loading languages:
  loader: async (language: string) => {
    return (await import(`./languages/${language}.ts`)) as LanguageModel;
  },

  // specify a primary language that will be preloaded:
  fallback: {
    language: "en",
    translation,
    modifiers,
  },
});

Wrap your app in the provider and (optionally) specify the language through its language prop. Now you can use the hook useTranslation:

function PresentName() {
  const t = useTranslation();

  return <>{t.introduce({ name: "John" })}</>; // "My name is John"
}

Reference modifiers safely

The library lets you create a utility, m:

import { createModifierHelper } from "@nanokit/i18n";

const m = createModifierHelper(modifiers);

This gives you typesafety when using modifiers:

`${m.pronoun_possessive("gender")} name is {{name}}`;
// equivalent to: "{{gender:pronoun_possessive}} name is {{name}}";

With this utility you avoid spelling mistakes and avoid using non-existent modifiers.

Nesting is also possible:

m.capitalize(m.pronoun_possessive("gender"));
// equivalent to: "{{gender:pronoun_possessive:capitalize}}"
0.0.1

12 months ago