Saltar al contenido principal

Localización

OpenMon incluye un sistema de localización personalizado respaldado por Google Sheets. Las traducciones se descargan como archivos TSV, se parsean en activos ScriptableLanguage y se sirven en tiempo de ejecución a través de la clase Localizer. El sistema vive en OpenMon.Foundation.Localization con posprocesadores específicos del juego en OpenMon.MCE.Localization.PostProcessors.

Descripción general de la arquitectura​

ClasePropósito
LocalizerServicio central de runtime. Carga paquetes de idioma, resuelve claves, aplica posprocesadores
ILocalizerInterfaz para el localizador, inyectada vía Zenject
ScriptableLanguageScriptableObject que contiene todos los pares clave-valor para un idioma
LocalizerSettingsConfiguración de desarrollador: directorio de paquetes de idioma, URLs de Google Sheet, posprocesadores
LocalizerConfigurationConfiguración de jugador: idioma seleccionado persistido (predeterminado: "English")
GoogleSheetLoaderHerramienta de editor que descarga hojas y genera activos ScriptableLanguage
LocalizedTextMeshProComponente UI que actualiza automáticamente un TMP_Text cuando cambia el idioma
LocalizationFontSwitcherIntercambia fuentes TMP por idioma (para soporte CJK)
LanguageFontMapScriptableObject que mapea nombres de idioma a sobreescrituras de TMP_FontAsset
LocalizedTextPostProcessorBase abstracta para posprocesar texto resuelto
ReplaceSubstringPosprocesador incorporado: buscar y reemplazar simple
InjectPlayerNamePosprocesador MCE: reemplaza {PlayerName} con el nombre del personaje del jugador
InjectDexValuesPosprocesador MCE: reemplaza {Dex.CaughtSpecies} con el conteo de capturados

Integración con Google Sheets​

Las traducciones se crean en Google Sheets con esta disposición:

ClaveEnglishSpanishJapanese
Menu/PlayPlayJugar...
Menu/OptionsOptionsOpciones...
Monsters/001/NameSproutylBrotino...

Cada columna después de la columna de clave representa un idioma. El encabezado de la columna se convierte en el nombre del activo ScriptableLanguage.

Importación desde el editor​

La utilidad de editor GoogleSheetLoader descarga cada URL de hoja desde LocalizerSettings.GoogleSheetsDownloadUrls, la convierte al formato de exportación TSV y parsea las filas en activos ScriptableLanguage guardados en Assets/Resources/{LanguagePackDirectory}/.

El flujo:

  1. Para cada URL, convertir la URL /edit de Google Sheets a /export?format=tsv
  2. Descargar el archivo TSV a Temp/
  3. Parsear los encabezados para descubrir los nombres de idioma
  4. Crear o actualizar un activo ScriptableLanguage por idioma
  5. Poblar Language[key] = value para cada fila
  6. Guardar activos y refrescar el AssetDatabase

Se soportan múltiples hojas. Si dos hojas definen la misma clave, gana la URL posterior (las URLs más abajo en la lista sobrescriben a las de arriba).

Redescarga en tiempo de ejecución​

El Localizer puede redescargar traducciones en runtime mediante ReDownloadLanguagesFromGoogleSheet(). Esto crea instancias temporales de ScriptableLanguage en memoria (no guardadas en disco) y recarga el sistema de idioma. Útil para probar traducciones sin reimportar en el editor.

ScriptableLanguage​

Cada idioma es un ScriptableLanguage ScriptableObject que contiene un SerializableDictionary<string, string> de todos los pares clave-valor. El activo se almacena en Resources/Languages/ (configurable mediante LocalizerSettings.LanguagePackDirectory).

// ScriptableLanguage resuelve claves con fallback a la clave original
string value = englishPack["Menu/Play"]; // "Play"
string missing = englishPack["Nonexistent"]; // "Nonexistent" (clave original como fallback)

Cuando no se encuentra una clave, el activo devuelve la misma clave. Esto hace que las traducciones faltantes sean inmediatamente visibles en el juego como cadenas de clave original.

Localizer​

El Localizer es el servicio principal de runtime, vinculado como ILocalizer mediante Zenject. Carga todos los activos ScriptableLanguage desde el directorio Resources al inicializarse.

Resolución de texto​

// Sintaxis de indexador (la más común)
string text = localizer["Menu/Play"]; // "Play"

// Con parámetros de sustitución
// Si el valor es "You caught {0}!" y "Monsters/025/Name" resuelve a "Thundermane"
string text = localizer["Messages/Caught", true, "Monsters/025/Name"];
// Resultado: "You caught Thundermane!"

// Parámetros como cadenas literales (no claves de localización)
string text = localizer.GetText("Messages/Caught", false, "Thundermane");

Los marcadores de posición {0}, {1}, etc. en los valores de traducción son reemplazados por la cadena modificadora correspondiente. Cuando modifiersAreLocalizableKeys es true (predeterminado), cada modificador se resuelve a través del localizador antes de la sustitución.

Cambio de idioma​

// Obtener idiomas disponibles
List<string> languages = localizer.GetAllLanguageIds(); // ["English", "Spanish", "Japanese"]

// Obtener idioma actual
string current = localizer.GetCurrentLanguage(); // "English"

// Cambiar idioma (persistido automáticamente en la configuración)
localizer.SetLanguage("Spanish");

// Cambiar por índice
localizer.SetLanguage(1);

