Skip to main content

JSONTranslator

Utility function that loads a translator from a folder or a table.

To get translations uploaded.

  1. Run the game
  2. Run prepare-localization-export in the Cmdr console. Translators load lazily -- the source language, plus whichever one a player reads -- so until you ask, no realm holds every locale (see TranslatorCmdrService)
  3. On the server, check LocalizationService.GeneratedJSONTable_Server
  4. Right click > Save as CSV
  5. Stop the game
  6. In Studio, go to plugins > "Localization Tools"
  7. Upload the CSV (update)

Functions

new

JSONTranslator.new(
translatorNamestring,--

Name of the translator. Used for source.

localeIdstring | Instance,--

a locale id, or the folder holding the locale files

dataTabletable?--

required with a locale id, unused with a folder

) → JSONTranslator

Constructs a new JSONTranslator from the given args.

local translator = JSONTranslator.new("MyTranslator", "en", {
	actions = {
		respawn = "Respawn {playerName}";
	};
})

print(translator:FormatByKey("actions.respawn"), { playerName = "Quenty"}) --> Respawn Quenty

-- Observing is preferred
maid:GiveTask(translator:ObserveFormatByKey("actions.respawn", {
	playerName = RxInstanceUtils.observeProperty(player, "DisplayName");
}):Subscribe(function(text)
	print(text) --> "Respawn Quenty"
end))

Instead of a locale and a table, the second argument may be an Instance holding one <locale>.json StringValue or ModuleScript per locale. Those are decoded lazily, a locale at a time, and fetched on demand rather than replicated to every client at join -- see InstanceLocaleLoader.

local translator = JSONTranslator.new("MyTranslator", script)
-- assume there is an `en.json` underneath the script with valid JSON.

ObserveFormatByKey

JSONTranslator.ObserveFormatByKey(
translationKeystring,
translationArgstable?--

May have observables (or convertable to observables) in it.

) → Observable<string>

Observes the translated value, re-emitting as the locale changes and as the key's data becomes readable.

Emits immediately on subscribe with the best text available rather than withholding, so a bound label is never blank for a frame. Until a key's data has landed that best text may be the source language, or the translation key itself for a key registered this frame; the correct translation replaces it once the key is readable. It will not go the other way: a locale swap never replaces good text with a fallback.

PromiseFormatByKey

JSONTranslator.PromiseFormatByKey(
translationKeystring,
argstable?
) → Promise<string>

Formats the resulting entry by args.

TIP

You should use JSONTranslator.ObserveFormatByKey instead of this to respond to locale changing.

_observeTranslationReady

JSONTranslator._observeTranslationReady(
translationKeystring
) → Observable<string?>

Observes when a translation key is readable for the current locale, emitting the locale id it is readable for, or nil while its data is still queued.

WARNING

This is an implementation detail, exposed for the translation stack itself and for diagnostics. You should not need it: every way of reading a translation (JSONTranslator.ObserveFormatByKey, JSONTranslator.PromiseFormatByKey, JSONTranslator.FormatByKey, JSONTranslator.ObserveTranslation) already waits for the key it reads, and re-reads when a locale swap brings new data in.

Localization writes are batched to the end of the frame (see TranslatorService.SetEntryValue), because a game streaming in can register thousands of entries in a single frame and each raw table write invalidates every AutoLocalize entry in the engine. That means a key is not readable the instant it is registered, and this is how the read paths above find out that it is.

Re-emits whenever the locale changes or new data lands for the key, so a locale swap is picked up without resubscribing. Nothing here yields.

PromiseTranslator

JSONTranslator.PromiseTranslator(selfJSONTranslator) → Promise<Translator>

Returns a promise that will resolve once the Roblox translator is loaded from the cloud.

ObserveTranslator

JSONTranslator.ObserveTranslator(selfJSONTranslator) → Observable<Translator>

Observes the current Roblox translator for this translator.

ObserveLocaleId

JSONTranslator.ObserveLocaleId(selfJSONTranslator) → Observable<string>

Observes the current locale id for this translator.

SetEntryValue

JSONTranslator.SetEntryValue(
translationKeystring,
sourcestring,
contextstring,
localeIdstring,
textstring
) → ()

Adds an entry value to the localization table itself. This can be useful for ensuring pseudo localization and/or generating localization values from the game data itself.

ObserveTranslation

JSONTranslator.ObserveTranslation(
prefixstring,
textstring,
translationArgstable?
) → Observable<string>

Observes a translation key and formats it with the given args.

ToTranslationKey

JSONTranslator.ToTranslationKey(
prefixstring,
textstring
) → string

Converts the given prefix and text into a translation key.

GetLocaleId

JSONTranslator.GetLocaleId(selfJSONTranslator) → string

Gets the current localeId of the translator if it's initialized, or a default if it is not.

GetLocalizationTable

JSONTranslator.GetLocalizationTable(selfJSONTranslator) → LocalizationTable

Gets the localization table the translation is using.

PromiseLoaded

JSONTranslator.PromiseLoaded(selfJSONTranslator) → Promise

Returns a promise that will resolve once this translator can answer: the cloud translator has been acquired, and the source locale -- the fallback for every key -- has been loaded.

The source locale is waited on because an instance-decoded translator fetches its locale files on demand, so on a live client the fallback arrives over the network. Acquiring the cloud translator takes far longer in practice, but "awaited PromiseLoaded, so JSONTranslator.FormatByKey works" should hold by construction rather than by luck.

FormatByKey

JSONTranslator.FormatByKey(
translationKeystring,
argstable?
) → string

Formats or errors if the cloud translations are not loaded.

TIP

You should use JSONTranslator.ObserveFormatByKey instead of this to respond to locale changing.

Queues the current locale's entries if they are not loaded yet, so a first synchronous read of a locale costs that decode; the observable path pays the same cost on subscribe.

A locale an instance-decoded translator has not fetched yet cannot be read this way at all -- nothing here can wait for the file. Await JSONTranslator.PromiseLoaded before reading synchronously, which covers the source locale, and prefer JSONTranslator.ObserveFormatByKey for anything else: it re-reads when the data lands.

Destroy

JSONTranslator.Destroy(selfJSONTranslator) → ()

