Fix empty emails critical bug (#13465)

Fixes https://github.com/twentyhq/twenty/issues/13398

This bug was introduced by https://github.com/twentyhq/twenty/pull/13215

The emails were empty in scenarios where the user wasn't authenticated
(reset passwords for instance) because in `hydrateGraphqlRequest` the
information about the locale was added only if the user was
authenticated `!this.isTokenPresent(request)`. So the locale was
undefined making ` i18n.activate(undefined) ` fail silently resulting in
an empty email.
This commit is contained in:
Raphaël Bosi
2025-07-28 12:56:29 +02:00
committed by GitHub
parent c59eb20886
commit ae47157818
3 changed files with 57 additions and 3 deletions

View File

@ -478,4 +478,53 @@ describe('resolveObjectMetadataStandardOverride', () => {
expect(mockI18n._).toHaveBeenCalledWith('auto.translation.id'); expect(mockI18n._).toHaveBeenCalledWith('auto.translation.id');
}); });
}); });
describe('Undefined locale handling', () => {
it('should use SOURCE_LOCALE fallback when locale is undefined for standard object', () => {
const objectMetadata = {
labelSingular: 'Standard Label',
labelPlural: 'Standard Labels',
description: 'Standard Description',
icon: 'default-icon',
isCustom: false,
standardOverrides: {
labelSingular: 'Source Override',
},
};
const result = resolveObjectMetadataStandardOverride(
objectMetadata,
'labelSingular',
undefined,
);
expect(result).toBe('Source Override');
expect(mockGenerateMessageId).not.toHaveBeenCalled();
expect(mockI18n._).not.toHaveBeenCalled();
});
it('should fall back to auto translation when locale is undefined and no SOURCE_LOCALE override exists', () => {
mockI18n._.mockReturnValue('Auto Translated Label');
mockGenerateMessageId.mockReturnValue('auto.translation.id');
const objectMetadata = {
labelSingular: 'Standard Label',
labelPlural: 'Standard Labels',
description: 'Standard Description',
icon: 'default-icon',
isCustom: false,
standardOverrides: undefined,
};
const result = resolveObjectMetadataStandardOverride(
objectMetadata,
'labelSingular',
undefined,
);
expect(result).toBe('Auto Translated Label');
expect(mockGenerateMessageId).toHaveBeenCalledWith('Standard Label');
expect(mockI18n._).toHaveBeenCalledWith('auto.translation.id');
});
});
}); });

View File

@ -19,6 +19,8 @@ export const resolveObjectMetadataStandardOverride = (
labelKey: 'labelPlural' | 'labelSingular' | 'description' | 'icon', labelKey: 'labelPlural' | 'labelSingular' | 'description' | 'icon',
locale: keyof typeof APP_LOCALES | undefined, locale: keyof typeof APP_LOCALES | undefined,
): string => { ): string => {
const safeLocale = locale ?? SOURCE_LOCALE;
if (objectMetadata.isCustom) { if (objectMetadata.isCustom) {
return objectMetadata[labelKey] ?? ''; return objectMetadata[labelKey] ?? '';
} }
@ -32,11 +34,10 @@ export const resolveObjectMetadataStandardOverride = (
if ( if (
isDefined(objectMetadata.standardOverrides?.translations) && isDefined(objectMetadata.standardOverrides?.translations) &&
isDefined(locale) &&
labelKey !== 'icon' labelKey !== 'icon'
) { ) {
const translationValue = const translationValue =
objectMetadata.standardOverrides.translations[locale]?.[labelKey]; objectMetadata.standardOverrides.translations[safeLocale]?.[labelKey];
if (isDefined(translationValue)) { if (isDefined(translationValue)) {
return translationValue; return translationValue;
@ -44,7 +45,7 @@ export const resolveObjectMetadataStandardOverride = (
} }
if ( if (
locale === SOURCE_LOCALE && safeLocale === SOURCE_LOCALE &&
isNonEmptyString(objectMetadata.standardOverrides?.[labelKey]) isNonEmptyString(objectMetadata.standardOverrides?.[labelKey])
) { ) {
return objectMetadata.standardOverrides[labelKey] ?? ''; return objectMetadata.standardOverrides[labelKey] ?? '';

View File

@ -128,6 +128,10 @@ export class MiddlewareService {
public async hydrateGraphqlRequest(request: Request) { public async hydrateGraphqlRequest(request: Request) {
if (!this.isTokenPresent(request)) { if (!this.isTokenPresent(request)) {
request.locale =
(request.headers['x-locale'] as keyof typeof APP_LOCALES) ??
SOURCE_LOCALE;
return; return;
} }