Skip to content
TransBoxTransBox

Plugin Specification (AI / Human Executable Contract)

This document is the authoritative implementation reference for TransBox extensions. All fields, signatures, and filenames follow the repository source (manifest.py, base.py, registry.py, glossary.py, plugins_mixin.py, and the extensions/ examples).

Suggested reading order: mod.json → pick a type → copy a minimal template → hot-debug locally → export.


1. Directory Structure

Each MOD is a single folder; the root must contain a manifest:

text
my_mod/
├── mod.json                 # Required (or manifest.json)
├── icon.svg                 # Recommended; icon.png/webp/jpg are also auto-detected
├── README.md                # Optional; Workshop display description
├── theme.json               # type=theme
├── terms.json               # type=glossary (recommended)
├── terms.txt                # type=glossary (= or Tab separated)
├── ja.json                  # type=i18n
├── my_provider.py           # Python class entry
└── CustomBackground.qml     # Component referenced by ui_extensions

2. mod.json Field Table

FieldTypeRequiredDefaultDescription
idstringYesPackages without an id are rejected. Suggested format author.type.name; do not use builtin.*
namestringRecommended"未命名 MOD"Workshop display name
versionstringRecommended"1.0.0"No strict semver validation
authorstringRecommended"未知作者"
descriptionstringNo""Short description
typestringRecommended"glossary"See §3
entrypointstringDepends on type""See §4
iconstring|nullNonullRelative path
preview_imagestring|nullNonullWorkshop preview image
steam_item_idint|nullNonullReadable; no additional runtime logic currently
config_schemaarrayNo[]See §6
tagsstring[]No[]
supported_langsstring[]No[]
min_app_versionstringNo"1.0.0"No runtime check currently
tierstringNo"free"No enforced validation
enabledboolNoType-dependenti18n/theme default false; others default true
has_settings_tabboolNofalseStandalone Settings Tab
tab_idstring|nullNonullDefaults to the plugin id
tab_titlestring|nullNonullDefaults to name
style_layerstringNo"full"Themes only: full/material/typography/effects
ui_extensionsobjectNo{}Slot name → QML relative path
language_codestring|nullNonulli18n only, e.g. ja-JP
language_namestring|nullNonulli18n only, e.g. 日本語

Do not put these in mod.json:

  • window_backdropignored. Window materials go in theme.json.
  • source_path / is_builtin / icon_url / category_tier — injected at runtime.

Security tier (read-only derived): theme/glossary/i18ndata; everything else → script.


3. Supported type Values

typeBase class / implementationDescription
glossaryBuilt-in GlossaryModTerm list JSON/TXT
themeData + ui_extensionsTheme and visuals
i18nPure JSONUI language pack
middlewareBasePipelineMiddlewarePipeline interception
asrBaseASRProviderSpeech recognition engine
mtBaseMTEngineProviderMachine translation engine
ttsBaseTTSProviderSpeech synthesis engine
caption_bridgeBuilt-inGenerally not for third parties

Do not use historical type names that no longer exist (such as audio_filter, bundle) — the code will not handle them.


4. entrypoint Rules

TypeFormatExample
Python (asr/mt/tts/middleware)relative_file.py:ClassNamemy_translator.py:CustomDemoMTProvider
Python (class name omitted)relative_file.pyClass name defaults to Plugin
glossaryTerm-list fileterms.json (default) / terms.txt / terms.tsv
themeTheme JSONtheme.json (default)
i18nDictionary JSONja.json (default locale.json)

Real examples (from repository extensions/):

text
example_custom_mt/my_translator.py:CustomDemoMTProvider
example_text_filter_mod/filter_middleware.py:TextFilterMiddleware
example_glossary_gaming/terms.json
example_i18n_japanese/ja.json
example_theme_cyberpunk/theme.json

5. Zero-Code Types: How To

5.1 Glossary

Recommended terms.json:

json
{
  "Elden Ring": "艾尔登法环",
  "Malenia": "玛莲妮亚"
}

Or:

json
[{ "src": "Elden Ring", "tgt": "艾尔登法环" }]

terms.txt / terms.tsv:

