mirror of
https://github.com/wekan/wekan.git
synced 2025-09-22 01:50:48 +02:00
Add email notifications language localization feature
This commit is contained in:
parent
a0b5c2e88c
commit
2ab9bd3172
6 changed files with 142 additions and 118 deletions
|
@ -275,22 +275,18 @@ if (Meteor.isServer) {
|
||||||
url: FlowRouter.url('sign-up'),
|
url: FlowRouter.url('sign-up'),
|
||||||
};
|
};
|
||||||
const lang = author.getLanguage();
|
const lang = author.getLanguage();
|
||||||
/*
|
// Use EmailLocalization utility to handle email in the proper language
|
||||||
if (process.env.MAIL_SERVICE !== '') {
|
if (typeof EmailLocalization !== 'undefined') {
|
||||||
let transporter = nodemailer.createTransport({
|
EmailLocalization.sendEmail({
|
||||||
service: process.env.MAIL_SERVICE,
|
|
||||||
auth: {
|
|
||||||
user: process.env.MAIL_SERVICE_USER,
|
|
||||||
pass: process.env.MAIL_SERVICE_PASSWORD
|
|
||||||
},
|
|
||||||
})
|
|
||||||
let info = transporter.sendMail({
|
|
||||||
to: icode.email,
|
to: icode.email,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject: TAPi18n.__('email-invite-register-subject', params, lang),
|
subject: 'email-invite-register-subject',
|
||||||
text: TAPi18n.__('email-invite-register-text', params, lang),
|
text: 'email-invite-register-text',
|
||||||
})
|
params: params,
|
||||||
|
language: lang
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Fallback if EmailLocalization is not available
|
||||||
Email.send({
|
Email.send({
|
||||||
to: icode.email,
|
to: icode.email,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
|
@ -298,13 +294,6 @@ if (Meteor.isServer) {
|
||||||
text: TAPi18n.__('email-invite-register-text', params, lang),
|
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) {
|
} catch (e) {
|
||||||
InvitationCodes.remove(_id);
|
InvitationCodes.remove(_id);
|
||||||
throw new Meteor.Error('email-fail', e.message);
|
throw new Meteor.Error('email-fail', e.message);
|
||||||
|
|
|
@ -1618,62 +1618,51 @@ if (Meteor.isServer) {
|
||||||
subBoard.addMember(user._id);
|
subBoard.addMember(user._id);
|
||||||
user.addInvite(subBoard._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(),
|
||||||
|
};
|
||||||
|
// Get the recipient user's language preference for the email
|
||||||
|
const lang = user.getLanguage();
|
||||||
|
|
||||||
try {
|
// Add code to send invitation with EmailLocalization
|
||||||
const fullName =
|
if (typeof EmailLocalization !== 'undefined') {
|
||||||
inviter.profile !== undefined &&
|
EmailLocalization.sendEmail({
|
||||||
inviter.profile.fullname !== undefined
|
to: user.emails[0].address,
|
||||||
? inviter.profile.fullname
|
from: Accounts.emailTemplates.from,
|
||||||
: '';
|
subject: 'email-invite-subject',
|
||||||
const userFullName =
|
text: 'email-invite-text',
|
||||||
user.profile !== undefined && user.profile.fullname !== undefined
|
params: params,
|
||||||
? user.profile.fullname
|
language: lang,
|
||||||
: '';
|
userId: user._id
|
||||||
const params = {
|
});
|
||||||
user:
|
} else {
|
||||||
userFullName != ''
|
// Fallback if EmailLocalization is not available
|
||||||
? userFullName + ' (' + user.username + ' )'
|
Email.send({
|
||||||
: user.username,
|
to: user.emails[0].address,
|
||||||
inviter:
|
from: Accounts.emailTemplates.from,
|
||||||
fullName != ''
|
subject: TAPi18n.__('email-invite-subject', params, lang),
|
||||||
? fullName + ' (' + inviter.username + ' )'
|
text: TAPi18n.__('email-invite-text', params, lang),
|
||||||
: 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),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Meteor.Error('email-fail', e.message);
|
throw new Meteor.Error('email-fail', e.message);
|
||||||
}
|
}
|
||||||
|
|
58
server/lib/emailLocalization.js
Normal file
58
server/lib/emailLocalization.js
Normal file
|
@ -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
server/lib/importer.js
Normal file
4
server/lib/importer.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// This file ensures the EmailLocalization utility is imported
|
||||||
|
// and available throughout the application
|
||||||
|
|
||||||
|
import './emailLocalization';
|
|
@ -2,6 +2,8 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { TAPi18n } from '/imports/i18n';
|
import { TAPi18n } from '/imports/i18n';
|
||||||
//var nodemailer = require('nodemailer');
|
//var nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
import EmailLocalization from '../lib/emailLocalization';
|
||||||
|
|
||||||
// buffer each user's email text in a queue, then flush them in single email
|
// buffer each user's email text in a queue, then flush them in single email
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
Notifications.subscribe('email', (user, title, description, params) => {
|
Notifications.subscribe('email', (user, title, description, params) => {
|
||||||
|
@ -14,6 +16,7 @@ Meteor.startup(() => {
|
||||||
quoteParams[key] = quoteParams[key] ? `${params[key]}` : '';
|
quoteParams[key] = quoteParams[key] ? `${params[key]}` : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get user's preferred language
|
||||||
const lan = user.getLanguage();
|
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 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;
|
const existing = user.getEmailBuffer().length > 0;
|
||||||
|
@ -42,35 +45,14 @@ Meteor.startup(() => {
|
||||||
const html = texts.join('<br/>\n\n');
|
const html = texts.join('<br/>\n\n');
|
||||||
user.clearEmailBuffer();
|
user.clearEmailBuffer();
|
||||||
try {
|
try {
|
||||||
/*
|
// Use EmailLocalization utility to ensure the correct language is used
|
||||||
if (process.env.MAIL_SERVICE !== '') {
|
EmailLocalization.sendEmail({
|
||||||
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({
|
|
||||||
to: user.emails[0].address.toLowerCase(),
|
to: user.emails[0].address.toLowerCase(),
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject,
|
subject,
|
||||||
html,
|
html,
|
||||||
|
language: user.getLanguage(),
|
||||||
|
userId: user._id
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -125,22 +125,31 @@ RulesHelper = {
|
||||||
const text = action.emailMsg || '';
|
const text = action.emailMsg || '';
|
||||||
const subject = action.emailSubject || '';
|
const subject = action.emailSubject || '';
|
||||||
try {
|
try {
|
||||||
/*
|
// Try to detect the recipient's language preference if it's a Wekan user
|
||||||
if (process.env.MAIL_SERVICE !== '') {
|
// Otherwise, use the default language for the rule-triggered emails
|
||||||
let transporter = nodemailer.createTransport({
|
let recipientUser = null;
|
||||||
service: process.env.MAIL_SERVICE,
|
let recipientLang = TAPi18n.getLanguage() || 'en';
|
||||||
auth: {
|
|
||||||
user: process.env.MAIL_SERVICE_USER,
|
// Check if recipient is a Wekan user to get their language
|
||||||
pass: process.env.MAIL_SERVICE_PASSWORD
|
if (to && to.includes('@')) {
|
||||||
},
|
recipientUser = ReactiveCache.getUser({ 'emails.address': to.toLowerCase() });
|
||||||
})
|
if (recipientUser && typeof recipientUser.getLanguage === 'function') {
|
||||||
let info = transporter.sendMail({
|
recipientLang = recipientUser.getLanguage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use EmailLocalization if available
|
||||||
|
if (typeof EmailLocalization !== 'undefined') {
|
||||||
|
EmailLocalization.sendEmail({
|
||||||
to,
|
to,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject,
|
subject,
|
||||||
text,
|
text,
|
||||||
})
|
language: recipientLang,
|
||||||
|
userId: recipientUser ? recipientUser._id : null
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Fallback to standard Email.send
|
||||||
Email.send({
|
Email.send({
|
||||||
to,
|
to,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
|
@ -148,13 +157,6 @@ RulesHelper = {
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
Email.send({
|
|
||||||
to,
|
|
||||||
from: Accounts.emailTemplates.from,
|
|
||||||
subject,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue