Browse Source

Add email notifications language localization feature

Omar Abid 2 weeks ago
parent
commit
2ab9bd3172
6 changed files with 143 additions and 119 deletions
  1. 9 20
      models/settings.js
  2. 45 56
      models/users.js
  3. 58 0
      server/lib/emailLocalization.js
  4. 4 0
      server/lib/importer.js
  5. 7 25
      server/notifications/email.js
  6. 20 18
      server/rulesHelper.js

+ 9 - 20
models/settings.js

@@ -275,22 +275,18 @@ if (Meteor.isServer) {
         url: FlowRouter.url('sign-up'),
       };
       const lang = author.getLanguage();
-/*
-      if (process.env.MAIL_SERVICE !== '') {
-        let transporter = nodemailer.createTransport({
-          service: process.env.MAIL_SERVICE,
-          auth: {
-            user: process.env.MAIL_SERVICE_USER,
-            pass: process.env.MAIL_SERVICE_PASSWORD
-          },
-        })
-        let info = transporter.sendMail({
+      // Use EmailLocalization utility to handle email in the proper language
+      if (typeof EmailLocalization !== 'undefined') {
+        EmailLocalization.sendEmail({
           to: icode.email,
           from: Accounts.emailTemplates.from,
-          subject: TAPi18n.__('email-invite-register-subject', params, lang),
-          text: TAPi18n.__('email-invite-register-text', params, lang),
-        })
+          subject: 'email-invite-register-subject',
+          text: 'email-invite-register-text',
+          params: params,
+          language: lang
+        });
       } else {
+        // Fallback if EmailLocalization is not available
         Email.send({
           to: icode.email,
           from: Accounts.emailTemplates.from,
@@ -298,13 +294,6 @@ if (Meteor.isServer) {
           text: TAPi18n.__('email-invite-register-text', params, lang),
         });
       }
-*/
-      Email.send({
-        to: icode.email,
-        from: Accounts.emailTemplates.from,
-        subject: TAPi18n.__('email-invite-register-subject', params, lang),
-        text: TAPi18n.__('email-invite-register-text', params, lang),
-      });
     } catch (e) {
       InvitationCodes.remove(_id);
       throw new Meteor.Error('email-fail', e.message);

+ 45 - 56
models/users.js

@@ -1618,62 +1618,51 @@ if (Meteor.isServer) {
           subBoard.addMember(user._id);
           user.addInvite(subBoard._id);
         }
-      }
-
-      try {
-        const fullName =
-          inviter.profile !== undefined &&
-            inviter.profile.fullname !== undefined
-            ? inviter.profile.fullname
-            : '';
-        const userFullName =
-          user.profile !== undefined && user.profile.fullname !== undefined
-            ? user.profile.fullname
-            : '';
-        const params = {
-          user:
-            userFullName != ''
-              ? userFullName + ' (' + user.username + ' )'
-              : user.username,
-          inviter:
-            fullName != ''
-              ? fullName + ' (' + inviter.username + ' )'
-              : inviter.username,
-          board: board.title,
-          url: board.absoluteUrl(),
-        };
-        const lang = user.getLanguage();
-
-        /*
-        if (process.env.MAIL_SERVICE !== '') {
-          let transporter = nodemailer.createTransport({
-            service: process.env.MAIL_SERVICE,
-            auth: {
-              user: process.env.MAIL_SERVICE_USER,
-              pass: process.env.MAIL_SERVICE_PASSWORD
-            },
-          })
-          let info = transporter.sendMail({
-            to: user.emails[0].address.toLowerCase(),
-            from: Accounts.emailTemplates.from,
-            subject: TAPi18n.__('email-invite-subject', params, lang),
-            text: TAPi18n.__('email-invite-text', params, lang),
-          })
-        } else {
-          Email.send({
-            to: user.emails[0].address.toLowerCase(),
-            from: Accounts.emailTemplates.from,
-            subject: TAPi18n.__('email-invite-subject', params, lang),
-            text: TAPi18n.__('email-invite-text', params, lang),
-          });
-        }
-*/
-        Email.send({
-          to: user.emails[0].address.toLowerCase(),
-          from: Accounts.emailTemplates.from,
-          subject: TAPi18n.__('email-invite-subject', params, lang),
-          text: TAPi18n.__('email-invite-text', params, lang),
-        });
+      }        try {
+          const fullName =
+            inviter.profile !== undefined &&
+              inviter.profile.fullname !== undefined
+              ? inviter.profile.fullname
+              : '';
+          const userFullName =
+            user.profile !== undefined && user.profile.fullname !== undefined
+              ? user.profile.fullname
+              : '';
+          const params = {
+            user:
+              userFullName != ''
+                ? userFullName + ' (' + user.username + ' )'
+                : user.username,
+            inviter:
+              fullName != ''
+                ? fullName + ' (' + inviter.username + ' )'
+                : inviter.username,
+            board: board.title,
+            url: board.absoluteUrl(),
+          };
+          // Get the recipient user's language preference for the email
+          const lang = user.getLanguage();
+
+          // Add code to send invitation with EmailLocalization
+          if (typeof EmailLocalization !== 'undefined') {
+            EmailLocalization.sendEmail({
+              to: user.emails[0].address,
+              from: Accounts.emailTemplates.from,
+              subject: 'email-invite-subject',
+              text: 'email-invite-text',
+              params: params,
+              language: lang,
+              userId: user._id
+            });
+          } else {
+            // Fallback if EmailLocalization is not available
+            Email.send({
+              to: user.emails[0].address,
+              from: Accounts.emailTemplates.from,
+              subject: TAPi18n.__('email-invite-subject', params, lang),
+              text: TAPi18n.__('email-invite-text', params, lang),
+            });
+          }
       } catch (e) {
         throw new Meteor.Error('email-fail', e.message);
       }

