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:
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_extensions2. mod.json Field Table
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | Packages without an id are rejected. Suggested format author.type.name; do not use builtin.* |
name | string | Recommended | "未命名 MOD" | Workshop display name |
version | string | Recommended | "1.0.0" | No strict semver validation |
author | string | Recommended | "未知作者" | — |
description | string | No | "" | Short description |
type | string | Recommended | "glossary" | See §3 |
entrypoint | string | Depends on type | "" | See §4 |
icon | string|null | No | null | Relative path |
preview_image | string|null | No | null | Workshop preview image |
steam_item_id | int|null | No | null | Readable; no additional runtime logic currently |
config_schema | array | No | [] | See §6 |
tags | string[] | No | [] | — |
supported_langs | string[] | No | [] | — |
min_app_version | string | No | "1.0.0" | No runtime check currently |
tier | string | No | "free" | No enforced validation |
enabled | bool | No | Type-dependent | i18n/theme default false; others default true |
has_settings_tab | bool | No | false | Standalone Settings Tab |
tab_id | string|null | No | null | Defaults to the plugin id |
tab_title | string|null | No | null | Defaults to name |
style_layer | string | No | "full" | Themes only: full/material/typography/effects |
ui_extensions | object | No | {} | Slot name → QML relative path |
language_code | string|null | No | null | i18n only, e.g. ja-JP |
language_name | string|null | No | null | i18n only, e.g. 日本語 |
Do not put these in mod.json:
window_backdrop— ignored. Window materials go intheme.json.source_path/is_builtin/icon_url/category_tier— injected at runtime.
Security tier (read-only derived): theme/glossary/i18n → data; everything else → script.
3. Supported type Values
| type | Base class / implementation | Description |
|---|---|---|
glossary | Built-in GlossaryMod | Term list JSON/TXT |
theme | Data + ui_extensions | Theme and visuals |
i18n | Pure JSON | UI language pack |
middleware | BasePipelineMiddleware | Pipeline interception |
asr | BaseASRProvider | Speech recognition engine |
mt | BaseMTEngineProvider | Machine translation engine |
tts | BaseTTSProvider | Speech synthesis engine |
caption_bridge | Built-in | Generally 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
| Type | Format | Example |
|---|---|---|
| Python (asr/mt/tts/middleware) | relative_file.py:ClassName | my_translator.py:CustomDemoMTProvider |
| Python (class name omitted) | relative_file.py | Class name defaults to Plugin |
| glossary | Term-list file | terms.json (default) / terms.txt / terms.tsv |
| theme | Theme JSON | theme.json (default) |
| i18n | Dictionary JSON | ja.json (default locale.json) |
Real examples (from repository extensions/):
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.json5. Zero-Code Types: How To
5.1 Glossary
Recommended terms.json:
{
"Elden Ring": "艾尔登法环",
"Malenia": "玛莲妮亚"
}Or:
[{ "src": "Elden Ring", "tgt": "艾尔登法环" }]terms.txt / terms.tsv:
# 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)
{
"id": "workshop.glossary.elden_ring",
"name": "《艾尔登法环》游戏专有术语库",
"version": "1.0.0",
"author": "TransBox",
"type": "glossary",
"entrypoint": "terms.json",
"description": "游戏专有名词对照"
}5.2 Theme
mod.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):
{
"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)
{
"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.
| Key | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Read via self._config["key"] |
label | string | Yes | UI label |
type | string | No | string (default) / password / number / boolean / select (file only appears in manifest comments; the UI falls back to a text box — not recommended) |
default | any | No | Default value |
description | string | No | Description |
choices | string[] | No | Options for select |
is_secret | bool | No | Masked display |
Actual controls:
select→ dropdownboolean→ toggle switchnumber→ numeric input (validated)passwordoris_secret: true→ password box- Anything else → text box
Python side:
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
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
@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 fragment7.3 BaseASRProvider
@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
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 translate7.5 BaseTTSProvider
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
@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:
{
"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:
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
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
- Launch TransBox → Settings → Extension Workshop
- Open the local MOD directory and drop in your folder
- Click Refresh
- Toggle the switch to enable; for engine types, also select the engine under the corresponding Settings Tab
- After changing code: disable → refresh/reload → enable again (or restart the client)
10. Export & Publish
- In Extension Workshop, click Export on your MOD
- This produces
transbox_mod_<id>_v<version>.zip - Upload to Steam Workshop (AppID 1316080)
Fact check: The current client does not shippython -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
| Item | Fact |
|---|---|
| Python | Host 3.11–3.12 |
| Commonly available | numpy、PySide6、sounddevice、requests |
| Do not assume available | websockets (not a guaranteed dependency) |
| Runtime pip | Forbidden: os.system("pip install ...") |
| Sandbox | None — same permissions as the host application |
| Privacy | Must not silently upload microphone / subtitle data |
| Configuration | Read/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
| Directory | type | Demonstrates |
|---|---|---|
example_glossary_gaming | glossary | terms.json |
example_theme_cyberpunk | theme | Full QML + theme.json |
community_theme_glassmorphism | theme | Multi-window frosted glass |
example_custom_waveform_ui | theme | audio_level |
example_cyberpunk_caption_ui | theme | Background slot |
example_text_filter_mod | middleware | Text filtering |
example_custom_mt | mt | Custom translation + config_schema |
example_i18n_japanese | i18n | Japanese pack |
13. Implementation Checklist for AI Agents
Verify each item before implementing:
- [ ]
mod.jsoncontains a non-empty uniqueid - [ ]
type∈ - [ ]
entrypointpath 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_backdropappears only intheme.json - [ ] UI slot names use the consumed names from the table below (§14)
- [ ] No undocumented third-party packages; no runtime pip
- [ ] Every
config_schemaitem has bothkeyandlabel - [ ] 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.