opsdocsConsole

Packages / ops-sdk

Translations at build time

Pull the project's composed translations from ops in build.rs and embed them in the binary.

Updated 13 Oct 2026View as Markdown

Feature: i18n.

A Rust service keeps its locale files in the repository, for example locales/da.json and locales/en.json. At build time i18n::Pull asks ops for each locale's composed tree, which is your project's own translations with the shared bundles and your overrides applied, and writes it where your crate can embed it. The deployed binary carries its translations and never fetches them while it runs.

For a Nuxt app the same pull is part of the Nuxt module: see Translations at build time.

Add the crate as a build dependency:

toml
[build-dependencies]
ops-sdk = { version = "0.1", registry = "ops", features = ["i18n"] }
rust
// build.rs
fn main() {
    ops_sdk::i18n::Pull::from_env("locales").run().expect("locales");
}
rust
// in the crate
const DA: &str = include_str!(concat!(env!("OUT_DIR"), "/ops-i18n/da.json"));

Configuration

variablewhat it is
OPS_URLthe address of ops. Falls back to OPS_PUBLIC_URL
OPS_PROJECTyour project's slug
OPS_TMS_BUNDLEwhich of the project's own bundles the composition starts from. The default is the one named like the project
OPS_API_TOKENa token that may read the project's translations

On an ops build none of these need setting: the build environment carries OPS_URL, OPS_PROJECT and an OPS_API_TOKEN that reads your project's translations for as long as the build runs.

Cargo reruns the build script when any of these or the locale directory changes. A translation edited in ops ships with the next build.

A pull never fails the build

The set of locales is the .json files in the directory: add a file to add a locale. For each one the pull writes the tree from ops, ordered and newline terminated, to $OUT_DIR/ops-i18n/<locale>.json. When there is no token (a contributor, a fork, CI without ops credentials), when ops cannot be reached, or when it refuses, the checked in file is copied instead and cargo prints a warning:

text
warning: ops-sdk i18n: da built from the checked in file: no OPS_API_TOKEN set

A key the checked in file has and ops does not is kept in the embedded tree, and the build names it, so a string added in the repository works before it reaches ops:

text
warning: ops-sdk i18n: da kept 1 key(s) ops does not have: booking.cancel

Ops wins for every key it has.

Only two things are errors, because then there is nothing to embed: a locale directory that does not exist, and an OUT_DIR that cannot be written.

The pull never writes your checked in files. To bring them up to date, run ops tms pull <locale> --project <slug> and commit the diff.

Only in a build script

run() refuses with NotInBuildScript unless OUT_DIR is set, which cargo does for a build script and nothing else. The API is blocking on purpose: nothing in this feature can sit in a request path. write_to(dir) does the same pull into any directory, for a test.