Compare commits

...

6 Commits

20 changed files with 333 additions and 18 deletions

View File

@@ -25,6 +25,8 @@ use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Contracts\Translation\TranslatorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
/**
* @see OnGenerationFailsTest for test suite
*/
@@ -40,6 +42,7 @@ final readonly class OnGenerationFails implements EventSubscriberInterface
private StoredObjectRepositoryInterface $storedObjectRepository,
private TranslatorInterface $translator,
private UserRepositoryInterface $userRepository,
// private LocaleSwitcher $localeSwitcher,
) {}
public static function getSubscribedEvents()
@@ -118,6 +121,25 @@ final readonly class OnGenerationFails implements EventSubscriberInterface
return;
}
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
/*
$this->localeSwitcher->runWithLocale($creator->getLocale(), function () use ($message, $errors, $template, $creator) {
$email = (new TemplatedEmail())
->to($message->getSendResultToEmail())
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed'))
->textTemplate('@ChillDocGenerator/Email/on_generation_failed_email.txt.twig')
->context([
'errors' => $errors,
'template' => $template,
'creator' => $creator,
'stored_object_id' => $message->getDestinationStoredObjectId(),
]);
$this->mailer->send($email);
});
*/
// Current implementation:
$email = (new TemplatedEmail())
->to($message->getSendResultToEmail())
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed'))

View File

@@ -27,6 +27,8 @@ use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
/**
* Handle the request of document generation.
*/
@@ -46,6 +48,7 @@ class RequestGenerationHandler implements MessageHandlerInterface
private readonly MailerInterface $mailer,
private readonly TranslatorInterface $translator,
private readonly StoredObjectManagerInterface $storedObjectManager,
// private readonly LocaleSwitcher $localeSwitcher,
) {}
public function __invoke(RequestGenerationMessage $message)
@@ -122,6 +125,30 @@ class RequestGenerationHandler implements MessageHandlerInterface
private function sendDataDump(StoredObject $destinationStoredObject, RequestGenerationMessage $message): void
{
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
// Note: This method sends emails to admin addresses, not user addresses, so locale switching may not be needed
/*
$this->localeSwitcher->runWithLocale('fr', function () use ($destinationStoredObject, $message) {
// Get the content of the document
$content = $this->storedObjectManager->read($destinationStoredObject);
$filename = $destinationStoredObject->getFilename();
$contentType = $destinationStoredObject->getType();
// Create the email with the document as an attachment
$email = (new TemplatedEmail())
->to($message->getSendResultToEmail())
->textTemplate('@ChillDocGenerator/Email/send_data_dump_to_admin.txt.twig')
->context([
'filename' => $filename,
])
->subject($this->translator->trans('docgen.data_dump_email.subject'))
->attach($content, $filename, $contentType);
$this->mailer->send($email);
});
*/
// Current implementation:
// Get the content of the document
$content = $this->storedObjectManager->read($destinationStoredObject);
$filename = $destinationStoredObject->getFilename();

View File

@@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\NotificationFlagManager;
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
use libphonenumber\PhoneNumber;
use Symfony\Component\Validator\Constraints as Assert;
final class UpdateProfileCommand
{
@@ -23,11 +24,13 @@ final class UpdateProfileCommand
public function __construct(
#[PhonenumberConstraint]
public ?PhoneNumber $phonenumber,
#[Assert\Choice(choices: ['fr', 'nl'], message: 'Locale must be either "fr" or "nl"')]
public string $locale = 'fr',
) {}
public static function create(User $user, NotificationFlagManager $flagManager): self
{
$updateProfileCommand = new self($user->getPhonenumber());
$updateProfileCommand = new self($user->getPhonenumber(), $user->getLocale());
foreach ($flagManager->getAllNotificationFlagProviders() as $provider) {
$updateProfileCommand->setNotificationFlag(

View File

@@ -18,6 +18,7 @@ final readonly class UpdateProfileCommandHandler
public function updateProfile(User $user, UpdateProfileCommand $command): void
{
$user->setPhonenumber($command->phonenumber);
$user->setLocale($command->locale);
foreach ($command->notificationFlags as $flag => $values) {
$user->setNotificationImmediately($flag, $values['immediate_email']);

View File

@@ -128,6 +128,12 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])]
private array $notificationFlags = [];
/**
* User's preferred locale.
*/
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 5, nullable: false, options: ['default' => 'fr'])]
private string $locale = 'fr';
/**
* User constructor.
*/
@@ -716,7 +722,18 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
public function getLocale(): string
{
return 'fr';
return $this->locale;
}
public function setLocale(string $locale): self
{
if (!in_array($locale, ['fr', 'nl'], true)) {
throw new \InvalidArgumentException('Locale must be either "fr" or "nl"');
}
$this->locale = $locale;
return $this;
}
#[Assert\Callback]

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserLocaleType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'choices' => [
'user.locale.choice.french' => 'fr',
'user.locale.choice.dutch' => 'nl',
],
'placeholder' => 'user.locale.placeholder',
'required' => true,
'label' => 'user.locale.label',
'help' => 'user.locale.help',
]);
}
public function getParent(): string
{
return ChoiceType::class;
}
}

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Form;
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Form\Type\NotificationFlagsType;
use Chill\MainBundle\Form\Type\UserLocaleType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -26,6 +27,7 @@ class UpdateProfileType extends AbstractType
->add('phonenumber', ChillPhoneNumberType::class, [
'required' => false,
])
->add('locale', UserLocaleType::class)
->add('notificationFlags', NotificationFlagsType::class)
;
}

