Deprecated. Please use https://lingui.js.org/ instead
typed-i18n
type-safe i18n library
Why
The aim of typed-i18n is to provide zero-configuration type-safe i18n feature.
Typical i18n libraries uses the syntax of t('group.key'), which is apparently not type-safe.
typed-i18n uses regular JS & TS syntax so you can get these benefits:
- Build-time type safety
- Editor supports
- Checking i18n coverage in TS level
Also typed-i18n supports two translation flows:
engineer-driven translation
Engineers directly write translations. We can completely make them type-safe.
translator-driven translation
translators fill yaml/json/spreadsheets/(any) then compile it. It is a common pattern, but type-safety will be lost to some extent.
Install
npm install --save typed-i18n
Or if you use Yarn:
Usage
You can write basic translations like this:
import TypedI18n from 'typed-i18n' const en = { hello: 'Hello', goodbye: 'Goodbye', } const ja = { hello: 'こんにちは', goodbye: 'さようなら', } type Lang = 'en' | 'ja' type Translations = typeof en & typeof ja const t = new TypedI18n<Lang, Translations>() .addLocale('en', en) .addLocale('ja', ja) t.setLocale('en') console.log(t.trans.hello) // => Hello t.setLocale('ja') console.log(t.trans.hello) // => こんにちは
If there are any mistakes in your translations, TS will check them.
Type-safety
In the above example, the following code will throw TypeScript errors.
// missing the locale t.setLocale('cn') // missing the key t.trans.notExistKey // trying to add a locale with missing keys t.addLocale('de', { hello: 'Guten Tag', })
Interpolation
Interpolation is one of the most used functionalities in I18N. It enables you to integrate dynamic values into your translations.
There are two options to implement it:
1. use interp function
import TypedI18n, { interp } from 'typed-i18n' const en = { greeting: interp(name => `Hello, ${name}`), } const t = new TypedI18n<'en', typeof en>().addLocale('en', en) console.log(t.trans.greeting('John')) // => Hello, John
2. define $index and call withArgs method
import TypedI18n from 'typed-i18n' const en = { greeting: `Hello, $1`, } const t = new TypedI18n<'en', typeof en>().addLocale('en', en) console.log(t.withArgs('John').trans.greeting) // => Hello, John
Nesting
You can nest your translations. There are two options to implement it:
1. use getter functions
import TypedI18n from 'typed-i18n' const en = { hello: 'Hello', goodbye: 'Goodbye', helloButGoodbye: () => `${en.hello}, but ${en.goodbye}.`, } const t = new TypedI18n<'en', typeof en>().addLocale('en', en) console.log(t.trans.helloButGoodbye) // => Hello, but Goodbye.
2. use $this expression
import TypedI18n from 'typed-i18n' const en = { hello: 'Hello', goodbye: 'Goodbye', helloButGoodbye: '$this.hello, but $this.goodbye.', } const t = new TypedI18n<'en', typeof en>().addLocale('en', en) console.log(t.trans.helloButGoodbye) // => Hello, but Goodbye.
React bindings
You can use typed-i18n with React:
npm install --save typed-i18n-react
// i18n.ts import TypedI18n, from 'typed-i18n' import createContextHooks from 'typed-i18n-react' const en = { hello: 'Hello', changeLocale: 'Change Locale', } const ja = { hello: 'こんにちは', changeLocale: '言語を変える', } type Lang = 'en' | 'ja' type Translations = typeof en & typeof ja const t = new TypedI18n<Lang, Translations>() .addLocale('en', en) .addLocale('ja', ja) export const { useTrans, useChangeLocale, Provider } = createContextHooks(t)
// App.tsx import React from 'react' import ReactDOM from 'react-dom' import { useTrans, useChangeLocale, Provider } from './i18n' function App() { const t = useTrans() const changeLocale = useChangeLocale() const changeLang = React.useCallback(() => { changeLocale(t.locale === 'en' ? 'ja' : 'en') }, [changeLocale, t.locale]) return ( <div className="App"> <div className="App-title">{t.trans.hello}</div> <div className="App-btn" onClick={changeLang}> {t.trans.changeLocale} </div> </div> ) } ReactDOM.render( <Provider> <App /> </Provider>, document.getElementById('root'), )
TODO
- Deeply nested object support
- React bindings
- Plurals