Compare commits

...

1 Commits

Author SHA1 Message Date
Raphaël Bosi d88a8e615e 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.
2025-07-28 12:57:50 +02:00
3 changed files with 57 additions and 3 deletions
@@ -478,4 +478,53 @@ describe('resolveObjectMetadataStandardOverride', () => {
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');
});
});
});
@@ -19,6 +19,8 @@ export const resolveObjectMetadataStandardOverride = (
labelKey: 'labelPlural' | 'labelSingular' | 'description' | 'icon',
locale: keyof typeof APP_LOCALES | undefined,
): string => {
const safeLocale = locale ?? SOURCE_LOCALE;
if (objectMetadata.isCustom) {
return objectMetadata[labelKey] ?? '';
}
@@ -32,11 +34,10 @@ export const resolveObjectMetadataStandardOverride = (
if (
isDefined(objectMetadata.standardOverrides?.translations) &&
isDefined(locale) &&
labelKey !== 'icon'
) {
const translationValue =
objectMetadata.standardOverrides.translations[locale]?.[labelKey];
objectMetadata.standardOverrides.translations[safeLocale]?.[labelKey];
if (isDefined(translationValue)) {
return translationValue;
@@ -44,7 +45,7 @@ export const resolveObjectMetadataStandardOverride = (
}
if (
locale === SOURCE_LOCALE &&
safeLocale === SOURCE_LOCALE &&
isNonEmptyString(objectMetadata.standardOverrides?.[labelKey])
) {
return objectMetadata.standardOverrides[labelKey] ?? '';
@@ -128,6 +128,10 @@ export class MiddlewareService {
public async hydrateGraphqlRequest(request: Request) {
if (!this.isTokenPresent(request)) {
request.locale =
(request.headers['x-locale'] as keyof typeof APP_LOCALES) ??
SOURCE_LOCALE;
return;
}