Cleans up the translator and deletes the localization table if it exists. Should be called by ServiceBag

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Constructs a new JSONTranslator from the given args.\n\n```lua\nlocal translator = JSONTranslator.new(\"MyTranslator\", \"en\", {\n\tactions = {\n\t\trespawn = \"Respawn {playerName}\";\n\t};\n})\n\nprint(translator:FormatByKey(\"actions.respawn\"), { playerName = \"Quenty\"}) --> Respawn Quenty\n\n-- Observing is preferred\nmaid:GiveTask(translator:ObserveFormatByKey(\"actions.respawn\", {\n\tplayerName = RxInstanceUtils.observeProperty(player, \"DisplayName\");\n}):Subscribe(function(text)\n\tprint(text) --> \"Respawn Quenty\"\nend))\n```\n\nInstead of a locale and a table, the second argument may be an Instance holding one\n`<locale>.json` StringValue or ModuleScript per locale. Those are decoded lazily, a locale\nat a time, and fetched on demand rather than replicated to every client at join -- see\n[InstanceLocaleLoader].\n\n```lua\nlocal translator = JSONTranslator.new(\"MyTranslator\", script)\n-- assume there is an `en.json` underneath the script with valid JSON.\n```",
            "params": [
                {
                    "name": "translatorName",
                    "desc": "Name of the translator. Used for source.",
                    "lua_type": "string"
                },
                {
                    "name": "localeId",
                    "desc": "a locale id, or the folder holding the locale files",
                    "lua_type": "string | Instance"
                },
                {
                    "name": "dataTable",
                    "desc": "required with a locale id, unused with a folder",
                    "lua_type": "table?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 111,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "ObserveFormatByKey",
            "desc": "Observes the translated value, re-emitting as the locale changes and as the key's data\nbecomes readable.\n\nEmits immediately on subscribe with the best text available rather than withholding, so a\nbound label is never blank for a frame. Until a key's data has landed that best text may be\nthe source language, or the translation key itself for a key registered this frame; the\ncorrect translation replaces it once the key is readable. It will not go the other way: a\nlocale swap never replaces good text with a fallback.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "translationKey",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "translationArgs",
                    "desc": "May have observables (or convertable to observables) in it.",
                    "lua_type": "table?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Observable<string>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 232,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "PromiseFormatByKey",
            "desc": "Formats the resulting entry by args.\n\n:::tip\nYou should use [JSONTranslator.ObserveFormatByKey] instead of this to respond\nto locale changing.\n:::",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "translationKey",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "args",
                    "desc": "",
                    "lua_type": "table?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise<string>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 278,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "_observeTranslationReady",
            "desc": "Observes when a translation key is readable for the current locale, emitting the locale\nid it is readable for, or nil while its data is still queued.\n\n:::warning\nThis is an implementation detail, exposed for the translation stack itself and for\ndiagnostics. You should not need it: every way of reading a translation\n([JSONTranslator.ObserveFormatByKey], [JSONTranslator.PromiseFormatByKey],\n[JSONTranslator.FormatByKey], [JSONTranslator.ObserveTranslation]) already waits for the\nkey it reads, and re-reads when a locale swap brings new data in.\n:::\n\nLocalization writes are batched to the end of the frame (see\n[TranslatorService.SetEntryValue]), because a game streaming in can register thousands of\nentries in a single frame and each raw table write invalidates every AutoLocalize entry\nin the engine. That means a key is not readable the instant it is registered, and this is\nhow the read paths above find out that it is.\n\nRe-emits whenever the locale changes or new data lands for the key, so a locale swap is\npicked up without resubscribing. Nothing here yields.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "translationKey",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Observable<string?>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 322,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "PromiseTranslator",
            "desc": "Returns a promise that will resolve once the Roblox translator is loaded from the cloud.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise<Translator>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 387,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "ObserveTranslator",
            "desc": "Observes the current Roblox translator for this translator.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Observable<Translator>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 396,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "ObserveLocaleId",
            "desc": "Observes the current locale id for this translator.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Observable<string>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 405,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "SetEntryValue",
            "desc": "Adds an entry value to the localization table itself. This can be useful\nfor ensuring pseudo localization and/or generating localization values\nfrom the game data itself.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "translationKey",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "source",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "context",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "localeId",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "text",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 420,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "ObserveTranslation",
            "desc": "Observes a translation key and formats it with the given args.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "prefix",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "text",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "translationArgs",
                    "desc": "",
                    "lua_type": "table?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Observable<string>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 455,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "ToTranslationKey",
            "desc": "Converts the given prefix and text into a translation key.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "prefix",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "text",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 474,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "GetLocaleId",
            "desc": "Gets the current localeId of the translator if it's initialized, or a default if it is not.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 504,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "GetLocalizationTable",
            "desc": "Gets the localization table the translation is using.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "LocalizationTable"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 513,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "PromiseLoaded",
            "desc": "Returns a promise that will resolve once this translator can answer: the cloud\ntranslator has been acquired, and the source locale -- the fallback for every key -- has\nbeen loaded.\n\nThe source locale is waited on because an instance-decoded translator fetches its locale\nfiles on demand, so on a live client the fallback arrives over the network. Acquiring\nthe cloud translator takes far longer in practice, but \"awaited PromiseLoaded, so\n[JSONTranslator.FormatByKey] works\" should hold by construction rather than by luck.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 529,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "FormatByKey",
            "desc": "Formats or errors if the cloud translations are not loaded.\n\n:::tip\nYou should use [JSONTranslator.ObserveFormatByKey] instead of this to respond\nto locale changing.\n:::\n\nQueues the current locale's entries if they are not loaded yet, so a first synchronous read\nof a locale costs that decode; the observable path pays the same cost on subscribe.\n\nA locale an instance-decoded translator has not fetched yet cannot be read this way at all --\nnothing here can wait for the file. Await [JSONTranslator.PromiseLoaded] before reading\nsynchronously, which covers the source locale, and prefer\n[JSONTranslator.ObserveFormatByKey] for anything else: it re-reads when the data lands.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                },
                {
                    "name": "translationKey",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "args",
                    "desc": "",
                    "lua_type": "table?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 553,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        },
        {
            "name": "Destroy",
            "desc": "Cleans up the translator and deletes the localization table if it exists.\nShould be called by [ServiceBag]",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "JSONTranslator"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 673,
                "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "JSONTranslator",
    "desc": "Utility function that loads a translator from a folder or a table.\n\nTo get translations uploaded.\n\n1. Run the game\n2. Run `prepare-localization-export` in the Cmdr console. Translators load lazily -- the\n   source language, plus whichever one a player reads -- so until you ask, no realm holds\n   every locale (see [TranslatorCmdrService])\n3. On the server, check LocalizationService.GeneratedJSONTable_Server\n4. Right click > Save as CSV\n5. Stop the game\n6. In Studio, go to plugins > \"Localization Tools\"\n7. Upload the CSV (update)",
    "source": {
        "line": 19,
        "path": "src/clienttranslator/src/Shared/JSONTranslator.lua"
    }
}