simstate-i18n
is a strongly-typed React i18n library based on simstate.
This library has been superceded by react-typed-i18n
. The new library:
- has all features of this library!
- does not depend on
simstate
- use
string
literals as id which are typechecked usingtemplate literal types
Please give it a try!
- Use text id in a strongly-typed manner
- Support nested text id
- Support placeholders on text definition
- Support async language loading for code splitting
- Hot change languages without page reloading
- Hot change texts without restarting the application
npm install --save simstate-i18n
My blog ddadaal.me is created with simstate-i18n.
Try changing the language by the LanguageSelector.
This library requires setting up necessary files and folders before using the components and store.
Check out the example folder for recommended file structure.
.
├── App.tsx
└── i18n
├── cn.ts
├── en.ts
└── index.ts
- Create a folder
i18n
(or anything you want) on yoursrc
folder - Create a file (
{language}.ts
, for examplecn.ts
,en.ts
) underi18n
folder for each language to support with the following content:- Every such file defines a language object for one language
- Language object contains the basic information (id, strings, names etc.) and the mappings from id to text
- Every language objects should have exactly identical structure.
// src/i18n/en.ts
// example: example/i18n/{en,cn}.ts
export default {
// The id of the language. Any unique string is acceptable.
id: "en",
// The name of the language
name: "English",
// The definitions of id and text template.
// Use "{}" as the placeholder for dynamically set text or React component.
definitions: {
navbar: {
home: "Home",
about: "About",
},
content: "Current time: {}. Thanks for using simstate-i18n."
}
}
- Create a
index.ts
under thei18n
folder with the following content:
// src/i18n/index.ts
// example: example/i18n/index.ts
// Imports
import cn from "./cn";
import { createI18nContext, I18nStoreDef, I18nStore } from "simstate-i18n";
import { useStore } from "simstate";
// Load English dynamically to support code splitting
const en = () => import("./en").then((x) => x.default);
// The actual Language type,
// might be useful when the Language object is extended and the extra properties are needed
export type Language = typeof cn;
// Create the I18nContext with cn as the default language.
export const i18nContext = createI18nContext(cn, { en });
// Destruct and export the members for easier usage
// Recommendation: rename the idAccessor to lang for shorter typing
export const { getLanguage, idAccessor: lang } = i18nContext;
// This function is shortcut to use I18nStore,
// and also specify the exact types of Language objects,
// which helps avoid type casting.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function useI18nStore() {
return useStore(I18nStore) as I18nStoreDef<Language["definitions"], Language>;
}
- Create and inject a new global
simstate
store with the i18nContext instance.
// example: example/App.tsx
import { i18nContext } from "./i18n";
import { createI18nStore } from "simstate-i18n";
import { StoreProvider } from "simstate";
const Root = () => {
// Create global i18nStore instance.
const [i18nStore] = useState(() => createI18nStore(i18nContext));
return (
<StoreProvider stores={[i18nStore]}>
<App />
</StoreProvider>
)
}
When the configurations are completed and the global I18nStore is injected, it is possible to use the provided components and store.
<LocalizedString />
or <Localized />
component are used in place of raw texts to provide i18n capabilities to anywhere a React component can be. It shows the text of the specified id of the current language.
All LocalizedString components will be updated when the current language is changed.
Example:
// import the idAccessor (renamed to lang) from i18n folder
// which is used to access the id of a text strongly-typedly.
import { lang } from "./i18n";
import { LocalizedString } from "simstate-i18n";
// Set the id of text as accessing properties of the lang object
// If the text has placeholders {},
// set the replacements prop with the replacement elements
// that will be inserted into the placeholders in order.
<LocalizedString id={lang.content} replacements={[Date.now()]} />
// The same as above but name is shorter
<Localized id={lang.content} replacements={[Date.now()]} />
This hook is used to suffice more advanced usage.
The following example behaves the same as the LocalizedString
example above, and will also be updated when the current language is updated.
Example:
import { lang } from "./i18n";
import { useLocalized } from "simstate-i18n";
const Component = () => {
const content = useLocalized(lang.content, [Date.now()]);
return content;
}
The I18nStore instance of current provider scope can be acquired with useStore
function provided by simstate
, which can be used to control the current language as well as getting some information.
Example:
import { I18nStore } from "simstate-i18n";
import { useStore } from "simstate";
const ControlPanel = () => {
const i18nStore = useStore(I18nStore);
return (
<div>
<p>
Current language: {i18nStore.currentLanguage.name}
</p>
{
i18nStore.switchingToId && `Switching to ${i18nStore.switchingToId}`
}
<ul>
{allLanguages.map((lang) => (
<li key={lang.id}>
<a onClick={() => i18nStore.changeLanguage(lang.id)}>
{lang.name}
</a>
</li>
))}
</ul>
</div>
)
}
simstate
: A Strongly-typed React State Management Tool Favoring React Hooks and TypeScript.
Strongly Typed i18n with TypeScript (English): This article of mine talks about the problems of using raw string as the text ids, and also introduces a proxy-based text id generation method which is now replaced with another method (src/i18nContext.ts) which should have better performance.
MIT © ddadaal