🌐🪶 Lightweight, Zero dependencies, Typesafe and Simple to use translations for TypeScript.
To use this package, install with npm:
npm install i4n
t
typed function for getting translations.switch
to change the language thet
function uses.lazy
to load data and use it in thet
function after loading.- fallback keys and fallback language.
- templates for easy control.
- custom data loader (for eg. JSON files)
To get started with translating, import the I4n class:
import { I4n } from "i4n";
Then create a set of translations:
const translations = {
en: {
hi: "Hello",
earth: "World",
what: {
happened: "What happened?",
},
},
es: {
hi: "Ola",
earth: "Mundo",
what: {
happened: "¿Qué pasó?",
},
},
} as const;
This package will not validate this structure or types inbetween translations(!), but having an object with languages is required for this module to work.
This will not work:
// BAD:
const translations = {
hi: "Hello",
earth: "World",
what: {
happened: "What happened?",
},
} as const;
⚠️ Warning: Watch out when making bulk translations, in that case be sure to type it yourself as this may cause thei4n
package to not work as intended or expected.
For example:
const translations = {
en: {
earth: "World",
},
es: {
aerth: "Mundo",
},
} as const;
This does not raise typescript errors because it is a valid object.
To overcome this, implement your own types:
type TranslationData = {
earth: string;
};
type Language = "en" | "es" | "fr";
type TranslationSet = {
[lang in Language]?: LanguageData;
};
const translations: TranslationSet = {
en: {
earth: "world",
},
es: {
aerth: "Mundo",
// ^ Typescript error
},
};
Then initialize the class with your translations and default language:
const i4n = new I4n({ ... });
i4n.t;
i4n.switch;
new I4n(config: {
translations?: Record<string, any>;
loader?: Promise<Record<string, any>> | Record<string, any>;
language: string;
fallbackLanguage?: string;
});
- translations: An object containing the translation data, organized by language codes.
- language: The default language to use.
- fallbackLanguage: (Optional) The language to fall back to if a translation is missing.
- loader: A loader to, for example, load json data from a
.json
file.
The types are configured to have the
translations
orloader
present in the arguments, leaving both out or putting both in will result in anI4nException
.
To use it there are 3 functions: t
, switch
and lazy
.
The t
function allows you to call your translations with a dot.notation
so you may get nested fields.
console.log(i4n.t("hi")); // "Hello"
Calling the t
method for just an object (not the nested key), will result in the object being returned.
console.log(i4n.t("what")); // { happened: "What happened?" }
When using i4n
with an API, you might have different translations for different error codes, but you do not want to type out all the HTTP error codes in your translations. Introducing: fallbacks.
With fallbacks, you can give a second key that should be used instead if the first key does not return a value.
This looks like this:
const translations = {
en: {
errors: {
"404": "Not found error",
"500": "Server error",
unspecified: "unspecified",
},
},
};
const i4n = new I4n({ translations, language });
// your api call for example ...
// you get an error code from the `fetch`, e.g: code: 401
const text = i4n.t([`errors.${code}`, "errors.unspecified"]); // "unspecified"
This will make development easier for not having to check if you have all the expected translations present.
You might forget to translate something, this config option makes sure you will have a language to fall back to.
// ... translations
const i4n = new I4n({ fallbackLanguage: "en" });
i4n.switch("es");
i4n.t("say-hi"); // does not exist in 'es' -> "Hello" from 'en'
The switch
method enables the application to switch languages at runtime, making it very useful for web translations.
i4n.switch("es");
console.log(i4n.t("hi")); // "Ola"
lazy({data?: Record<string, any>, loader?: () => Promise<Record<string, any>> | Record<string, any>})
Enables the user to load in translations after initialization of the I4n
class.
const i4n = new I4n({...}); // {en, es}
i4n.ready; // true
i4n.t("french-key"); // undefined
i4n.lazy({ loader: myCustomLoader }); // {fr}
i4n.ready; // false
await i4n.loaded({lang: "fr"})
i4n.ready; //true
i4n.t("french-key"); // "mon-key".
When working with i4n
, you might find that you want to modify the output.
With templates, there are a few small steps required to get started:
Step 1: Edit your translations to include a template function.
This can be any function, I chose for it to look like this:
const translations: TranslationSet = {
en: {
sayHiTo: (name: string) => `Hello ${name}`,
},
es: {
sayHiTo: (name: string) => `Ola ${name}`,
},
};
Step 2: Call your new template with custom parameters.
You will then call the function t
the same way, but with the parameters you defined:
i4n.t("sayHiTo", "John"); // "Hello John"
💡 As mentioned, this works with the parameters you defined. Objects and custom classes will also work!
This may seem small, but this is fully typed, making it very easy to define and use your templates in your code.
To use json files, it is recommended to use a custom loader
.
async function myJsonLoader(): Promise<TranslationSet> {
return await import("./file-to-translations.json");
}
const i4n = new I4n<TranslationSet>({
loader: myJsonLoader,
});
The types you pass in the I4n
class should be the same as the loader.
💡 Since you cannot ensure type safety on JSON files, it is recommended to use a tool like zod to ensure type safety.
You can then wait for the data to be loaded using the .loaded
function:
await i4n.loaded();
When you do not wish to block the process, you can check with the .ready if the loader has completed and the translations can be accessed.
const i4n = new I4n({...}); // with loader;
// later in the program:
i4n.ready;// boolean;
When using this package, be sure not to force types.
The translations
prop in the I4n
class can only accept objects, no Map
, Set
or Array
.
In that case, the module will throw a I4nException
error.
The same error is thrown when (also forced) switching to a language that is not in the translations.
These errors will need to be catched if you need to force some functionality, but this is strongly discouraged because it may break any end-user experience.
To showcase the i4n
package, there have been made several examples.
The following examples are present in this repository: