Files
twenty/packages/twenty-emails/lingui.config.ts
T
Charles Bochet 4fc3650b2d fix(emails): deterministic i18n catalog order to stop recurring i18n PR conflicts (#22803)
## Problem

The automated `i18n - translations` PR (branch `i18n`) conflicts on
`twenty-emails` `.po` files on **every** cycle. Each time, the msgid
*set* is identical to main — only the entry **order** differs.

## Root cause

`twenty-emails` uses explicit message ids (`js-lingui-explicit-id`).
With lingui's default ordering, **`lingui extract` is non-idempotent for
this catalog** — two consecutive runs on identical source produce
different `.po` orderings:

```
# no source change between runs
lingui extract  # run 1
lingui extract  # run 2  -> ~170 lines reordered vs run 1
```

So the order produced by the `i18n-push` extract on main, the order
stored in Crowdin, and the order the `i18n-pull` bot downloads never
agree, and the translation PR re-conflicts perpetually.
`twenty-front`/`twenty-server` use hashed ids and are already idempotent
— this is isolated to emails.

## Fix

Set `orderBy: 'messageId'` in `twenty-emails/lingui.config.ts`. Verified
this makes extraction idempotent — two consecutive extracts now produce
byte-identical output.

This commit includes the one-time reorder of the existing catalogs into
the stable order. Generated `.ts` output is unchanged (already
order-independent). After merge, one push cycle syncs Crowdin to the
stable order, after which the recurring conflicts stop.

## Test plan

- `nx run twenty-emails:lingui:extract` twice → no diff on the second
run.
- `nx run twenty-emails:lingui:compile` → succeeds, generated `.ts`
unchanged.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22803?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 15:16:33 +02:00

31 lines
1.1 KiB
TypeScript

import { defineConfig } from '@lingui/conf';
import { formatter } from '@lingui/format-po';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
export default defineConfig({
sourceLocale: 'en',
locales: Object.values(APP_LOCALES),
pseudoLocale: 'pseudo-en',
fallbackLocales: {
default: SOURCE_LOCALE,
},
extractorParserOptions: {
tsExperimentalDecorators: true,
},
catalogs: [
{
path: '<rootDir>/src/locales/{locale}',
include: ['src'],
},
],
catalogsMergePath: '<rootDir>/src/locales/generated/{locale}',
// Sort catalog entries by message id. These emails use explicit ids
// (js-lingui-explicit-id), for which the default order is non-deterministic:
// `lingui extract` reorders the .po on every run even with no source change,
// so main and the Crowdin sync never agree and every i18n pull re-conflicts.
// Sorting by messageId makes extraction idempotent and the order stable.
orderBy: 'messageId',
compileNamespace: 'ts',
format: formatter({ lineNumbers: false, printLinguiId: true }),
});