Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork86
A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects.
License
codingcommons/typesafe-i18n
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects.
Created by Ivan Hofer (1995-2023)
🐤lightweight (~1kb)
👌easy to use syntax
🏃fast and efficient
🦺prevents you from making mistakes (also inplain JavaScript projects)
👷creates boilerplate code for you
💬supports plural rules
📅 allowsformatting of values e.g. locale-dependent date or number formats
⬇️ option forasynchronous loading of locales
📚 supports multiplenamespaces
⏱️ supports SSR (Server-Side Rendering)
🤝 can be used forfrontend, backend and API projects
🔍locale-detection for browser and server environments
🔄import andexport translations from/to files or services
⛔ no external dependencies
Clickhere to see an interactive demo oftypesafe-i18n showing some key aspects of the type-checking capabilities of this internationalization library.
- Get started - how to add
typesafe-i18nto your project - Usage - how to implement different use-cases
- Typesafety - how to get the best typesafety features
- Syntax - how to use the translation functions
- Dictionary - how to structure your translations
- Namespaces - how to optimize loading of your translations
- Formatters - how to format dates and numbers
- Switch-Case - how to output different words depending on an argument
- Locale-detection - how to detect an user's locale
- Utility functions - useful utility functions
- Integrations - how to integrate other i18n services
- Sizes - how much does
typesafe-i18nadd to your bundle size - Performance - how efficient is
typesafe-i18nimplemented - Sponsors - how to help this project grow
- FAQs - how to get your questions answered
⌨️ Run the setup process andautomatically detect the config needed
npx typesafe-i18n --setup-auto
ormanually configure
typesafe-i18nby answering a few questionsnpx typesafe-i18n --setup
It didn't work? Seehere for possible troubleshooting.
👀 Take a look at the generated files and it'sfolder-structure after running
npm run typesafe-i18n(ornpx typesafe-i18n)📖 Explore the assets
typesafe-i18noffers a lot. Just presscmd + Fto search on this page or see thetable of contents that will link you to more specific subpages with more details.⭐ Star this project onGitHub
Thanks! This helps the project to grow.
Having trouble setting uptypesafe-i18n? Reach out to us viaGithub Discussions or onDiscord.
npm install typesafe-i18n
The changelog of this project can be foundhere
- to version
5.x.x: see therelease post - to version
4.x.x: see therelease post - to version
3.x.x: see therelease post
Curious about what comes next? Seethis discussion to learn more about the plans for the future of this project.
If you would like to get involved within this project, take a look at thisdiscussion.
The package can be used inside JavaScript and TypeScript applications. You will get a lot of benefits by running thegenerator since it will create a few wrappers to provide you with full typesafety.
You can usetypesafe-i18n in a variety of project-setups:
- Angular applications
- Node.js apis, backends, scripts, ...
- React / Next.js applications
- Solid.js applications
- Svelte / SvelteKit / Sapper applications
- Vue.js / Nuxt.js applications
- Browser (via CDN) projects
- other frameworks
All you need is inside thegenerated filei18n-util.ts. You can use the functions in there to create a small wrapper for your application.
Feel free to open a newdiscussion if you need a guide for a specific framework.
Seehere if you want to learn how you can usetypesafe-i18n to implement your own specific use-case.
The library should work in allmodern browsers. It uses some functionality from theIntl namespace. You can see the list of supported browsershere. If you want to support older browsers that don't include these functions, you would need to include a polyfill likeintl-pluralrules.
If you want to get the best typesafety features, you will need to use thegenerator in order to create types and boilerplate code for you
Here you can see some examples wheretypesafe-i18n can help you:
Thetypesafe-i18n package allows us to be 100% typesafe for our translation functions and even the translations for other locales itself. Thegenerator outputs TypeScript definitions based on your base locale.
You will also benefit from full typesafe JavaScript code viaJSDoc-annotations.
typesafe-i18n comes with an API that allows other services to read and update translations. You can connect other services by using theimporter andexporter functionality.
There also exists an official plugin forInlang. It allows you to usetypesafe-i18n together with the tooling Inlang provides. You can find ithere.
The footprint of thetypesafe-i18n package is smaller compared to other existing i18n packages. Most of the magic happens in development mode, where the generator creates TypeScript definitions for your translations. This means, you don't have to ship the whole package to your users. The only two parts, that are needed in production are:
- string-parser: detects variables, formatters and plural-rules in your localized strings
- translation function: injects arguments, formats them and finds the correct plural form for the given arguments
These parts are bundled into thecore functions. The sizes of the core functionalities are:
- i18nString: 948 bytes gzipped
- i18nObject: 1089 bytes gzipped
- i18n: 1119 bytes gzipped
Apart from that there can be a small overhead depending on which utilities and wrappers you use.
There also exists a useful wrapper for some frameworks:
typesafe-i18nangular-service: 1230 bytes gzippedtypesafe-i18nreact-context: 1602 bytes gzippedtypesafe-i18nsolid-context: 1403 bytes gzippedtypesafe-i18nsvelte-store: 1342 bytes gzippedtypesafe-i18nvue-plugin: 1256 bytes gzipped
The package was optimized for performance:
- the amount of network traffic is kept small
The translation functions aresmall. Only the locales that are used areloaded - no unnecessary workload
Parsing your translation file for variables and formatters will only be performed when you access a translation for the first time. The result of that parsing process will be stored in an optimized object and kept in memory. - fast translations
Passing variables to thetranslation function will be fast, because its treated like a simple string concatenation. For formatting values, a single function is called performatter.
If you usetypesafe-i18n you will get a smaller bundle compared to other i18n solutions. But that doesn't mean, we should stop there. There are some possible optimizations planned todecrease the bundle size even further.
Become a sponsor ❤️ if you want to support my open source contributions.
Thanks for sponsoring my open source work!
Dou you still have some questions? Reach out to us viaGithub Discussions or onDiscord.
You probably forgot toload the locale first before using it. CallingloadLocaleAsync('en') orloadAllLocales() will fix it.
Running thenpx command with anpm version<7.0.0 will probably fail because itwill not includepeerDependencies.
You could try installing it locally via:
npm install typesafe-i18n
and then run the setup-command from within thenode_modules folder via:
./node_modules/typesafe-i18n/cli/typesafe-i18n.mjs --setup-auto
here is the original issue with some additional information:#142
I added a new translation to my locale file, but TypeScript gives me the ErrorProperty 'XYZ' does not exist on type 'TranslationFunctions'
Make sure to run thegenerator after you make changes to your base translation file. The generator willgenerate and update the types for you.
Yes, you can. See theusage section for instructions. Even if you don't use TypeScript you can still improve from some typesafety features viaJSDoc-annotations.
Thegenerator will only look for changes in your base locale file. Make sure to always update your base locale file first, in order to get the correct auto-generated types. If you want tochange your base locale file, make sure to give it the type ofBaseTranslation. All other locales should have the type ofTranslation. E.g. if you set your base locale to italian, you would need to do it like this:
set your base locale to italian (
it) in ´.typesafe-i18n.json`:{"baseLocale":"it"}define the type of your base locale as
BaseTranslation// file 'src/i18n/it/index.ts'importtype{BaseTranslation}from'../i18n-types'constit:BaseTranslation={WELCOME:"Benvenuto!"}exportdefaultit
define the type of your other locales as
Translation// file 'src/i18n/en/index.ts'importtype{Translation}from'../i18n-types'consten:Translation={WELCOME:"Welcome!"}exportdefaulten
Thegenerator creates some helpful wrappers for you. If you want to write your own wrappers, you can disable the generation of these files by setting thegenerateOnlyTypes option totrue.
Yes, you can configurei18n-ally likethis. There is currently also an openPR that will add official support fortypesafe-i18n.
When you want to dynamically access a translation, you can use the usual JavaScript syntax to access a property via a variable (myObject[myVariable]).
- define your translations
// i18n/en.tsimporttype{BaseTranslation}from'../i18n-types'consten:BaseTranslation={category:{simple:{title:'Simple title',description:'I am a description for the "simple" category',},advanced:{title:'Advanced title',description:'I am a description for the "advanced" category',}}}exportdefaulten
- use it in your components
<scriptlang="ts">// Component.svelteimportLLfrom'$i18n/i18n-svelte'importtype{Translation}from'$i18n/i18n-types'// ! do not type it as `string`// by restricting the type, you don't loose the typesafety featuresexportletcategory:keyofTranslation['category']='simple'</script><h2>{$LL.category[category].title()}<p> {$LL.category[category].description()}<p>
Validation libraries likezod,yup,joi etc. usually provide a way to define custom error messages. You can usetypesafe-i18n to translate these messages.
But you need to create the validation schema dynamically, after you have initialized theLL object ([]i18nObject](https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/runtime#i18nObject)).
You can do that like this by passing theLL object to a function that returns the validation schema:
import{z}from'zod'importtype{TranslationFunctions}from'./i18n/i18n-types'exportconstcreateLoginSchema=(LL:TranslationFunctions)=>z.object({email:z.string().min(1,LL.validation.emptyField()).email(LL.validation.invalidEmail()),password:z.string().min(1,LL.validation.emptyField()),})
By defaulttypesafe-i18n at this time does not provide such a functionality. Basically you will need to write a function that splits the translated message and renders a component between the parts. You can define your split characters yourself but you would always need to make sure you add them in any translation sincetypesafe-i18n doesn't provide any typesafety for these characters (yet).
With the strong typesafety features, you'll know if a locale is missing a translation. But in rare cases you might want to use your base translation as a fallback for other locales.
See thenext FAQ entry. The same concept can be applied to prefill your translations with the base translation and then just override the parts that are translated.
You'll loose the some sort of typesafety with that approach since you can't know which parts are translated and which are not. Using the base translation as a fallback is not recommended because your UI will contain two different locales which might confuse your users.
I have two similar locales (only a few translations are different) but I don't want to duplicate my translations
Your locale translation files can be any kind of JavaScript object. So you can make object-transformations inside your translation file. The only restriction is: in the end it has to contain a default export with typeTranslation. You could do something like this:
create your
BaseTranslation// file 'src/i18n/en/index.ts'importtype{BaseTranslation}from'../i18n-types'consten:BaseTranslation={WELCOME:"Welcome to XYZ",// ... some other translationsCOLOR:"colour"}exportdefaulten
create your other translation that overrides specific translations
// file 'src/i18n/en-US/index.ts'importtype{Translation}from'../i18n-types'importenfrom'../en'// import translations from 'en' localeconsten_US:Translation={ ...enasTranslation,// use destructuring to copy all translations from your 'en' localeCOLOR:"color"// override specific translations}exportdefaulten_US
If you are using nested translations, you should use the provided
extendDictionaryfunction that usesjust-extendunder the hood.import{extendDictionary}from'../i18n-utils'importenfrom'../en'// import translations from 'en' localeconsten_US=extendDictionary(en,{labels:{color:"color"// override specific translations}})exportdefaulten_US
For certain locales I don't want to output a variable, but due to the strict typing I have to specify it in my translation
The generated types are really strict. It helps you from making unintentional mistakes. If you want to opt-out for certain translations, you can use theany keyword.
create your
BaseTranslationwith a translation containing a parameter// file 'src/i18n/en/index.ts'importtype{BaseTranslation}from'../i18n-types'consten:BaseTranslation={HELLO:"Hi {name}!",}exportdefaulten
create another locale without that parameter by disabling the strict type checking with
as any// file 'src/i18n/de/index.ts'importtype{Translation}from'../i18n-types'constde:Translation={HELLO:"Hallo!"asany// we don't want to output the 'name' variable}exportdefaultde
WARNING! the usage of 'any' can introduce unintentional mistakes in future. It should only be used when really necessary and you know what you are doing.
A better approach would be to create a custom formatter e.g.
create your translation and add a formatter to your variable
// file 'src/i18n/en/index.ts'importtype{BaseTranslation}from'../i18n-types'consten:BaseTranslation={HELLO:"Hi {name|nameFormatter}!",}exportdefaulten
// file 'src/i18n/de/index.ts'importtype{Translation}from'../i18n-types'constde:Translation={HELLO:"Hallo {name|nameFormatter}!"}exportdefaultde
create the formatter based on the locale
// file 'src/i18n/formatters.ts'importtype{FormattersInitializer}from'typesafe-i18n'importtype{Locales,Formatters}from'./i18n-types'import{identity,ignore}from'typesafe-i18n/formatters'exportconstinitFormatters:FormattersInitializer<Locales,Formatters>=(locale:Locales)=>{constnameFormatter=locale==='de'// return an empty string for locale 'de' ?ignore// same as: () => ''// return the unmodified parameter :identity// same as: (value) => valueconstformatters:Formatters={nameFormatter:nameFormatter}returnformatters}
With the help ofLocalizedString you could enforce texts in your application to be translated. Lets take an Error message as example:
constshowErrorMessage(message:string)=>alert(message)constcreateUser=(name:string,password:string)=>{if(name.length===0){showErrorMessage(LL.user.create.nameNotProvided())return}if(isStrongPassword(password)){showErrorMessage('Password is too weak')return}// ... create user in DB}
In this example we can pass in any string, so it can also happen that some parts of your application are not translated. To improve your i18n experience a bit we can take advantage of theLocalizedString type:
importtype{LocalizedString}from'typesafe-i18n'constshowErrorMessage(message:LocalizedString)=>alert(message)constcreateUser=(name:string,password:string)=>{if(name.length===0){showErrorMessage(LL.user.create.nameNotProvided())return}if(isStrongPassword(password)){showErrorMessage('Password is too weak')// => ERROR: Argument of type 'string' is not assignable to parameter of type 'LocalizedString'.return}// ... create user in DB}
With the typeLocalizedString you can restrict your functions to only translated strings.
Unfortunately there are some open issues in theJest repository regarding modern package export formats sojest doesn't know where to load files from.
You need to manually telljest where these files should be loaded from, by definingmoduleNameMapper inside yourjest.config.js:
// jest.config.jsmodule.exports={moduleNameMapper:{"typesafe-i18n/angular":"typesafe-i18n/angular/index.cjs","typesafe-i18n/react":"typesafe-i18n/react/index.cjs","typesafe-i18n/solid":"typesafe-i18n/solid/index.cjs","typesafe-i18n/svelte":"typesafe-i18n/svelte/index.cjs","typesafe-i18n/vue":"typesafe-i18n/vue/index.cjs","typesafe-i18n/formatters":"typesafe-i18n/formatters/index.cjs","typesafe-i18n/detectors":"typesafe-i18n/detectors/index.cjs",}};
here is the original issue with some additional information:#140
Node.JS, by default, does not come with the fullintl support. To reduce the size of the node installment it will only include 'en' as locale. You would need to add it yourself. The easiest way is to install theintl package
> npm install intland then add following lines on top of yoursrc/i18n/formatters.ts file:
constintl=require('intl')intl.__disableRegExpRestore()globalThis.Intl.DateTimeFormat=intl.DateTimeFormat
Then you should be able to use formatters from theIntl namespace with all locales.
Note: this is an older approach to the problem. You should not need this when using Node.js version > 16.
Yarn uses a strange way to install dependencies in a monorepo setup. The issue lays in the "hoisting" of packages (seethis issue). Therefore it might be that thetypesafe-i18n dependencies cannot be found.
Changing the workspace config in package.json will fix the issue:
- "workspaces": [- "apps/*",- "packages/*"- ],+ "workspaces": {+ "packages": [+ "apps/*",+ "packages/*"+ ],+ "nohoist": [+ "**/typesafe-i18n",+ "**/typesafe-i18n/**"+ ]+ },
About
A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects.
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.








