# Internalization (/start/translate)



In visual novels and similar games, it is common to allow players to select their native language.

For your Pixi’VN project, you should use an external library to handle translations. This gives you the flexibility to choose the implementation that best fits your needs.

The most popular and widely compatible library is **i18next**.

<Accordions>
  <Accordion title="What is i18next?" id="what-is-i18next">
    i18next is an internationalization framework for JavaScript. To use it, you need to install and initialize it. Learn more on the [i18next website](https://www.i18next.com/).
  </Accordion>
</Accordions>

<CalloutContainer type="info">
  <CalloutTitle>
    Templates
  </CalloutTitle>

  <CalloutDescription>
    In all templates, i18next is already included and configured. You can start translating your game right away.
  </CalloutDescription>
</CalloutContainer>

Translations are stored in multiple JSON files (one per language), using key-value pairs. The key is a unique identifier for the text, and the value is the translated string.

<Accordions>
  <Accordion title="Initialize" id="initialize">
    ```ts title="lib/i18n.ts"
    import i18n, { type ReadCallback } from "i18next";
    import LanguageDetector from "i18next-browser-languagedetector";
    import ChainedBackend from "i18next-chained-backend";
    import { initReactI18next } from "react-i18next";

    class Backend extends ChainedBackend {
        override async read(lng: string, ns: string, callback: ReadCallback) {
            try {
                const object = await getLocalesResource(lng);
                const result = object[ns];
                callback(null, result);
            } catch (error) {
                callback(error as Error, { data: null });
            }
        }
    }

    export const useI18n = async () => {
        if (!i18n.isInitialized) {
            return await i18n
                .use(Backend)
                .use(LanguageDetector)
                .use(initReactI18next)
                .init({
                    debug: false,
                    fallbackLng: "en",
                    supportedLngs: ["en"],
                    interpolation: {
                        escapeValue: false,
                    },
                    load: "currentOnly",
                    detection: {
                        order: ["localStorage"],
                        caches: ["localStorage"],
                    },
                    missingInterpolationHandler(_text, value, _options) {
                        return value[1];
                    },
                });
        }
    };

    function getLocalesResource(
        lng: string,
    ): Promise<Record<string, Record<string, string>>> {
        return import(`./../locales/${lng}.json`);
    }
    ```
  </Accordion>

  <Accordion title="Translate the UI" id="translate-ui">
    To translate the UI, use the `t` function provided by i18next. Pass the translation key to `t` to get the corresponding text in the player's language.

    For example:

    <CodeBlockTabs defaultValue="React">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="React">
          React
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Vue">
          Vue
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Svelte">
          Svelte
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="React">
        ```tsx
        import { useTranslation } from "react-i18next";

        export default function MyComponent() {
            const { t } = useTranslation("ui");

            return (
                <div>
                    <Button>{t("text_speed")}</Button>
                </div>
            );
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Vue">
        ```vue
        <script setup>
        import { useTranslation } from "i18next-vue";
        const { t } = useTranslation("ui");
        </script>

        <template>
            <div>
                <button>{{ t("text_speed") }}</button>
            </div>
        </template>
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Svelte">
        ```svelte
        <script>
            import { useTranslation } from 'svelte-i18n';
            const { t } = useTranslation("ui");
        </script>

        <div>
            <button>{t('text_speed')}</button>
        </div>
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>

  <Accordion title="Translate the narration (TypeScript/JavaScript)" id="translate-narration">
    <CalloutContainer type="info">
      <CalloutTitle>
        Templates
      </CalloutTitle>

      <CalloutDescription>
        In all templates, the `StepLabelProps` interface already includes a `t` function. See `pixi-vn.d.ts`.
      </CalloutDescription>
    </CalloutContainer>

    <CalloutContainer type="info">
      <CalloutDescription>
        It is recommended to use `t` inside the <DynamicLink href="/start/labels">label</DynamicLink> rather than directly in the UI, so you can take advantage of [i18n Interpolation](https://i18next.com/translation-function/interpolation).
      </CalloutDescription>
    </CalloutContainer>

    In Pixi’VN projects, you often need to translate narration text in TypeScript/JavaScript <DynamicLink href="/start/labels">`labels`</DynamicLink>. To enable this, the translation function must be in the <DynamicLink href="/start/labels#steplabelprops">`StepLabelProps` interface</DynamicLink>.

    It is recommended to use the original string as the translation key.

    <CodeBlockTabs defaultValue="content/labels/start.label.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="content/labels/start.label.ts">
          content/labels/start.label.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pixi-vn.d.ts">
          pixi-vn.d.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="locales/es.json">
          locales/es.json
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="lib/hooks/props-hooks.ts">
          lib/hooks/props-hooks.ts
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="content/labels/start.label.ts">
        ```ts
        export const startLabel = newLabel("start", [
            ({ t }) => {
                narration.dialogue = {
                    character: liam,
                    text: t("Hello, my name is {{name}}", { name: "Liam" }),
                };
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pixi-vn.d.ts">
        ```ts
        import type { TFunction } from "i18next";

        declare module "@drincs/pixi-vn" {
            interface StepLabelProps {
                /**
                 * Translate a key to a string.
                 * @param key The key to translate.
                 * @returns The translated string.
                 */
                t: TFunction<[string], undefined>;
                // ...
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="locales/es.json">
        ```ts
        {
            "narration": {
                "Hello, my name is {{name}}": "Hola, mi nombre es {{name}}"
                // ...
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="lib/hooks/props-hooks.ts">
        ```ts
        import { useTranslation } from "react-i18next";

        export function useGameProps(): StepLabelProps {
            const { t } = useTranslation(["narration"]);

            return {
                // ...
                t,
            };
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>
</Accordions>

## Create [#create]

<CalloutContainer type="info">
  <CalloutTitle>
    *ink*
  </CalloutTitle>

  <CalloutDescription>
    If you use *ink*, you can automatically extract strings from *ink* text for translation. See more <DynamicLink href="/ink/translate">here</DynamicLink>.
  </CalloutDescription>
</CalloutContainer>

Translation JSON files must be created manually. It is recommended to split translations into two sections (see `es.json`):

* UI texts: for screens, settings, quick buttons, etc.—everything not part of the narration.
* Narration texts: for dialogues, choice menus, etc.

<CodeBlockTabs defaultValue="locales/es.json">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="locales/es.json">
      locales/es.json
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="locales/es.json">
    ```json
    {
        "ui": {
            "text_speed": "Velocidad del texto"
            // ...
        },
        "narration": {
            "Hello, my name is {{name}}": "Hola, mi nombre es {{name}}"
            // ...
        }
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>
