Install Asset
Install via Godot
To maintain one source of truth, Godot Asset Library is just a mirror of the old asset library so you can download directly on Godot via the integrated asset library browser
Quick Information
One line makes your Godot game multilingual. Add the Transable autoload and call Transable.t("hello", "Hello") or Transable.translate_batch([...]) — UI strings are translated via the Transable API into 90+ languages and cached on disk for instant, offline subsequent launches. Safe mode keeps emails, paths, versions and user input out of the network. Never throws into game code — any failure degrades to the source text.EXPERIMENTAL PREVIEW (0.1.0-preview.2): the addon compiles and runs in Godot 4.7; the API may change. Get a free API key at https://transable.to/apps. Docs: https://transable.to/docs/apps/godot
EXPERIMENTAL PREVIEW — Transable SDK for Godot (addons/transable 0.1.0-preview.2)
Build status (2026-07-08): compiled and run in Godot 4.7-stable (headless)
on the build machine. The addon parses clean with the real GDScript compiler
(zero parse/type errors across all 11 scripts); the EditorPlugin enables and
the Transable autoload loads and runs. A runtime smoke test confirmed the
facade behaves: Transable.init(...) succeeds, language resolution picks the
requested language (current_language() → "de"), translate_batch(...)
executes through the SafeMode filter + client, and on a network failure it
degrades to source text without ever throwing or crashing. Three real
GDScript-4 type-inference bugs found by the compiler (untyped := from
Dictionary/nullable helpers) were fixed. What is still unverified: a live
end-to-end translation through Godot — on this machine Godot's bundled mbedTLS
fails the TLS handshake to the Cloudflare-fronted transable.to
(RESULT 5 / -0x2700, a local network/cert-store quirk; the identical request
succeeds through the OS HTTP stack), so the HTTP-2xx response-parse and
disk-cache-write paths ran only against failures, not a real 200. Also unverified:
RegEx PCRE2 \p{...} edge cases and per-platform user:// resolution beyond
Windows. The public API may still change. Treat as a preview, not a shipped
product.
Unlike the Unity package (which is thin glue over the precompiled, tested
Transable.Sdk .NET core), this addon is a full, self-contained port of
protocol v1 in pure GDScript — there is no shared native core for GDScript,
so the transport, batching, rate limiting, two-level cache, SafeMode filtering,
and language management are all reimplemented here the way the iOS and Flutter
scaffolds do. It uses only Godot's own facilities and has zero third-party
dependencies.
C# / Mono users: you do not have to use this GDScript addon. The tested
Transable.Sdk.NET core (netstandard2.1) works from Godot's C#/Mono builds directly. This GDScript addon exists so that all Godot users — including the GDScript-only majority — can localize without a .NET build.
What is implemented (protocol v1)
- Autoload facade
Transable(registered by the editor plugin):Transable.init("tk_live_...", { ...options })— idempotent one-call setup. A missing key leaves the SDK disabled and the UI in the source language.Transable.t("greeting", "Welcome back") -> String— returns the cached translation for the current language if present; otherwise returns the fallback and queues the text for debounced background translation. The next call returns the translation once it arrives, andtranslations_updatedfires.await Transable.translate_batch(["Save", "Cancel", ...], "de") -> Array— index-aligned; cache + SafeMode + batch. Skipped/failed entries keep the source text. (Coroutine —awaitit.)Transable.set_language("de"),Transable.current_language(),Transable.source_language(),Transable.available_languages().- Signals:
language_changed(code),translations_updated,languages_changed.
- HTTP transport (
client.gd):GET {base}/languages,POST {base}/batch, fire-and-forgetPOST {base}/discover(≤200 texts/call). Base defaults tohttps://transable.to/api/v1/translate(override withapi_base_url; trailing slashes trimmed). A small pool ofHTTPRequestnodes enforces ≤2 concurrent requests and ≥500 ms between request starts; up to 3 attempts with exponential backoff honoringRetry-After.User-Agent: Transable-Godot-Sdk/0.1.0-preview.2;Content-Type: application/json; charset=utf-8. - Error matrix (mirrors the reference exactly):
- 2xx parsed; a JSON parse failure is treated as transient and retried.
429+ body containingquota→ persistent per-UTC-day "quota blocked" flag (insettings.json); silent until the next UTC day.429+ body containingcaptcha→ 15-minute cooldown, one warning.429else → honorRetry-After(default 60 s); short waits retry, long waits set a cooldown.401/402/403→ fatal: translation disabled for the whole session, one loud error log.403+language_not_enabledis not fatal — the language manager drops that target language instead.413 {error, limit}→ learn the server char limit (shrink only, never grow within a session), re-chunk and retry each sub-chunk once; a single text bigger than the limit is dropped with one warning.400/404→ drop the batch with a warning (no retry).5xx/other → transient with backoff.
- Limits: ≤500 texts/batch, default 8000-char per-request budget (shrink-only on 413, min 200), ≤200 texts/discover.
- Cache identity (
text_normalizer.gd):normalize(text)collapses every Unicode White_Space run to a single0x20then trims; the key is the lowercase-hex SHA-256 (nativeHashingContext) of the UTF-8 bytes of the normalized text. NFC is skipped — see Known deviations. - Two-level cache (
cache.gd) atuser://transable/v1/{first16charsOfApiKey}/{lang}.jsonl(non-[0-9A-Za-z_-]key characters →_), one JSON object per line ({"s":"<sha256>","t":"<translated>"}), 10 MB per-language cap. Level 1:hash → translatedText. Level 2: session-only "identity" set (server echoed the source, or nothing to translate) — identity hits are never re-requested and never written to disk. - SafeMode (
safe_mode.gd, ON by default, applied before any network I/O so filtered text never leaves the machine). Skips: trimmed length < 2; no Unicode letter; emails; URLs (scheme://orwww.); Windows/UNC paths (C:\…,\\…); Unix paths (/a/b/c); canonical GUIDs; versions (v?N(.N)+[-suffix]); leading ISO-8601 timestamp log lines; snake_case/SCREAMING_SNAKE single tokens; camelCase/PascalCase identifiers with ≥2 humps (single words like "Save" / "Cancel" DO translate); and a single charset-restricted token ≥24 chars containing a digit (API keys, base64/hex). Everything else translates. - Language resolution order: persisted choice (when
persist_language_choice) →default_language→ OS/engine locale (OS.get_locale()full tag, then the language subtag) → serversourceLanguage(⇒ translation is a no-op). pageUrltelemetry: texts are attributed toapp://{app_name}/{scene-or-manual}(manualfort(),apifortranslate_batch).
Files
addons/transable/
plugin.cfg plugin manifest (name "Transable", 0.1.0-preview.2)
plugin.gd EditorPlugin: registers/removes the Transable autoload
transable.gd the autoload facade + background translation queue
client.gd HTTP transport, pacing, error matrix, 413 re-chunk
models.gd LanguageInfo / LanguagesResponse / BatchTranslation
text_normalizer.gd normalize + lowercase-hex SHA-256 (HashingContext)
safe_mode.gd SafeMode content filter (RegEx / PCRE2)
cache.gd two-level JSONL cache (protocol path layout)
language_manager.gd resolution order + settings.json persistence
transable_label.gd class_name TransableLabel (auto-translating Label)
README.md CHANGELOG.md
Install & enable
- Copy
addons/transable/into your project'sres://addons/folder (so you haveres://addons/transable/plugin.cfg). - Project → Project Settings → Plugins → enable Transable. This
registers the
Transableautoload singleton (you can also verify it under Project Settings → Autoload). - Godot 4.1 or newer (the SafeMode filter caches compiled
RegExobjects instatic vars, a Godot 4.1 feature). No.import/build step and no third-party assets are required.
Example (one T() call)
Bootstrap once (e.g. in an autoload of your own, or your main scene's
_ready):
func _ready() -> void:
Transable.init("tk_live_your_key_here", {
"app_name": "my-game",
"logger": func(level, message): print("[Transable/%s] %s" % [level, message]),
# "default_language": "de", # else OS locale is used
# "cache_directory": "user://transable", # this is the default
})
Transable.set_language("de")
Then translate individual strings anywhere:
# Returns "Welcome back" immediately, queues a background translation, and
# returns the German text on the next call once it lands (listen to
# Transable.translations_updated to refresh your UI):
$Label.text = Transable.t("greeting", "Welcome back")
…or just use the drop-in label (set an existing Label's script to
transable_label.gd, or add a TransableLabel node) — it captures its authored
text as the source and re-applies the translation automatically on
translations_updated / language_changed:
var label := TransableLabel.new()
label.text = "Save changes" # authored source; translated in place
add_child(label)
Batch (API-style, index-aligned, await it):
var out: Array = await Transable.translate_batch(["Save", "Cancel", "[email protected]"])
# "[email protected]" comes back unchanged — SafeMode filters it before it ever
# reaches the network.
Language selector: read Transable.available_languages() (each entry exposes
code / name / native_name / flag; listen to languages_changed for when
the list loads) and call Transable.set_language(code).
Cache directory
Disk layout (protocol v1):
{cache_directory}/v1/{first16charsOfApiKey}/{lang}.jsonl, plus settings.json
(persisted language choice and the monthly-quota UTC-day flag) and
languages.json (offline copy of the language list). The default
cache_directory is user://transable, which Godot maps to a per-user,
per-project writable location (see the Godot docs on user://). Pass an
absolute path or a different user://… path via the cache_directory option if
you want to relocate it. If the directory cannot be created, the SDK falls back
to an in-memory-only cache (translations work for the session but are not
persisted).
Failure philosophy
Nothing here throws into game code. Missing key, no network, HTTP 4xx/5xx,
quota exhaustion, a malformed server response, a throwing logger — every
failure degrades to source-language text with (at most) a message on the
configured logger. Filtered text never leaves the machine. The SDK never
reads, caches, or sends user-input contents: it only ever translates strings you
explicitly pass to t() / translate_batch() or author on a TransableLabel.
TransableLabel deliberately does not sweep the scene tree, so it can never
pick up text a player typed into a LineEdit/TextEdit.
Known deviations from the reference
- No Unicode NFC normalization. GDScript's
Stringhas no NFC normalizer and this addon has no dependencies, so NFC is skipped — identical to the Flutter scaffold. Already-NFC input (the normal case for UI copy and string literals) behaves identically to the server; decomposed input may miss the local disk cache (translations still arrive — batch responses are matched by index). Seetext_normalizer.gd. - No persisted key registry.
t(key, fallback)accepts thekeyfor source-compatibility with the C# facade, but resolution is purely content-hash based in this preview; the key argument is otherwise unused. - SHA-256 uses Godot's native
HashingContext(not a hand-rolled port), so — unlike the pure-Dart scaffold — there is no in-repo known-answer test here; correctness rides on the engine's implementation. It fingerprints UI strings for cache identity only; it is not used for security. RegExis PCRE2, so\p{L}/\p{N}Unicode property classes and inline(?i)are used directly. The Unicode upper/lower detection in the camelCase heuristic usesString.to_upper()/to_lower()round-trips (full Unicode case folding) rather than\p{Lu}/\p{Ll}single-char regexes, for speed. Behavior is intended to match the reference; unverified on your build.- No automatic scene-tree text sweep ("Magic mode"). The manual paths
(
t(),translate_batch(),TransableLabel) are the whole surface. This mirrors the Flutter scaffold's explicit "no widget-tree magic" scope. - Web/HTML5 export untested.
HTTPRequestworks on web, butuser://persistence, threading assumptions, and CORS to the API are unverified here. - Not integration-tested at all. See the banner: no Godot editor was available to even parse these scripts. Expect to fix things — and please report what broke.
One line makes your Godot game multilingual. Add the Transable autoload and call Transable.t("hello", "Hello") or Transable.translate_batch([...]) — UI strings are translated via the Transable API into 90+ languages and cached on disk for instant, offline subsequent launches. Safe mode keeps emails, paths, versions and user input out of the network. Never throws into game code — any failure degrades to the source text.
EXPERIMENTAL PREVIEW (0.1.0-preview.2): the addon compiles and runs in Godot 4.7; the API may change. Get a free API key at https://transable.to/apps. Docs: https://transable.to/docs/apps/godot
Reviews
Quick Information
One line makes your Godot game multilingual. Add the Transable autoload and call Transable.t("hello", "Hello") or Transable.translate_batch([...]) — UI strings are translated via the Transable API into 90+ languages and cached on disk for instant, offline subsequent launches. Safe mode keeps emails, paths, versions and user input out of the network. Never throws into game code — any failure degrades to the source text.EXPERIMENTAL PREVIEW (0.1.0-preview.2): the addon compiles and runs in Godot 4.7; the API may change. Get a free API key at https://transable.to/apps. Docs: https://transable.to/docs/apps/godot