Cuando cambia el idioma, la selección se persiste mediante IConfigurationManager para que sobreviva a los reinicios de la aplicación. Todos los suscriptores al evento de cambio de idioma son notificados.

Eventos de cambio de idioma​

// Suscribirse con nombre de idioma
localizer.SubscribeToLanguageChange((string newLang) =>
{
Debug.Log("Language changed to: " + newLang);
});

// Suscribirse sin parámetro (solo una notificación)
localizer.SubscribeToLanguageChange(() => RefreshUI());

// Cancelar suscripción
localizer.UnsubscribeFromLanguageChange(myCallback);

Convenciones de claves de localización​

Las claves siguen un formato de ruta jerárquica:

DominioPatrónEjemplo
UI / MenúsMenu/{Screen}/{Element}Menu/Battle/Run
MonstruosMonsters/{DexNumber}/NameMonsters/001/Name
Descripciones de monstruosMonsters/{DexNumber}/DescriptionMonsters/001/Description
MovimientosMoves/{MoveName}/NameMoves/Tackle/Name
ObjetosItems/{ItemName}/NameItems/Potion/Name
MisionesQuests/{QuestName}Quests/FindTheProfessor
Objetivos de misiónQuests/{QuestName}/Objectives/{N}Quests/FindTheProfessor/Objectives/0
DiálogosDialog/{SceneName}/{NPC}/{Line}Dialog/Route1/OldMan/0

Posprocesadores de texto​

Después de que una clave se resuelve y los modificadores se sustituyen, el texto pasa por un pipeline de instancias de LocalizedTextPostProcessor configuradas en LocalizerSettings.TextPostProcessors. Cada procesador puede modificar la cadena in-situ.

Incorporado: ReplaceSubstring​

Un procesador simple de buscar y reemplazar. Se crea mediante OpenMon > Localization > TextPostProcessors > ReplaceSubstring. Configure los campos Original y Replacement en el inspector. Útil para sustituciones globales como reemplazar \n con saltos de línea reales.

MCE: InjectPlayerName​

Reemplaza {PlayerName} en cualquier cadena localizada con el LocalizableName del personaje del jugador. Se crea mediante MCE > Localization > TextPostProcessors > InjectPlayerName.

// Valor de traducción: "Welcome, {PlayerName}! Ready to explore?"
// Después del posprocesado: "Welcome, Red! Ready to explore?"

MCE: InjectDexValues​

Reemplaza {Dex.CaughtSpecies} con el número de especies que el jugador ha capturado. Se crea mediante MCE > Localization > TextPostProcessors > InjectDexValues.

Posprocesadores personalizados​

Extienda LocalizedTextPostProcessor para crear el suyo propio:

[CreateAssetMenu(menuName = "MyGame/Localization/MyPostProcessor")]
public class MyPostProcessor : LocalizedTextPostProcessor
{
public override bool PostProcessText(ref string text, params object[] extraParams)
{
text = text.Replace("{MyToken}", GetMyValue());
return true;
}
}

Agregue el activo a LocalizerSettings.TextPostProcessors para que se incluya en el pipeline.

UI: LocalizedTextMeshPro​

El componente LocalizedTextMeshPro vincula un TMP_Text a una clave de localización. Se actualiza automáticamente cuando cambia el idioma.

// Establecer desde código
localizedText.SetValue("Menu/Play");

// Con modificadores
localizedText.SetValue("Messages/Caught", true, "Monsters/025/Name");

Agregue este componente a cualquier GameObject con un TMP_Text. Establezca el campo LocalizationKey en el inspector. Cuando SetOnEnable es true (predeterminado), el texto se resuelve inmediatamente al habilitarse.

Soporte de fuentes CJK​

Para idiomas como japonés que requieren glifos diferentes, use el componente LocalizationFontSwitcher junto con un activo LanguageFontMap.

Configuración​

  1. Cree un activo LanguageFontMap mediante OpenMon > Localization > Language Font Map
  2. Agregue entradas que mapeen nombres de idioma a referencias de TMP_FontAsset:
    • "Japanese" -> NotoSansJP-Regular SDF
    • Opcionalmente, establezca una variante negrita por idioma
  3. Agregue LocalizationFontSwitcher a su Canvas raíz
  4. Asigne el LanguageFontMap al campo FontMap
  5. Establezca SearchScope a Children (predeterminado) o Scene

Cuando cambia el idioma, el conmutador itera todos los componentes TMP_Text en el ámbito configurado e intercambia su fuente. Almacena en caché la fuente original para que al volver a un idioma no mapeado se restaure la predeterminada.

Configuración de inyección de dependencias​

El LocalizationInstaller (crear mediante OpenMon > Installers > Localization) vincula:

  • LocalizerSettings como singleton (inyectado en Localizer)
  • ILocalizer a Localizer como singleton lazy
  • Todos los TextPostProcessors configurados se encolan para inyección
SceneContext
+-- LocalizationInstaller (referencia al activo LocalizerSettings)

Agregar un nuevo idioma​

  1. Agregue una nueva columna a su Google Sheet con el nombre del idioma como encabezado (ej., French)
  2. Complete las traducciones para todas las claves
  3. Vuelva a ejecutar la importación de Google Sheet en el editor
  4. Aparecerá un nuevo activo French.asset en Resources/Languages/
  5. Si el idioma requiere una fuente diferente, agregue una entrada a su LanguageFontMap

No se requieren cambios de código. El Localizer descubre todos los activos ScriptableLanguage en el directorio configurado al iniciar.