opsdocsConsole

Packages / nuxt-ops

Translations at build time

Refresh the app's locale files from ops before every build, with the checked in files as the fallback.

Updated 13 Oct 2026View as Markdown

Before every build, the module asks ops for each locale's composed tree and writes it into the matching file in the locale directory. The composed tree is your project's own translations with the shared bundles and the package bundles it uses, and your overrides applied.

text
i18n/locales/da.json   <- the composed Danish tree of project rungholt
i18n/locales/en.json   <- the composed English tree

A Rust service pulls the same tree in its build script: see Translations at build time in the SDK.

The set of locales is the .json files already in the directory: add a file to add a locale. Each file is written ordered at every depth, two space indented and newline terminated, so a pull you commit shows only what changed.

Wiring it into @nuxtjs/i18n

The module writes the files; @nuxtjs/i18n reads them. List @syncronet/nuxt-ops first, so the pull has finished before the i18n module loads the files, and point the i18n module at the same directory, which both default to (i18n/locales, relative to the app's root):

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@syncronet/nuxt-ops', '@nuxtjs/i18n'],
  ops: { project: 'rungholt' },
  i18n: {
    defaultLocale: 'da',
    strategy: 'no_prefix',
    locales: [
      { code: 'da', name: 'Dansk', file: 'da.json' },
      { code: 'en', name: 'English', file: 'en.json' },
    ],
  },
})

Every file must exist before the first pull, because the set of locales is the files already there. Components use the keys as they always do, $t('app.title') or useI18n(). A key from a package bundle, such as a layer you install, is in the same file under its own namespace, so the app reads it the same way and an override you set in ops wins over the package's value.

Change the directory with localesDir here and langDir there, together.

What it needs

settingwhat it is
project or OPS_PROJECTthe project to pull
buildUrl, url, OPS_URL or OPS_PUBLIC_URLthe address of ops
OPS_API_TOKENa token that may read the project's translations
bundle or OPS_TMS_BUNDLEoptional: which of the project's own bundles to start from

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. Set them yourself only to pull on your own machine or in another CI.

Keys only your repository has

A string usually starts in the repository: the component and its locale entry land in one commit, and the key reaches ops later. The pull keeps it. Ops wins for every key it has, and a key that only the checked in file has stays in the file, named in the build log:

text
nuxt-ops: project rungholt/da kept 2 key(s) from the checked in file that ops does not have: booking.cancel, booking.confirm. Add them in ops

Add those keys in ops and the line goes away. A key you delete in ops stays in the file the same way until you remove it there too.

A pull never fails the build

Without OPS_API_TOKEN (a contributor, a fork, CI without ops credentials) the pull logs one line and the build goes on with the checked in files. The same happens when ops cannot be reached, when it refuses a locale, or when the locale directory is missing. The checked in files are always the fallback, so keep them committed.

text
nuxt-ops: no OPS_API_TOKEN, skipping the TMS pull and building from the checked in locale files
nuxt-ops: pulled project rungholt/da

In development

The pull also runs when nuxt dev starts, so a shell with a token rewrites the locale files on every start. Set tms: false in the app's config to stop that.

One bundle as it is

platform (or OPS_TMS_PLATFORM) pulls a single bundle without composing anything, from <home>/<bundle>. A bare bundle name is looked up in your own project. When platform is set it wins over the composed pull, so leave it empty unless that is what you want.