“hi18n”, a TypeScript-first internationalization library — getting started guide
"hi18n" is an internationalization library (more specifically, translation management library) for JavaScript/TypeScript and is currently under active development in Wantedly.
hi18n: message internationalization meets immutability and type-safety
Quickstart
Installation:
npm install @hi18n/core @hi18n/react-context @hi18n/react
npm install -D @hi18n/cli
# Or:
yarn add @hi18n/core @hi18n/react-context @hi18n/react
yarn add -D @hi18n/cli
Put the following file named like src/locale/index.ts
:
import { Book, Catalog, Message, msg } from "@hi18n/core";
type Vocabulary = {
"example/greeting": Message<{ name: string }>;
};
const catalogEn = new Catalog<Vocabulary>("en", {
"example/greeting": msg("Hello, {name}!"),
});
export const book = new Book<Vocabulary>({ en: catalogEn });
And you can use the translation anywhere like:
import React from "react";
import { useI18n } from "@hi18n/react";
import { book } from "../locale";
export const Greeting: React.FC = () => {
// Locale can be
…It roughly has the following features:
- TypeScript compiler ensures that you are using correct translation ids and translation parameters.
- Designed to match declarative paradigms such as React.
- Integrates well with existing ecosystems (such as Webpack) without extra configuration. For example, Webpack’s hot reloading and chunk splitting work out-of-the-box with hi18n. This happens just thanks to its modern architecture and requires no bundler-specific code.
We’ll describe design principles in detail in the coming blog posts. In this article, however, we provide a quick start for those interested in our library.
Setting it up
In this article, we assume you have a React + TypeScript project. First, install the following packages:
npm install @hi18n/core @hi18n/react-context @hi18n/react
npm install -D @hi18n/cli
# Or:
yarn add @hi18n/core @hi18n/react-context @hi18n/react
yarn add -D @hi18n/cli
Then create files for translation data. Depending on your preference, you can configure it in two ways: single-file or multi-files. We use the latter in this guide.
In the multi-file configuration, you have N+1 files where N is the number of languages.
// src/locale/index.ts
// (other names are fine)
// This file defines the list of translatable strings.
import { Book, Message } from "@hi18n/core";
import catalogEn from "./en";
import catalogJa from "./ja";
export type Vocabulary = {
// "<Translation ID>": Message<{ <Parameters> }>; (or simply Message; if you don't need parameters)
"example/greeting": Message<{ name: string }>;
};
// This is a bundle of translations for all languages (English and Japanese in this case).
export const book = new Book<Vocabulary>({
en: catalogEn,
ja: catalogJa,
});
// src/locale/en.ts
// (other names are fine)
// This file contains translated messages in a specific language (i.e. English).
import { Catalog, msg } from "@hi18n/core";
import type { Vocabulary } from ".";
export default new Catalog<Vocabulary>({
// "<Translation ID>": msg(<translated message>),
"example/greeting": msg("Hello, {name}!"),
});
// src/locale/ja.ts (in the same manner as en.ts)
import { Catalog, msg } from "@hi18n/core";
import type { Vocabulary } from ".";
export default new Catalog<Vocabulary>({
"example/greeting": msg("こんにちは、{name}さん!"),
});
Then define a command for synchronizing translations and translation IDs:
// package.json
{
"scripts": {
"i18n:sync": "hi18n sync 'src/**/*.ts' 'src/**/*.tsx'"
}
}
Then you can start using hi18n.
If you are using ESLint, we recommend our ESLint plugin. Just install the package and extend the preset called plugin:@hi18n/recommended
.
Using translated messages
There are several ways to use the translated messages in your application code.
With useI18n
This is the most standard way if you are using React. To use useI18n
, you first need to configure a locale (usually at the root of the tree).
// An example with explicit ReactDOM call.
// You may need to place it in a different place (like _app.ts).
import { LocaleProvider } from "@hi18n/react";
const root = ReactDOMClient.createRoot(/* ... */);
root.render(
<LocaleProvider locales="ja">
{/* ... */}
</LocaleProvider>
);
Then use useI18n to start translating everywhere in the tree.
import { useI18n } from "@hi18n/react";
// You need to explicitly import the translation data (which we call a book).
import { book } from "../../locale";
const Greet: React.FC = () => {
// It returns a function for translation using the current locale (from the context) and the book you supplied.
const { t } = useI18n(book);
return <>{t("example/greeting", { name: "太郎" })}</>;
};
With <Translate>
The <Translate>
component is suitable for translating messages interleaved with markups or React elements.
import { Translate } from "@hi18n/react";
// You need to explicitly import the translation data (which we call a book).
import { book } from "../../locale";
const Greet: React.FC = () => {
return <Translate book={book} id="example/greeting" name="太郎" />;
};
Special to <Translate>
, you can pass a React element as a parameter to the translation:
const UnreadMessages: React.FC = () => {
const unreadCount = 2;
if (unreadCount === 0) return null;
// en: "You have <link>{count,plural,one{# unread message}other{# unread messages}}</link>"
// ja: "<link>{count,number}通の未読メッセージ</link>があります"
return <Translate book={book} id="example/unread" count={unreadCount}>
<a key="link" href="https://example.com/inbox" />
</Translate>;
};
Synchronizing translations and translation IDs
Use hi18n sync command to synchronize translation IDs between the application and the translation data.
hi18n sync <globs...> [--exclude <glob>] [--check | -c]
Define a task using package.json:
// package.json
{
"scripts": {
"i18n:sync": "hi18n sync 'src/**/*.ts' 'src/**/*.tsx'"
}
}
and use it to update your translations:
npm run i18n:sync
# Or:
yarn i18n:sync
Watch out for your unsaved changes to the translations; it may rewrite TypeScript/JavaScript files containing the translations.
This command does the following:
- it comments out unused translations, and
- it generates a boilerplate for translations required by the application, but not defined yet.
Sometimes translations that are still in use are accidentally commented out. This is likely caused by a mistake in the application:
- You may have used hi18n in a way that the tool cannot detect translation usages.
- Or you may have commented out some portion of the application for debugging, and forgot to uncomment them before running the sync command.
In any case, just fix the problem in the application and rerun the synchronization. Then the command undoes the comment-out.
Adding a new translatable message
To add a new message, you can follow the steps below:
First, add a message using t.todo
or <Translate.Todo>
instead of t
or <Translate>
.
t.todo("example/new");
<Translate.Todo book={book} id="example/new" />;
Then use the hi18n sync
command to generate the boilerplate. Fill in the actual translations and remove .todo
or .Todo
.
You may also get the same result by editing everything by hand.
Other things you can do with hi18n
These things will be covered in later posts.
- Plural forms and number formats. The
Intl
API must be available in the browser to use them. -
t.dynamic
,Translate.Dynamic
, andtranslationId
API to dynamically select messages. - Our ESLint plugin
@hi18n/eslint-plugin
ensures the correct use of hi18n. It also has helper rules such as migration from Lingui. - You can simply create multiple Books if you need to split translation data. This is useful if you need to reduce bundle size or if you need to use hi18n in a library.
Colophon
This article was originally written in Japanese and translated to English by the author.
Top comments (0)