# Quick start

{% hint style="info" %}
This project is designed to be used in SPAs (Single page applications) with no server side rendering. &#x20;

If you're not using either Vite or Create-React-App, i18nifty is probably not the best choice for you. &#x20;
{% endhint %}

{% tabs %}
{% tab title="yarn" %}

```bash
yarn add --dev i18nifty
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install --save-dev i18nifty
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev i18nifty
```

{% endtab %}

{% tab title="npmp" %}

```bash
pnpm add --save-dev i18nifty
```

{% endtab %}
{% endtabs %}

Before diving into the thick of things let's make sure you can do local imports relative to your src directory. It will prevent you from having to write imports like:

`import { useTranslations } from "../../../../i18n";`&#x20;

{% code title="tsconfig.json" %}

```diff
 {
     "compilerOptions": {
         "target": "es5",
+        "baseUrl": "src"
     // ...
     }
 }
```

{% endcode %}

If you are using Vite (If you're using CRA you don't need the vite-tsconfig-paths plugin):

{% tabs %}
{% tab title="yarn" %}

```bash
yarn add --dev vite-tsconfig-paths
```

{% endtab %}

{% tab title="npm" %}

```bash
npm install --save-dev vite-tsconfig-paths
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev vite-tsconfig-paths
```

{% endtab %}

{% tab title="npmp" %}

```bash
pnpm add --save-dev vite-tsconfig-paths
```

{% endtab %}
{% endtabs %}

{% code title="vite.config.ts" %}

```diff
 import { defineConfig } from "vite";
 import tsconfigPaths from "vite-tsconfig-paths";
 import react from "@vitejs/plugin-react";

 // https://vitejs.dev/config/
 export default defineConfig({
    "plugins": [
        react(),
+       tsconfigPaths()
    ]
 });

```

{% endcode %}

Start by declaring the text keys you'll need in each component.&#x20;

`src/components/MyComponent.tsx`

```diff
+import { declareComponentKeys } from "i18nifty";

 type Props = {
     name: string;
 };

 export function MyComponent(props: Props) {
     const { name } = props;
     
     return (
         <>
             <h1>Hello {name}</h1>
             <h3>How are you feeling today?</h3>
             <p>
                 Click <a href="https://example.com">hrere</a> to 
                 learn more about this website
             </p>
         </>
     );
 }

+const { i18n } = declareComponentKeys<
+    | { K: "greating"; P: { who: string; } }
+    | "how are you"
+    | { K: "learn more"; P: { href: string; }; R: JSX.Element }
+>()({ MyComponent });
+export type I18n = typeof i18n;
```

`src/components/MyOtherComponent.tsx`

```diff
+import { declareComponentKeys } from "i18nifty";

 type Props = {
     messageCount: number;
 };

 export function MyOtherComponent(props: Props) {
     const { messageCount } = props;
     
     return (
         <>
             <span>You have {messageCount} unread messages.</span>
             <button>Open</button>
             <button>Delete</button>
         </>
     );
 }

+const { i18n } = declareComponentKeys<
+    | "open"
+    | "delete"
+    | { K: "unread messages"; P: { howMany: number; } }
+>()({ MyOtherComponent });
+export type I18n = typeof i18n;
```

then create your `src/i18n.tsx` file: &#x20;

