#Internationalization (i18n)

SillyTavern supports multiple languages. This guide explains how to add and manage translations.

You're probably here because some piece of text is untranslated in your language, and it's driving you nuts. First I'll show you how I fixed some missing translations in the Chinese (Traditional) locale. Each was missing for a different reason, so you'll get a good idea of how to fix your own missing translations.

In the second half, we look at

If you're developing an extension or modifying the core code, write your HTML and JavaScript with i18n in mind. This way your work is ready for other people to translate it into their language.

Nobody knows 15 languages by themselves. We work together to make SillyTavern accessible to everyone.

Everyone in the world should be able to use their own language on phones and computers.


#Let's fix some missing translations!

#Generate Image

The text "Generate Image" is untranslated in the Chinese (Traditional) locale. Why?

generate-image-pre.png
generate-image-pre.png

Right-click on the element and inspect it. You'll see the HTML:

<!--rendered HTML--> <div class="list-group-item flex-container flexGap5 interactable" id="sd_gen" tabindex="0"> <div data-i18n="[title]Trigger Stable Diffusion" title="觸發 Stable Diffusion" class="fa-solid fa-paintbrush extensionsMenuExtensionButton"></div> <span>Generate Image</span> </div>

Where is its data-i18n attribute? It's missing! Let's add it. We find it in the source code:

<!--public/scripts/extensions/stable-diffusion/button.html--> <div id="sd_gen" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div> <span>Generate Image</span> </div> <div id="sd_stop_gen" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-circle-stop extensionsMenuExtensionButton" title="Abort current image generation task" data-i18n="[title]Abort current image generation task"></div> <span>Stop Image Generation</span> </div>

We are in luck, that string Generate Image is in many of the language files, including in Chinese (Traditional).

generate-image-lang.png
generate-image-lang.png

{ "Generate Image": "生成图片" }

Why isn't it showing up? We have to wire the element up correctly:

<!--public/scripts/extensions/stable-diffusion/button.html--> <div id="sd_gen" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div> <span data-i18n="Generate Image">Generate Image</span> </div> <div id="sd_stop_gen" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-circle-stop extensionsMenuExtensionButton" title="Abort current image generation task" data-i18n="[title]Abort current image generation task"></div> <span>Stop Image Generation</span> </div>

Now it works! Reload the page and see.

generate-image-post.png
generate-image-post.png

But while we have the HTML open, what's with Stop Image Generation just under it? The HTML doesn't look right.

If we generate an image and then open the wand menu while it's generating, we see untranslated text.

stop-generating-image-pre.png
stop-generating-image-pre.png

First fix the HTML:

<!--public/scripts/extensions/stable-diffusion/button.html--> <div id="sd_gen" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div> <span data-i18n="Generate Image">Generate Image</span> </div> <div id="sd_stop_gen" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-circle-stop extensionsMenuExtensionButton" title="Abort current image generation task" data-i18n="[title]Abort current image generation task"></div> <span data-i18n="Stop Image Generation">Stop Image Generation</span> </div>

This isn't enough to fix the issue. There are no translations for "Stop Image Generation" in the Chinese (Traditional) file. We can add it! Here's one possible translation:

{ "Stop Image Generation": "停止生成图片" }

... which we can add to the JSON file just after the "Generate Image" translation.

{ "Generate Image": "生成图片", "Stop Image Generation": "停止生成图片" }

After some discussion with Claude, we're actually going to go with the following translations:

  • Traditional Chinese: "Stop Image Generation": "終止圖片生成"
  • Simplified Chinese: "Stop Image Generation": "中止图像生成"
  • Japanese: "Stop Image Generation": "画像生成を停止"

stop-generating-post-2.png
stop-generating-post-2.png

#Generate Caption

"Generate Caption" is untranslated in the Chinese (Traditional) locale. Let's fix it!

generate-image-post.png
generate-image-post.png

Where is it? Inspect the element.

<!--rendered HTML--> <div id="send_picture" class="list-group-item flex-container flexGap5 interactable" tabindex="0"> <div class="fa-solid fa-image extensionsMenuExtensionButton"></div> Generate Caption </div>

Turns out that this HTML is produced by JavaScript. Let's find the source code.

// public/scripts/extensions/caption/index.js const sendButton = $(` <div id="send_picture" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-image extensionsMenuExtensionButton"></div> Generate Caption </div>`);

We will first have to fix the code:

// public/scripts/extensions/caption/index.js const sendButton = $(` <div id="send_picture" class="list-group-item flex-container flexGap5"> <div class="fa-solid fa-image extensionsMenuExtensionButton"></div> <span data-i18n="Generate Caption">Generate Caption</span> </div>`);

There are also no translations for "Generate Caption" in the Chinese (Traditional) file. Let's add it!

{ "Generate Caption": "生成圖片說明" }

We will use the following translations:

  • Traditional Chinese: "Generate Caption": "生成圖片說明"
  • Simplified Chinese: "Generate Caption": "生成图片说明"
  • Japanese: "Generate Caption": "画像説明を生成"

generate-caption-post.png
generate-caption-post.png

#Inspect Prompts

The text "Inspect Prompts" is untranslated in the Chinese (Traditional) locale. Why? This one is a bit more complicated. The text is generated by JavaScript, and the translation is missing.

// Extension-PromptInspector/index.js const enabledText = 'Stop Inspecting'; const disabledText = 'Inspect Prompts';

Well wouldn't you know it... neither phrase is in the i18n files. Let's add them.

{ "Stop Inspecting": "停止檢查", "Inspect Prompts": "檢查提示" }

Now we have to fix the JavaScript code. It has to use the t function to get the translation.

// Extension-PromptInspector/index.js import {t} from '../../../i18n.js'; const enabledText = t`Stop Inspecting`; const disabledText = t`Inspect Prompts`;

We got these suggestions from Claude. Keep the strings, ignore the code. They have to be added to the JSON files.

// 1. Simplified Chinese (zh-cn): const enabledText = t`停止检查`; const disabledText = t`检查提示词`; // 2. Traditional Chinese (zh-tw): const enabledText = t`停止檢查`; const disabledText = t`檢查提示詞`; // 3. Japanese (ja-jp): const enabledText = t`検査を停止`; const disabledText = t`プロンプトを検査`;

We will merge those into the JSON files.

{ "Stop Inspecting": "停止检查", "Inspect Prompts": "检查提示词" }
{ "Stop Inspecting": "停止檢查", "Inspect Prompts": "檢查提示詞" }
{ "Stop Inspecting": "検査を停止", "Inspect Prompts": "プロンプトを検査" }

toggle-prompt-inspection-post-tt.png
toggle-prompt-inspection-post-tt.png

A pity about that tooltip. The problem is that the code doesn't use the t function.

launchButton.title = 'Toggle prompt inspection';

We will have to fix that in the extension code.

launchButton.title = t`Toggle prompt inspection`;

We also need to add the translations to the JSON files.

{ "Toggle prompt inspection": "切换提示检查" }
{ "Toggle prompt inspection": "切换提示词检查" }
{ "Toggle prompt inspection": "プロンプト検査の切り替え" }

Prompt inspector is a separate extension, so we will PR the code fixes to that repo: https://github.com/SillyTavern/Extension-PromptInspector/pull/1

The translations will be added to the main SillyTavern repo. https://github.com/SillyTavern/SillyTavern/pull/3198

start-inspecting-post.png
start-inspecting-post.png

#Language files

Each language has a JSON file in public/locales/ named with its language code (e.g., ru-ru.json).

The file contains key-value pairs where:

  • Keys are either the original English text or unique identifiers
  • Values are the translated text

Example:

{ "Save": "Сохранить", "Cancel": "Отмена", "Could not find proxy with name '${0}'": "Не удалось найти прокси с названием '${0}'" }

#How translations work

There are two ways translations are used in the application:

  1. HTML Elements: Using data-i18n attributes

    <div data-i18n="some_key">Default Text</div>

    The default text in the HTML will be replaced with the translated text if available.

  2. Template Strings: In the JavaScript code using the t function

    t`Some text with ${variable}`

    These strings should be translated keeping the ${0}, ${1}, etc. placeholders intact.

SillyTavern uses HTML elements with data-i18n attributes to mark translatable content. There are several ways to use this:

#1. Translating Element Text

For simple text content:

<span data-i18n="Role:">Role:</span>
{ "Role:": "Роль:" }

This replaces the element's text content with the translation of "Role:".

#2. Translating Attributes

To translate an attribute like a title or placeholder:

<a class="menu_button fa-chain fa-solid fa-fw" title="Insert prompt" data-i18n="[title]Insert prompt"></a>
{ "Insert prompt": "Вставить промпт" }

The [title] prefix indicates which attribute to translate. The rest of the attribute value is the text that will be used as a lookup key in the JSON file. It is common for coders to use the English text as the key, but it is not required. The key can be any unique identifier.

The original English text must be present in the corresponding attribute (title="Insert prompt") though. It's used as a fallback if the translation is missing. Most notably, there is no translation file for English.

Here is an example of using a unique identifier no_items_text as the key, rather than the English text:

<!--suppress HtmlUnknownAttribute --> <div class="openai_logit_bias_list" no_items_text="No items" data-i18n="[no_items_text]openai_logit_bias_no_items"></div>
{ "openai_logit_bias_no_items": "没有相关产品" }

#3. Multiple Translations Per Element

Some elements need both content and attribute translations, separated by semicolons. The most common pattern is translating both the element's text content and its title attribute:

<div data-source="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai"> Authorize </div>
{ "Authorize": "Авторизоваться", "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai" }

This translates:

  • The element's text content using the key "Authorize"
  • The title attribute using the key "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai"

Note that both the title attribute and the element's text content are provided in English as fallbacks.

You can also translate multiple attributes:

<!--suppress HtmlUnknownAttribute --> <textarea id="send_textarea" name="text" class="mdHotkeys" data-i18n="[no_connection_text]Not connected to API!;[connected_text]Type a message, or /? for help" placeholder="Not connected to API!" no_connection_text="Not connected to API!" connected_text="Type a message, or /? for help"></textarea>

The corresponding translations in your language file would look like:

{ "Not connected to API!": "Нет соединения с API!", "Type a message, or /? for help": "Введите сообщение или /? для помощи" }

When the page loads, the system:

  1. Finds all elements with data-i18n attributes
  2. Parses any attribute prefixes like [title] or [placeholder]
  3. Looks up each key in your language's JSON file
  4. Replaces the element's content or attributes with the translated text

#Dynamic Text

For dynamic text in JavaScript code, translations use either:

  1. Template literals with the t function:
    toastr.warn(t`Tag ${tagName} not found.`);
  2. Direct translation function:
    translate("Some text", "optional_key")

#Variable Placeholders

Some strings contain placeholders for dynamic values using ${0}, ${1}, etc:

toastr.error(t`Could not find proxy with name '${presetName}'`);
{ "Could not find proxy with name '${0}'": "Не удалось найти прокси с названием '${0}'" }

Keep the placeholders the same for key and translation. The system will replace ${0} with the value of presetName, etc.

#Finding missing translations

Let's say you don't just want to fix one annoying missing translation, you want to find them all.

That's a big ambition! Even fixing one translation is worth it. But if you want to catch 'em all, you need a tool.

#SillyTavern-i18n

https://github.com/SillyTavern/SillyTavern-i18n

Tools for working with frontend localization files.

Features:

  • Automatically add new keys to translate from HTML files.
  • Prune missing keys from localization files.
  • Use automatic Google translation to auto-populate missing values.
  • Sort JSON files by keys.

#Inbuilt debug functions

These are under User Settings > Debug Menu.

#Get missing translations

Detects missing localization data in the current locale and dumps the data into the browser console. If the current locale is English, searches all other locales.

The console will show a table of missing translations with:

  • key: The text or identifier needing translation
  • language: Your current language code
  • value: The English text to translate

#Apply locale

Reapplies the currently selected locale to the page

#Adding a new language

To add support for a new language:

  1. Add your language to public/locales/lang.json:

    { "lang": "xx-xx", "display": "Language Name (English Name)" }
  2. Create public/locales/xx-xx.json with your translations

#Contributing

When your translations are ready:

  1. Verify your JSON file is valid
  2. Test thoroughly in the application
  3. Submit via GitHub pull request