Fix i18n issues on datamodel translation (#13710)

As per title, I believe calling i18n is bad because it's a singleton.

This singleton is being set back and force by users in
i18n.middleware.ts

Strategy:
we need to load the translations dynamically! Should be straight
forward:
- singleton (i18nService) containing all translationMessages
- singleton also containing a map of i18n instances loaded for each
locale => I've checked it's not heavy on RAM at all
- some methods to get the translation for a messageId x locale or the
whole i18n instance

==> use it everywhere, this PR only takes care of data model translation
This commit is contained in:
Charles Bochet
2025-08-07 01:28:40 +02:00
committed by GitHub
parent 58d25ebd83
commit fecf8ca913
5 changed files with 79 additions and 14 deletions
@@ -31,6 +31,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(result).toBe('Custom Label');
@@ -49,6 +50,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'description',
undefined,
mockI18n,
);
expect(result).toBe('Custom Description');
@@ -67,6 +69,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'icon',
SOURCE_LOCALE,
mockI18n,
);
expect(result).toBe('custom-icon');
@@ -89,6 +92,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'icon',
'fr-FR',
mockI18n,
);
expect(result).toBe('override-icon');
@@ -113,13 +117,19 @@ describe('resolveFieldMetadataStandardOverride', () => {
};
expect(
resolveFieldMetadataStandardOverride(fieldMetadata, 'label', 'fr-FR'),
resolveFieldMetadataStandardOverride(
fieldMetadata,
'label',
'fr-FR',
mockI18n,
),
).toBe('Libellé traduit');
expect(
resolveFieldMetadataStandardOverride(
fieldMetadata,
'description',
'fr-FR',
mockI18n,
),
).toBe('Description traduite');
});
@@ -146,6 +156,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(result).toBe('Standard Label');
@@ -173,6 +184,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'description',
'fr-FR',
mockI18n,
);
expect(result).toBe('Standard Description');
@@ -200,6 +212,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
undefined,
mockI18n,
);
expect(result).toBe('Standard Label');
@@ -225,6 +238,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
SOURCE_LOCALE,
mockI18n,
),
).toBe('Overridden Label');
expect(
@@ -232,6 +246,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'description',
SOURCE_LOCALE,
mockI18n,
),
).toBe('Overridden Description');
expect(
@@ -239,6 +254,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'icon',
SOURCE_LOCALE,
mockI18n,
),
).toBe('overridden-icon');
});
@@ -261,6 +277,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(result).toBe('Standard Label');
@@ -284,6 +301,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
SOURCE_LOCALE,
mockI18n,
);
expect(result).toBe('Standard Label');
@@ -307,6 +325,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
SOURCE_LOCALE,
mockI18n,
);
expect(result).toBe('Standard Label');
@@ -330,6 +349,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(mockGenerateMessageId).toHaveBeenCalledWith('Standard Label');
@@ -355,6 +375,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(result).toBe('Standard Label');
@@ -382,6 +403,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'fr-FR',
mockI18n,
);
expect(result).toBe('Translation Override');
@@ -404,6 +426,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
SOURCE_LOCALE,
mockI18n,
);
expect(result).toBe('Source Override');
@@ -427,6 +450,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
fieldMetadata,
'label',
'de-DE',
mockI18n,
);
expect(result).toBe('Auto Translated Label');
@@ -1,4 +1,4 @@
import { i18n } from '@lingui/core';
import { I18n } from '@lingui/core';
import { isNonEmptyString } from '@sniptt/guards';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
@@ -13,6 +13,7 @@ export const resolveFieldMetadataStandardOverride = (
>,
labelKey: 'label' | 'description' | 'icon',
locale: keyof typeof APP_LOCALES | undefined,
i18nInstance: I18n,
): string => {
if (fieldMetadata.isCustom) {
return fieldMetadata[labelKey] ?? '';
@@ -43,7 +44,8 @@ export const resolveFieldMetadataStandardOverride = (
}
const messageId = generateMessageId(fieldMetadata[labelKey] ?? '');
const translatedMessage = i18n._(messageId);
const translatedMessage = i18nInstance._(messageId);
if (translatedMessage === messageId) {
return fieldMetadata[labelKey] ?? '';