Skip to content
Ege Yilmaz edited this page Dec 11, 2025 · 3 revisions

Dynamic Translation Framework – User Guide

Overview

The Dynamic Translation Framework is a plug-in that allows UI elements and in-game strings to be translated at runtime. Instead of hard-coded translations, the framework intercepts translation requests for keys that begin with $ and forwards them to your own code, native SKSE plug-ins, or Papyrus scripts.

When Skyrim asks Scaleform to translate a string, the framework inspects the translation key -- it removes the $ prefix and looks up a provider registered for that key. The provider (you) is (are) responsible for generating a translation string. After getting a translation from the provider, the mod replaces the original translation with the your value.

Providers are registered from JSON configuration files placed in a special folder (described below).

Alternatively, you do not need to do any of this and can just call the Papyrus API function that is shipped with this mod to change any translation any time you want:

Function DynamicTranslateV1(String a_key, String a_val)

Give the key without $ prefix.


Configuration Files

Location and File Format

  • Location: Place your JSON files in:

    Data\SKSE\Plugins\DynamicTranslationFramework
    

Each .json file in this folder is loaded when the plug-in starts.

  • Format: Each file can contain either a single JSON object or an array of objects. Each object defines a translation provider and must include at least the strings field along with either a skse field or a papyrus field (or both).

Fields

Field Required Description
strings Yes A list of translation keys (without the leading $). When the game requests translation for $SomeKey, the framework will remove the $ and look up "SomeKey" in this list. Multiple keys can map to the same provider.
skse Optional Name of a native SKSE DLL (without file extension) that exports a function named OnDynamicTranslationRequest. When a key is mapped to this provider, the framework loads the DLL, resolves the function, and calls it to obtain a translation.
papyrus Optional Editor ID of a form that has a Papyrus script attached. The script must implement an OnDynamicTranslationRequest function. When a key is mapped to this provider, the framework will invoke the Papyrus function asynchronously to retrieve the translation.

Only one of skse or papyrus is required. If both are present, the native SKSE function is attempted first; if it does not return a result, the Papyrus function will be called.


Example JSON Configurations

Example 1 – Native SKSE provider

{
  "strings": ["HELLO_WORLD", "HELLO_AGAIN"],
  "skse": "MyTranslationPlugin"
}
  • strings: This provider will handle $HELLO_WORLD and $HELLO_AGAIN.
  • skse: The framework will load MyTranslationPlugin.dll from your plug-ins folder and look for a function called OnDynamicTranslationRequest.

Example 2 – Papyrus provider

{
  "strings": ["FAREWELL"],
  "papyrus": "MyTranslationScript"
}
  • papyrus: The provider refers to a script named MyTranslationScript attached to a form. When $FAREWELL is requested, the framework will call the script’s OnDynamicTranslationRequest method.

Implementing a Provider

Writing a Native SKSE Provider (C++/DLL)

  1. Create a DLL using the SKSE plug-in template.
  2. In your DLL, export a function named OnDynamicTranslationRequest exactly. The framework resolves this symbol by name and expects it to use the following signature:
// SKSE native translation function
extern "C" __declspec(dllexport)
const wchar_t* OnDynamicTranslationRequest(std::string_view a_key);
  • a_key is the translation key (without $). Use it to look up the appropriate translation.
  • The function must return a null-terminated wide-character string (const wchar_t*). The string must remain valid after the function returns. You can return a pointer to a static or global std::wstring or allocate memory on the heap; do not return a pointer to a stack buffer.

Example implementation

#include <string>

// Example translation table
static std::wstring Translate(const std::string_view key) {
    if (key == "HELLO_WORLD") {
        return L"Hallo Welt"; // German example
    }
    if (key == "DYNAMIC_GREETING") {
        return L"Willkommen, Abenteurer!";
    }
    // return empty string if key not handled
    return L"";
}

extern "C" __declspec(dllexport)
const wchar_t* OnDynamicTranslationRequest(std::string_view a_key) {
    static std::wstring result;
    result = Translate(a_key);
    return result.c_str();
}

Deploy the DLL into your SKSE plug-ins folder, for example:

Data\SKSE\Plugins\MyTranslationPlugin.dll

Then reference it in the JSON configuration with the skse field (without the .dll extension).


Writing a Papyrus Provider

  1. Attach a Papyrus script to any form (e.g., a quest or an empty activator) in the Creation Kit. The script’s editor ID must match the value specified in the papyrus field of your JSON configuration.
  2. Implement a function named OnDynamicTranslationRequest that returns a string. The function should match this signature:
ScriptName MyTranslationScript extends Quest

String Function OnDynamicTranslationRequest(String a_key)
    If a_key == "FAREWELL"
        return "Auf Wiedersehen"
    EndIf
    ; return empty string if key is not handled
    return ""
EndFunction
  • a_key is the translation key without $.
  • Return a non-empty string to translate the key; return an empty string to fall back to native translation or to the game’s default translation.
  1. Register the script in JSON: Add the script’s editor ID in the papyrus field of your config file. The framework will use the editor ID to locate the script instance and call its function asynchronously.

How the Framework Works

  1. During game startup, the plug-in loads all JSON configuration files and builds a mapping from translation keys to providers.
  2. When Skyrim’s Scaleform translator receives a key beginning with $, the plug-in’s hook removes the $ and looks up a provider for the key.
  3. The plug-in attempts to call the native SKSE provider first (if defined) and uses its return value as the translation.
  4. If the native function is not present or returns an empty string, it then calls the Papyrus provider.
  5. The resulting wide string is inserted into the translation result so that the UI element displays your dynamic text.

Tips and Notes

  • String Keys: Do not include the leading $ in the strings array; the framework removes it automatically.
  • Error Handling: If neither skse nor papyrus is provided for a given entry, the configuration entry is ignored.
  • Multiple Keys: You can list multiple keys under a single provider; the provider will be invoked for any of the keys.
  • Asynchronous Papyrus: Papyrus calls run asynchronously. The native SKSE provider will return immediately; Papyrus results may be slightly delayed but are cached by the framework.

By defining simple JSON configuration files and implementing a single function in either C++ or Papyrus, the Dynamic Translation Framework allows your mod to provide rich, runtime-generated translations for Skyrim.