Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be 📖

Transable — App Translation

An asset by Dzonilay
The page banner background of a mountain and forest
Transable — App Translation hero image

Quick Information

0 ratings
Transable — App Translation icon image
Dzonilay
Transable — App Translation

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

Supported Engine Version
4.1
Version String
0.1.0-preview.2
License Version
MIT
Support Level
community
Modified Date
7 hours ago
Git URL
Issue URL

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, and translations_updated fires.
    • await Transable.translate_batch(["Save", "Cancel", ...], "de") -> Array — index-aligned; cache + SafeMode + batch. Skipped/failed entries keep the source text. (Coroutine — await it.)
    • 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-forget POST {base}/discover (≤200 texts/call). Base defaults to https://transable.to/api/v1/translate (override with api_base_url; trailing slashes trimmed). A small pool of HTTPRequest nodes enforces ≤2 concurrent requests and ≥500 ms between request starts; up to 3 attempts with exponential backoff honoring Retry-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 containing quotapersistent per-UTC-day "quota blocked" flag (in settings.json); silent until the next UTC day.
    • 429 + body containing captcha → 15-minute cooldown, one warning.
    • 429 else → honor Retry-After (default 60 s); short waits retry, long waits set a cooldown.
    • 401/402/403fatal: translation disabled for the whole session, one loud error log. 403 + language_not_enabled is 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 single 0x20 then trims; the key is the lowercase-hex SHA-256 (native HashingContext) of the UTF-8 bytes of the normalized text. NFC is skipped — see Known deviations.
  • Two-level cache (cache.gd) at user://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:// or www.); 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) → server sourceLanguage (⇒ translation is a no-op).
  • pageUrl telemetry: texts are attributed to app://{app_name}/{scene-or-manual} (manual for t(), api for translate_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

  1. Copy addons/transable/ into your project's res://addons/ folder (so you have res://addons/transable/plugin.cfg).
  2. Project → Project Settings → Plugins → enable Transable. This registers the Transable autoload singleton (you can also verify it under Project Settings → Autoload).
  3. Godot 4.1 or newer (the SafeMode filter caches compiled RegEx objects in static 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

  1. No Unicode NFC normalization. GDScript's String has 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). See text_normalizer.gd.
  2. No persisted key registry. t(key, fallback) accepts the key for source-compatibility with the C# facade, but resolution is purely content-hash based in this preview; the key argument is otherwise unused.
  3. 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.
  4. RegEx is PCRE2, so \p{L} / \p{N} Unicode property classes and inline (?i) are used directly. The Unicode upper/lower detection in the camelCase heuristic uses String.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.
  5. 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.
  6. Web/HTML5 export untested. HTTPRequest works on web, but user:// persistence, threading assumptions, and CORS to the API are unverified here.
  7. 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

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
Transable — App Translation icon image
Dzonilay
Transable — App Translation

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

Supported Engine Version
4.1
Version String
0.1.0-preview.2
License Version
MIT
Support Level
community
Modified Date
7 hours ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers