# Internacionalización (i18n)

SillyTavern admite múltiples idiomas. Esta guía explica cómo agregar y administrar traducciones.

Probablemente estés aquí porque algún fragmento de texto no está traducido en tu idioma y te está sacando de quicio. Primero te mostraré cómo corregí algunas traducciones faltantes en la configuración regional de chino (tradicional). Cada una faltaba por una razón diferente, por lo que tendrás una buena idea de cómo corregir tus propias traducciones faltantes.

En la segunda mitad, vemos

  • cómo funciona i18n en SillyTavern,
  • escribir traducciones y código para usarlas,
  • funciones de depuración para encontrar traducciones faltantes,
  • agregar un nuevo idioma,
  • y contribuir con tus cambios.

Si estás desarrollando una extensión o modificando el código principal, escribe tu HTML y JavaScript teniendo en cuenta i18n. De esta manera tu trabajo estará listo para que otras personas lo traduzcan a su idioma.

Nadie conoce 15 idiomas por sí solo. Trabajamos juntos para hacer que SillyTavern sea accesible para todos.

Todos en el mundo deberían poder usar su propio idioma en teléfonos y computadoras.


# ¡Arreglemos algunas traducciones faltantes!

# Generate Image

El texto "Generate Image" no está traducido en la configuración regional de chino (tradicional). ¿Por qué?

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

Haz clic derecho en el elemento e inspecciónalo. Verás el 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>

¿Dónde está su atributo data-i18n? ¡Falta! Agreguémoslo. Lo encontramos en el código fuente:

<!--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>

Tenemos suerte, la cadena Generate Image está en muchos de los archivos de idioma, incluido el de chino (tradicional).

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

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

¿Por qué no aparece? Tenemos que conectar el elemento correctamente:

<!--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>

¡Ahora funciona! Recarga la página y mira.

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

Pero mientras tenemos el HTML abierto, ¿qué pasa con Stop Image Generation justo debajo? El HTML no se ve correcto.

Si generamos una imagen y luego abrimos el menú de varita mientras se está generando, vemos texto sin traducir.

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

Primero corrige el 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>

Esto no es suficiente para solucionar el problema. No hay traducciones para "Stop Image Generation" en el archivo de chino (tradicional). ¡Podemos agregarlo! Aquí hay una posible traducción:

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

... que podemos agregar al archivo JSON justo después de la traducción "Generate Image".

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

Después de algunas discusiones con Claude, en realidad vamos a usar las siguientes traducciones:

  • Chino Tradicional: "Stop Image Generation": "終止圖片生成"
  • Chino Simplificado: "Stop Image Generation": "中止图像生成"
  • Japonés: "Stop Image Generation": "画像生成を停止"

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

# Generate Caption

"Generate Caption" no está traducido en la configuración regional de chino (tradicional). ¡Arreglémoslo!

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

¿Dónde está? Inspecciona el elemento.

<!--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>

Resulta que este HTML es producido por JavaScript. Busquemos el código fuente.

// 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>`);

Primero tendremos que corregir el código:

// 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>`);

Tampoco hay traducciones para "Generate Caption" en el archivo de chino (tradicional). ¡Agreguémoslo!

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

Usaremos las siguientes traducciones:

  • Chino Tradicional: "Generate Caption": "生成圖片說明"
  • Chino Simplificado: "Generate Caption": "生成图片说明"
  • Japonés: "Generate Caption": "画像説明を生成"

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

# Inspect Prompts

El texto "Inspect Prompts" no está traducido en la configuración regional de chino (tradicional). ¿Por qué? Este es un poco más complicado. El texto es generado por JavaScript y falta la traducción.

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

Bueno, lo que sea que sea... ninguna de las dos frases está en los archivos i18n. Agreguémoslos.

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

Ahora tenemos que corregir el código JavaScript. Tiene que usar la función t para obtener la traducción.

// Extension-PromptInspector/index.js
import {t} from '../../../i18n.js';

const enabledText = t`Stop Inspecting`;
const disabledText = t`Inspect Prompts`;

Obtuvimos estas sugerencias de Claude. Mantén las cadenas, ignora el código. Tienen que agregarse a los archivos JSON.

// 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`プロンプトを検査`;

Fusionaremos esos en los archivos JSON.

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

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

Una lástima ese tooltip. El problema es que el código no usa la función t.

launchButton.title = 'Toggle prompt inspection';

Tendremos que arreglar eso en el código de la extensión.

launchButton.title = t`Toggle prompt inspection`;

También necesitamos agregar las traducciones a los archivos JSON.

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

Prompt inspector es una extensión separada, así que enviaremos las correcciones de código a ese repositorio: https://github.com/SillyTavern/Extension-PromptInspector/pull/1

Las traducciones se agregarán al repositorio principal de SillyTavern. https://github.com/SillyTavern/SillyTavern/pull/3198

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

# Archivos de idioma

Cada idioma tiene un archivo JSON en public/locales/ con nombre según su código de idioma (por ejemplo, ru-ru.json).

El archivo contiene pares clave-valor donde:

  • Las claves son el texto inglés original o identificadores únicos
  • Los valores son el texto traducido

Ejemplo:

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

# Cómo funcionan las traducciones

Hay dos formas en que se usan las traducciones en la aplicación:

  1. Elementos HTML: Usando atributos data-i18n

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

    El texto predeterminado en el HTML será reemplazado con el texto traducido si está disponible.

  2. Template Strings: En el código JavaScript usando la función t

    t`Some text with ${variable}`

    Estas cadenas deben traducirse manteniendo intactos los placeholders ${0}, ${1}, etc.

SillyTavern utiliza elementos HTML con atributos data-i18n para marcar contenido traducible. Hay varias formas de usar esto:

# 1. Traducir el texto del elemento

Para contenido de texto simple:

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

Esto reemplaza el contenido de texto del elemento con la traducción de "Role:".

# 2. Traducir atributos

Para traducir un atributo como un título o marcador de posición:

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

El prefijo [title] indica qué atributo traducir. El resto del valor del atributo es el texto que se utilizará como clave de búsqueda en el archivo JSON. Es común que los codificadores usen el texto inglés como clave, pero no es obligatorio. La clave puede ser cualquier identificador único.

El texto inglés original debe estar presente en el atributo correspondiente (title="Insert prompt"). Se utiliza como un fallback si falta la traducción. En particular, no hay archivo de traducción para inglés.

Aquí hay un ejemplo de usar un identificador único no_items_text como clave, en lugar del texto inglés:

<!--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. Traducciones múltiples por elemento

Algunos elementos necesitan tanto contenido como traducciones de atributos, separadas por punto y coma. El patrón más común es traducir tanto el contenido de texto del elemento como su atributo de título:

<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"
}

Esto traduce:

  • El contenido de texto del elemento usando la clave "Authorize"
  • El atributo de título usando la clave "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai"

Ten en cuenta que tanto el atributo title como el contenido de texto del elemento se proporcionan en inglés como fallback.

También puedes traducir varios atributos:

<!--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>

Las traducciones correspondientes en tu archivo de idioma se verían así:

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

Cuando se carga la página, el sistema:

  1. Encuentra todos los elementos con atributos data-i18n
  2. Analiza cualquier prefijo de atributo como [title] o [placeholder]
  3. Busca cada clave en el archivo JSON de tu idioma
  4. Reemplaza el contenido o atributos del elemento con el texto traducido

# Texto dinámico

Para texto dinámico en código JavaScript, las traducciones usan:

  1. Template literals con la función t:

    toastr.warn(t`Tag ${tagName} not found.`);
  2. Función de traducción directa:

    translate("Some text", "optional_key")

# Placeholders de variables

Algunas cadenas contienen placeholders para valores dinámicos usando ${0}, ${1}, etc:

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

Mantén los placeholders iguales para la clave y la traducción. El sistema reemplazará ${0} con el valor de presetName, etc.

# Encontrar traducciones faltantes

Digamos que no solo quieres corregir una traducción molesta faltante, quieres encontrarlas todas.

¡Esa es una gran ambición! Incluso corregir una traducción vale la pena. Pero si quieres atraparlas todas, necesitas una herramienta.

# SillyTavern-i18n

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

Herramientas para trabajar con archivos de localización frontend.

Características:

  • Agregar automáticamente nuevas claves para traducir desde archivos HTML.
  • Eliminar claves faltantes de archivos de localización.
  • Usar traducción automática de Google para auto-completar valores faltantes.
  • Ordenar archivos JSON por claves.

# Funciones de depuración integradas

Estos están bajo Configuración de usuario > Menú de depuración.

# Obtener traducciones faltantes

Detecta datos de localización faltantes en la configuración regional actual y vuelca los datos en la consola del navegador. Si la configuración regional actual es inglés, busca en todas las demás configuraciones regionales.

La consola mostrará una tabla de traducciones faltantes con:

  • key: El texto o identificador que necesita traducción
  • language: Tu código de idioma actual
  • value: El texto en inglés para traducir

# Aplicar configuración regional

Reaaplica la configuración regional seleccionada actualmente a la página

# Agregar un nuevo idioma

Para agregar compatibilidad con un nuevo idioma:

  1. Agrega tu idioma a public/locales/lang.json:

    {
      "lang": "xx-xx",
      "display": "Language Name (English Name)"
    }
  2. Crea public/locales/xx-xx.json con tus traducciones

# Contribuir

Cuando tus traducciones estén listas:

  1. Verifica que tu archivo JSON sea válido
  2. Prueba minuciosamente en la aplicación
  3. Envía a través de una solicitud de extracción de GitHub