text
# Comment line
Elden Ring=艾尔登法环
Malenia	玛莲妮亚
  • Separator: the first =; otherwise Tab
  • # comments and blank lines are skipped
  • -> and => are not supported (the entire line is discarded)
json
{
  "id": "workshop.glossary.elden_ring",
  "name": "《艾尔登法环》游戏专有术语库",
  "version": "1.0.0",
  "author": "TransBox",
  "type": "glossary",
  "entrypoint": "terms.json",
  "description": "游戏专有名词对照"
}

5.2 Theme

mod.json:

json
{
  "id": "workshop.theme.cyberpunk",
  "name": "赛博朋克霓虹",
  "version": "1.0.0",
  "author": "TransBox",
  "type": "theme",
  "style_layer": "full",
  "entrypoint": "theme.json",
  "enabled": false,
  "ui_extensions": {
    "overlay_background": "CyberDeepNeonBackground.qml",
    "audio_level": "CyberDeepNeonAudioLevel.qml"
  }
}

theme.json (window_backdrop goes only here):

json
{
  "theme_id": "workshop.theme.cyberpunk",
  "name": "赛博朋克霓虹",
  "style_layer": "full",
  "window_backdrop": "none",
  "tokens": {
    "primary": "#00f0ff",
    "primaryHover": "#38f9d7",
    "primaryGlow": "rgba(0, 240, 255, 0.45)",
    "glassSurface": "rgba(7, 11, 25, 0.75)",
    "glassBorder": "rgba(0, 240, 255, 0.35)"
  },
  "style_overrides": {
    "bgColor": "#070b19",
    "textColor": "#00f0ff",
    "sourceColor": "#ff2a85",
    "fontSize": 28,
    "fontFamily": "MiSans VF",
    "bgOpacity": 0.65,
    "fontOpacity": 1.0
  }
}

style_overrides also accepts snake_case aliases (such as text_color, font_size).
window_backdrop valid values: acrylic | mica | mica_alt | none | blur.

5.3 i18n (Language Pack)

json
{
  "id": "example.i18n.japanese",
  "name": "日本語 UI 言語パック",
  "version": "1.0.0",
  "type": "i18n",
  "author": "TransBox",
  "language_code": "ja-JP",
  "language_name": "日本語",
  "entrypoint": "ja.json"
}

The dictionary is flat "key": "translation". You may keep _meta (containing code/name/short_label). Missing keys fall back en-US → zh-CN.


6. config_schema

Array elements must contain both key and label; otherwise the item is skipped.

KeyTypeRequiredDescription
keystringYesRead via self._config["key"]
labelstringYesUI label
typestringNostring (default) / password / number / boolean / select (file only appears in manifest comments; the UI falls back to a text box — not recommended)
defaultanyNoDefault value
descriptionstringNoDescription
choicesstring[]NoOptions for select
is_secretboolNoMasked display

Actual controls:

  • select → dropdown
  • boolean → toggle switch
  • number → numeric input (validated)
  • password or is_secret: true → password box
  • Anything else → text box

Python side:

python
def initialize(self, config=None):
    super().initialize(config)
    self.api_key = self._config.get("api_key", "")

UI edits are persisted and call update_config.


7. Python SPI Base Classes (signatures)

7.1 BasePlugin

python
class BasePlugin(ABC):
    def __init__(self, manifest: ModManifest) -> None: ...
    @property
    def id(self) -> str: ...
    @property
    def name(self) -> str: ...
    @property
    def plugin_type(self) -> str: ...
    def initialize(self, config: dict | None = None) -> None: ...
    def shutdown(self) -> None: ...
    def update_config(self, new_config: dict) -> None: ...
    def handle_action(self, action: str, params: dict) -> Any: ...
    # handle_action calls action_<action>(**params)

7.2 Data Types

python
@dataclass
class WordTime:
    word: str
    start: float
    end: float

@dataclass
class Transcript:
    text: str
    language: str | None = None
    words: list[WordTime] | None = None

@dataclass
class Translation:
    text: str
    source_prefix: str | None = None
    complete: bool = True  # False means an incremental fragment

7.3 BaseASRProvider

python
@dataclass
class ASRCapabilities:
    has_native_vad: bool = False
    supports_streaming: bool = False
    supports_word_timestamps: bool = False
    supports_language_detection: bool = True

class BaseASRProvider(BasePlugin):
    @property
    def capabilities(self) -> ASRCapabilities: ...
    @property
    def is_streaming_capable(self) -> bool: ...  # default False
    def ensure_loaded(self, progress_callback=None) -> None: ...
    @abstractmethod
    def transcribe(
        self,
        audio: np.ndarray,          # float32 mono 16 kHz, [-1, 1]
        sample_rate: int = 16000,
        language: str = "auto",
        stream_id: str = "default",
        with_alignment: bool = False,
        on_token=None,
    ) -> Transcript: ...
    # Optional streaming:
    def stream_open(self, stream_id, language="auto"): ...
    def stream_feed(self, stream_id, pcm_chunk): ...
    def stream_events(self, stream_id): ...  # yield (text, is_final)
    def stream_close(self, stream_id) -> str: ...
    def close_all_streams(self) -> None: ...

7.4 BaseMTEngineProvider

python
class BaseMTEngineProvider(BasePlugin):
    def ensure_loaded(self) -> None: ...
    @abstractmethod
    def translate(
        self,
        text: str,
        source_lang: str = "en",
        target_lang: str = "zh",
        glossary_context: str | None = None,
        target: str | None = None,
        source: str | None = None,
    ) -> Translation: ...
    def translate_partial(
        self, text, source_lang="en", target_lang="zh",
        committed_prefix="", glossary_context=None,
        target=None, source=None,
    ) -> Translation: ...  # defaults to full-sentence translate

7.5 BaseTTSProvider

python
class BaseTTSProvider(BasePlugin):
    @property
    def sample_rate(self) -> int: ...  # default 24000
    @property
    def is_streaming_capable(self) -> bool: ...
    def ensure_loaded(self) -> None: ...
    @abstractmethod
    def synthesize(
        self,
        text: str,
        voice_id: str = "",
        voice: str | None = None,
        language: str = "zh",
    ) -> tuple[np.ndarray, int]: ...  # float32 mono
    def stream_tts(self, text, voice_id="", voice=None, language="zh"):
        ...
    def list_voices(self) -> list[dict]: ...  # [{'id','name','lang'}]
    def register_voice(self, voice_name, audio_samples,
                       reference_text="", sample_rate=24000) -> bool: ...

7.6 BasePipelineMiddleware

python
@dataclass
class PipelineContext:
    session_id: str = ""
    sample_rate: int = 16000
    source: str = "peer"   # 'peer' | 'self'
    source_lang: str = "auto"
    target_lang: str = "zh"
    extra: dict = field(default_factory=dict)

class BasePipelineMiddleware(BasePlugin):
    @property
    def priority(self) -> int: ...  # default 100, smaller runs first
    def process_audio(self, audio: np.ndarray, context) -> np.ndarray: ...
    def process_transcript(self, transcript: Transcript, context) -> Transcript: ...
    def process_translation(self, translation: Translation, context) -> Translation: ...
    def process_tts_audio(self, pcm_audio, sample_rate, context) -> tuple[np.ndarray, int]: ...

8. Minimal Runnable Templates

8.1 Custom MT (rename and use as-is)

mod.json:

json
{
  "id": "myteam.mt.demo",
  "name": "我的示范翻译引擎",
  "version": "1.0.0",
  "author": "Me",
  "type": "mt",
  "entrypoint": "my_translator.py:MyMTProvider",
  "config_schema": [
    {
      "key": "prefix_tag",
      "label": "译文前缀",
      "type": "string",
      "default": "【Demo】"
    }
  ]
}

my_translator.py:

python
from __future__ import annotations

from typing import Any, Dict, Optional

from transbox.plugins.base import BaseMTEngineProvider, Translation
from transbox.plugins.manifest import ModManifest


