Angular · Transloco

Transloco reads the same JSON Mergua writes.

Mergua was built alongside an Angular app running Transloco, and its defaults are that layout: nested JSON in src/assets/i18n, whether that is one file per language or a folder of scope files per language. Point the sync script at your repo and the round trip needs no flags at all.

Angular + Transloco

/for/angular-transloco

First-class

locale files

src/assets/i18n

json shape

nested

01

The files you already have

Mergua reads what is in your repo. Nothing is renamed, nothing moves, and no package is added to your project — the sync script is fetched with curl when the pipeline runs.

src/assets/i18n/
├── en.json      ← source locale
├── de.json
└── fr.json
src/assets/i18n/en.json
{
  "cart": {
    "summary": {
      "title": "Your cart",
      "items": "{{count}} items",
      "checkout": "Checkout"
    }
  }
}
02

How Transloco reads them

This is Transloco's own setup, not Mergua's — if you already have it, skip the panel. It is here so the page is a working path from empty project to translated screen rather than a page about a product.

src/app/cart/cart-summary.ts
import { Component } from '@angular/core';
import { TranslocoPipe } from '@jsverse/transloco';

@Component({
  selector: 'app-cart-summary',
  imports: [TranslocoPipe],
  template: `
    <h1>{{ 'cart.summary.title' | transloco }}</h1>
    <p>{{ 'cart.summary.items' | transloco: { count: 3 } }}</p>
  `,
})
export class CartSummaryComponent {}

src/app/app.config.ts

import { ApplicationConfig, isDevMode } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideTransloco } from '@jsverse/transloco';

import { TranslocoHttpLoader } from './transloco-loader';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch()),
    provideTransloco({
      config: {
        availableLangs: ['en', 'de', 'fr'],
        defaultLang: 'en',
        reRenderOnLangChange: true,
        prodMode: !isDevMode(),
      },
      loader: TranslocoHttpLoader,
    }),
  ],
};
03

Back into the build

One line in the job that already builds the app. It pushes keys Mergua has not seen yet, pulls back whatever has been translated since, and commits the changed locale files to the branch it is running on.

The job runs on the branch it is already on, so a feature branch syncs against the matching Mergua branch and main against main. That is the part no other translation tool does.

And you do not have to run it on a timer: a project can POST to a URL of yours when translations change or a branch is merged, so the job above starts on the change. The delivery is signed, it is filtered to the branch you name, and an import of four hundred keys arrives as one call.

# src/assets/i18n and nested JSON are the CLI's defaults,
# so an Angular project needs no flags.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync
04

Placeholders

Transloco writes a variable as {{count}}. Mergua treats it as part of the string: if a translation comes back without it, the validator says so on the way out, and the AI translator is told to carry it through.

the source string
"items": "{{count}} items"
what fills it in
{{ 'cart.summary.items' | transloco: { count: 3 } }}

Worth knowing on Angular

On Angular 18+, pass --path

The CLI defaults to src/assets/i18n, which is where ng add @jsverse/transloco puts things on the classic layout. Newer Angular projects scaffold a public/ folder instead — then the files are in public/i18n and the sync job needs --path public/i18n. The "public/" tab above is that job.

Scopes are files, and one project holds them all

A Mergua project keeps as many files per language as you have scopes. It reads a folder per language with the scope files inside — src/assets/i18n/en/checkout.json — and the file name is what the keys are filed under, so checkout.json becomes checkout and a plain en.json is read as the language. The one adjustment is in the loader: Transloco asks it for "checkout/en" while the file is en/checkout.json. The "scopes" tab above is that loader.

Splitting a language across several files

SSR reads the same files

A Transloco loader that goes over HttpClient has no origin to fetch from during a server render. Import the JSON directly on the server side and keep the HTTP path for the browser — the same two-branch loader this site runs on.

Nested is what Transloco wants

Transloco resolves cart.summary.title through nested objects, so leave --format at its nested default. Pass --format flat only if you keep literally dotted keys in the file, which Transloco reads as one long key rather than a path.

A different stack?

It is JSON in and JSON out, so anything that reads a locale file works. These are the three with a page of their own.

Run it on one branch.

Upload the src/assets/i18n/en.json you already have and see the whole loop on the free tier.

Create your account