View File

@@ -24,6 +24,8 @@ use Symfony\Component\Mime\Email;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
readonly class NotificationMailer
{
public function __construct(
@@ -31,6 +33,7 @@ readonly class NotificationMailer
private LoggerInterface $logger,
private MessageBusInterface $messageBus,
private TranslatorInterface $translator,
// private LocaleSwitcher $localeSwitcher,
) {}
public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void
@@ -56,7 +59,7 @@ readonly class NotificationMailer
$email
->to($dest->getEmail())
->subject('Re: '.$comment->getNotification()->getTitle())
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig')
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.md.twig')
->context([
'comment' => $comment,
'dest' => $dest,
@@ -137,13 +140,53 @@ readonly class NotificationMailer
return;
}
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
/*
$this->localeSwitcher->runWithLocale($addressee->getLocale(), function () use ($notification, $addressee) {
if ($notification->isSystem()) {
$email = new Email();
$email->text($notification->getMessage());
} else {
$email = new TemplatedEmail();
$email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.md.twig')
->context([
'notification' => $notification,
'dest' => $addressee,
]);
}
$email
->subject($notification->getTitle())
->to($addressee->getEmail());
try {
$this->mailer->send($email);
$this->logger->info('[NotificationMailer] Email sent successfully', [
'notification_id' => $notification->getId(),
'addressee_email' => $addressee->getEmail(),
'locale' => $addressee->getLocale(),
]);
} catch (TransportExceptionInterface $e) {
$this->logger->warning('[NotificationMailer] Could not send an email notification', [
'to' => $addressee->getEmail(),
'notification_id' => $notification->getId(),
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
]);
throw $e;
}
});
*/
// Current implementation:
if ($notification->isSystem()) {
$email = new Email();
$email->text($notification->getMessage());
} else {
$email = new TemplatedEmail();
$email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.fr.md.twig')
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.md.twig')
->context([
'notification' => $notification,
'dest' => $addressee,
@@ -182,9 +225,43 @@ readonly class NotificationMailer
return;
}
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
/*
$this->localeSwitcher->runWithLocale($user->getLocale(), function () use ($user, $notifications) {
$email = new TemplatedEmail();
$email
->htmlTemplate('@ChillMain/Notification/email_daily_digest.md.twig')
->context([
'user' => $user,
'notifications' => $notifications,
'notification_count' => count($notifications),
])
->subject($this->translator->trans('notification.Daily Notification Digest'))
->to($user->getEmail());
try {
$this->mailer->send($email);
$this->logger->info('[NotificationMailer] Daily digest email sent successfully', [
'user_email' => $user->getEmail(),
'notification_count' => count($notifications),
'locale' => $user->getLocale(),
]);
} catch (TransportExceptionInterface $e) {
$this->logger->warning('[NotificationMailer] Could not send daily digest email', [
'to' => $user->getEmail(),
'notification_count' => count($notifications),
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
]);
throw $e;
}
});
*/
// Current implementation:
$email = new TemplatedEmail();
$email
->htmlTemplate('@ChillMain/Notification/email_daily_digest.fr.md.twig')
->htmlTemplate('@ChillMain/Notification/email_daily_digest.md.twig')
->context([
'user' => $user,
'notifications' => $notifications,
@@ -222,7 +299,7 @@ readonly class NotificationMailer
$email = new TemplatedEmail();
$email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.fr.md.twig')
->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.md.twig')
->context([
'notification' => $notification,
'dest' => $emailAddress,

View File

@@ -14,7 +14,7 @@
Vous pouvez visualiser la notification et y répondre ici:
{{ absolute_url(path('chill_main_notification_show', {'_locale': 'fr', 'id': notification.id }, false)) }}
{{ absolute_url(path('chill_main_notification_show', {'_locale': dest.locale, 'id': notification.id }, false)) }}
--
Le logiciel Chill

View File

@@ -13,7 +13,7 @@ Commentaire:
Vous pouvez visualiser la notification et y répondre ici:
{{ absolute_url(path('chill_main_notification_show', {'_locale': 'fr', 'id': comment.notification.id }, false)) }}
{{ absolute_url(path('chill_main_notification_show', {'_locale': dest.locale, 'id': comment.notification.id }, false)) }}
--
Le logiciel Chill

View File

@@ -71,6 +71,8 @@
</tbody>
</table>
{{ form_row(form.locale) }}
<ul class="record_actions">
<li>
<button type="submit" class="btn btn-save">{{ 'Save'|trans }}</button>

View File

@@ -1,16 +1,16 @@
{{ dest.label }},
Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ workflow.text }}
{{ 'workflow.notification.content.new_step_reached'|trans({'%workflow%': workflow.text}) }}
Titre du workflow: "{{ title }}".
{{ 'workflow.notification.content.workflow_title'|trans({'%title%': title}) }}
{% if is_dest %}
Vous êtes invités à valider cette étape au plus tôt.
{{ 'workflow.notification.content.validation_needed'|trans }}
{% endif %}
Vous pouvez visualiser le workflow sur cette page:
{{ 'workflow.notification.content.view_workflow'|trans }}
{{ absolute_url(path('chill_main_workflow_show', {'id': entity_workflow.id, '_locale': 'fr'})) }}
{{ absolute_url(path('chill_main_workflow_show', {'id': entity_workflow.id, '_locale': dest.locale|default('fr')})) }}
Cordialement,
{{ 'workflow.notification.content.regards'|trans }}

View File

@@ -1,5 +1,5 @@
{%- if is_dest -%}
Un suivi {{ workflow.text }} demande votre attention: {{ title }}
{{ 'workflow.notification.title.attention_needed'|trans({'%workflow%': workflow.text, '%title%': title}) }}
{%- else -%}
Un suivi {{ workflow.text }} a atteint une nouvelle étape: {{ place.text }}: {{ title }}
{{ 'workflow.notification.title.new_step'|trans({'%workflow%': workflow.text, '%place%': place.text, '%title%': title}) }}
{%- endif -%}

View File

@@ -16,11 +16,13 @@ use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
// use Symfony\Component\Translation\LocaleSwitcher;
class RecoverPasswordHelper
{
final public const RECOVER_PASSWORD_ROUTE = 'password_recover';
public function __construct(private readonly TokenManager $tokenManager, private readonly UrlGeneratorInterface $urlGenerator, private readonly MailerInterface $mailer) {}
public function __construct(private readonly TokenManager $tokenManager, private readonly UrlGeneratorInterface $urlGenerator, private readonly MailerInterface $mailer/* , private readonly LocaleSwitcher $localeSwitcher */) {}
/**
* @param bool $absolute
@@ -53,6 +55,24 @@ class RecoverPasswordHelper
throw new \UnexpectedValueException('No emaail associated to the user');
}
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
/*
$this->localeSwitcher->runWithLocale($user->getLocale(), function () use ($user, $expiration, $template, $templateParameters, $emailSubject, $additionalUrlParameters) {
$email = (new TemplatedEmail())
->subject($emailSubject)
->to($user->getEmail())
->textTemplate($template)
->context([
'user' => $user,
'url' => $this->generateUrl($user, $expiration, true, $additionalUrlParameters),
...$templateParameters,
]);
$this->mailer->send($email);
});
*/
// Current implementation:
$email = (new TemplatedEmail())
->subject($emailSubject)
->to($user->getEmail())

View File

@@ -22,6 +22,8 @@ use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Workflow\Event\Event;
use Symfony\Component\Workflow\Registry;
// use Symfony\Component\Translation\LocaleSwitcher;
final readonly class NotificationToUserGroupsOnTransition implements EventSubscriberInterface
{
public function __construct(
@@ -31,6 +33,7 @@ final readonly class NotificationToUserGroupsOnTransition implements EventSubscr
private MailerInterface $mailer,
private EntityManagerInterface $entityManager,
private EntityWorkflowManager $entityWorkflowManager,
// private LocaleSwitcher $localeSwitcher,
) {}
public static function getSubscribedEvents(): array
@@ -87,6 +90,24 @@ final readonly class NotificationToUserGroupsOnTransition implements EventSubscr
'title' => $title,
];
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
// Note: This sends emails to user groups, not individual users, so locale switching may use default locale
/*
$this->localeSwitcher->runWithLocale('fr', function () use ($context, $userGroup) {
$email = new TemplatedEmail();
$email
->htmlTemplate('@ChillMain/Workflow/workflow_notification_on_transition_completed_content_to_user_group.fr.txt.twig')
->context($context)
->subject(
$this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig', $context)
)
->to($userGroup->getEmail());
$this->mailer->send($email);
});
*/
// Current implementation:
$email = new TemplatedEmail();
$email
->htmlTemplate('@ChillMain/Workflow/workflow_notification_on_transition_completed_content_to_user_group.fr.txt.twig')

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251022140718 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add locale field to users table for user language preferences';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD locale VARCHAR(5) DEFAULT \'fr\' NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE users DROP locale');
}
}

View File

@@ -56,6 +56,13 @@ user:
no job: Pas de métier assigné
no scope: Pas de cercle assigné
notification_preferences: Préférences pour mes notifications
locale:
label: Langue de communication
help: Langue utilisée pour les notifications par email et autres communications.
placeholder: Choisissez une langue
choice:
french: Français
dutch: Nederlands
user_group:
inactive: Inactif
@@ -668,6 +675,17 @@ workflow:
reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer%
waiting_for: En attente de modification de l'état de la signature
notification:
title:
attention_needed: "Attention requise dans le workflow %workflow% pour %title%"
new_step: "Nouvelle étape dans le workflow %workflow% (%place%) pour %title%"
content:
new_step_reached: "Une nouvelle étape a été atteinte dans le workflow %workflow%."
workflow_title: "Titre du workflow : %title%"
validation_needed: "Votre validation est nécessaire pour cette étape."
view_workflow: "Vous pouvez consulter le workflow ici :"
regards: "Cordialement,"
attachments:
title: Pièces jointes
@@ -745,7 +763,22 @@ notification:
greeting: "Bonjour %user%"
intro: "Vous avez reçu %notification_count% nouvelle(s) notification(s)."
view_notification: "Vous pouvez visualiser la notification et y répondre ici:"
signature: "Le logiciel Chill"
signature: "L'équipe Chill"
daily_notifications: "{1}Vous avez 1 nouvelle notification.|]1,Inf[Vous avez %notification_count% nouvelles notifications."
docgen:
failure_email:
"The generation of a document failed": "La génération d'un document a échoué"
"The generation of the document %template_name% failed": "La génération du document %template_name% a échoué"
"Forward this email to your administrator for solving": "Transmettez cet email à votre administrateur pour résolution"
"References": "Références"
"The following errors were encoutered": "Les erreurs suivantes ont été rencontrées"
data_dump_email:
subject: "Export de données disponible"
"Dear": "Cher utilisateur,"
"data_dump_ready_and_attached": "Votre export de données est prêt et joint à cet email."
"filename": "Nom du fichier : %filename%"
CHILL_MAIN_COMPOSE_EXPORT: Exécuter des exports et les sauvegarder
CHILL_MAIN_GENERATE_SAVED_EXPORT: Exécuter et modifier des exports préalablement sauvegardés

View File

@@ -46,6 +46,14 @@ No title: Geen titel
User profile: Mijn gebruikersprofiel
Phonenumber successfully updated!: Telefoonnummer bijgewerkt!
user:
locale:
label: Communicatietaal
help: Taal gebruikt voor e-mailmeldingen en andere communicatie.
placeholder: Kies een taal
choice:
french: Français
dutch: Nederlands
Edit: Bewerken
Update: Updaten
@@ -423,6 +431,17 @@ workflow:
For: Pour
Cc: Cc
notification:
title:
attention_needed: "Aandacht vereist in workflow %workflow% voor %title%"
new_step: "Nieuwe stap in workflow %workflow% (%place%) voor %title%"
content:
new_step_reached: "Een nieuwe stap is bereikt in workflow %workflow%."
workflow_title: "Workflow titel: %title%"
validation_needed: "Uw validatie is nodig voor deze stap."
view_workflow: "U kunt de workflow hier bekijken:"
regards: "Met vriendelijke groeten,"
Subscribe final: Recevoir une notification à l'étape finale
Subscribe all steps: Recevoir une notification à chaque étape