setup localization for twenty-emails (#9806)
One of the steps to address #8128 How to test: Please change the locale in the settings and click on change password button. A password reset email in the preferred locale will be sent.   Todo: - Remove the hardcoded locales for invitation, warn suspended workspace email, clean suspended workspace emails - Need to test invitation, email verification, warn suspended workspace email, clean suspended workspace emails - The duration variable `5 minutes` is always in english. Do we need to do something about that? It does seems odd in case of chinese translations. Notes: - Only tested the password reset , password update notify templates. - Cant test email verification due to error during sign up `Internal server error: New workspace setup is disabled` --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
committed by
GitHub
parent
4b9414a002
commit
39e7f6cec3
@ -1,5 +1,5 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
@ -33,6 +33,7 @@ import { TransientTokenService } from 'src/engine/core-modules/auth/token/servic
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
@ -336,6 +337,7 @@ export class AuthResolver {
|
||||
@Mutation(() => EmailPasswordResetLink)
|
||||
async emailPasswordResetLink(
|
||||
@Args() emailPasswordResetInput: EmailPasswordResetLinkInput,
|
||||
@Context() context: I18nContext,
|
||||
): Promise<EmailPasswordResetLink> {
|
||||
const resetToken =
|
||||
await this.resetPasswordService.generatePasswordResetToken(
|
||||
@ -345,6 +347,7 @@ export class AuthResolver {
|
||||
return await this.resetPasswordService.sendEmailPasswordResetLink(
|
||||
resetToken,
|
||||
emailPasswordResetInput.email,
|
||||
context.req.headers['x-locale'] || 'en',
|
||||
);
|
||||
}
|
||||
|
||||
@ -352,13 +355,18 @@ export class AuthResolver {
|
||||
async updatePasswordViaResetToken(
|
||||
@Args()
|
||||
{ passwordResetToken, newPassword }: UpdatePasswordViaResetTokenInput,
|
||||
@Context() context: I18nContext,
|
||||
): Promise<InvalidatePassword> {
|
||||
const { id } =
|
||||
await this.resetPasswordService.validatePasswordResetToken(
|
||||
passwordResetToken,
|
||||
);
|
||||
|
||||
await this.authService.updatePassword(id, newPassword);
|
||||
await this.authService.updatePassword(
|
||||
id,
|
||||
newPassword,
|
||||
context.req.headers['x-locale'] || 'en',
|
||||
);
|
||||
|
||||
return await this.resetPasswordService.invalidatePasswordResetToken(id);
|
||||
}
|
||||
|
||||
@ -3,10 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import { render } from '@react-email/components';
|
||||
import { render } from '@react-email/render';
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { PasswordUpdateNotifyEmail } from 'twenty-emails';
|
||||
import { APP_LOCALES } from 'twenty-shared';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/environment/interfaces/node-environment.interface';
|
||||
@ -379,6 +380,7 @@ export class AuthService {
|
||||
async updatePassword(
|
||||
userId: string,
|
||||
newPassword: string,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
): Promise<UpdatePassword> {
|
||||
if (!userId) {
|
||||
throw new AuthException(
|
||||
@ -415,14 +417,11 @@ export class AuthService {
|
||||
userName: `${user.firstName} ${user.lastName}`,
|
||||
email: user.email,
|
||||
link: this.domainManagerService.getBaseUrl().toString(),
|
||||
locale,
|
||||
});
|
||||
|
||||
const html = render(emailTemplate, {
|
||||
pretty: true,
|
||||
});
|
||||
const text = render(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
const html = render(emailTemplate, { pretty: true });
|
||||
const text = render(emailTemplate, { plainText: true });
|
||||
|
||||
this.emailService.send({
|
||||
from: `${this.environmentService.get(
|
||||
|
||||
@ -9,11 +9,11 @@ import {
|
||||
AppTokenType,
|
||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
import { ResetPasswordService } from './reset-password.service';
|
||||
|
||||
@ -149,6 +149,7 @@ describe('ResetPasswordService', () => {
|
||||
const result = await service.sendEmailPasswordResetLink(
|
||||
mockToken,
|
||||
'test@example.com',
|
||||
'en',
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
@ -162,6 +163,7 @@ describe('ResetPasswordService', () => {
|
||||
service.sendEmailPasswordResetLink(
|
||||
{} as any,
|
||||
'nonexistent@example.com',
|
||||
'en',
|
||||
),
|
||||
).rejects.toThrow(AuthException);
|
||||
});
|
||||
|
||||
@ -7,6 +7,7 @@ import { render } from '@react-email/render';
|
||||
import { addMilliseconds, differenceInMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { PasswordResetLinkEmail } from 'twenty-emails';
|
||||
import { APP_LOCALES } from 'twenty-shared';
|
||||
import { IsNull, MoreThan, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
@ -106,6 +107,7 @@ export class ResetPasswordService {
|
||||
async sendEmailPasswordResetLink(
|
||||
resetToken: PasswordResetToken,
|
||||
email: string,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
): Promise<EmailPasswordResetLink> {
|
||||
const user = await this.userRepository.findOneBy({
|
||||
email,
|
||||
@ -133,16 +135,13 @@ export class ResetPasswordService {
|
||||
long: true,
|
||||
},
|
||||
),
|
||||
locale,
|
||||
};
|
||||
|
||||
const emailTemplate = PasswordResetLinkEmail(emailData);
|
||||
const html = render(emailTemplate, {
|
||||
pretty: true,
|
||||
});
|
||||
|
||||
const text = render(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
const html = render(emailTemplate, { pretty: true });
|
||||
const text = render(emailTemplate, { plainText: true });
|
||||
|
||||
this.emailService.send({
|
||||
from: `${this.environmentService.get(
|
||||
|
||||
Reference in New Issue
Block a user