[PATCH 0/6] Localize notification emails
This patch series implements FS#31850. The preparatory work is quite invasive and includes a complete rewrite of the notification module. The last patch adds the new translatable strings to our message catalog. Comments welcome! Lukas Fleischer (6): notify.py: Do not add stray newlines t2500: Add test cases for all notifications Refactor the notification script Localize notification emails Use modern format strings in notification messages Update message catalog aurweb/l10n.py | 16 + aurweb/scripts/notify.py | 880 +++++++++++++++++++++------------------ po/Makefile | 21 +- po/aur.pot | 136 +++++- test/setup.sh | 18 +- test/t2500-notify.sh | 350 +++++++++++++++- 6 files changed, 1003 insertions(+), 418 deletions(-) create mode 100644 aurweb/l10n.py -- 2.17.0
Make sure we are consistent with not adding newlines at the end of notification emails. Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> --- aurweb/scripts/notify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py index 693abff..4fed6ad 100755 --- a/aurweb/scripts/notify.py +++ b/aurweb/scripts/notify.py @@ -35,7 +35,7 @@ def send_notification(to, subject, body, refs, headers={}): if refs: body = wrapped + '\n' + refs else: - body = wrapped + body = wrapped.rstrip() for recipient in to: msg = email.mime.text.MIMEText(body, 'plain', 'utf-8') @@ -304,7 +304,7 @@ def comaintainer_add(conn, pkgbase_id, uid): subject = 'AUR Co-Maintainer Notification for %s' % (pkgbase) body = 'You were added to the co-maintainer list of %s [1].' % (pkgbase) - refs = '[1] ' + pkgbase_uri + '\n' + refs = '[1] ' + pkgbase_uri send_notification(to, subject, body, refs) @@ -318,7 +318,7 @@ def comaintainer_remove(conn, pkgbase_id, uid): subject = 'AUR Co-Maintainer Notification for %s' % (pkgbase) body = ('You were removed from the co-maintainer list of %s [1].' % (pkgbase)) - refs = '[1] ' + pkgbase_uri + '\n' + refs = '[1] ' + pkgbase_uri send_notification(to, subject, body, refs) @@ -378,7 +378,7 @@ def request_open(conn, uid, reqid, reqtype, pkgbase_id, merge_into=None): (user, reqtype, pkgbase) body += '\n\n' + text refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri + '\n' + refs += '[2] ' + pkgbase_uri thread_id = '<pkg-request-' + reqid + '@aur.archlinux.org>' # Use a deterministic Message-ID for the first email referencing a request. headers = headers_msgid(thread_id) -- 2.17.0
Check that for all kinds of notifications, the generated messages match what we expect. Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> --- test/t2500-notify.sh | 350 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 348 insertions(+), 2 deletions(-) diff --git a/test/t2500-notify.sh b/test/t2500-notify.sh index 3997682..46c2753 100755 --- a/test/t2500-notify.sh +++ b/test/t2500-notify.sh @@ -6,7 +6,7 @@ test_description='notify tests' test_expect_success 'Test out-of-date notifications.' ' cat <<-EOD | sqlite3 aur.db && - INSERT INTO PackageBases (ID, Name, MaintainerUID, SubmittedTS, ModifiedTS, FlaggerComment) VALUES (1, "foobar", 1, 0, 0, ""); + INSERT INTO PackageBases (ID, Name, MaintainerUID, SubmittedTS, ModifiedTS, FlaggerComment) VALUES (1, "foobar", 1, 0, 0, "This is a test OOD comment."); INSERT INTO PackageBases (ID, Name, MaintainerUID, SubmittedTS, ModifiedTS, FlaggerComment) VALUES (2, "foobar2", 2, 0, 0, ""); INSERT INTO PackageBases (ID, Name, MaintainerUID, SubmittedTS, ModifiedTS, FlaggerComment) VALUES (3, "foobar3", NULL, 0, 0, ""); INSERT INTO PackageBases (ID, Name, MaintainerUID, SubmittedTS, ModifiedTS, FlaggerComment) VALUES (4, "foobar4", 1, 0, 0, ""); @@ -27,7 +27,353 @@ test_expect_success 'Test out-of-date notifications.' ' To: user@localhost EOD grep "^\(Subject\|To\)" sendmail.out >sendmail.parts && - test_cmp sendmail.parts expected + test_cmp sendmail.parts expected && + cat <<-EOD | sqlite3 aur.db + DELETE FROM PackageComaintainers; + EOD +' + +test_expect_success 'Test subject and body of reset key notifications.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE Users SET ResetKey = "12345678901234567890123456789012" WHERE ID = 1; + EOD + >sendmail.out && + "$NOTIFY" send-resetkey 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Password Reset + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + A password reset request was submitted for the account user associated + with your email address. If you wish to reset your password follow the + link [1] below, otherwise ignore this message and nothing will happen. + + [1] https://aur.archlinux.org/passreset/?resetkey=123456789012345678901234567890... + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of welcome notifications.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE Users SET ResetKey = "12345678901234567890123456789012" WHERE ID = 1; + EOD + >sendmail.out && + "$NOTIFY" welcome 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: Welcome to the Arch User Repository + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + Welcome to the Arch User Repository! In order to set an initial + password for your new account, please click the link [1] below. If the + link does not work, try copying and pasting it into your browser. + + [1] https://aur.archlinux.org/passreset/?resetkey=123456789012345678901234567890... + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of comment notifications.' ' + cat <<-EOD | sqlite3 aur.db && + INSERT INTO PackageComments (ID, PackageBaseID, UsersID, Comments, RenderedComment) VALUES (1, 1, 1, "This is a test comment.", "This is a test comment."); + INSERT INTO PackageNotifications (PackageBaseID, UserID) VALUES (1, 2); + UPDATE Users SET CommentNotify = 1 WHERE ID = 2; + EOD + >sendmail.out && + "$NOTIFY" comment 1 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Comment for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] added the following comment to foobar [2]: + + This is a test comment. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of update notifications.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE Users SET UpdateNotify = 1 WHERE ID = 2; + EOD + >sendmail.out && + "$NOTIFY" update 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Package Update: foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] pushed a new commit to foobar [2]. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of out-of-date notifications.' ' + >sendmail.out && + "$NOTIFY" flag 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Out-of-date Notification for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + Your package foobar [1] has been flagged out-of-date by user [2]: + + This is a test OOD comment. + + [1] https://aur.archlinux.org/pkgbase/foobar/ + [2] https://aur.archlinux.org/account/user/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of adopt notifications.' ' + >sendmail.out && + "$NOTIFY" adopt 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Ownership Notification for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + The package foobar [1] was adopted by user [2]. + + [1] https://aur.archlinux.org/pkgbase/foobar/ + [2] https://aur.archlinux.org/account/user/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of co-maintainer addition notifications.' ' + >sendmail.out && + "$NOTIFY" comaintainer-add 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Co-Maintainer Notification for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + You were added to the co-maintainer list of foobar [1]. + + [1] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of co-maintainer removal notifications.' ' + >sendmail.out && + "$NOTIFY" comaintainer-remove 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Co-Maintainer Notification for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + You were removed from the co-maintainer list of foobar [1]. + + [1] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of delete notifications.' ' + >sendmail.out && + "$NOTIFY" delete 1 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Package deleted: foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] deleted foobar [2]. + + You will no longer receive notifications about this package. + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of merge notifications.' ' + >sendmail.out && + "$NOTIFY" delete 1 1 2 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Package deleted: foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] merged foobar [2] into foobar2 [3]. + + If you no longer wish receive notifications about the new package, + please go to [3] and click "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + [3] https://aur.archlinux.org/pkgbase/foobar2/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of request open notifications.' ' + cat <<-EOD | sqlite3 aur.db && + INSERT INTO PackageRequests (ID, PackageBaseID, PackageBaseName, UsersID, ReqTypeID, Comments, ClosureComment) VALUES (1, 1, "foobar", 1, 1, "This is a request test comment.", ""); + EOD + >sendmail.out && + "$NOTIFY" request-open 1 1 orphan 1 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: [PRQ#1] Orphan Request for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] filed a orphan request for foobar [2]: + + This is a request test comment. + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of request open notifications for merge requests.' ' + >sendmail.out && + "$NOTIFY" request-open 1 1 merge 1 foobar2 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: [PRQ#1] Merge Request for foobar + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] filed a request to merge foobar [2] into foobar2 [3]: + + This is a request test comment. + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + [3] https://aur.archlinux.org/pkgbase/foobar2/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of request close notifications.' ' + >sendmail.out && + "$NOTIFY" request-close 1 1 accepted && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: [PRQ#1] Request Accepted + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + Request #1 has been accepted by user [1]. + + [1] https://aur.archlinux.org/account/user/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of request close notifications (auto-accept).' ' + >sendmail.out && + "$NOTIFY" request-close 0 1 accepted && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: [PRQ#1] Request Accepted + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + Request #1 has been accepted automatically by the Arch User Repository + package request system. + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of request close notifications with closure comment.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE PackageRequests SET ClosureComment = "This is a test closure comment." WHERE ID = 1; + EOD + >sendmail.out && + "$NOTIFY" request-close 1 1 accepted && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: [PRQ#1] Request Accepted + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + Request #1 has been accepted by user [1]: + + This is a test closure comment. + + [1] https://aur.archlinux.org/account/user/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of TU vote reminders.' ' + >sendmail.out && + "$NOTIFY" tu-vote-reminder 1 && + grep ^Subject: sendmail.out | head -1 >actual && + cat <<-EOD >expected && + Subject: TU Vote Reminder: Proposal 1 + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | head -4 | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + Please remember to cast your vote on proposal 1 [1]. The voting period + ends in less than 48 hours. + + [1] https://aur.archlinux.org/tu/?id=1 + EOD + test_cmp actual expected ' test_done -- 2.17.0
Reimplement most of the notification script logic. Create a separate class for each notification type. Each class provides methods for generating the list of recipients, the message subject, the message body, the references to add at the end of the message and the message headers. Additionally, a method for sending notification emails is provided. One major benefit of the new implementation is that both the generation of recipients and message contents are much more flexible. For example, it is now easily possible to make user-specific adjustments to every single notification of a batch. Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> --- aurweb/scripts/notify.py | 822 ++++++++++++++++++++------------------- 1 file changed, 429 insertions(+), 393 deletions(-) diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py index 4fed6ad..a637013 100755 --- a/aurweb/scripts/notify.py +++ b/aurweb/scripts/notify.py @@ -9,11 +9,6 @@ import aurweb.config import aurweb.db aur_location = aurweb.config.get('options', 'aur_location') -aur_request_ml = aurweb.config.get('options', 'aur_request_ml') - -sendmail = aurweb.config.get('notifications', 'sendmail') -sender = aurweb.config.get('notifications', 'sender') -reply_to = aurweb.config.get('notifications', 'reply-to') def headers_cc(cclist): @@ -28,29 +23,6 @@ def headers_reply(thread_id): return {'In-Reply-To': thread_id, 'References': thread_id} -def send_notification(to, subject, body, refs, headers={}): - wrapped = '' - for line in body.splitlines(): - wrapped += textwrap.fill(line, break_long_words=False) + '\n' - if refs: - body = wrapped + '\n' + refs - else: - body = wrapped.rstrip() - - for recipient in to: - msg = email.mime.text.MIMEText(body, 'plain', 'utf-8') - msg['Subject'] = subject - msg['From'] = sender - msg['Reply-to'] = reply_to - msg['To'] = recipient - - for key, value in headers.items(): - msg[key] = value - - p = subprocess.Popen([sendmail, '-t', '-oi'], stdin=subprocess.PIPE) - p.communicate(msg.as_bytes()) - - def username_from_id(conn, uid): cur = conn.execute('SELECT UserName FROM Users WHERE ID = ?', [uid]) return cur.fetchone()[0] @@ -73,380 +45,444 @@ def get_user_email(conn, uid): return cur.fetchone()[0] -def get_flag_recipients(conn, pkgbase_id): - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + - 'LEFT JOIN PackageComaintainers ' + - 'ON PackageComaintainers.UsersID = Users.ID ' + - 'INNER JOIN PackageBases ' + - 'ON PackageBases.MaintainerUID = Users.ID OR ' + - 'PackageBases.ID = PackageComaintainers.PackageBaseID ' + - 'WHERE PackageBases.ID = ?', [pkgbase_id]) - return [row[0] for row in cur.fetchall()] - - -def get_recipients(conn, pkgbase_id, uid): - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + - 'INNER JOIN PackageNotifications ' + - 'ON PackageNotifications.UserID = Users.ID WHERE ' + - 'PackageNotifications.UserID != ? AND ' + - 'PackageNotifications.PackageBaseID = ?', - [uid, pkgbase_id]) - return [row[0] for row in cur.fetchall()] - - -def get_comment_recipients(conn, pkgbase_id, uid): - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + - 'INNER JOIN PackageNotifications ' + - 'ON PackageNotifications.UserID = Users.ID WHERE ' + - 'Users.CommentNotify = 1 AND ' + - 'PackageNotifications.UserID != ? AND ' + - 'PackageNotifications.PackageBaseID = ?', - [uid, pkgbase_id]) - return [row[0] for row in cur.fetchall()] - - -def get_update_recipients(conn, pkgbase_id, uid): - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + - 'INNER JOIN PackageNotifications ' + - 'ON PackageNotifications.UserID = Users.ID WHERE ' + - 'Users.UpdateNotify = 1 AND ' + - 'PackageNotifications.UserID != ? AND ' + - 'PackageNotifications.PackageBaseID = ?', - [uid, pkgbase_id]) - return [row[0] for row in cur.fetchall()] - - -def get_ownership_recipients(conn, pkgbase_id, uid): - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + - 'INNER JOIN PackageNotifications ' + - 'ON PackageNotifications.UserID = Users.ID WHERE ' + - 'Users.OwnershipNotify = 1 AND ' + - 'PackageNotifications.UserID != ? AND ' + - 'PackageNotifications.PackageBaseID = ?', - [uid, pkgbase_id]) - return [row[0] for row in cur.fetchall()] - - -def get_request_recipients(conn, reqid): - cur = conn.execute('SELECT DISTINCT Users.Email FROM PackageRequests ' + - 'INNER JOIN PackageBases ' + - 'ON PackageBases.ID = PackageRequests.PackageBaseID ' + - 'INNER JOIN Users ' + - 'ON Users.ID = PackageRequests.UsersID ' + - 'OR Users.ID = PackageBases.MaintainerUID ' + - 'WHERE PackageRequests.ID = ?', [reqid]) - return [row[0] for row in cur.fetchall()] - - -def get_tu_vote_reminder_recipients(conn, vote_id): - cur = conn.execute('SELECT Email FROM Users ' + - 'WHERE AccountTypeID IN (2, 4) AND ID NOT IN ' + - '(SELECT UserID FROM TU_Votes ' + - 'WHERE TU_Votes.VoteID = ?)', [vote_id]) - return [row[0] for row in cur.fetchall()] - - -def get_comment(conn, comment_id): - cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?', - [comment_id]) - return cur.fetchone()[0] - - -def get_flagger_comment(conn, pkgbase_id): - cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ID = ?', - [pkgbase_id]) - return cur.fetchone()[0] - - -def get_request_comment(conn, reqid): - cur = conn.execute('SELECT Comments FROM PackageRequests WHERE ID = ?', - [reqid]) - return cur.fetchone()[0] - - -def get_request_closure_comment(conn, reqid): - cur = conn.execute('SELECT ClosureComment FROM PackageRequests ' + - 'WHERE ID = ?', [reqid]) - return cur.fetchone()[0] - - -def send_resetkey(conn, uid): - cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' + - 'WHERE ID = ?', [uid]) - username, to, resetkey = cur.fetchone() - - subject = 'AUR Password Reset' - body = 'A password reset request was submitted for the account %s ' \ - 'associated with your email address. If you wish to reset your ' \ - 'password follow the link [1] below, otherwise ignore this ' \ - 'message and nothing will happen.' % (username) - refs = '[1] ' + aur_location + '/passreset/?resetkey=' + resetkey - - send_notification([to], subject, body, refs) - - -def welcome(conn, uid): - cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' + - 'WHERE ID = ?', [uid]) - username, to, resetkey = cur.fetchone() - - subject = 'Welcome to the Arch User Repository' - body = 'Welcome to the Arch User Repository! In order to set an initial ' \ - 'password for your new account, please click the link [1] below. ' \ - 'If the link does not work, try copying and pasting it into your ' \ - 'browser.' - refs = '[1] ' + aur_location + '/passreset/?resetkey=' + resetkey - - send_notification([to], subject, body, refs) - - -def comment(conn, uid, pkgbase_id, comment_id): - user = username_from_id(conn, uid) - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = get_comment_recipients(conn, pkgbase_id, uid) - text = get_comment(conn, comment_id) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Comment for %s' % (pkgbase) - body = '%s [1] added the following comment to %s [2]:' % (user, pkgbase) - body += '\n\n' + text + '\n\n' - body += 'If you no longer wish to receive notifications about this ' \ - 'package, please go to the package page [2] and select "%s".' % \ - ('Disable notifications') - refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri - thread_id = '<pkg-notifications-' + pkgbase + '@aur.archlinux.org>' - headers = headers_reply(thread_id) - - send_notification(to, subject, body, refs, headers) - - -def update(conn, uid, pkgbase_id): - user = username_from_id(conn, uid) - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = get_update_recipients(conn, pkgbase_id, uid) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Package Update: %s' % (pkgbase) - body = '%s [1] pushed a new commit to %s [2].' % (user, pkgbase) - body += '\n\n' - body += 'If you no longer wish to receive notifications about this ' \ - 'package, please go to the package page [2] and select "%s".' % \ - ('Disable notifications') - refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri - thread_id = '<pkg-notifications-' + pkgbase + '@aur.archlinux.org>' - headers = headers_reply(thread_id) - - send_notification(to, subject, body, refs, headers) - - -def flag(conn, uid, pkgbase_id): - user = username_from_id(conn, uid) - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = get_flag_recipients(conn, pkgbase_id) - text = get_flagger_comment(conn, pkgbase_id) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Out-of-date Notification for %s' % (pkgbase) - body = 'Your package %s [1] has been flagged out-of-date by %s [2]:' % \ - (pkgbase, user) - body += '\n\n' + text - refs = '[1] ' + pkgbase_uri + '\n' - refs += '[2] ' + user_uri - - send_notification(to, subject, body, refs) - - -def adopt(conn, pkgbase_id, uid): - user = username_from_id(conn, uid) - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = get_ownership_recipients(conn, pkgbase_id, uid) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Ownership Notification for %s' % (pkgbase) - body = 'The package %s [1] was adopted by %s [2].' % (pkgbase, user) - refs = '[1] ' + pkgbase_uri + '\n' - refs += '[2] ' + user_uri - - send_notification(to, subject, body, refs) - - -def disown(conn, pkgbase_id, uid): - user = username_from_id(conn, uid) - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = get_ownership_recipients(conn, pkgbase_id, uid) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Ownership Notification for %s' % (pkgbase) - body = 'The package %s [1] was disowned by %s [2].' % (pkgbase, user) - refs = '[1] ' + pkgbase_uri + '\n' - refs += '[2] ' + user_uri - - send_notification(to, subject, body, refs) - - -def comaintainer_add(conn, pkgbase_id, uid): - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = [get_user_email(conn, uid)] - - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Co-Maintainer Notification for %s' % (pkgbase) - body = 'You were added to the co-maintainer list of %s [1].' % (pkgbase) - refs = '[1] ' + pkgbase_uri - - send_notification(to, subject, body, refs) - - -def comaintainer_remove(conn, pkgbase_id, uid): - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = [get_user_email(conn, uid)] - - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = 'AUR Co-Maintainer Notification for %s' % (pkgbase) - body = ('You were removed from the co-maintainer list of %s [1].' % - (pkgbase)) - refs = '[1] ' + pkgbase_uri - - send_notification(to, subject, body, refs) - - -def delete(conn, uid, old_pkgbase_id, new_pkgbase_id=None): - user = username_from_id(conn, uid) - old_pkgbase = pkgbase_from_id(conn, old_pkgbase_id) - if new_pkgbase_id: - new_pkgbase = pkgbase_from_id(conn, new_pkgbase_id) - to = get_recipients(conn, old_pkgbase_id, uid) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + old_pkgbase + '/' - - subject = 'AUR Package deleted: %s' % (old_pkgbase) - if new_pkgbase_id: - new_pkgbase_uri = aur_location + '/pkgbase/' + new_pkgbase + '/' - body = '%s [1] merged %s [2] into %s [3].\n\n' \ - 'If you no longer wish receive notifications about the new ' \ - 'package, please go to [3] and click "%s".' %\ - (user, old_pkgbase, new_pkgbase, 'Disable notifications') - refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri + '\n' - refs += '[3] ' + new_pkgbase_uri - else: - body = '%s [1] deleted %s [2].\n\n' \ - 'You will no longer receive notifications about this ' \ - 'package.' % (user, old_pkgbase) - refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri - - send_notification(to, subject, body, refs) - - -def request_open(conn, uid, reqid, reqtype, pkgbase_id, merge_into=None): - user = username_from_id(conn, uid) - pkgbase = pkgbase_from_id(conn, pkgbase_id) - to = [aur_request_ml] - cc = get_request_recipients(conn, reqid) - text = get_request_comment(conn, reqid) - - user_uri = aur_location + '/account/' + user + '/' - pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' - - subject = '[PRQ#%d] %s Request for %s' % \ - (int(reqid), reqtype.title(), pkgbase) - if merge_into: - merge_into_uri = aur_location + '/pkgbase/' + merge_into + '/' - body = '%s [1] filed a request to merge %s [2] into %s [3]:' % \ - (user, pkgbase, merge_into) - body += '\n\n' + text - refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri + '\n' - refs += '[3] ' + merge_into_uri - else: - body = '%s [1] filed a %s request for %s [2]:' % \ - (user, reqtype, pkgbase) - body += '\n\n' + text - refs = '[1] ' + user_uri + '\n' - refs += '[2] ' + pkgbase_uri - thread_id = '<pkg-request-' + reqid + '@aur.archlinux.org>' - # Use a deterministic Message-ID for the first email referencing a request. - headers = headers_msgid(thread_id) - headers.update(headers_cc(cc)) - - send_notification(to, subject, body, refs, headers) - - -def request_close(conn, uid, reqid, reason): - to = [aur_request_ml] - cc = get_request_recipients(conn, reqid) - text = get_request_closure_comment(conn, reqid) - - subject = '[PRQ#%d] Request %s' % (int(reqid), reason.title()) - if int(uid): - user = username_from_id(conn, uid) - user_uri = aur_location + '/account/' + user + '/' - body = 'Request #%d has been %s by %s [1]' % (int(reqid), reason, user) - refs = '[1] ' + user_uri - else: - body = 'Request #%d has been %s automatically by the Arch User ' \ - 'Repository package request system' % (int(reqid), reason) - refs = None - if text.strip() == '': - body += '.' - else: - body += ':\n\n' + text - thread_id = '<pkg-request-' + reqid + '@aur.archlinux.org>' - headers = headers_reply(thread_id) - headers.update(headers_cc(cc)) - - send_notification(to, subject, body, refs, headers) - - -def tu_vote_reminder(conn, vote_id): - to = get_tu_vote_reminder_recipients(conn, vote_id) - - vote_uri = aur_location + '/tu/?id=' + vote_id - - subject = 'TU Vote Reminder: Proposal %d' % (int(vote_id)) - body = 'Please remember to cast your vote on proposal %d [1]. ' \ - 'The voting period ends in less than 48 hours.' % (int(vote_id)) - refs = '[1] ' + vote_uri - - send_notification(to, subject, body, refs) +class Notification: + def get_refs(self): + return () + + def get_headers(self): + return {} + + def send(self): + body = '' + for line in self.get_body().splitlines(): + body += textwrap.fill(line, break_long_words=False) + '\n' + for i, ref in enumerate(self.get_refs()): + body += '\n' + '[%d] %s' % (i + 1, ref) + body = body.rstrip() + + sendmail = aurweb.config.get('notifications', 'sendmail') + sender = aurweb.config.get('notifications', 'sender') + reply_to = aurweb.config.get('notifications', 'reply-to') + + for recipient in self.get_recipients(): + msg = email.mime.text.MIMEText(body, 'plain', 'utf-8') + msg['Subject'] = self.get_subject() + msg['From'] = sender + msg['Reply-to'] = reply_to + msg['To'] = recipient + + for key, value in self.get_headers().items(): + msg[key] = value + + p = subprocess.Popen([sendmail, '-t', '-oi'], + stdin=subprocess.PIPE) + p.communicate(msg.as_bytes()) + + +class ResetKeyNotification(Notification): + def __init__(self, conn, uid): + cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' + + 'WHERE ID = ?', [uid]) + self._username, self._to, self._resetkey = cur.fetchone() + + def get_recipients(self): + return [self._to] + + def get_subject(self): + return 'AUR Password Reset' + + def get_body(self): + return 'A password reset request was submitted for the account %s ' \ + 'associated with your email address. If you wish to reset ' \ + 'your password follow the link [1] below, otherwise ignore ' \ + 'this message and nothing will happen.' % (self._username) + + def get_refs(self): + return (aur_location + '/passreset/?resetkey=' + self._resetkey,) + + +class WelcomeNotification(ResetKeyNotification): + def get_subject(self): + return 'Welcome to the Arch User Repository' + + def get_body(self): + return 'Welcome to the Arch User Repository! In order to set an ' \ + 'initial password for your new account, please click the ' \ + 'link [1] below. If the link does not work, try copying and ' \ + 'pasting it into your browser.' + + +class CommentNotification(Notification): + def __init__(self, conn, uid, pkgbase_id, comment_id): + self._user = username_from_id(conn, uid) + self._pkgbase = pkgbase_from_id(conn, pkgbase_id) + cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'INNER JOIN PackageNotifications ' + + 'ON PackageNotifications.UserID = Users.ID WHERE ' + + 'Users.CommentNotify = 1 AND ' + + 'PackageNotifications.UserID != ? AND ' + + 'PackageNotifications.PackageBaseID = ?', + [uid, pkgbase_id]) + self._to = [row[0] for row in cur.fetchall()] + cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?', + [comment_id]) + self._text = cur.fetchone()[0] + + def get_recipients(self): + return self._to + + def get_subject(self): + return 'AUR Comment for %s' % (self._pkgbase) + + def get_body(self): + body = '%s [1] added the following comment to %s [2]:' % \ + (self._user, self._pkgbase) + body += '\n\n' + self._text + '\n\n' + body += 'If you no longer wish to receive notifications about this ' \ + 'package, please go to the package page [2] and select ' \ + '"%s".' % ('Disable notifications') + return body + + def get_refs(self): + return (aur_location + '/account/' + self._user + '/', + aur_location + '/pkgbase/' + self._pkgbase + '/') + + def get_headers(self): + thread_id = '<pkg-notifications-' + self._pkgbase + \ + '@aur.archlinux.org>' + return headers_reply(thread_id) + + +class UpdateNotification(Notification): + def __init__(self, conn, uid, pkgbase_id): + self._user = username_from_id(conn, uid) + self._pkgbase = pkgbase_from_id(conn, pkgbase_id) + cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'INNER JOIN PackageNotifications ' + + 'ON PackageNotifications.UserID = Users.ID WHERE ' + + 'Users.UpdateNotify = 1 AND ' + + 'PackageNotifications.UserID != ? AND ' + + 'PackageNotifications.PackageBaseID = ?', + [uid, pkgbase_id]) + self._to = [row[0] for row in cur.fetchall()] + + def get_recipients(self): + return self._to + + def get_subject(self): + return 'AUR Package Update: %s' % (self._pkgbase) + + def get_body(self): + body = '%s [1] pushed a new commit to %s [2].' % \ + (self._user, self._pkgbase) + body += '\n\n' + body += 'If you no longer wish to receive notifications about this ' \ + 'package, please go to the package page [2] and select ' \ + '"%s".' % ('Disable notifications') + return body + + def get_refs(self): + return (aur_location + '/account/' + self._user + '/', + aur_location + '/pkgbase/' + self._pkgbase + '/') + + def get_headers(self): + thread_id = '<pkg-notifications-' + self._pkgbase + \ + '@aur.archlinux.org>' + return headers_reply(thread_id) + + +class FlagNotification(Notification): + def __init__(self, conn, uid, pkgbase_id): + self._user = username_from_id(conn, uid) + self._pkgbase = pkgbase_from_id(conn, pkgbase_id) + cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'LEFT JOIN PackageComaintainers ' + + 'ON PackageComaintainers.UsersID = Users.ID ' + + 'INNER JOIN PackageBases ' + + 'ON PackageBases.MaintainerUID = Users.ID OR ' + + 'PackageBases.ID = PackageComaintainers.PackageBaseID ' + + 'WHERE PackageBases.ID = ?', [pkgbase_id]) + self._to = [row[0] for row in cur.fetchall()] + cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' + + 'ID = ?', [pkgbase_id]) + self._text = cur.fetchone()[0] + + def get_recipients(self): + return self._to + + def get_subject(self): + return 'AUR Out-of-date Notification for %s' % (self._pkgbase) + + def get_body(self): + body = 'Your package %s [1] has been flagged out-of-date by ' \ + '%s [2]:' % (self._pkgbase, self._user) + body += '\n\n' + self._text + return body + + def get_refs(self): + return (aur_location + '/pkgbase/' + self._pkgbase + '/', + aur_location + '/account/' + self._user + '/') + + +class OwnershipEventNotification(Notification): + def __init__(self, conn, uid, pkgbase_id): + self._user = username_from_id(conn, uid) + self._pkgbase = pkgbase_from_id(conn, pkgbase_id) + cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'INNER JOIN PackageNotifications ' + + 'ON PackageNotifications.UserID = Users.ID WHERE ' + + 'Users.OwnershipNotify = 1 AND ' + + 'PackageNotifications.UserID != ? AND ' + + 'PackageNotifications.PackageBaseID = ?', + [uid, pkgbase_id]) + self._to = [row[0] for row in cur.fetchall()] + cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' + + 'ID = ?', [pkgbase_id]) + self._text = cur.fetchone()[0] + + def get_recipients(self): + return self._to + + def get_subject(self): + return 'AUR Ownership Notification for %s' % (self._pkgbase) + + def get_refs(self): + return (aur_location + '/pkgbase/' + self._pkgbase + '/', + aur_location + '/account/' + self._user + '/') + + +class AdoptNotification(OwnershipEventNotification): + def get_body(self): + return 'The package %s [1] was adopted by %s [2].' % \ + (self._pkgbase, self._user) + + +class DisownNotification(OwnershipEventNotification): + def get_body(self): + return 'The package %s [1] was disowned by %s [2].' % \ + (self._pkgbase, self._user) + + +class ComaintainershipEventNotification(Notification): + def __init__(self, conn, uid, pkgbase_id): + self._pkgbase = pkgbase_from_id(conn, pkgbase_id) + self._to = get_user_email(conn, uid) + + def get_recipients(self): + return [self._to] + + def get_subject(self): + return 'AUR Co-Maintainer Notification for %s' % (self._pkgbase) + + def get_refs(self): + return (aur_location + '/pkgbase/' + self._pkgbase + '/',) + + +class ComaintainerAddNotification(ComaintainershipEventNotification): + def get_body(self): + return 'You were added to the co-maintainer list of %s [1].' % \ + (self._pkgbase) + + +class ComaintainerRemoveNotification(ComaintainershipEventNotification): + def get_body(self): + return 'You were removed from the co-maintainer list of %s [1].' % \ + (self._pkgbase) + + +class DeleteNotification(Notification): + def __init__(self, conn, uid, old_pkgbase_id, new_pkgbase_id=None): + self._user = username_from_id(conn, uid) + self._old_pkgbase = pkgbase_from_id(conn, old_pkgbase_id) + if new_pkgbase_id: + self._new_pkgbase = pkgbase_from_id(conn, new_pkgbase_id) + else: + self._new_pkgbase = None + cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'INNER JOIN PackageNotifications ' + + 'ON PackageNotifications.UserID = Users.ID WHERE ' + + 'PackageNotifications.UserID != ? AND ' + + 'PackageNotifications.PackageBaseID = ?', + [uid, old_pkgbase_id]) + self._to = [row[0] for row in cur.fetchall()] + + def get_recipients(self): + return self._to + + def get_subject(self): + return 'AUR Package deleted: %s' % (self._old_pkgbase) + + def get_body(self): + if self._new_pkgbase: + return '%s [1] merged %s [2] into %s [3].\n\n' \ + 'If you no longer wish receive notifications about the ' \ + 'new package, please go to [3] and click "%s".' % \ + (self._user, self._old_pkgbase, self._new_pkgbase, + 'Disable notifications') + else: + return '%s [1] deleted %s [2].\n\n' \ + 'You will no longer receive notifications about this ' \ + 'package.' % (self._user, self._old_pkgbase) + + def get_refs(self): + refs = (aur_location + '/account/' + self._user + '/', + aur_location + '/pkgbase/' + self._old_pkgbase + '/') + if self._new_pkgbase: + refs += (aur_location + '/pkgbase/' + self._new_pkgbase + '/',) + return refs + + +class RequestOpenNotification(Notification): + def __init__(self, conn, uid, reqid, reqtype, pkgbase_id, merge_into=None): + self._user = username_from_id(conn, uid) + self._pkgbase = pkgbase_from_id(conn, pkgbase_id) + cur = conn.execute('SELECT DISTINCT Users.Email FROM PackageRequests ' + + 'INNER JOIN PackageBases ' + + 'ON PackageBases.ID = PackageRequests.PackageBaseID ' + + 'INNER JOIN Users ' + + 'ON Users.ID = PackageRequests.UsersID ' + + 'OR Users.ID = PackageBases.MaintainerUID ' + + 'WHERE PackageRequests.ID = ?', [reqid]) + self._to = aurweb.config.get('options', 'aur_request_ml') + self._cc = [row[0] for row in cur.fetchall()] + cur = conn.execute('SELECT Comments FROM PackageRequests WHERE ID = ?', + [reqid]) + self._text = cur.fetchone()[0] + self._reqid = int(reqid) + self._reqtype = reqtype + self._merge_into = merge_into + + def get_recipients(self): + return [self._to] + + def get_subject(self): + return '[PRQ#%d] %s Request for %s' % \ + (self._reqid, self._reqtype.title(), self._pkgbase) + + def get_body(self): + if self._merge_into: + body = '%s [1] filed a request to merge %s [2] into %s [3]:' % \ + (self._user, self._pkgbase, self._merge_into) + body += '\n\n' + self._text + else: + body = '%s [1] filed a %s request for %s [2]:' % \ + (self._user, self._reqtype, self._pkgbase) + body += '\n\n' + self._text + return body + + def get_refs(self): + refs = (aur_location + '/account/' + self._user + '/', + aur_location + '/pkgbase/' + self._pkgbase + '/') + if self._merge_into: + refs += (aur_location + '/pkgbase/' + self._merge_into + '/',) + return refs + + def get_headers(self): + thread_id = '<pkg-request-' + str(self._reqid) + '@aur.archlinux.org>' + # Use a deterministic Message-ID for the first email referencing a + # request. + headers = headers_msgid(thread_id) + headers.update(headers_cc(self._cc)) + return headers + + +class RequestCloseNotification(Notification): + def __init__(self, conn, uid, reqid, reason): + self._user = username_from_id(conn, uid) if int(uid) else None + cur = conn.execute('SELECT DISTINCT Users.Email FROM PackageRequests ' + + 'INNER JOIN PackageBases ' + + 'ON PackageBases.ID = PackageRequests.PackageBaseID ' + + 'INNER JOIN Users ' + + 'ON Users.ID = PackageRequests.UsersID ' + + 'OR Users.ID = PackageBases.MaintainerUID ' + + 'WHERE PackageRequests.ID = ?', [reqid]) + self._to = aurweb.config.get('options', 'aur_request_ml') + self._cc = [row[0] for row in cur.fetchall()] + cur = conn.execute('SELECT ClosureComment FROM PackageRequests ' + + 'WHERE ID = ?', [reqid]) + self._text = cur.fetchone()[0] + self._reqid = int(reqid) + self._reason = reason + + def get_recipients(self): + return [self._to] + + def get_subject(self): + return '[PRQ#%d] Request %s' % (self._reqid, self._reason.title()) + + def get_body(self): + if self._user: + body = 'Request #%d has been %s by %s [1]' % \ + (self._reqid, self._reason, self._user) + else: + body = 'Request #%d has been %s automatically by the Arch User ' \ + 'Repository package request system' % \ + (self._reqid, self._reason) + if self._text.strip() == '': + body += '.' + else: + body += ':\n\n' + self._text + return body + + def get_refs(self): + if self._user: + return (aur_location + '/account/' + self._user + '/',) + else: + return () + + def get_headers(self): + thread_id = '<pkg-request-' + str(self._reqid) + '@aur.archlinux.org>' + headers = headers_reply(thread_id) + headers.update(headers_cc(self._cc)) + return headers + + +class TUVoteReminderNotification(Notification): + def __init__(self, conn, vote_id): + self._vote_id = int(vote_id) + cur = conn.execute('SELECT Email FROM Users ' + + 'WHERE AccountTypeID IN (2, 4) AND ID NOT IN ' + + '(SELECT UserID FROM TU_Votes ' + + 'WHERE TU_Votes.VoteID = ?)', [vote_id]) + self._to = [row[0] for row in cur.fetchall()] + + def get_recipients(self): + return self._to + + def get_subject(self): + return 'TU Vote Reminder: Proposal %d' % (self._vote_id) + + def get_body(self): + return 'Please remember to cast your vote on proposal %d [1]. ' \ + 'The voting period ends in less than 48 hours.' % \ + (self._vote_id) + + def get_refs(self): + return (aur_location + '/tu/?id=' + str(self._vote_id),) def main(): action = sys.argv[1] action_map = { - 'send-resetkey': send_resetkey, - 'welcome': welcome, - 'comment': comment, - 'update': update, - 'flag': flag, - 'adopt': adopt, - 'disown': disown, - 'comaintainer-add': comaintainer_add, - 'comaintainer-remove': comaintainer_remove, - 'delete': delete, - 'request-open': request_open, - 'request-close': request_close, - 'tu-vote-reminder': tu_vote_reminder, + 'send-resetkey': ResetKeyNotification, + 'welcome': WelcomeNotification, + 'comment': CommentNotification, + 'update': UpdateNotification, + 'flag': FlagNotification, + 'adopt': AdoptNotification, + 'disown': DisownNotification, + 'comaintainer-add': ComaintainerAddNotification, + 'comaintainer-remove': ComaintainerRemoveNotification, + 'delete': DeleteNotification, + 'request-open': RequestOpenNotification, + 'request-close': RequestCloseNotification, + 'tu-vote-reminder': TUVoteReminderNotification, } conn = aurweb.db.Connection() - action_map[action](conn, *sys.argv[2:]) + notification = action_map[action](conn, *sys.argv[2:]) + notification.send() conn.commit() conn.close() -- 2.17.0
Add support for translating notification emails and send localized notifications, based on the user's language preferences. Also, update the translations Makefile to add strings from the notification script to the message catalog. Implements FS#31850. Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> --- aurweb/l10n.py | 16 +++ aurweb/scripts/notify.py | 258 ++++++++++++++++++++++----------------- po/Makefile | 21 +++- test/setup.sh | 18 +-- 4 files changed, 189 insertions(+), 124 deletions(-) create mode 100644 aurweb/l10n.py diff --git a/aurweb/l10n.py b/aurweb/l10n.py new file mode 100644 index 0000000..e58e3fe --- /dev/null +++ b/aurweb/l10n.py @@ -0,0 +1,16 @@ +import gettext + + +class Translator: + def __init__(self): + self._translator = {} + + def translate(self, s, lang): + if lang == 'en': + return s + if lang not in self._translator: + self._translator[lang] = gettext.translation("aur", + "../../web/locale", + languages=[lang]) + self._translator[lang].install() + return _(s) diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py index a637013..9f1f7a5 100755 --- a/aurweb/scripts/notify.py +++ b/aurweb/scripts/notify.py @@ -7,6 +7,7 @@ import textwrap import aurweb.config import aurweb.db +import aurweb.l10n aur_location = aurweb.config.get('options', 'aur_location') @@ -40,36 +41,37 @@ def pkgbase_from_pkgreq(conn, reqid): return cur.fetchone()[0] -def get_user_email(conn, uid): - cur = conn.execute('SELECT Email FROM Users WHERE ID = ?', [uid]) - return cur.fetchone()[0] - - class Notification: + def __init__(self): + self._l10n = aurweb.l10n.Translator() + def get_refs(self): return () def get_headers(self): return {} - def send(self): + def get_body_fmt(self, lang): body = '' - for line in self.get_body().splitlines(): + for line in self.get_body(lang).splitlines(): body += textwrap.fill(line, break_long_words=False) + '\n' for i, ref in enumerate(self.get_refs()): body += '\n' + '[%d] %s' % (i + 1, ref) - body = body.rstrip() + return body.rstrip() + def send(self): sendmail = aurweb.config.get('notifications', 'sendmail') sender = aurweb.config.get('notifications', 'sender') reply_to = aurweb.config.get('notifications', 'reply-to') for recipient in self.get_recipients(): - msg = email.mime.text.MIMEText(body, 'plain', 'utf-8') - msg['Subject'] = self.get_subject() + to, lang = recipient + msg = email.mime.text.MIMEText(self.get_body_fmt(lang), + 'plain', 'utf-8') + msg['Subject'] = self.get_subject(lang) msg['From'] = sender msg['Reply-to'] = reply_to - msg['To'] = recipient + msg['To'] = to for key, value in self.get_headers().items(): msg[key] = value @@ -81,66 +83,76 @@ class Notification: class ResetKeyNotification(Notification): def __init__(self, conn, uid): - cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' + - 'WHERE ID = ?', [uid]) - self._username, self._to, self._resetkey = cur.fetchone() + cur = conn.execute('SELECT UserName, Email, LangPreference, ' + + 'ResetKey FROM Users WHERE ID = ?', [uid]) + self._username, self._to, self._lang, self._resetkey = cur.fetchone() + super().__init__() def get_recipients(self): - return [self._to] + return [(self._to, self._lang)] - def get_subject(self): - return 'AUR Password Reset' + def get_subject(self, lang): + return self._l10n.translate('AUR Password Reset', lang) - def get_body(self): - return 'A password reset request was submitted for the account %s ' \ - 'associated with your email address. If you wish to reset ' \ - 'your password follow the link [1] below, otherwise ignore ' \ - 'this message and nothing will happen.' % (self._username) + def get_body(self, lang): + return self._l10n.translate( + 'A password reset request was submitted for the account %s ' + 'associated with your email address. If you wish to reset ' + 'your password follow the link [1] below, otherwise ignore ' + 'this message and nothing will happen.', lang) % \ + (self._username) def get_refs(self): return (aur_location + '/passreset/?resetkey=' + self._resetkey,) class WelcomeNotification(ResetKeyNotification): - def get_subject(self): - return 'Welcome to the Arch User Repository' + def get_subject(self, lang): + return self._l10n.translate('Welcome to the Arch User Repository', + lang) - def get_body(self): - return 'Welcome to the Arch User Repository! In order to set an ' \ - 'initial password for your new account, please click the ' \ - 'link [1] below. If the link does not work, try copying and ' \ - 'pasting it into your browser.' + def get_body(self, lang): + return self._l10n.translate( + 'Welcome to the Arch User Repository! In order to set an ' + 'initial password for your new account, please click the ' + 'link [1] below. If the link does not work, try copying and ' + 'pasting it into your browser.', lang) class CommentNotification(Notification): def __init__(self, conn, uid, pkgbase_id, comment_id): self._user = username_from_id(conn, uid) self._pkgbase = pkgbase_from_id(conn, pkgbase_id) - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + - 'INNER JOIN PackageNotifications ' + + cur = conn.execute('SELECT DISTINCT Users.Email, Users.LangPreference ' + 'FROM Users INNER JOIN PackageNotifications ' + 'ON PackageNotifications.UserID = Users.ID WHERE ' + 'Users.CommentNotify = 1 AND ' + 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._to = [row[0] for row in cur.fetchall()] + self._recipients = cur.fetchall() cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?', [comment_id]) self._text = cur.fetchone()[0] + super().__init__() def get_recipients(self): - return self._to + return self._recipients - def get_subject(self): - return 'AUR Comment for %s' % (self._pkgbase) + def get_subject(self, lang): + return self._l10n.translate('AUR Comment for %s', lang) % \ + (self._pkgbase) - def get_body(self): - body = '%s [1] added the following comment to %s [2]:' % \ - (self._user, self._pkgbase) + def get_body(self, lang): + body = self._l10n.translate( + '%s [1] added the following comment to %s [2]:', lang) % \ + (self._user, self._pkgbase) body += '\n\n' + self._text + '\n\n' - body += 'If you no longer wish to receive notifications about this ' \ - 'package, please go to the package page [2] and select ' \ - '"%s".' % ('Disable notifications') + dnlabel = self._l10n.translate('Disable notifications', lang) + body += self._l10n.translate( + 'If you no longer wish to receive notifications about this ' + 'package, please go to the package page [2] and select ' + '"%s".', lang) % dnlabel return body def get_refs(self): @@ -157,28 +169,33 @@ class UpdateNotification(Notification): def __init__(self, conn, uid, pkgbase_id): self._user = username_from_id(conn, uid) self._pkgbase = pkgbase_from_id(conn, pkgbase_id) - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + cur = conn.execute('SELECT DISTINCT Users.Email, ' + + 'Users.LangPreference FROM Users ' + 'INNER JOIN PackageNotifications ' + 'ON PackageNotifications.UserID = Users.ID WHERE ' + 'Users.UpdateNotify = 1 AND ' + 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._to = [row[0] for row in cur.fetchall()] + self._recipients = cur.fetchall() + super().__init__() def get_recipients(self): - return self._to + return self._recipients - def get_subject(self): - return 'AUR Package Update: %s' % (self._pkgbase) + def get_subject(self, lang): + return self._l10n.translate('AUR Package Update: %s', lang) % \ + (self._pkgbase) - def get_body(self): - body = '%s [1] pushed a new commit to %s [2].' % \ - (self._user, self._pkgbase) + def get_body(self, lang): + body = self._l10n.translate('%s [1] pushed a new commit to %s [2].', + lang) % (self._user, self._pkgbase) body += '\n\n' - body += 'If you no longer wish to receive notifications about this ' \ - 'package, please go to the package page [2] and select ' \ - '"%s".' % ('Disable notifications') + dnlabel = self._l10n.translate('Disable notifications', lang) + body += self._l10n.translate( + 'If you no longer wish to receive notifications about this ' + 'package, please go to the package page [2] and select ' + '"%s".', lang) % dnlabel return body def get_refs(self): @@ -195,27 +212,31 @@ class FlagNotification(Notification): def __init__(self, conn, uid, pkgbase_id): self._user = username_from_id(conn, uid) self._pkgbase = pkgbase_from_id(conn, pkgbase_id) - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + cur = conn.execute('SELECT DISTINCT Users.Email, ' + + 'Users.LangPreference FROM Users ' + 'LEFT JOIN PackageComaintainers ' + 'ON PackageComaintainers.UsersID = Users.ID ' + 'INNER JOIN PackageBases ' + 'ON PackageBases.MaintainerUID = Users.ID OR ' + 'PackageBases.ID = PackageComaintainers.PackageBaseID ' + 'WHERE PackageBases.ID = ?', [pkgbase_id]) - self._to = [row[0] for row in cur.fetchall()] + self._recipients = cur.fetchall() cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' + 'ID = ?', [pkgbase_id]) self._text = cur.fetchone()[0] + super().__init__() def get_recipients(self): - return self._to + return self._recipients - def get_subject(self): - return 'AUR Out-of-date Notification for %s' % (self._pkgbase) + def get_subject(self, lang): + return self._l10n.translate('AUR Out-of-date Notification for %s', + lang) % (self._pkgbase) - def get_body(self): - body = 'Your package %s [1] has been flagged out-of-date by ' \ - '%s [2]:' % (self._pkgbase, self._user) + def get_body(self, lang): + body = self._l10n.translate( + 'Your package %s [1] has been flagged out-of-date by ' + '%s [2]:', lang) % (self._pkgbase, self._user) body += '\n\n' + self._text return body @@ -228,23 +249,26 @@ class OwnershipEventNotification(Notification): def __init__(self, conn, uid, pkgbase_id): self._user = username_from_id(conn, uid) self._pkgbase = pkgbase_from_id(conn, pkgbase_id) - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + cur = conn.execute('SELECT DISTINCT Users.Email, ' + + 'Users.LangPreference FROM Users ' + 'INNER JOIN PackageNotifications ' + 'ON PackageNotifications.UserID = Users.ID WHERE ' + 'Users.OwnershipNotify = 1 AND ' + 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._to = [row[0] for row in cur.fetchall()] + self._recipients = cur.fetchall() cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' + 'ID = ?', [pkgbase_id]) self._text = cur.fetchone()[0] + super().__init__() def get_recipients(self): - return self._to + return self._recipients - def get_subject(self): - return 'AUR Ownership Notification for %s' % (self._pkgbase) + def get_subject(self, lang): + return self._l10n.translate('AUR Ownership Notification for %s', + lang) % (self._pkgbase) def get_refs(self): return (aur_location + '/pkgbase/' + self._pkgbase + '/', @@ -252,42 +276,50 @@ class OwnershipEventNotification(Notification): class AdoptNotification(OwnershipEventNotification): - def get_body(self): - return 'The package %s [1] was adopted by %s [2].' % \ - (self._pkgbase, self._user) + def get_body(self, lang): + return self._l10n.translate( + 'The package %s [1] was adopted by %s [2].', lang) % \ + (self._pkgbase, self._user) class DisownNotification(OwnershipEventNotification): def get_body(self): - return 'The package %s [1] was disowned by %s [2].' % \ - (self._pkgbase, self._user) + return self._l10n.translate( + 'The package %s [1] was disowned by %s [2].', lang) % \ + (self._pkgbase, self._user) class ComaintainershipEventNotification(Notification): def __init__(self, conn, uid, pkgbase_id): self._pkgbase = pkgbase_from_id(conn, pkgbase_id) - self._to = get_user_email(conn, uid) + cur = conn.execute('SELECT Email, LangPreference FROM Users ' + + 'WHERE ID = ?', [uid]) + self._to, self._lang = cur.fetchone() + super().__init__() def get_recipients(self): - return [self._to] + return [(self._to, self._lang)] - def get_subject(self): - return 'AUR Co-Maintainer Notification for %s' % (self._pkgbase) + def get_subject(self, lang): + return self._l10n.translate('AUR Co-Maintainer Notification for %s', + lang) % (self._pkgbase) def get_refs(self): return (aur_location + '/pkgbase/' + self._pkgbase + '/',) class ComaintainerAddNotification(ComaintainershipEventNotification): - def get_body(self): - return 'You were added to the co-maintainer list of %s [1].' % \ - (self._pkgbase) + def get_body(self, lang): + return self._l10n.translate( + 'You were added to the co-maintainer list of %s [1].', + lang) % (self._pkgbase) class ComaintainerRemoveNotification(ComaintainershipEventNotification): - def get_body(self): - return 'You were removed from the co-maintainer list of %s [1].' % \ - (self._pkgbase) + def get_body(self, lang): + return self._l10n.translate( + 'You were removed from the co-maintainer list of %s [1].', + lang) % (self._pkgbase) class DeleteNotification(Notification): @@ -298,31 +330,36 @@ class DeleteNotification(Notification): self._new_pkgbase = pkgbase_from_id(conn, new_pkgbase_id) else: self._new_pkgbase = None - cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' + + cur = conn.execute('SELECT DISTINCT Users.Email, ' + + 'Users.LangPreference FROM Users ' + 'INNER JOIN PackageNotifications ' + 'ON PackageNotifications.UserID = Users.ID WHERE ' + 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, old_pkgbase_id]) - self._to = [row[0] for row in cur.fetchall()] + self._recipients = cur.fetchall() + super().__init__() def get_recipients(self): - return self._to + return self._recipients - def get_subject(self): - return 'AUR Package deleted: %s' % (self._old_pkgbase) + def get_subject(self, lang): + return self._l10n.translate('AUR Package deleted: %s', lang) % \ + (self._old_pkgbase) - def get_body(self): + def get_body(self, lang): if self._new_pkgbase: - return '%s [1] merged %s [2] into %s [3].\n\n' \ - 'If you no longer wish receive notifications about the ' \ - 'new package, please go to [3] and click "%s".' % \ - (self._user, self._old_pkgbase, self._new_pkgbase, - 'Disable notifications') + dnlabel = self._l10n.translate('Disable notifications', lang) + return self._l10n.translate( + '%s [1] merged %s [2] into %s [3].\n\n' + 'If you no longer wish receive notifications about the ' + 'new package, please go to [3] and click "%s".', lang) % \ + (self._user, self._old_pkgbase, self._new_pkgbase, dnlabel) else: - return '%s [1] deleted %s [2].\n\n' \ - 'You will no longer receive notifications about this ' \ - 'package.' % (self._user, self._old_pkgbase) + return self._l10n.translate( + '%s [1] deleted %s [2].\n\n' + 'You will no longer receive notifications about this ' + 'package.', lang) % (self._user, self._old_pkgbase) def get_refs(self): refs = (aur_location + '/account/' + self._user + '/', @@ -353,13 +390,13 @@ class RequestOpenNotification(Notification): self._merge_into = merge_into def get_recipients(self): - return [self._to] + return [(self._to, 'en')] - def get_subject(self): + def get_subject(self, lang): return '[PRQ#%d] %s Request for %s' % \ (self._reqid, self._reqtype.title(), self._pkgbase) - def get_body(self): + def get_body(self, lang): if self._merge_into: body = '%s [1] filed a request to merge %s [2] into %s [3]:' % \ (self._user, self._pkgbase, self._merge_into) @@ -405,12 +442,12 @@ class RequestCloseNotification(Notification): self._reason = reason def get_recipients(self): - return [self._to] + return [(self._to, 'en')] - def get_subject(self): + def get_subject(self, lang): return '[PRQ#%d] Request %s' % (self._reqid, self._reason.title()) - def get_body(self): + def get_body(self, lang): if self._user: body = 'Request #%d has been %s by %s [1]' % \ (self._reqid, self._reason, self._user) @@ -440,22 +477,25 @@ class RequestCloseNotification(Notification): class TUVoteReminderNotification(Notification): def __init__(self, conn, vote_id): self._vote_id = int(vote_id) - cur = conn.execute('SELECT Email FROM Users ' + + cur = conn.execute('SELECT Email, LangPreference FROM Users ' + 'WHERE AccountTypeID IN (2, 4) AND ID NOT IN ' + '(SELECT UserID FROM TU_Votes ' + 'WHERE TU_Votes.VoteID = ?)', [vote_id]) - self._to = [row[0] for row in cur.fetchall()] + self._recipients = cur.fetchall() + super().__init__() def get_recipients(self): - return self._to + return self._recipients - def get_subject(self): - return 'TU Vote Reminder: Proposal %d' % (self._vote_id) + def get_subject(self, lang): + return self._l10n.translate('TU Vote Reminder: Proposal %d', lang) % \ + (self._vote_id) - def get_body(self): - return 'Please remember to cast your vote on proposal %d [1]. ' \ - 'The voting period ends in less than 48 hours.' % \ - (self._vote_id) + def get_body(self, lang): + return self._l10n.translate( + 'Please remember to cast your vote on proposal %d [1]. ' + 'The voting period ends in less than 48 hours.', lang) % \ + (self._vote_id) def get_refs(self): return (aur_location + '/tu/?id=' + str(self._vote_id),) diff --git a/po/Makefile b/po/Makefile index d0ed741..78073f8 100644 --- a/po/Makefile +++ b/po/Makefile @@ -48,23 +48,32 @@ all: ${MOFILES} lang=`echo $@ | sed -e 's/\.po-update$$//'`; \ msgmerge -U --no-location --lang="$$lang" $< aur.pot -POTFILES: - find ../web -type f -name '*.php' -printf '%P\n' | sort >POTFILES +POTFILES-php: + find ../web -type f -name '*.php' -printf '%P\n' | sort >POTFILES-php -update-pot: POTFILES +POTFILES-py: + find ../aurweb -type f -name '*.py' -printf '%P\n' | sort >POTFILES-py + +update-pot: POTFILES-php POTFILES-py pkgname=AUR; \ pkgver=`sed -n 's/.*"AURWEB_VERSION", "\(.*\)".*/\1/p' ../web/lib/version.inc.php`; \ xgettext --default-domain=aur -L php --keyword=__ --keyword=_n:1,2 \ --add-location=file --add-comments=TRANSLATORS: \ --package-name="$$pkgname" --package-version="$$pkgver" \ --msgid-bugs-address='${MSGID_BUGS_ADDRESS}' \ - --directory ../web --files-from POTFILES -o aur.pot + --directory ../web --files-from POTFILES-php -o aur.pot; \ + xgettext --default-domain=aur -L python --join-existing \ + --keyword=translate \ + --add-location=file --add-comments=TRANSLATORS: \ + --package-name="$$pkgname" --package-version="$$pkgver" \ + --msgid-bugs-address='${MSGID_BUGS_ADDRESS}' \ + --directory ../aurweb --files-from POTFILES-py -o aur.pot update-po: ${MAKE} ${UPDATEPOFILES} clean: - rm -f *.mo *.po\~ POTFILES + rm -f *.mo *.po\~ POTFILES-php POTFILES-py install: all for l in ${LOCALES}; do mkdir -p ${DESTDIR}${PREFIX}/$$l/LC_MESSAGES/; done @@ -73,4 +82,4 @@ install: all uninstall: for l in ${LOCALES}; do rm -rf ${DESTDIR}${PREFIX}/$$l/LC_MESSAGES/; done -.PHONY: all update-pot update-po clean install uninstall POTFILES +.PHONY: all update-pot update-po clean install uninstall POTFILES-php POTFILES-py diff --git a/test/setup.sh b/test/setup.sh index 5e10fec..535e819 100644 --- a/test/setup.sh +++ b/test/setup.sh @@ -114,15 +114,15 @@ DBSCHEMA="$TOPLEVEL/schema/aur-schema-sqlite.sql" rm -f aur.db sqlite3 aur.db <"$DBSCHEMA" -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 2);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (3, 'dev', '!', 'dev@localhost', 3);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (4, 'user2', '!', 'user2@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (5, 'user3', '!', 'user3@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (6, 'user4', '!', 'user4@localhost', 1);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (7, 'tu2', '!', 'tu2@localhost', 2);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (8, 'tu3', '!', 'tu3@localhost', 2);" | sqlite3 aur.db -echo "INSERT INTO Users (ID, UserName, Passwd, Email, AccountTypeID) VALUES (9, 'tu4', '!', 'tu4@localhost', 2);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (1, 'user', '!', 'user@localhost', 'en', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (2, 'tu', '!', 'tu@localhost', 'en', 2);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (3, 'dev', '!', 'dev@localhost', 'en', 3);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (4, 'user2', '!', 'user2@localhost', 'en', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (5, 'user3', '!', 'user3@localhost', 'en', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (6, 'user4', '!', 'user4@localhost', 'en', 1);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (7, 'tu2', '!', 'tu2@localhost', 'en', 2);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (8, 'tu3', '!', 'tu3@localhost', 'en', 2);" | sqlite3 aur.db +echo "INSERT INTO Users (ID, UserName, Passwd, Email, LangPreference, AccountTypeID) VALUES (9, 'tu4', '!', 'tu4@localhost', 'en', 2);" | sqlite3 aur.db echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (1, '$AUTH_FINGERPRINT_USER', '$AUTH_KEYTYPE_USER $AUTH_KEYTEXT_USER');" | sqlite3 aur.db echo "INSERT INTO SSHPubKeys (UserID, Fingerprint, PubKey) VALUES (2, '$AUTH_FINGERPRINT_TU', '$AUTH_KEYTYPE_TU $AUTH_KEYTEXT_TU');" | sqlite3 aur.db -- 2.17.0
User modern Python format() strings with curly braces. Also, convert all placeholders to named arguments. This allows translators to reorder messages. Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> --- aurweb/scripts/notify.py | 96 ++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py index 9f1f7a5..beca8b4 100755 --- a/aurweb/scripts/notify.py +++ b/aurweb/scripts/notify.py @@ -96,11 +96,11 @@ class ResetKeyNotification(Notification): def get_body(self, lang): return self._l10n.translate( - 'A password reset request was submitted for the account %s ' - 'associated with your email address. If you wish to reset ' - 'your password follow the link [1] below, otherwise ignore ' - 'this message and nothing will happen.', lang) % \ - (self._username) + 'A password reset request was submitted for the account ' + '{user} associated with your email address. If you wish to ' + 'reset your password follow the link [1] below, otherwise ' + 'ignore this message and nothing will happen.', + lang).format(user=self._username) def get_refs(self): return (aur_location + '/passreset/?resetkey=' + self._resetkey,) @@ -140,19 +140,19 @@ class CommentNotification(Notification): return self._recipients def get_subject(self, lang): - return self._l10n.translate('AUR Comment for %s', lang) % \ - (self._pkgbase) + return self._l10n.translate('AUR Comment for {pkgbase}', + lang).format(pkgbase=self._pkgbase) def get_body(self, lang): body = self._l10n.translate( - '%s [1] added the following comment to %s [2]:', lang) % \ - (self._user, self._pkgbase) + '{user} [1] added the following comment to {pkgbase} [2]:', + lang).format(user=self._user, pkgbase=self._pkgbase) body += '\n\n' + self._text + '\n\n' dnlabel = self._l10n.translate('Disable notifications', lang) body += self._l10n.translate( 'If you no longer wish to receive notifications about this ' 'package, please go to the package page [2] and select ' - '"%s".', lang) % dnlabel + '"{label}".', lang).format(label=dnlabel) return body def get_refs(self): @@ -184,18 +184,20 @@ class UpdateNotification(Notification): return self._recipients def get_subject(self, lang): - return self._l10n.translate('AUR Package Update: %s', lang) % \ - (self._pkgbase) + return self._l10n.translate('AUR Package Update: {pkgbase}', + lang).format(pkgbase=self._pkgbase) def get_body(self, lang): - body = self._l10n.translate('%s [1] pushed a new commit to %s [2].', - lang) % (self._user, self._pkgbase) + body = self._l10n.translate('{user} [1] pushed a new commit to ' + '{pkgbase} [2].', lang).format( + user=self._user, + pkgbase=self._pkgbase) body += '\n\n' dnlabel = self._l10n.translate('Disable notifications', lang) body += self._l10n.translate( 'If you no longer wish to receive notifications about this ' 'package, please go to the package page [2] and select ' - '"%s".', lang) % dnlabel + '"{label}".', lang).format(label=dnlabel) return body def get_refs(self): @@ -230,13 +232,15 @@ class FlagNotification(Notification): return self._recipients def get_subject(self, lang): - return self._l10n.translate('AUR Out-of-date Notification for %s', - lang) % (self._pkgbase) + return self._l10n.translate('AUR Out-of-date Notification for ' + '{pkgbase}', + lang).format(pkgbase=self._pkgbase) def get_body(self, lang): body = self._l10n.translate( - 'Your package %s [1] has been flagged out-of-date by ' - '%s [2]:', lang) % (self._pkgbase, self._user) + 'Your package {pkgbase} [1] has been flagged out-of-date by ' + '{user} [2]:', lang).format(pkgbase=self._pkgbase, + user=self._user) body += '\n\n' + self._text return body @@ -267,8 +271,8 @@ class OwnershipEventNotification(Notification): return self._recipients def get_subject(self, lang): - return self._l10n.translate('AUR Ownership Notification for %s', - lang) % (self._pkgbase) + return self._l10n.translate('AUR Ownership Notification for {pkgbase}', + lang).format(pkgbase=self._pkgbase) def get_refs(self): return (aur_location + '/pkgbase/' + self._pkgbase + '/', @@ -278,15 +282,16 @@ class OwnershipEventNotification(Notification): class AdoptNotification(OwnershipEventNotification): def get_body(self, lang): return self._l10n.translate( - 'The package %s [1] was adopted by %s [2].', lang) % \ - (self._pkgbase, self._user) + 'The package {pkgbase} [1] was adopted by {user} [2].', + lang).format(pkgbase=self._pkgbase, user=self._user) class DisownNotification(OwnershipEventNotification): - def get_body(self): + def get_body(self, lang): return self._l10n.translate( - 'The package %s [1] was disowned by %s [2].', lang) % \ - (self._pkgbase, self._user) + 'The package {pkgbase} [1] was disowned by {user} ' + '[2].', lang).format(pkgbase=self._pkgbase, + user=self._user) class ComaintainershipEventNotification(Notification): @@ -301,8 +306,9 @@ class ComaintainershipEventNotification(Notification): return [(self._to, self._lang)] def get_subject(self, lang): - return self._l10n.translate('AUR Co-Maintainer Notification for %s', - lang) % (self._pkgbase) + return self._l10n.translate('AUR Co-Maintainer Notification for ' + '{pkgbase}', + lang).format(pkgbase=self._pkgbase) def get_refs(self): return (aur_location + '/pkgbase/' + self._pkgbase + '/',) @@ -311,15 +317,15 @@ class ComaintainershipEventNotification(Notification): class ComaintainerAddNotification(ComaintainershipEventNotification): def get_body(self, lang): return self._l10n.translate( - 'You were added to the co-maintainer list of %s [1].', - lang) % (self._pkgbase) + 'You were added to the co-maintainer list of {pkgbase} [1].', + lang).format(pkgbase=self._pkgbase) class ComaintainerRemoveNotification(ComaintainershipEventNotification): def get_body(self, lang): return self._l10n.translate( - 'You were removed from the co-maintainer list of %s [1].', - lang) % (self._pkgbase) + 'You were removed from the co-maintainer list of {pkgbase} ' + '[1].', lang).format(pkgbase=self._pkgbase) class DeleteNotification(Notification): @@ -344,22 +350,24 @@ class DeleteNotification(Notification): return self._recipients def get_subject(self, lang): - return self._l10n.translate('AUR Package deleted: %s', lang) % \ - (self._old_pkgbase) + return self._l10n.translate('AUR Package deleted: {pkgbase}', + lang).format(pkgbase=self._old_pkgbase) def get_body(self, lang): if self._new_pkgbase: dnlabel = self._l10n.translate('Disable notifications', lang) return self._l10n.translate( - '%s [1] merged %s [2] into %s [3].\n\n' + '{user} [1] merged {old} [2] into {new} [3].\n\n' 'If you no longer wish receive notifications about the ' - 'new package, please go to [3] and click "%s".', lang) % \ - (self._user, self._old_pkgbase, self._new_pkgbase, dnlabel) + 'new package, please go to [3] and click "{label}".', + lang).format(user=self._user, old=self._old_pkgbase, + new=self._new_pkgbase, label=dnlabel) else: return self._l10n.translate( - '%s [1] deleted %s [2].\n\n' + '{user} [1] deleted {pkgbase} [2].\n\n' 'You will no longer receive notifications about this ' - 'package.', lang) % (self._user, self._old_pkgbase) + 'package.', lang).format(user=self._user, + pkgbase=self._old_pkgbase) def get_refs(self): refs = (aur_location + '/account/' + self._user + '/', @@ -488,14 +496,14 @@ class TUVoteReminderNotification(Notification): return self._recipients def get_subject(self, lang): - return self._l10n.translate('TU Vote Reminder: Proposal %d', lang) % \ - (self._vote_id) + return self._l10n.translate('TU Vote Reminder: Proposal {id}', + lang).format(id=self._vote_id) def get_body(self, lang): return self._l10n.translate( - 'Please remember to cast your vote on proposal %d [1]. ' - 'The voting period ends in less than 48 hours.', lang) % \ - (self._vote_id) + 'Please remember to cast your vote on proposal {id} [1]. ' + 'The voting period ends in less than 48 hours.', + lang).format(id=self._vote_id) def get_refs(self): return (aur_location + '/tu/?id=' + str(self._vote_id),) -- 2.17.0
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> --- po/aur.pot | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/po/aur.pot b/po/aur.pot index 2d0ac60..e49dc13 100644 --- a/po/aur.pot +++ b/po/aur.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: AUR v4.5.1\n" +"Project-Id-Version: AUR v4.6.0\n" "Report-Msgid-Bugs-To: https://bugs.archlinux.org/index.php?project=2\n" -"POT-Creation-Date: 2017-11-27 16:23+0100\n" +"POT-Creation-Date: 2018-05-17 22:58+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -526,6 +526,12 @@ msgid "" "packages: " msgstr "" +#: html/pkgdisown.php +msgid "" +"By selecting the checkbox, you confirm that you want to no longer be a " +"package co-maintainer." +msgstr "" + #: html/pkgdisown.php #, php-format msgid "" @@ -1412,7 +1418,7 @@ msgstr "" msgid "Vote for this package" msgstr "" -#: template/pkgbase_actions.php +#: template/pkgbase_actions.php scripts/notify.py msgid "Disable notifications" msgstr "" @@ -1999,3 +2005,127 @@ msgstr "" #: template/tu_list.php msgid "Back" msgstr "" + +#: scripts/notify.py +msgid "AUR Password Reset" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "" +"A password reset request was submitted for the account {user} associated " +"with your email address. If you wish to reset your password follow the link " +"[1] below, otherwise ignore this message and nothing will happen." +msgstr "" + +#: scripts/notify.py +msgid "Welcome to the Arch User Repository" +msgstr "" + +#: scripts/notify.py +msgid "" +"Welcome to the Arch User Repository! In order to set an initial password for " +"your new account, please click the link [1] below. If the link does not " +"work, try copying and pasting it into your browser." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "AUR Comment for {pkgbase}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "{user} [1] added the following comment to {pkgbase} [2]:" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "" +"If you no longer wish to receive notifications about this package, please go " +"to the package page [2] and select \"{label}\"." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "AUR Package Update: {pkgbase}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "{user} [1] pushed a new commit to {pkgbase} [2]." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "AUR Out-of-date Notification for {pkgbase}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "Your package {pkgbase} [1] has been flagged out-of-date by {user} [2]:" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "AUR Ownership Notification for {pkgbase}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "The package {pkgbase} [1] was adopted by {user} [2]." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "The package {pkgbase} [1] was disowned by {user} [2]." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "AUR Co-Maintainer Notification for {pkgbase}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "You were added to the co-maintainer list of {pkgbase} [1]." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "You were removed from the co-maintainer list of {pkgbase} [1]." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "AUR Package deleted: {pkgbase}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "" +"{user} [1] merged {old} [2] into {new} [3].\n" +"\n" +"If you no longer wish receive notifications about the new package, please go " +"to [3] and click \"{label}\"." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "" +"{user} [1] deleted {pkgbase} [2].\n" +"\n" +"You will no longer receive notifications about this package." +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "TU Vote Reminder: Proposal {id}" +msgstr "" + +#: scripts/notify.py +#, python-brace-format +msgid "" +"Please remember to cast your vote on proposal {id} [1]. The voting period " +"ends in less than 48 hours." +msgstr "" -- 2.17.0
participants (1)
-
Lukas Fleischer