Skip to content

Publish language packs

Blah can serve Telegram-compatible language packs from a public git repository served over HTTPS. Translators publish versions with Git tags; operators configure each DC to poll that repository. Clients fetch translations from the DC, so the repository is never on a client request path. This does not replace the web client’s own bundled strings.

Prepare the repository

Use one folder per client platform and language, optionally under a common prefix:

translations/
ios/
en-gb/
info.json
Welcome/value
UnreadMessages/one
UnreadMessages/other

The platform folder must match the client’s lang_pack, such as ios, android, tdesktop, macos, android_x or webk. It accepts lowercase letters, digits and underscores, with no hyphens. Language folders use lowercase letters or digits separated by single hyphens, such as en, pt-br or zh-hans.

Each string key is a directory name of 1–255 UTF-8 bytes that does not start with a dot. A plain string contains only value. A plural string contains any of zero, one, two, few, many, and the required other. Do not mix value and plural files or put other files in a string directory: the entire string is skipped with a warning. Deeper nesting is ignored, as are files other than info.json directly inside a language folder.

Write UTF-8 text. The importer strips exactly one final newline (\n or \r\n); other whitespace is preserved. Invalid UTF-8 makes that string invalid.

Each language needs info.json:

{
"name": "English (United Kingdom)",
"native_name": "English (UK)",
"plural_code": "en",
"lang_code": "en-gb",
"base_lang_code": "en",
"official": false,
"rtl": false,
"beta": true
}

name, native_name, and plural_code are required. official, rtl, and beta default to false. Optional lang_code must equal the folder name. base_lang_code names a language clients fetch separately; publish that base as its own language. The server does not merge base strings. Optional strings_count and translated_count default to the valid string count. Optional translations_url defaults to the configured repository URL. Unknown metadata keys are ignored. Missing or invalid metadata fails that language’s update and keeps its previous version.

Keep each variant file and info.json at or below 64 KiB, each language at or below 50,000 strings, and fetched text at or below 32 MiB per language per synchronization pass. Exceeding a limit fails that language’s update.

Publish a version

Commit the language and tag that commit with <platform>-<language>-v<N>:

Terminal window
git tag ios-en-gb-v1
git push origin ios-en-gb-v1

Versions are positive decimal integers up to 2,147,483,647, without leading zeros. If the operator configures a tag prefix, prepend it literally, for example release-ios-en-gb-v2. Untagged folders are not served. Each language advances to its highest tagged version; intermediate versions need not be imported individually.

Publish corrections under a higher version. Moving an already imported tag does not rewrite history. Deleting only the newest tag does not roll clients back. Removing every tag for a language withdraws it after a successful complete tag listing; retained data becomes available again when its tags reappear.

Changed and removed strings are sent as differences. Clients whose versions predate the DC’s available history, or exceed its current version, receive a full replacement pack. A failed update leaves the last successful version available. Matching live clients, including clients still signing in, receive a hint to fetch the new difference. A base language’s publication also notifies clients using a language derived from it.

Configure the DC

In /admin, set these managed server options and restart:

OptionValue
langPackRepositoryAn HTTPS clone URL, for example https://git.example.com/team/translations.git
langPackRepositoryTokenOptional read-only token, masked as a secret; empty means anonymous
langPackRepositoryUsernameHTTP Basic username; defaults to x-access-token; set the username your host requires
langPackPathPrefixOptional folder prefix, for example translations; defaults to empty
langPackTagPrefixOptional literal tag prefix, for example release-; defaults to empty
langPackSyncIntervalPoll interval in seconds; defaults to 900, minimum 60

Some hosts rate-limit anonymous git traffic even for public repositories. An optional read-only token with no other scopes is enough; it is sent as the HTTP Basic password only to the repository’s origin and is dropped on cross-origin redirects. Rotating credentials preserves imported history. Usernames must be nonempty without colons, and neither usernames nor tokens may contain control characters.

Use a public repository URL without embedded credentials, query strings or fragments. Scheme and host are case-insensitive, the default HTTPS port and one trailing slash are removed, and the remaining path (including .git) is significant. The folder prefix has its leading and trailing slashes trimmed and cannot contain . or .. components.

The server must support HTTPS smart-HTTP with git protocol v2, partial-clone filters (tree:0, blob:none), and fetching reachable objects by id. A self-hosted git http-backend needs uploadpack.allowFilter=true and uploadpack.allowAnySHA1InWant=true (or uploadpack.allowReachableSHA1InWant=true), and its proxy must pass the Git-Protocol header through.

A background pass starts at each server startup without delaying client service. Subsequent passes use the maintenance queue. No repository means no synchronization or network traffic, and language-pack RPCs return LANG_PACK_INVALID. The RPCs are available before sign-in after the client’s normal connection initialization; bot sessions cannot call them.

Changing the repository, folder prefix or tag prefix replaces the DC’s imported state on the next pass. Clients recover with a full pack when necessary. Each DC keeps its own cache and version history in its business database, included in ordinary database backups.