+ 58 - 0
server/lib/emailLocalization.js

@@ -0,0 +1,58 @@
+// emailLocalization.js
+// Utility functions to handle email localization in Wekan
+
+import { TAPi18n } from '/imports/i18n';
+import { ReactiveCache } from '/imports/reactiveCache';
+
+// Main object for email localization utilities
+EmailLocalization = {
+  /**
+   * Send an email using the recipient's preferred language
+   * @param {Object} options - Standard email sending options plus language options
+   * @param {String} options.to - Recipient email address
+   * @param {String} options.from - Sender email address
+   * @param {String} options.subject - Email subject i18n key
+   * @param {String} options.text - Email text i18n key
+   * @param {Object} options.params - Parameters for i18n translation
+   * @param {String} options.language - Language code to use (if not provided, will try to detect)
+   * @param {String} options.userId - User ID to determine language (if not provided with language)
+   */
+  sendEmail(options) {
+    // Determine the language to use
+    let lang = options.language;
+
+    // If no language is specified but we have a userId, try to get the user's language
+    if (!lang && options.userId) {
+      const user = ReactiveCache.getUser(options.userId);
+      if (user) {
+        lang = user.getLanguage();
+      }
+    }
+
+    // If no language could be determined, use the site default
+    if (!lang) {
+      lang = TAPi18n.getLanguage() || 'en';
+    }
+
+    // Translate subject and text using the determined language
+    const subject = TAPi18n.__(options.subject, options.params || {}, lang);
+    let text = options.text;
+
+    // If text is an i18n key, translate it
+    if (typeof text === 'string' && text.startsWith('email-')) {
+      text = TAPi18n.__(text, options.params || {}, lang);
+    }
+
+    // Send the email with translated content
+    return Email.send({
+      to: options.to,
+      from: options.from || Accounts.emailTemplates.from,
+      subject: subject,
+      text: text,
+      html: options.html
+    });
+  }
+};
+
+// Add module.exports to make it accessible from other files
+module.exports = EmailLocalization;

+ 4 - 0
server/lib/importer.js

@@ -0,0 +1,4 @@
+// This file ensures the EmailLocalization utility is imported
+// and available throughout the application
+
+import './emailLocalization';

