Angular · ngx-translate

Keep ngx-translate. Your files already fit.

ngx-translate loads nested JSON from src/assets/i18n by default, and that is the folder the sync script reads when you tell it nothing. No migration and no new package in your app: the loader you have keeps loading the files Mergua writes.

Angular + ngx-translate

/for/ngx-translate

Supported

locale files

src/assets/i18n

file format

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 ngx-translate reads them

This is ngx-translate'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 { TranslatePipe } from '@ngx-translate/core';

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

src/app/app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideTranslateService } from '@ngx-translate/core';
import { provideTranslateHttpLoader } from '@ngx-translate/http-loader';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch()),
    provideTranslateService({
      // No prefix: the loader reads /assets/i18n/<lang>.json.
      loader: provideTranslateHttpLoader(),
      fallbackLang: 'en',
      lang: 'en',
    }),
  ],
};
03

Back into the build

One line in the job that already builds the app. The sync script reads the folder the loader fetches from, pushes the keys Mergua has not seen, pulls back what has been translated since and commits the locale files to the branch it is running on — the files TranslateHttpLoader loads on the next deploy.

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 is both the loader's default and the CLI's,
# so an ngx-translate project on that layout needs no flags.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync
04

Placeholders

ngx-translate 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' | translate: { count: 3 } }}
05

Between the push and the pull

The job above is both ends of one loop, and what happens in between is people. A translation gets written, somebody else approves it, and sometimes a whole language leaves for an agency and comes back — none of which touches a file in your repo until the next pull.

One resource at a time, or all of them

Each file the loader lists as a resource is a file in Mergua too, and the editor filters to one of them, so a reviewer reads checkout.json through on its own. A key is approved, or rejected with a reason that puts it back on its writer's list; asking for a review mails everyone who can give one. Until every key on a branch is reviewed the branch does not merge, so what TranslateHttpLoader loads from main is never half unread.

How a key gets from draft to reviewed

An agency gets XLIFF, your app keeps JSON

A language that goes to a translation agency leaves as one XLIFF 2.0 document, every resource inside it as a file element named after its JSON, and it comes back into the same files. ngx-translate never sees the document: the loader still fetches JSON, and the sync script, which only ever writes JSON, does not carry the format at all.

What a CAT tool does with the document

Worth knowing on Angular

On Angular 18+, the files are in public/

A fresh ng new serves public/ and not src/assets, so the loader 404s on its default prefix. Put the files in public/i18n, give the loader prefix: '/i18n/' and the sync job --path public/i18n — the two "public/" tabs above.

Several files merge into one tree

Mergua writes a language folder of files, en/checkout.json, and the loader reads it with one resource per file. It then merges them all into one tree: the file name is not part of the key, the way it is in Mergua. So give each file one top-level key of its own — checkout.json starting with "checkout" — or two files will overwrite each other.

Splitting a language across several files

One space inside the braces, at most

ngx-translate fills {{count}} and {{ count }} alike, but leaves {{ count }} — two spaces — in the text as it is. Mergua compares placeholders without the whitespace, so it reads all three as the same placeholder and will not flag the third. Keep to one.

Moving to Transloco? The files stay

Nothing on this page asks you to: Mergua runs on ngx-translate as it is, and it will keep running there. If your team does move, the locale files do not change. Transloco reads nested JSON with {{ count }} from src/assets/i18n too, so the Mergua project, its branches and the sync line carry over untouched; what changes is your code — the pipe, the providers and the loader. With several files per language, Transloco reads the file name as a scope, which is the one difference worth reading up on first.

Mergua on Angular with Transloco

A different stack?

Mergua writes plain locale files, so anything that loads one works. These stacks have a page of their own as well.

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