A strongly typed i18n library for react.
npm install --save react-typed-i18n
- Typechecked text id using TypeScript's Template Literal Types
- Interpolation with
string
andReact.ReactNode
- Async language loading for code splitting
- Hot language reloading without reloading page
- No external dependency and 1.3 KiB gzipped
- 100% line and branch test coverage
This library is the successor of simstate-i18n
. Most concepts and functionalities remain unchanged, but this library
- removes the
simstate
dependency - use Template Literal Types to typecheck the text id
- is way easier to setup
My personal website ddadaal.me is built with this library.
A example project is provided under the example
folder. Run the following commands to run it.
# On the library project root
npm install
npm run build
cd example
npm run dev
- Define your definitions (one file per language)
- use
{}
or{key}
as placeholders for interpolation (more details about interpolation) - object can be nested
- all languages should have identical structures
- this object is called
Language
- use
// ./src/i18n/en
export default {
hello: {
world: "Hello {} World {}",
}
}
// ./src/i18n/cn
export default {
hello: {
world: "你好 {} 世界 {}",
}
}
- Define all your languages and create elements from
createI18n
- The key of
languages
is the id of the language; - The value of
languages
isLanguage
or() => Promise<Language>
- Use
languageDictionary
helper to create the initialization arg
- The key of
// ./src/i18n/index.ts
import { createI18n, languageDictionary } from "react-typed-i18n";
const cn = () => import("./cn").then((x) => x.default);
const en = () => import("./en").then((x) => x.default);
export const languages = languageDictionary({
cn,
en,
});
export const { Localized, Provider, id, prefix, useI18n } = createI18n(languages);
- Wrap the component tree with
Provider
component- A
Language
object and its corresponding id must be provided for theProvider
compoennt - In some circumstances (like SSR), rather than importing
Language
directly,Language
can be asyncly loaded and provided.
- A
// ./src/Root.tsx
import React from "react";
import en from "./i18n/en";
import { Provider } from "./i18n";
import App from "./App";
export default () => {
return (
<Provider initialLanguage={{
id: "en",
definitions: en,
}}>
<App />
</Provider>
);
}
- Use
Localized
in places of raw texts- Use
args
prop to interpolate args into the placeholders (you can pass array or object as arguments. Learn More) - A type error will be reported if the id is not valid
- The
Localized
must be imported from where thecreateI18n
is called (for example,./src/i18n
) - The below displays: Hello AAA World BBB
- Use
// ./src/App.tsx
import React from "react";
import { Localized } from "./src/i18n";
export default () => {
return (
<div>
<p>
<Localized
id="hello.world"
args={[
<strong key="1">AAA</strong>,
<strong key="2">BBB</strong>,
]}
/>
</p>
</div>
);
}
- Use
useI18n
hook to get helper functions likesetLanguageById
- After clicking the button, the p will display: 你好 AAA 世界 BBB
// ./src/App.tsx
import React from "react";
import { Localized, useI18n } from "./src/i18n";
export default () => {
const { setLanguageById } = useI18n();
return (
<div>
<p>
<Localized
id="hello.world"
args={[
<strong key="1">AAA</strong>,
<strong key="2">BBB</strong>,
]}
/>
</p>
<button onClick={() => setLanguageById("cn")}>
Change to cn
</button>
</div>
);
}
react-typed-i18
uses {}
or {key}
as placeholders for interpolations.
{key}
will be replaced withargs[key]
{}
will be replaced withargs[i]
, wherei
isi
th occurrence of{}
(not counting{key}
) in the definition string- if
args
is not an array, it will be replaced withObject.values(args)[i]
.- It's not recommended since the order of the resulting array may not be trivial.
- if
- If the string to be replaced is
undefined
, it will be replaced as empty string - If
{}
or{key}
is prefixed with\
, it will be escaped. Prefix a\
with a\
will not escape the following{}
or{key}
{}
and {key}
can co-exist in one definition.
See the examples:
definition | args |
result |
---|---|---|
{} {} |
["1", "2"] |
1 2 |
{1} {0} |
["1", "2"] |
2 1 |
{key2} {key1} |
{"key1": "value1", "key2": "value2"} |
value2 value1 |
{1} {0} |
{"key1": "value1", "key2": "value2"} |
value2 value1 |
{} {1} {0} {} |
["1", "2"] |
1 2 1 2 |
{} {key2} {1} {} |
["1", "2"] |
1 2 1 |
{} {key2} {1} {} |
{"key1": "value1", "key2": "value2"} |
value1 value2 value2 |
\{0} \\{0} \\\{0} |
["1"] |
\{0} \1 \\{0} |
import { prefix, id } from "./i18n";
// id is just an identity function with typecheck
const i = id("hello.world"); // id === "hello.world"
// prefix generates a prefix function.
// When the function is called,
// two part are concatenated.
// both part are typechecked.
const p = prefix("hello.");
const fullId = p("world");
// src/i18n/en.ts
export default {
a: "a",
b: {
c: "c",
},
};
// src/i18n/index.ts
import { TextIdFromLangDict } from "react-typed-i18n";
// "a" | "b.c"
export type TextId = TextIdFromLangDict<typeof languages>;
MIT