---
title: Translations at build time
description: Refresh the app's locale files from ops before every build, with the checked in files as the fallback.
order: 2
---

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](/ops-sdk/i18n) 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

| setting | what it is |
| --- | --- |
| `project` or `OPS_PROJECT` | the project to pull |
| `buildUrl`, `url`, `OPS_URL` or `OPS_PUBLIC_URL` | the address of ops |
| `OPS_API_TOKEN` | a token that may read the project's translations |
| `bundle` or `OPS_TMS_BUNDLE` | optional: 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.