class MyMTProvider(BaseMTEngineProvider):
    def __init__(self, manifest: ModManifest) -> None:
        super().__init__(manifest)
        self.prefix = "【Demo】"

    def initialize(self, config: Optional[Dict[str, Any]] = None) -> None:
        super().initialize(config)
        self.prefix = self._config.get("prefix_tag", "【Demo】")

    def translate(
        self,
        text: str,
        source_lang: str = "en",
        target_lang: str = "zh",
        glossary_context: Optional[str] = None,
        target: Optional[str] = None,
        source: Optional[str] = None,
    ) -> Translation:
        tgt = target or target_lang
        clean = (text or "").strip()
        return Translation(text=f"{self.prefix} {clean}{tgt}", complete=True)

After enabling: Settings → Machine Translation → select this engine in the engine dropdown.

8.2 Middleware

python
from transbox.plugins.base import BasePipelineMiddleware, Transcript, Translation


class TextFilterMiddleware(BasePipelineMiddleware):
    @property
    def priority(self) -> int:
        return 50

    def process_transcript(self, transcript: Transcript, context) -> Transcript:
        text = transcript.text.replace("那个...", "")
        return Transcript(text=text, language=transcript.language)

    def process_translation(self, translation: Translation, context) -> Translation:
        text = translation.text.replace("坏词", "***")
        return Translation(text=text, complete=translation.complete)

entrypoint: "filter_middleware.py:TextFilterMiddleware"


9. Local Hot Debugging

  1. Launch TransBox → Settings → Extension Workshop
  2. Open the local MOD directory and drop in your folder
  3. Click Refresh
  4. Toggle the switch to enable; for engine types, also select the engine under the corresponding Settings Tab
  5. After changing code: disable → refresh/reload → enable again (or restart the client)

10. Export & Publish

  1. In Extension Workshop, click Export on your MOD
  2. This produces transbox_mod_<id>_v<version>.zip
  3. Upload to Steam Workshop (AppID 1316080)

Fact check: The current client does not ship
python -m transbox.plugins.steam_ugc --upload
as a CLI. The steam_ugc module provides Workshop path discovery and export_mod_for_workshop packaging; use the Steam client / Workshop web flow for uploads. See Workshop Packaging & Publishing.


11. Runtime Environment & Hard Rules

ItemFact
PythonHost 3.11–3.12
Commonly availablenumpyPySide6sounddevicerequests
Do not assume availablewebsockets (not a guaranteed dependency)
Runtime pipForbidden: os.system("pip install ...")
SandboxNone — same permissions as the host application
PrivacyMust not silently upload microphone / subtitle data
ConfigurationRead/write only this plugin’s config; do not change host global settings

Heavy models: run a separate process + local HTTP; the plugin only makes requests.


12. Official Example Index

DirectorytypeDemonstrates
example_glossary_gamingglossaryterms.json
example_theme_cyberpunkthemeFull QML + theme.json
community_theme_glassmorphismthemeMulti-window frosted glass
example_custom_waveform_uithemeaudio_level
example_cyberpunk_caption_uithemeBackground slot
example_text_filter_modmiddlewareText filtering
example_custom_mtmtCustom translation + config_schema
example_i18n_japanesei18nJapanese pack

13. Implementation Checklist for AI Agents

Verify each item before implementing:

  • [ ] mod.json contains a non-empty unique id
  • [ ] type
  • [ ] entrypoint path and class name really exist in the package
  • [ ] Python class inherits the correct base class and implements all @abstractmethods
  • [ ] Glossary text separator is = or Tab (not ->)
  • [ ] window_backdrop appears only in theme.json
  • [ ] UI slot names use the consumed names from the table below (§14)
  • [ ] No undocumented third-party packages; no runtime pip
  • [ ] Every config_schema item has both key and label
  • [ ] After dropping into extensions/, the MOD is visible and enableable in the Workshop list

14. UI Slots (Summary)

Consumed and replaceable:

overlay_background · caption_view · audio_level · alert_capsule · overlay_decorations · style_bar_actions · settings_background · history_background · dialog_background · window_background

Declared but currently unconsumed (do not promise replaceability):
overlay_root · style_bar

Special key settings_view (not in the standard slot list): custom QML for a plugin settings page; injects pluginId / bridge / rootWin / pluginConfig.

Full properties: UI Slots & HostContext.

TransBox — empowering borderless cross-language communication for everyone