+ 7 - 25
server/notifications/email.js

@@ -2,6 +2,8 @@ import { ReactiveCache } from '/imports/reactiveCache';
 import { TAPi18n } from '/imports/i18n';
 //var nodemailer = require('nodemailer');
 
+import EmailLocalization from '../lib/emailLocalization';
+
 // buffer each user's email text in a queue, then flush them in single email
 Meteor.startup(() => {
   Notifications.subscribe('email', (user, title, description, params) => {
@@ -14,6 +16,7 @@ Meteor.startup(() => {
       quoteParams[key] = quoteParams[key] ? `${params[key]}` : '';
     });
 
+    // Get user's preferred language
     const lan = user.getLanguage();
     const subject = TAPi18n.__(title, params, lan); // the original function has a fault, i believe the title should be used according to original author
     const existing = user.getEmailBuffer().length > 0;
@@ -42,35 +45,14 @@ Meteor.startup(() => {
       const html = texts.join('<br/>\n\n');
       user.clearEmailBuffer();
       try {
-/*
-        if (process.env.MAIL_SERVICE !== '') {
-          let transporter = nodemailer.createTransport({
-            service: process.env.MAIL_SERVICE,
-            auth: {
-              user: process.env.MAIL_SERVICE_USER,
-              pass: process.env.MAIL_SERVICE_PASSWORD
-            },
-          })
-          let info = transporter.sendMail({
-            to: user.emails[0].address.toLowerCase(),
-            from: Accounts.emailTemplates.from,
-            subject,
-            html,
-          })
-        } else {
-          Email.send({
-            to: user.emails[0].address.toLowerCase(),
-            from: Accounts.emailTemplates.from,
-            subject,
-            html,
-          });
-        }
-*/
-        Email.send({
+        // Use EmailLocalization utility to ensure the correct language is used
+        EmailLocalization.sendEmail({
           to: user.emails[0].address.toLowerCase(),
           from: Accounts.emailTemplates.from,
           subject,
           html,
+          language: user.getLanguage(),
+          userId: user._id
         });
       } catch (e) {
         return;

+ 20 - 18
server/rulesHelper.js

@@ -125,22 +125,31 @@ RulesHelper = {
       const text = action.emailMsg || '';
       const subject = action.emailSubject || '';
       try {
-/*
-        if (process.env.MAIL_SERVICE !== '') {
-          let transporter = nodemailer.createTransport({
-            service: process.env.MAIL_SERVICE,
-            auth: {
-              user: process.env.MAIL_SERVICE_USER,
-              pass: process.env.MAIL_SERVICE_PASSWORD
-            },
-          })
-          let info = transporter.sendMail({
+        // Try to detect the recipient's language preference if it's a Wekan user
+        // Otherwise, use the default language for the rule-triggered emails
+        let recipientUser = null;
+        let recipientLang = TAPi18n.getLanguage() || 'en';
+
+        // Check if recipient is a Wekan user to get their language
+        if (to && to.includes('@')) {
+          recipientUser = ReactiveCache.getUser({ 'emails.address': to.toLowerCase() });
+          if (recipientUser && typeof recipientUser.getLanguage === 'function') {
+            recipientLang = recipientUser.getLanguage();
+          }
+        }
+
+        // Use EmailLocalization if available
+        if (typeof EmailLocalization !== 'undefined') {
+          EmailLocalization.sendEmail({
             to,
             from: Accounts.emailTemplates.from,
             subject,
             text,
-          })
+            language: recipientLang,
+            userId: recipientUser ? recipientUser._id : null
+          });
         } else {
+          // Fallback to standard Email.send
           Email.send({
             to,
             from: Accounts.emailTemplates.from,
@@ -148,13 +157,6 @@ RulesHelper = {
             text,
           });
         }
-*/
-        Email.send({
-          to,
-          from: Accounts.emailTemplates.from,
-          subject,
-          text,
-        });
       } catch (e) {
         // eslint-disable-next-line no-console
         console.error(e);