```tsx
import { 
    createI18nApi, 
    declareComponentKeys, 
    type LocalizedString as LocalizedString_base 
} from "i18nifty";
export { declareComponentKeys };

//List the languages you with to support
export const languages = ["en", "fr"] as const;

//If the user's browser language doesn't match any 
//of the languages above specify the language to fallback to:  
export const fallbackLanguage = "en";

export type Language = typeof languages[number];

export type LocalizedString = LocalizedString_base<Language>;

export const { 
	useTranslation, 
	resolveLocalizedString, 
	useLang, 
	$lang,
	useResolveLocalizedString,
	/** For use outside of React */
	getTranslation 
} = createI18nApi<
    | import ("components/MyComponent").I18n
    | import ("components/MyOtherComponent").I18n
>()(
    { 
      languages, 
      fallbackLanguage
    },
    {
        "en": {
            "MyComponent": {
                "greating": ({ who })=> `Hello ${who}`,
                "how are you": "How are you feeling today?",
                "learn more": ({ href }) => (
                    <>
                        Learn more about 
                        <a href={href}>this website</a>.
                    </>
                )
            },
            "MyOtherComponent": {
                "open": "Open",
                "delete": "Delete",
                "unread messages": ({ howMany })=> {
                    switch(howMany){
                        case 0: return `You don't have any new message`;
                        case 1: return `You have a new message`;
                        default: return `You have ${howMany} new messages`;
                    }
                }
            },
        },
	/* spell-checker: disable */
	"fr": {
            "MyComponent": {
                "greating": ({ who })=> `Bonjour ${who}`,
                "how are you": "Comment vous sentez vous au jour d'hui?",
                "learn more": ({ href }) => (
                    <>
                        En savoir plus à propos de  
                        <a href={href}>ce site web</a>.
                    </>
                )
            },
            "MyOtherComponent": {
                "open": "Ouvrir",
                "delete": "Supprimer",
                //We will translate this later, for now, fallback to english
                "unread messages": undefined
            },
        }
	/* spell-checker: enable */
    }
);
```

Now go back to your component and use the translation function: &#x20;

{% code title="MyComponent.ts" %}

```diff
+import { useTranslation, declareComponentKeys } from "i18n"; //You can import it like that thanks to baseUrl
   
 type Props = {
     name: string;
 };

 export function MyComponent(props: Props) {
     const { name } = props;
     
+    const { t } = useTranslation({ MyComponent });
     
     return (
         <>
-            <h1>Hello {name}</h1>
+            <h1>{t("greeting", { who: name })}</h1>
-            <h3>How are you feeling today?</h3>
+            <h3>{t("how are you")}</h3>
-            <p>
-                Click <a href="https://example.com">hrere</a> to 
-                learn more about this website
-            </p>
+            <p>{t("learn more", { href: "https://example.com" })}</p>
         </>
     );
 }

 const { i18n } = declareComponentKeys<
     | { K: "greating"; P: { who: string; } }
     | "how are you"
     | { K: "learn more"; P: { href: string; }; R: JSX.Element }
 >()({ MyComponent });
 export type I18n = typeof i18n;
```

{% endcode %}

And so forth for your other components.

Now this setup is great if you're supporting only a few languages and you're app does not contain a lot of text. As you app grow however, you probably want to enable only only the resources for a specific language to be dowloaded. &#x20;

## Eslint

You should add this rule to your eslint config: &#x20;

<pre class="language-javascript" data-title="eslint.config.js"><code class="lang-javascript">export default tseslint.config(
    rules: {
<strong>      "@typescript-eslint/no-unused-vars": [
</strong><strong>        "error",
</strong><strong>        { varsIgnorePattern: "^i18n$" },
</strong><strong>      ],
</strong>    },
  }
);

</code></pre>

[Asynchronous locale resources download](/asynchronous-locale-resources-download)


# Asynchronous locale resources download

To minimize the bundle size, especially as your application scales, you may want to take advantage of code splitting.&#x20;

For instance, if the user's default language is English, only the English text resources are initially downloaded. If the user switches to another language, such as French, the corresponding French text resources are then downloaded asynchronously.

Find 👉 [here](https://stackblitz.com/edit/react-ts-zgmo8u?file=i18n%2Fi18n.ts) 👈 a live example of how you should setup your repo to enable code splitting. &#x20;


# useLang

Hook for changing the currently active language.

```tsx
//This is the custom useLang, generated and exported by 
//you sin the src/i18n.tsx filed.
import { useLang } from "i18n";

function MyComponent(){

    const { lang, setLang } = useLang();
      
    return (
        <>
            <span>The app is currently in {(()=>{
                switch(lang){
                    case "en": return "English";
                    case "fr": return "French";
                }
            })()}</span>
            <button onClick={()=> setLang("en")}>Put the app in English</button>
            <button onClick={()=> setLang("fr")}>Put the app in French</button>
        </>
    );
    
}


```


# $lang

Set the language at any time, from anywhere.

`evtLang` enable you to  switch the language without using `useLang` that can only be used in a react component. &#x20;

```tsx
import { $lang } from "i18n";

setTimeout(
    ()=> {
        console.log(`The app is currently in ${evtLang.state}`);
        console.log("We switch to fr now!");
        //This will trigger components to re-render.
        $lang.current= "fr";
    },
    30_000
);
```

Have a global callback that is invoked whenever the language is changed.

```typescript
import { $lang } from "i18n";

evtLang
    .subscribe(lang=> console.log(`The app has just beed switched to ${lang}`));

```


# LocalizedString

A localized string is a common type that represent either a plain string or a map lang -> text.

```typescript
type LocalizedString = string | Partial<Record<"en" | "fr", string>>;

//You can import your tailor-made localized string with
import type { LocalizedString } from "i18n";

//Example of LocalizedStrings:

const name: LocalizedString = "A text always in english";

const description: LocalizedString = {
    "en": "TypeScript module for internationalization",
    "fr": "Module TypeScript pour l'internationalisation"
};
```

### resolveLocalizedString

```typescript
import { resolveLocalizedString, type LocalizedString } from "i18n";

{

//Usually received from an API
const localizedString: LocalizedString = {
    "en": "Hello",
    "fr": "Bonjour"
};

//Assuming the current lang is "en" text will be "Hello"
const text = resolveLocalizedString(localizedString);

}

{

const localizedString: LocalizedString = "Hello";

//The text will be "Hello"
const text = resolveLocalizedString(localizedString);

}


{

//Usually received from an API
const localizedString: LocalizedString = {
    "en": "Hello",
    "fr": "Bonjour"
};

//Assuming the current lang is "en"
//node === <>Hello</>
//Assuming the current lang is "fr"
//node === <>Bonjour</>
//Assuming the current lang is "it" and the fallback language is "en"
//node === <span lang="en">Hello</span>
const node = resolveLocalizedString(
    localizedString, 
    { "labelWhenMismatchingLanguage": true }
);


}

{

//Usually received from an API
const localizedString: LocalizedString = "Hello";

//Assuming the current lang is "en" and the fallback lang is "en"
//node === <>Hello</>
//Assuming the current lang is not "en" and the fallback lang is not "en"
//node === <span lang="en">Hello</>
const node = resolveLocalizedString(
    localizedString, 
    { "labelWhenMismatchingLanguage": true }
);

//NOTE: By default when the localizedString is a plain string we assume
// it's in the fallbackLanguage. You can configure this behavior by using:
// { "labelWhenMismatchingLanguage": { "ifStringAssumeLanguage": "it" } }
// In this case we assume all non-internationalized strings are in italian
// and we must label them as such whenever the current language isn't Italian.

}


```

{% hint style="warning" %}
Do not use resolveLocalizedString in a react component. When the language changes the component won't be rerendered. Use [useResolveLocalizedString](/api-reference/localizedstring#useresolvelocalizedstring) instead.
{% endhint %}

### useResolveLocalizedString

```tsx
import { useResolveLocalizedString } from "i18n";
import type { LocalizedString } from "i18n";

type Props= {
    description: LocalizedString;
};

export function MyComponent(props: Props){

    const { description } = props;

    //NOTE: Optionally useResolveLocalizedString accept 
    // { labelWhenMismatchingLanguage: boolean | { ifStringAssumeLanguage: Language; }}
    // as argument, see above section for more details.  
    const { resolveLocalizedString } = useResolveLocalizedString();

    return (
        <span>{resolveLocalizedString(description)}</span>
    );
    
}
```


# useTranslation

Returns the usual t function

```tsx
 import { declareComponentKeys } from "i18nifty";
 import { useTranslation } from "i18n"; 

 type Props = {
     name: string;
 };

 function MyComponent(props: Props) {
     const { name } = props;
     
     const { t } = useTranslatation({ MyComponent });
     
     return (
         <>
            <h1>{t("greeting", { who: name })}</h1>
            <h3>{t("how are you")}</h3>
            <p>{t("learn more", { href: "https://example.com" })}</p>
         </>
     );
 }

 export const { i18n } = declareComponentKeys<
     | { K: "geeting"; P: { who: string; } }
     | "how are you"
     | { K: "learn more"; P: { href: string; }; R: JSX.Element }
 >()({ MyComponent });
```

{% hint style="info" %}
See [Quick start](/) for more details.
{% endhint %}


# getTranslation

Use the translation function outside of React components.

It's like useTranslation but it's not a hook, it can be used outside of React. &#x20;

```typescript
const { getTranslation } = createI18nApi(...);

const { t } = getTranslation("MyComponent");  

t("greating", { "who": "Jhon" });
```

{% hint style="warning" %}
Be mindfull, local resources are downloaded lazyly in with [asynchronous locale download](/asynchronous-locale-resources-download).\
If the resources aren't downloaded yet you'll get empty strings.  \
Also you have to subscribe to language changes to get the updated values.
{% endhint %}


# Migration guides


# v2 -> v3

Nothing. I just released bumped to v3 by mistake.&#x20;


