diff --git a/.gitignore b/.gitignore index 8291b25..50e4a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Mailnag/plugins/messagingmenuplugin.py *.egg-info/ .python-version +*~ diff --git a/AUTHORS b/AUTHORS index 49722e0..52bf68a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Popper was written by Ralf Hersel . Code, docs and packaging contributors: ====================================== +André Auzi Amin Bandali Andreas Angerer Balló György diff --git a/Mailnag/backends/base.py b/Mailnag/backends/base.py index 9ef31a6..c6e1a92 100644 --- a/Mailnag/backends/base.py +++ b/Mailnag/backends/base.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2020 Patrick Ulbrich # Copyright 2016, 2024 Timo Kankare # @@ -54,9 +55,9 @@ def is_open(self) -> bool: raise NotImplementedError @abstractmethod - def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: + def list_messages(self) -> Iterator[tuple[str, Message, str | None, dict[str, Any]]]: """Lists unseen messages from the mailbox for this account. - Yields tuples (folder, message, flags) for every message. + Yields tuples (folder, message, uid, flags) for every message. """ raise NotImplementedError @@ -78,7 +79,7 @@ def mark_as_seen(self, mails: list[Mail]): This may raise an exception if mailbox does not support this action. """ raise NotImplementedError - + def supports_notifications(self) -> bool: """Returns True if mailbox supports notifications.""" # Default implementation @@ -105,3 +106,9 @@ def cancel_notifications(self) -> None: """ raise NotImplementedError + @abstractmethod + def fetch_text(self, mail: Mail) -> str | None: + """Fetches the text body of the message identified by an uid. + This should raise an exception if uid is None. + """ + raise NotImplementedError diff --git a/Mailnag/backends/imap.py b/Mailnag/backends/imap.py index 72a8dcd..0db0915 100644 --- a/Mailnag/backends/imap.py +++ b/Mailnag/backends/imap.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2011 - 2021 Patrick Ulbrich # Copyright 2020 Andreas Angerer # Copyright 2016, 2024 Timo Kankare @@ -26,6 +27,7 @@ import email import logging import re +import time from collections.abc import Callable from email.message import Message from typing import Any, Iterator, Optional @@ -35,12 +37,16 @@ from Mailnag.common.imaplib2 import AUTH from Mailnag.common.exceptions import InvalidOperationException from Mailnag.common.mutf7 import encode_mutf7, decode_mutf7 -from Mailnag.daemon.mails import Mail +from Mailnag.daemon.mails import Mail, message_text +from Mailnag.common.utils import dbgindent, get_goa_account_id, refresh_goa_token +_LOGGER = logging.getLogger(__name__) class IMAPMailboxBackend(MailboxBackend): """Implementation of IMAP mail boxes.""" + THROTTLE_TIME=0.25 + def __init__( self, name: str = '', @@ -64,15 +70,19 @@ def __init__( self.folders = [encode_mutf7(folder) for folder in folders] self.idle = idle self._conn: Optional[imaplib.IMAP4] = None + self._last_access = None + self.goa_account_id = None + if self.oauth2string: + self.goa_account_id = get_goa_account_id(name, user) def open(self) -> None: if self._conn != None: raise InvalidOperationException("Account is aready open") - + self._conn = self._connect() - + def close(self) -> None: # if conn has already been closed, don't try to close it again if self._conn is not None: @@ -84,12 +94,12 @@ def close(self) -> None: def is_open(self) -> bool: return self._conn != None - - def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: + + def list_messages(self) -> Iterator[tuple[str, Message, str | None, dict[str, Any]]]: self._ensure_open() assert self._conn is not None conn = self._conn - + if len(self.folders) == 0: folder_list = ['INBOX'] else: @@ -101,27 +111,61 @@ def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: try: status, data = conn.uid('SEARCH', None, '(UNSEEN)') # ALL or UNSEEN except: - logging.warning('Folder %s does not exist.', folder) + _LOGGER.warning('Folder %s does not exist.', folder) continue if status != 'OK' or None in [d for d in data]: - logging.debug('Folder %s in status %s | Data: %s', (folder, status, data)) + _LOGGER.debug('Folder %s in status %s | Data: %s', folder, status, data) continue # Bugfix LP-735071 + for num in data[0].split(): - typ, msg_data = conn.uid('FETCH', num, '(BODY.PEEK[HEADER])') # header only (without setting READ flag) + typ, msg_data = conn.uid('FETCH', num, '(BODY.PEEK[HEADER])') # header (without setting READ flag) + _LOGGER.debug("Msg data (length=%d):\n%s", len(msg_data), + dbgindent(msg_data)) + header = None for response_part in msg_data: if isinstance(response_part, tuple): - try: - msg = email.message_from_bytes(response_part[1]) - except: - logging.debug("Couldn't get IMAP message.") - continue - yield (folder, msg, {'uid' : num.decode("utf-8"), 'folder' : folder}) + if b'BODY[HEADER]' in response_part[0]: + header = email.message_from_bytes(response_part[1]) + if header: + _LOGGER.debug("Msg header:\n%s", dbgindent(header)) + yield (folder, header, num.decode("utf-8"), { 'folder' : folder }) + + def fetch_text(self, mail: Mail) -> str | None: + text = self._fetch_text(mail) + + # NOTE: Sometimes a server does not return the text immediately + if text is not None: + return text + + _LOGGER.warning('Retry fetch_text.') + return self._fetch_text(mail) + + def _fetch_text(self, mail: Mail) -> str | None: + self._ensure_open() + assert self._conn is not None + conn = self._conn + + #AAU#typ, msg_data = conn.uid('FETCH', mail.uid.encode('utf-8'), '(RFC822)') # body text (setting READ flag) + typ, msg_data = conn.uid('FETCH', mail.uid.encode('utf-8'), '(BODY.PEEK[])') # body text (without setting READ flag) + _LOGGER.debug("Msg data (length=%d):\n%s", len(msg_data), + dbgindent(msg_data)) + + bbb = b'' + for response_part in msg_data: + if isinstance(response_part, tuple): + bbb += response_part[1] + + msg = email.message_from_bytes(bbb) + if msg is not None: + return message_text(msg) + + return None def request_folders(self) -> list[str]: lst = [] - + # Always create a new connection as an existing one may # be used for IMAP IDLE. conn = self._connect() @@ -130,16 +174,16 @@ def request_folders(self) -> list[str]: status, data = conn.list() finally: self._disconnect(conn) - + for d in data: match = re.match(r'.+\s+("."|"?NIL"?)\s+"?([^"]+)"?$', d.decode('utf-8')) if match is None: - logging.warning("Folder format not supported.") + _LOGGER.warning("Folder format not supported.") else: folder = match.group(2) lst.append(decode_mutf7(folder)) - + return lst @@ -151,11 +195,11 @@ def mark_as_seen(self, mails: list[Mail]) -> None: # Always create a new connection as an existing one may # be used for IMAP IDLE. conn = self._connect() - + try: sorted_mails = sorted(mails, key = lambda m : m.flags['folder'] if 'folder' in m.flags else '') last_folder = '' - + for m in sorted_mails: if ('uid' in m.flags) and ('folder' in m.flags): try: @@ -165,12 +209,13 @@ def mark_as_seen(self, mails: list[Mail]) -> None: last_folder = folder status, data = conn.uid("STORE", m.flags['uid'], "+FLAGS", r"(\Seen)") except: - logging.warning("Failed to set mail with uid %s to seen on server (account: '%s').", m.flags['uid'], self.name) + _LOGGER.warning("Failed to set mail with uid %s to seen on server (account: '%s').", + m.flags['uid'], self.name) finally: self._disconnect(conn) - - + + def supports_notifications(self) -> bool: """Returns True if mailbox supports notifications. IMAP mailbox supports notifications if idle parameter is True""" @@ -184,7 +229,7 @@ def notify_next_change( ) -> None: self._ensure_open() assert self._conn is not None - + # register idle callback that is called whenever an idle event # arrives (new mail / mail deleted). # the callback is called after minutes at the latest. @@ -197,7 +242,7 @@ def _idle_callback(args: tuple[Any, Any, tuple[str, int]]) -> None: # call actual callback if callback is not None: callback(error) - + self._conn.idle(callback = _idle_callback, timeout = timeout) @@ -206,7 +251,7 @@ def cancel_notifications(self) -> None: # Analogous to close(). # (Otherwise cleanup code like in Idler._idle() will fail) # self._ensure_open() - + try: if self._conn is not None: # Exit possible active idle state. @@ -214,11 +259,39 @@ def cancel_notifications(self) -> None: self._conn.noop() except: pass - + + + def _throttle(self, reset=False): + if not reset and self._last_access is not None: + duration = time.time() - self._last_access + if duration < self.THROTTLE_TIME: + time.sleep(self.THROTTLE_TIME - duration) + self._last_access = time.time() + + + def _refresh_token(self): + _LOGGER.debug("Refresh token...") + if not self.goa_account_id: + return True + + token = refresh_goa_token(self.goa_account_id) + if token is None: + _LOGGER.debug("Refresh GOA token did not return token.") + return False + + oauth2string = 'user=%s\1auth=Bearer %s\1\1' % (self.user, token[0]) + if oauth2string == self.oauth2string: + _LOGGER.debug("OAuth2string did not change.") + return True + + self.oauth2string = oauth2string + _LOGGER.debug("Token refreshed") + return True + def _connect(self) -> imaplib.IMAP4: conn: Optional[imaplib.IMAP4] = None - + try: if self.ssl: if self.port == '': @@ -230,19 +303,22 @@ def _connect(self) -> imaplib.IMAP4: conn = imaplib.IMAP4(self.server) else: conn = imaplib.IMAP4(self.server, int(self.port)) - + if 'STARTTLS' in conn.capabilities: conn.starttls() else: - logging.warning("Using unencrypted connection for account '%s'" % self.name) - + _LOGGER.warning("Using unencrypted connection for account '%s'", self.name) + if self.oauth2string != '': + self._refresh_token() conn.authenticate('XOAUTH2', lambda x: self.oauth2string) elif 'AUTH=CRAM-MD5' in conn.capabilities: # use CRAM-MD5 auth if available conn.login_cram_md5(self.user, self.password) else: conn.login(self.user, self.password) + + self._throttle(reset=True) except: try: if conn is not None: @@ -250,31 +326,31 @@ def _connect(self) -> imaplib.IMAP4: conn.logout() except: pass raise # re-throw exception - + # notify_next_change() (IMAP IDLE) requires a selected folder if conn.state == AUTH: self._select_single_folder(conn) - + return conn def _disconnect(self, conn: imaplib.IMAP4) -> None: try: conn.close() - finally: - # Closing the connection may fail (e.g. wrong state), + finally: + # Closing the connection may fail (e.g. wrong state), # but resources need to be freed anyway. conn.logout() - - + + def _select_single_folder(self, conn: imaplib.IMAP4) -> None: if len(self.folders) == 1: folder = self.folders[0] else: folder = "INBOX" conn.select(f'"{folder}"', readonly = True) - + def _ensure_open(self) -> None: if not self.is_open(): raise InvalidOperationException("Account is not open") - + self._throttle() diff --git a/Mailnag/backends/local.py b/Mailnag/backends/local.py index 4052c55..194ec1e 100644 --- a/Mailnag/backends/local.py +++ b/Mailnag/backends/local.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2020 Patrick Ulbrich # Copyright 2016, 2024 Timo Kankare # @@ -26,6 +27,7 @@ from Mailnag.backends.base import MailboxBackend from Mailnag.common.exceptions import InvalidOperationException +from Mailnag.daemon.mails import message_text class MBoxBackend(MailboxBackend): @@ -55,7 +57,7 @@ def is_open(self) -> bool: return self._opened - def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: + def list_messages(self) -> Iterator[tuple[str, Message, str | None, dict[str, Any]]]: """List unread messages from the mailbox. Yields tuples (folder, message, flags) where folder is always ''. """ @@ -66,13 +68,28 @@ def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: mbox = mailbox.mbox(self._path, create=False) folder = '' try: - for msg in mbox: + for key, msg in mbox.iteritems(): if 'R' not in msg.get_flags(): - yield (folder, msg, {}) + yield (folder, msg, str(key), {}) finally: mbox.close() + def fetch_text(self, mail: Mail) -> str | None: + if self._path is None: + raise InvalidOperationException( + "Path is not defined for Mbox '{self._name}'" + ) + mbox = mailbox.mbox(self._path, create=False) + + msg = mbox.get(mail.uid) + if msg is not None: + return message_text(msg) + + return None + + + def request_folders(self) -> list[str]: """mbox does not suppoert folders.""" raise NotImplementedError("mbox does not support folders") @@ -133,7 +150,7 @@ def is_open(self) -> bool: return self._opened - def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: + def list_messages(self) -> Iterator[tuple[str, Message, str | None, dict[str, Any]]]: """List unread messages from the mailbox. Yields tuples (folder, message, flags). """ diff --git a/Mailnag/backends/pop3.py b/Mailnag/backends/pop3.py index 7835726..ebc9aff 100644 --- a/Mailnag/backends/pop3.py +++ b/Mailnag/backends/pop3.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2011 - 2019 Patrick Ulbrich # Copyright 2016, 2024 Timo Kankare # Copyright 2016 Thomas Haider @@ -30,11 +31,13 @@ from Mailnag.backends.base import MailboxBackend from Mailnag.common.exceptions import InvalidOperationException +from Mailnag.daemon.mails import message_text +_LOGGER = logging.getLogger(__name__) class POP3MailboxBackend(MailboxBackend): """Implementation of POP3 mail boxes.""" - + def __init__( self, name: str = '', @@ -59,9 +62,9 @@ def __init__( def open(self) -> None: if self._conn != None: raise InvalidOperationException("Account is aready open") - + conn: Optional[poplib.POP3] = None - + try: if self.ssl: if self.port == '': @@ -73,12 +76,12 @@ def open(self) -> None: conn = poplib.POP3(self.server) else: conn = poplib.POP3(self.server, int(self.port)) - + try: conn.stls() except: - logging.warning("Using unencrypted connection for account '%s'" % self.name) - + _LOGGER.warning("Using unencrypted connection for account '%s'", self.name) + conn.getwelcome() conn.user(self.user) conn.pass_(self.password) @@ -88,7 +91,7 @@ def open(self) -> None: conn.quit() except: pass raise # re-throw exception - + self._conn = conn @@ -103,12 +106,12 @@ def is_open(self) -> bool: ('sock' in self._conn.__dict__) - def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: + def list_messages(self) -> Iterator[tuple[str, Message, str | None, dict[str, Any]]]: self._ensure_open() assert self._conn is not None conn = self._conn folder = '' - + # number of mails on the server mail_total = len(conn.list()[1]) for i in range(1, mail_total + 1): # for each mail @@ -116,18 +119,63 @@ def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: # header plus first 0 lines from body message = conn.top(i, 0)[1] except: - logging.debug("Couldn't get POP message.") + _LOGGER.debug("Couldn't get POP message.") continue - + + uid = None + try: + # uid of message + uid = conn.uidl(i).split()[3] + except: + _LOGGER.debug("Couldn't get POP message uid.") + # convert list to byte sequence message_bytes = b'\n'.join(message) - + try: msg = email.message_from_bytes(message_bytes) except: - logging.debug("Couldn't get msg from POP message.") + _LOGGER.debug("Couldn't get msg from POP message.") continue - yield (folder, msg, {}) + yield (folder, msg, uid, {}) + + def fetch_text(self, mail: Mail) -> str | None: + if mail.uid == None: + raise NotImplementedError("POP3 does not support uidl") + + self._ensure_open() + assert self._conn is not None + conn = self._conn + + for entry in conn.uidl(): + msgnum, msguid = tuple(entry.split()) + i = int(msgnum) + if mail.uid == msguid: + try: + # get message size + size = int(conn.stat(i)[1]) + except: + _LOGGER.debug("Couldn't get msg size from POP.") + break + + try: + # header plus lines from body + message = conn.top(i, size)[1] + except: + _LOGGER.debug("Couldn't get POP message.") + break + + message_bytes = b'\n'.join(message) + + try: + msg = email.message_from_bytes(message_bytes) + return message_text(msg) + except: + _LOGGER.debug("Couldn't get msg from POP message.") + break + + break + return None def request_folders(self) -> list[str]: diff --git a/Mailnag/common/accounts.py b/Mailnag/common/accounts.py index fffa75b..2203161 100644 --- a/Mailnag/common/accounts.py +++ b/Mailnag/common/accounts.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2011 - 2020 Patrick Ulbrich # Copyright 2016 Thomas Haider # Copyright 2016, 2018, 2024 Timo Kankare @@ -34,16 +35,18 @@ from Mailnag.common.dist_cfg import PACKAGE_NAME from Mailnag.daemon.mails import Mail +_LOGGER = logging.getLogger(__name__) + account_defaults = { 'enabled' : '0', 'type' : 'imap', - 'name' : '', + 'name' : '', 'user' : '', 'password' : '', 'server' : '', 'port' : '', 'ssl' : '1', - 'imap' : '1', + 'imap' : '1', 'idle' : '1', 'folder' : '[]' } @@ -121,20 +124,55 @@ def close(self) -> None: self._get_backend().close() - # Indicates whether the account + # Indicates whether the account # holds an active existing connection. def is_open(self) -> bool: """Returns true if the mailbox is opened.""" return self._get_backend().is_open() - def list_messages(self) -> Iterator[tuple[str, Message, dict[str, Any]]]: + def list_messages(self) -> Iterator[tuple[str, Message, str | None, dict[str, Any]]]: """Lists unseen messages from the mailbox for this account. Yields a set of tuples (folder, message, flags). """ return self._get_backend().list_messages() + def fetch_text(self, mail: Mail) -> str | None: + ret = None + + # open mailbox for this account + try: + if not self.is_open(): + self.open() + except Exception as ex: + _LOGGER.error("Failed to open mailbox for account '%s' (%s).", self.name, ex) + return ret + + try: + ret = self._get_backend().fetch_text(mail) + except Exception as ex: + # Catch exceptions here, so remaining accounts will still be checked + # if a specific account has issues. + # + # Re-throw the exception for accounts that support notifications (i.e. imap IDLE), + # so the calling idler thread can handle the error and reset the connection if needed (see idlers.py). + # NOTE: Idler threads always check single accounts (i.e. len(self._accounts) == 1), + # so there are no remaining accounts to be checked for now. + if self.supports_notifications(): + raise + else: + _LOGGER.error("An error occured while processing mails of account '%s' (%s).", self.name, ex) + finally: + # leave account with notifications open, so that it can + # send notifications about new mails + if not self.supports_notifications(): + # disconnect from Email-Server + self.close() + + return ret + + def supports_notifications(self) -> bool: """Returns True if account supports notifications.""" return self._get_backend().supports_notifications() @@ -176,8 +214,8 @@ def supports_mark_as_seen(self) -> bool: def mark_as_seen(self, mails: list[Mail]): """Marks mails as seen.""" self._get_backend().mark_as_seen(mails) - - + + def get_id(self) -> str: """Returns unique id for the account.""" # Assumption: The name of the account is unique. @@ -221,52 +259,52 @@ def __init__(self) -> None: self._accounts: list[Account] = [] self._removed: list[Account] = [] self._secretstore = SecretStore.get_default() - + if self._secretstore is None: - logging.warning("Failed to create secretstore - account passwords will be stored in plaintext config file.") + _LOGGER.warning("Failed to create secretstore - account passwords will be stored in plaintext config file.") + - def __len__(self) -> int: """Returns number of accounts""" return len(self._accounts) - - + + def __iter__(self) -> Iterator[Account]: """Returns iterator for accounts""" for acc in self._accounts: yield acc - + def __contains__(self, item: Account) -> bool: """Checks if account is in account managers collection.""" return (item in self._accounts) - - + + def add(self, account: Account) -> None: """Adds account to account manager.""" self._accounts.append(account) - - + + def remove(self, account: Account) -> None: """Removes account from account manager.""" self._accounts.remove(account) self._removed.append(account) - - + + def clear(self) -> None: """Removes all accounts.""" for acc in self._accounts: self._removed.append(acc) del self._accounts[:] - - + + def to_list(self) -> list[Account]: """Returns list of accounts.""" # Don't pass a ref to the internal accounts list. # (Accounts must be removed via the remove() method only.) return self._accounts[:] - - + + def load_from_cfg( self, cfg: RawConfigParser, @@ -275,13 +313,13 @@ def load_from_cfg( """Loads accounts from configuration.""" del self._accounts[:] del self._removed[:] - + i = 1 section_name = "account" + str(i) - + while cfg.has_section(section_name): enabled = bool(int(self._get_account_cfg(cfg, section_name, 'enabled'))) - + if (not enabled_only) or (enabled_only and enabled): if cfg.has_option(section_name, 'type'): mailbox_type = self._get_account_cfg(cfg, section_name, 'type') @@ -295,7 +333,7 @@ def load_from_cfg( options = self._get_cfg_options(cfg, section_name, option_spec) # TODO: Getting a password from the secretstore is mailbox specific. - # Not every backend requires a password. + # Not every backend requires a password. user = options.get('user') server = options.get('server') if self._secretstore is not None and user and server: @@ -313,7 +351,7 @@ def load_from_cfg( i = i + 1 section_name = "account" + str(i) - + def save_to_cfg(self, cfg: RawConfigParser) -> None: """Saves accounts to configuration.""" @@ -324,27 +362,27 @@ def save_to_cfg(self, cfg: RawConfigParser) -> None: cfg.remove_section(section_name) i = i + 1 section_name = "account" + str(i) - + # Delete secrets of removed accounts from the secretstore - # (it's important to do this before adding accounts, + # (it's important to do this before adding accounts, # in case multiple accounts with the same id exist). if self._secretstore is not None: for acc in self._removed: self._secretstore.remove(self._get_account_id(acc.user, acc.server, acc.imap)) - + del self._removed[:] - + # Add accounts i = 1 for acc in self._accounts: if acc.oauth2string != '': - logging.warning("Saving of OAuth2 based accounts is not supported. Account '%s' skipped." % acc.name) + _LOGGER.warning("Saving of OAuth2 based accounts is not supported. Account '%s' skipped.", acc.name) continue - + section_name = "account" + str(i) - + cfg.add_section(section_name) - + cfg.set(section_name, 'enabled', str(int(acc.enabled))) cfg.set(section_name, 'type', acc.mailbox_type) cfg.set(section_name, 'name', acc.name) @@ -353,7 +391,7 @@ def save_to_cfg(self, cfg: RawConfigParser) -> None: option_spec = get_mailbox_parameter_specs(acc.mailbox_type) # TODO: Storing a password is mailbox specific. - # Not every backend requires a password. + # Not every backend requires a password. if self._secretstore is not None: self._secretstore.set( self._get_account_id(acc.user, acc.server, acc.imap), @@ -365,13 +403,13 @@ def save_to_cfg(self, cfg: RawConfigParser) -> None: self._set_cfg_options(cfg, section_name, config, option_spec) i = i + 1 - - + + def _get_account_id(self, user: str, server: str, is_imap: bool) -> str: # TODO : Introduce account.uuid when rewriting account and backend code return hashlib.md5((user + server + str(is_imap)).encode('utf-8')).hexdigest() - - + + def _get_account_cfg( self, cfg: RawConfigParser, @@ -428,4 +466,3 @@ def _set_cfg_options( else: value = s.default_value cfg.set(section_name, s.option_name, value) - diff --git a/Mailnag/common/config.py b/Mailnag/common/config.py index 2d2a443..bab914f 100644 --- a/Mailnag/common/config.py +++ b/Mailnag/common/config.py @@ -44,6 +44,7 @@ def cfg_exists() -> bool: def read_cfg() -> RawConfigParser: cfg = RawConfigParser() + cfg.optionxform = str cfg.read_dict(mailnag_defaults) if os.path.exists(cfg_file): diff --git a/Mailnag/common/imaplib2.py b/Mailnag/common/imaplib2.py index 70fa8d3..7996685 100644 --- a/Mailnag/common/imaplib2.py +++ b/Mailnag/common/imaplib2.py @@ -52,7 +52,8 @@ Fix for correct Python 3 exception handling by Tobias Brink August 2015. Fix to allow interruptible IDLE command by Tim Peoples September 2015. Add support for TLS levels by Ben Boeckel September 2015. -Fix for shutown exception by Sebastien Gross November 2015.""" +Fix for shutown exception by Sebastien Gross November 2015. +Fix unknown string escape sequences by André Auzi November 2025.""" __author__ = "Piers Lauder " __URL__ = "http://imaplib2.sourceforge.net" __license__ = "Python License" @@ -2466,7 +2467,7 @@ def ParseFlags(resp): ('select', ('imaplib2_test2',)), ('search', (None, 'SUBJECT', '"IMAP4 test"')), ('fetch', ('1:*', '(FLAGS INTERNALDATE RFC822)')), - ('store', ('1', 'FLAGS', r'(\Deleted)')), + ('store', ('1', 'FLAGS', '(\\Deleted)')), ('namespace', ()), ('expunge', ()), ('recent', ()), @@ -2571,7 +2572,7 @@ def run(cmd, args, cb=True): if not uid: continue run('uid', ('FETCH', uid[-1], '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)')) - run('uid', ('STORE', uid[-1], 'FLAGS', r'(\Deleted)')) + run('uid', ('STORE', uid[-1], 'FLAGS', '(\\Deleted)')) run('expunge', ()) if 'IDLE' in M.capabilities: @@ -2585,10 +2586,10 @@ def run(cmd, args, cb=True): dat = run('fetch', (num, '(FLAGS INTERNALDATE RFC822)'), cb=False) M._mesg('fetch %s => %s' % (num, repr(dat))) run('idle', (2,)) - run('store', (num, '-FLAGS', r'(\Seen)'), cb=False), + run('store', (num, '-FLAGS', '(\\Seen)'), cb=False), dat = run('fetch', (num, '(FLAGS INTERNALDATE RFC822)'), cb=False) M._mesg('fetch %s => %s' % (num, repr(dat))) - run('uid', ('STORE', num, 'FLAGS', r'(\Deleted)')) + run('uid', ('STORE', num, 'FLAGS', '(\\Deleted)')) run('expunge', ()) if idle_intr: M._mesg('HIT CTRL-C to interrupt IDLE') diff --git a/Mailnag/common/plugins.py b/Mailnag/common/plugins.py index f6ef94b..2b4f150 100644 --- a/Mailnag/common/plugins.py +++ b/Mailnag/common/plugins.py @@ -34,20 +34,21 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk +_LOGGER = logging.getLogger(__name__) # # All known hook types. # class HookTypes(Enum): - # func signature: + # func signature: # IN: List of loaded accounts # OUT: None ACCOUNTS_LOADED = 'accounts-loaded' - # func signature: + # func signature: # IN: None # OUT: None MAIL_CHECK = 'mail-check' - # func signature: + # func signature: # IN: new mails, all mails # OUT: None MAILS_ADDED = 'mails-added' @@ -65,7 +66,7 @@ class HookTypes(Enum): # Registry class for plugin hooks. # # Registered hook functions must not block the mailnag daemon. -# Hook functions with an execution time > 1s should be +# Hook functions with an execution time > 1s should be # implemented non-blocking (i. e. asynchronously). class HookRegistry: def __init__(self) -> None: @@ -74,9 +75,9 @@ def __init__(self) -> None: HookTypes.MAIL_CHECK : [], HookTypes.MAILS_ADDED : [], HookTypes.MAILS_REMOVED : [], - HookTypes.FILTER_MAILS : [] + HookTypes.FILTER_MAILS : [] } - + # Priority should be an integer value fom 0 (very high) to 100 (very low) # Plugin hooks will be called in order from high to low priority. def register_hook_func( @@ -86,21 +87,21 @@ def register_hook_func( priority: int = 100 ) -> None: self._hooks[hooktype].append((priority, func)) - - + + def unregister_hook_func(self, hooktype: HookTypes, func: Callable) -> None: pairs = self._hooks[hooktype] pair = next(pa for pa in pairs if (pa[1] == func)) pairs.remove(pair) - - + + def get_hook_funcs(self, hooktype: HookTypes) -> list[Callable]: pairs_by_prio = sorted(self._hooks[hooktype], key = lambda p: p[0]) funcs = [f for p, f in pairs_by_prio] return funcs -# Abstract base class for a MailnagController instance +# Abstract base class for a MailnagController instance # passed to plugins. class MailnagController(ABC): # Returns a HookRegistry object. @@ -129,74 +130,74 @@ def mark_mail_as_read(self, mail_id: str) -> None: # class Plugin: def __init__(self) -> None: - # Plugins shouldn't do anything in the constructor. - # They are expected to start living if they are actually + # Plugins shouldn't do anything in the constructor. + # They are expected to start living if they are actually # enabled (i.e. in the enable() method). # Plugin data isn't enabled yet and call to methods like # get_mailnag_controller() or get_config(). pass - + # - # Abstract methods, + # Abstract methods, # to be overriden by derived plugin types. # def enable(self) -> None: # Plugins are expected to # register all hooks here. raise NotImplementedError - - + + def disable(self) -> None: # Plugins are expected to - # unregister all hooks here, - # free all allocated resources, + # unregister all hooks here, + # free all allocated resources, # and terminate threads (if any). raise NotImplementedError - - + + def get_manifest(self) -> tuple[str, str, str, str]: # Plugins are expected to # return a tuple of the following form: # (name, description, version, author). raise NotImplementedError - - + + def get_default_config(self) -> dict[str, Any]: # Plugins are expected to return a # dictionary with default values. raise NotImplementedError - - + + def has_config_ui(self) -> bool: # Plugins are expected to return True if # they provide a configuration widget, # otherwise they must return False. raise NotImplementedError - - + + def get_config_ui(self) -> Optional[Gtk.Widget]: # Plugins are expected to # return a GTK widget here. - # Return None if the plugin + # Return None if the plugin # does not need a config widget. raise NotImplementedError - + def load_ui_from_config(self, config_ui: Gtk.Widget) -> None: # Plugins are expected to - # load their config values (get_config()) + # load their config values (get_config()) # in the widget returned by get_config_ui(). raise NotImplementedError - - + + def save_ui_to_config(self, config_ui: Gtk.Widget) -> None: # Plugins are expected to # save the config values of the widget # returned by get_config_ui() to their config # (get_config()). raise NotImplementedError - - + + # # Public methods # @@ -207,50 +208,50 @@ def init( mailnag_controller: Optional[MailnagController] ) -> None: config = {} - + # try to load plugin config if cfg.has_section(modname): for name, value in cfg.items(modname): config[name] = value - + # sync with default config default_config = self.get_default_config() for k, v in default_config.items(): if k not in config: config[k] = v - + self._modname = modname self._config = config self._mailnag_controller = mailnag_controller - - + + def get_name(self) -> str: name = self.get_manifest()[0] return name - - + + def get_modname(self) -> str: return self._modname - - + + def get_config(self) -> dict[str, Any]: return self._config - - + + # # Protected methods # def get_mailnag_controller(self) -> MailnagController: assert self._mailnag_controller is not None, "Plugin is not initialized with valid MailnagController" return self._mailnag_controller - - + + # # Static methods # - - # Note : Plugin instances do not own - # a reference to MailnagController object + + # Note : Plugin instances do not own + # a reference to MailnagController object # when instantiated in *config mode*. @staticmethod def load_plugins( @@ -260,41 +261,51 @@ def load_plugins( ) -> list["Plugin"]: plugins = [] plugin_types = Plugin._load_plugin_types() - - for modname, t in plugin_types: + + for modname, t in plugin_types: try: if (filter_names is None) or (modname in filter_names): p = t() p.init(modname, cfg, mailnag_controller) plugins.append(p) except: - logging.exception("Failed to instantiate plugin '%s'" % modname) - + _LOGGER.exception("Failed to instantiate plugin '%s'", modname) + return plugins - - + + @staticmethod def _load_plugin_types() -> list[tuple[str, type["Plugin"]]]: plugin_types = [] - - for path in get_plugin_paths(): + + plugin_paths = get_plugin_paths() + modname_prefixes = { + plugin_paths[0]: 'Mailnag.plugins.', + plugin_paths[1]: 'Config.plugins.' + } + + for path in plugin_paths: if not path.is_dir(): continue - + + modname_p = modname_prefixes.get(path, None) + for f in path.iterdir(): mod = None modname, ext = os.path.splitext(f.name) filename = str(path / f.name) - + + full_modname = modname_p + modname if modname_p else modname + try: if ext.lower() == '.py': - loader: importlib.abc.SourceLoader = importlib.machinery.SourceFileLoader(modname, filename) + loader: importlib.abc.SourceLoader = importlib.machinery.SourceFileLoader(full_modname, filename) elif ext.lower() == '.pyc': - loader = importlib.machinery.SourcelessFileLoader(modname, filename) + loader = importlib.machinery.SourcelessFileLoader(full_modname, filename) else: continue - spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + spec = importlib.util.spec_from_file_location(full_modname, filename, loader=loader) if spec is None: continue @@ -309,6 +320,6 @@ def _load_plugin_types() -> list[tuple[str, type["Plugin"]]]: if issubclass(attr, Plugin) and attr != Plugin: plugin_types.append((modname, attr)) except: - logging.exception("Error while opening plugin file '%s'" % f) - + _LOGGER.exception("Error while opening plugin file '%s'", f) + return plugin_types diff --git a/Mailnag/common/subproc.py b/Mailnag/common/subproc.py index 3e654f4..7be215d 100644 --- a/Mailnag/common/subproc.py +++ b/Mailnag/common/subproc.py @@ -23,6 +23,8 @@ from collections.abc import Callable from typing import Optional, Union +_LOGGER = logging.getLogger(__name__) + # Note : All functions of this module *are* thread-safe. # Protects access to the proc dictionary. @@ -63,7 +65,7 @@ def thread() -> None: pid = -1 try: p = subprocess.Popen(args, shell = shell) - except: logging.exception('Caught an exception.') + except: _LOGGER.exception('Caught an exception.') if p is not None: pid = p.pid @@ -96,7 +98,7 @@ def terminate_subprocesses(timeout: float = 3.0) -> None: # waiting for p.wait(). # Note : terminate() does not block. try: p.terminate() - except: logging.debug('p.terminate() failed') + except: _LOGGER.debug('p.terminate() failed') # Start a watchdog thread that will kill # all processes that didn't terminate within seconds. @@ -110,7 +112,7 @@ def terminate_subprocesses(timeout: float = 3.0) -> None: wd.stop() if not wd.triggered: - logging.info('All subprocesses exited normally.') + _LOGGER.info('All subprocesses exited normally.') # Internal Watchdog class @@ -125,7 +127,7 @@ def __init__(self, timeout: float): def run(self) -> None: self._event.wait(self._timeout) if not self._event.is_set(): - logging.warning('Process termination took too long - watchdog starts killing...') + _LOGGER.warning('Process termination took too long - watchdog starts killing...') self.triggered = True with _proc_lock: for t, p in _procs.items(): @@ -133,8 +135,8 @@ def run(self) -> None: # Kill process p and quit the thread # waiting for p to terminate (p.wait()). p.kill() - logging.info('Watchdog killed process %s' % p.pid) - except: logging.debug('p.kill() failed') + _LOGGER.info('Watchdog killed process %d', p.pid) + except: _LOGGER.debug('p.kill() failed') def stop(self) -> None: diff --git a/Mailnag/common/utils.py b/Mailnag/common/utils.py index 6317a8e..64102b3 100644 --- a/Mailnag/common/utils.py +++ b/Mailnag/common/utils.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2024 Timo Kankare # Copyright 2011 - 2019 Patrick Ulbrich # Copyright 2007 Marco Ferragina @@ -22,16 +23,22 @@ import time import dbus import logging +import logging.config import logging.handlers from collections.abc import Callable from typing import TypeVar +import gi +gi.require_version('Goa', '1.0') +from gi.repository import Goa +from Mailnag.common.config import read_cfg from Mailnag.common.dist_cfg import DBUS_BUS_NAME, DBUS_OBJ_PATH LOG_FORMAT = '%(levelname)s (%(asctime)s): %(message)s' LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' +_LOGGER = logging.getLogger(__name__) def init_logging( enable_stdout: bool = True, @@ -42,20 +49,57 @@ def init_logging( format = LOG_FORMAT, datefmt = LOG_DATE_FORMAT, level = log_level) - + logger = logging.getLogger('') - + if not enable_stdout: stdout_handler = logger.handlers[0] logger.removeHandler(stdout_handler) - + if enable_syslog: syslog_handler = logging.handlers.SysLogHandler(address='/dev/log') syslog_handler.setLevel(log_level) syslog_handler.setFormatter(logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)) - + logger.addHandler(syslog_handler) + configure_logging() + +def configure_logging() -> None: + VALID_LEVELS = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} + + config = { + "version": 1, + "disable_existing_loggers": False, + "incremental": True, + "root": { + "level": logging.getLevelName(logging.getLogger().getEffectiveLevel()) + }, + "loggers": {}, + } + + cfg = read_cfg() + + if not 'logger_levels' in cfg: + return + + raw_levels = dict(cfg.items('logger_levels')) + for name, level in raw_levels.items(): + clean_level = str(level).upper().strip() + + if clean_level not in VALID_LEVELS: + continue + + if name == "root": + config["root"]["level"] = clean_level + else: + config["loggers"][name] = { + "level": clean_level, + "propagate": True + } + + logging.config.dictConfig(config) + def splitstr(strn: str, delimeter: str) -> list[str]: return [s.strip() for s in strn.split(delimeter) if s.strip()] @@ -76,27 +120,78 @@ def try_call(f: Callable[[], T], err_retval: T) -> T: try: return f() except: - logging.exception('Caught an exception.') + _LOGGER.exception('Caught an exception.') return err_retval def shutdown_existing_instance(wait_for_completion: bool = True) -> None: bus = dbus.SessionBus() - + if bus.name_has_owner(DBUS_BUS_NAME): sys.stdout.write('Shutting down existing Mailnagger process...') sys.stdout.flush() - + try: proxy = bus.get_object(DBUS_BUS_NAME, DBUS_OBJ_PATH) shutdown = proxy.get_dbus_method('Shutdown', DBUS_BUS_NAME) - + shutdown() - + if wait_for_completion: while bus.name_has_owner(DBUS_BUS_NAME): time.sleep(2) - + print('OK') except: print('FAILED') + + +def get_goa_account_id(name, user): + _LOGGER.debug("Get GOA account: name: %s, user: %s", name, user) + + client = Goa.Client.new_sync(None) + goa_accounts = client.get_accounts() + + for obj in goa_accounts: + account = obj.get_account() + if account is None or account.props.mail_disabled: + continue + mail = obj.get_mail() + if mail is None or not mail.props.imap_supported: + continue + + _LOGGER.debug(" account: name: %s, user: %s", + mail.props.email_address, + mail.props.imap_user_name) + if (name == mail.props.email_address + and user == mail.props.imap_user_name): + identity = account.get_property('id') + _LOGGER.debug(" account: name: %s, user: %s, id: %s", + mail.props.email_address, + mail.props.imap_user_name, + identity) + return identity + return None + + +def refresh_goa_token(account_id): + client = Goa.Client.new_sync(None) + obj = client.lookup_by_id(account_id) + if obj is None: + return None + + oauth2_based = obj.get_oauth2_based() + if oauth2_based is None: + return None + + return oauth2_based.call_get_access_token_sync(None) + + +def strlimit(txt: str) -> str: + txt = str(txt) + return txt[:min(80, len(txt))] + '...' + + +def dbgindent(txt: str) -> str: + txt = strlimit(str(txt).strip()) + return ' ' + '\n '.join(txt.splitlines()) diff --git a/Mailnag/configuration/configwindow.py b/Mailnag/configuration/configwindow.py index 5260fed..30cd57b 100644 --- a/Mailnag/configuration/configwindow.py +++ b/Mailnag/configuration/configwindow.py @@ -21,6 +21,7 @@ import gi gi.require_version('Gtk', '3.0') +import logging import os import xdg.BaseDirectory as bd from gi.repository import Gtk @@ -35,6 +36,7 @@ import Mailnag.configuration.ui import Mailnag.configuration.desktop +_LOGGER = logging.getLogger(__name__) class ConfigWindow: def __init__(self, app): @@ -262,7 +264,7 @@ def _create_autostart(self): f.write(strn) except Exception as e: import logging - logging.info(f"failed setting autostart: {e}") + _LOGGER.info(f"failed setting autostart: {e}") return diff --git a/Mailnag/daemon/dbus.py b/Mailnag/daemon/dbus.py index 8735dce..5c4184a 100644 --- a/Mailnag/daemon/dbus.py +++ b/Mailnag/daemon/dbus.py @@ -25,6 +25,8 @@ from Mailnag.common.plugins import MailnagController from Mailnag.daemon.mails import Mail +_LOGGER = logging.getLogger(__name__) + MAX_INT32 = ((0xFFFFFFFF // 2) - 1) @@ -113,7 +115,7 @@ def _convert_mails(self, mails: list[Mail]) -> list[dict[str, Union[str, int]]]: name, addr = m.sender if m.datetime > MAX_INT32: - logging.warning('dbusservice: datetime out of range (mailnag dbus api uses int32 timestamps).') + _LOGGER.warning('dbusservice: datetime out of range (mailnag dbus api uses int32 timestamps).') datetime = 0 else: datetime = m.datetime diff --git a/Mailnag/daemon/idlers.py b/Mailnag/daemon/idlers.py index 11816eb..59e6eea 100644 --- a/Mailnag/daemon/idlers.py +++ b/Mailnag/daemon/idlers.py @@ -26,6 +26,7 @@ from Mailnag.common.exceptions import InvalidOperationException from Mailnag.common.accounts import Account +_LOGGER = logging.getLogger(__name__) # # Idler class @@ -64,7 +65,7 @@ def dispose(self) -> None: self._thread.join() self._disposed = True - logging.info('Idler closed') + _LOGGER.info('Idler closed') # idle thread @@ -74,9 +75,9 @@ def _idle(self) -> None: try: self._account.open() except Exception as ex: - logging.error("Failed to open mailbox for account '%s' (%s)." % (self._account.name, ex)) - logging.info("Trying to reconnect Idler thread for account '%s' in %s minutes" % - (self._account.name, str(self.RECONNECT_RETRY_INTERVAL))) + _LOGGER.error("Failed to open mailbox for account '%s' (%s).", self._account.name, ex) + _LOGGER.info("Trying to reconnect Idler thread for account '%s' in %d minutes", + self._account.name, self.RECONNECT_RETRY_INTERVAL) self._wait(60 * self.RECONNECT_RETRY_INTERVAL) # don't hammer the server while not self._disposing: @@ -90,7 +91,7 @@ def _idle(self) -> None: # (in idle callback or in dispose()) self._event.wait() except: - logging.exception('Caught an exception.') + _LOGGER.exception('Caught an exception.') # Reset current connection if the call to notify_next_change() fails # as this is probably connection related (e.g. conn terminated). self._reset_conn() @@ -109,7 +110,7 @@ def _idle(self) -> None: # Fetch and sync mails from account self._sync_callback(self._account) except: - logging.exception('Caught an exception.') + _LOGGER.exception('Caught an exception.') # Reset current connection if the call to sync_callback() (mail sync) fails. # An immediate sync has already been performed successfully on startup, # so if an error occurs here, it may be connection related (e.g. conn terminated). @@ -126,7 +127,8 @@ def _idle_callback(self, error: Optional[tuple[str, int]]) -> None: # flag the need for a connection reset in case of an error (e.g. conn terminated) if error is not None: error_type, error_val = error - logging.error("Idle callback for account '%s' returned an error (%s - %s)." % (self._account.name, error_type, str(error_val))) + _LOGGER.error("Idle callback for account '%s' returned an error (%s - %s).", + self._account.name, error_type, str(error_val)) self._needreset = True # trigger waiting _idle thread @@ -135,7 +137,7 @@ def _idle_callback(self, error: Optional[tuple[str, int]]) -> None: def _reset_conn(self) -> None: # Try to reset the connection to recover from a possible connection error (e.g. after system suspend) - logging.info("Resetting connection for account '%s'" % self._account.name) + _LOGGER.info("Resetting connection for account '%s'", self._account.name) try: self._account.close() except: pass self._reconnect() @@ -143,17 +145,17 @@ def _reset_conn(self) -> None: def _reconnect(self) -> None: # connection has been reset by provider -> try to reconnect - logging.info("Idler thread for account '%s' has been disconnected" % self._account.name) + _LOGGER.info("Idler thread for account '%s' has been disconnected", self._account.name) while (not self._account.is_open()) and (not self._disposing): - logging.info("Trying to reconnect Idler thread for account '%s'." % self._account.name) + _LOGGER.info("Trying to reconnect Idler thread for account '%s'.", self._account.name) try: self._account.open() - logging.info("Successfully reconnected Idler thread for account '%s'." % self._account.name) + _LOGGER.info("Successfully reconnected Idler thread for account '%s'.", self._account.name) except Exception as ex: - logging.error("Failed to reconnect Idler thread for account '%s' (%s)." % (self._account.name, ex)) - logging.info("Trying to reconnect Idler thread for account '%s' in %s minutes" % - (self._account.name, str(self.RECONNECT_RETRY_INTERVAL))) + _LOGGER.error("Failed to reconnect Idler thread for account '%s' (%s).", self._account.name, ex) + _LOGGER.info("Trying to reconnect Idler thread for account '%s' in %d minutes", + self._account.name, self.RECONNECT_RETRY_INTERVAL) self._wait(60 * self.RECONNECT_RETRY_INTERVAL) # don't hammer the server @@ -187,7 +189,7 @@ def start(self) -> None: idler.start() self._idlerlist.append(idler) except Exception as ex: - logging.error("Error: Failed to create an idler thread for account '%s' (%s)" % (acc.name, ex)) + _LOGGER.error("Error: Failed to create an idler thread for account '%s' (%s)", acc.name, ex) def dispose(self) -> None: diff --git a/Mailnag/daemon/mailchecker.py b/Mailnag/daemon/mailchecker.py index e09d196..27b48a2 100644 --- a/Mailnag/daemon/mailchecker.py +++ b/Mailnag/daemon/mailchecker.py @@ -30,6 +30,7 @@ from Mailnag.daemon.dbus import DBusService from Mailnag.daemon.mails import MailSyncer, Memorizer, Mail +_LOGGER = logging.getLogger(__name__) class MailChecker: @@ -56,13 +57,15 @@ def check(self, accounts: list[Account]) -> None: # make sure multiple threads (idler and polling thread) # don't check for mails simultaneously. with self._mailcheck_lock: - logging.info('Checking %s email account(s).' % len(accounts)) + _LOGGER.info('Checking %d email account(s).', len(accounts)) + if _LOGGER.getEffectiveLevel() == logging.DEBUG and len(accounts): + _LOGGER.debug("Accounts:\n - %s", '\n - '.join([a.name for a in accounts])) for f in self._hookreg.get_hook_funcs(HookTypes.MAIL_CHECK): try_call(f, None) if self._conntest.is_offline(): - logging.warning('No internet connection.') + _LOGGER.warning('No internet connection.') return all_mails = self._mailsyncer.sync(accounts) diff --git a/Mailnag/daemon/mailnagdaemon.py b/Mailnag/daemon/mailnagdaemon.py index 516c928..1595d96 100644 --- a/Mailnag/daemon/mailnagdaemon.py +++ b/Mailnag/daemon/mailnagdaemon.py @@ -39,6 +39,7 @@ from Mailnag.common.config import read_cfg from Mailnag.common.utils import try_call +_LOGGER = logging.getLogger(__name__) testmode_mapping = { 'auto' : TestModes.NETWORKMONITOR, # Legacy, deprecated @@ -107,12 +108,12 @@ def dispose(self) -> None: # clean up resources if (self._start_thread != None) and (self._start_thread.is_alive()): self._start_thread.join() - logging.info('Starter thread exited successfully.') + _LOGGER.info('Starter thread exited successfully.') if (self._poll_thread is not None) and (self._poll_thread.is_alive()): self._poll_thread_stop.set() self._poll_thread.join() - logging.info('Polling thread exited successfully.') + _LOGGER.info('Polling thread exited successfully.') if self._idlrunner is not None: self._idlrunner.dispose() @@ -190,7 +191,7 @@ def _start(self) -> None: try: self._mailchecker.check(self._accounts) except: - logging.exception('Caught an exception.') + _LOGGER.exception('Caught an exception.') idle_accounts = self._get_idle_accounts(self._accounts) non_idle_accounts = self._get_non_idle_accounts(self._accounts) @@ -210,7 +211,7 @@ def poll_func() -> None: try: self._mailchecker.check(non_idle_accounts) except: - logging.exception('Caught an exception.') + _LOGGER.exception('Caught an exception.') self._poll_thread = threading.Thread(target = poll_func) self._poll_thread.start() @@ -225,7 +226,7 @@ def sync_func(account: Account) -> None: self._idlrunner = IdlerRunner(idle_accounts, sync_func, idle_timeout) self._idlrunner.start() except Exception as ex: - logging.exception('Caught an exception.') + _LOGGER.exception('Caught an exception.') if self._fatal_error_handler is not None: self._fatal_error_handler(ex) @@ -233,7 +234,7 @@ def sync_func(account: Account) -> None: def _wait_for_inet_connection(self) -> bool: """Wait that internet connection is available.""" if self._conntest.is_offline(): - logging.info('Waiting for internet connection...') + _LOGGER.info('Waiting for internet connection...') while True: if self._disposed: @@ -266,9 +267,9 @@ def _load_plugins(self, cfg: RawConfigParser) -> None: for p in self._plugins: try: p.enable() - logging.info("Successfully enabled plugin '%s'." % p.get_modname()) + _LOGGER.info("Successfully enabled plugin '%s'.", p.get_modname()) except: - logging.error("Failed to enable plugin '%s'." % p.get_modname()) + _LOGGER.error("Failed to enable plugin '%s'.", p.get_modname()) def _unload_plugins(self) -> None: @@ -281,9 +282,9 @@ def _unload_plugins(self) -> None: p.disable() except: err = True - logging.error("Failed to disable plugin '%s'." % p.get_modname()) + _LOGGER.error("Failed to disable plugin '%s'.", p.get_modname()) if not err: - logging.info('Plugins disabled successfully.') + _LOGGER.info('Plugins disabled successfully.') diff --git a/Mailnag/daemon/mails.py b/Mailnag/daemon/mails.py index 30d77fa..0c0a813 100644 --- a/Mailnag/daemon/mails.py +++ b/Mailnag/daemon/mails.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2011 - 2021 Patrick Ulbrich # Copyright 2020 Andreas Angerer # Copyright 2016, 2018, 2024 Timo Kankare @@ -26,17 +27,141 @@ import os import logging import hashlib +import re +import html +from html.parser import HTMLParser +from io import StringIO from configparser import RawConfigParser from email.header import decode_header, make_header from email.message import Message +from email.generator import Generator from typing import Any, TYPE_CHECKING from Mailnag.common.i18n import _ from Mailnag.common.config import cfg_folder +_LOGGER = logging.getLogger(__name__) + if TYPE_CHECKING: from Mailnag.common.accounts import Account +def clean_html(raw_html: str) -> str: + if not raw_html: + return '' + + class HTMLTextExtractor(HTMLParser): + BLOCK_TAGS = { + 'p', 'div', 'br', 'hr', 'h1', 'h2', 'h3', + 'h4', 'h5', 'h6', 'li', 'tr', 'header', 'footer' + } + START_SPACED_TAGS = {'a'} + END_SPACED_TAGS = {'td', 'th', 'a'} + IGNORE_TAGS = {'style', 'script', 'head', 'meta', 'title'} + + def __init__(self): + super().__init__() + self.result = [] + self.ignore = False + + def _handle_newline(self): + if self.result and self.result[-1] != '\n': + self.result.append('\n') + + def _handle_space(self): + if (self.result + and not self.result[-1][-1:].isdigit() + and self.result[-1] != '\n'): + self.result.append(' ') + + def handle_starttag(self, tag, attrs): + if tag in self.IGNORE_TAGS: + self.ignore = True + + if self.ignore: + return + + if tag in self.START_SPACED_TAGS: + self._handle_space() + + def handle_endtag(self, tag): + if tag in self.IGNORE_TAGS: + self.ignore = False + return + + if self.ignore: + return + + if tag in self.END_SPACED_TAGS: + self._handle_space() + elif tag in self.BLOCK_TAGS: + self._handle_newline() + + def handle_data(self, data): + if self.ignore: + return + + cleaned_data = data.strip() + if cleaned_data: + self.result.append(cleaned_data) + + def get_text(self): + full_text = "".join(self.result) + + lines = [line.strip() for line in full_text.splitlines()] + clean_text = "\n".join(lines) + clean_text = re.sub(r'[ \t]+', ' ', clean_text) + return re.sub(r'\n{2,}', '\n\n', clean_text).strip() + + parser = HTMLTextExtractor() + parser.feed(raw_html) + return parser.get_text() + +def dumps(text): + with open('dumps.txt', 'w', encoding='utf-8') as fout: + fout.write(text) + return text + +def message_text(msg: Message) -> str | None: + """Extract the text body of the given message. + """ + + txt = None + html_txt = None + for part in msg.walk(): + if part.is_multipart(): + continue + + content_type = part.get_content_type() + + if content_type not in ('text/plain', 'text/html'): + continue + + payload = part.get_payload(decode=True) + if not payload: + continue + + charset = part.get_content_charset() or 'utf-8' + try: + content = payload.decode(charset, errors='replace') + except Exception: + content = payload.decode('latin-1', errors='replace') + + if content_type == 'text/plain': + # On a trouvé la version texte brut, c'est l'idéal pour les codes ! + txt = content.strip() + if txt: + break + elif content_type == 'text/html': + html_txt = content.strip() + + if txt: + return txt + + if html_txt: + return clean_html(html_txt) + + return None + # # Mail class @@ -49,6 +174,7 @@ def __init__( sender: tuple[str, str], id: str, account: "Account", + uid: str | None, flags: dict[str, Any] ): self.datetime = datetime @@ -57,6 +183,10 @@ def __init__( self.account = account self.id = id self.flags = flags + self.uid = uid + + def fetch_text(self): + return self.account.fetch_text(self) # @@ -66,26 +196,26 @@ class MailCollector: def __init__(self, cfg: RawConfigParser, accounts: list["Account"]): self._cfg = cfg self._accounts = accounts - - + + def collect_mail(self, sort: bool = True) -> list[Mail]: mail_list: list[Mail] = [] mail_ids: dict[str, None] = {} - + for acc in self._accounts: # open mailbox for this account try: if not acc.is_open(): acc.open() except Exception as ex: - logging.error("Failed to open mailbox for account '%s' (%s)." % (acc.name, ex)) + _LOGGER.error("Failed to open mailbox for account '%s' (%s).", acc.name, ex) continue try: - for folder, msg, flags in acc.list_messages(): + for folder, msg, uid, flags in acc.list_messages(): sender, subject, datetime, msgid = self._get_header(msg) id = self._get_id(msgid, acc, folder, sender, subject, datetime) - + # Discard mails with identical IDs (caused # by mails with a non-unique fallback ID, # i.e. mails received in the same folder with @@ -94,20 +224,20 @@ def collect_mail(self, sort: bool = True) -> list[Mail]: # Also filter duplicates caused by Gmail labels. if id not in mail_ids: mail_list.append(Mail(datetime, subject, - sender, id, acc, flags)) + sender, id, acc, uid, flags)) mail_ids[id] = None except Exception as ex: - # Catch exceptions here, so remaining accounts will still be checked + # Catch exceptions here, so remaining accounts will still be checked # if a specific account has issues. # # Re-throw the exception for accounts that support notifications (i.e. imap IDLE), # so the calling idler thread can handle the error and reset the connection if needed (see idlers.py). - # NOTE: Idler threads always check single accounts (i.e. len(self._accounts) == 1), - # so there are no remaining accounts to be checked for now. + # NOTE: Idler threads always check single accounts (i.e. len(self._accounts) == 1), + # so there are no remaining accounts to be checked for now. if acc.supports_notifications(): raise else: - logging.error("An error occured while processing mails of account '%s' (%s)." % (acc.name, ex)) + _LOGGER.error("An error occured while processing mails of account '%s' (%s).", acc.name, ex) finally: # leave account with notifications open, so that it can # send notifications about new mails @@ -115,11 +245,11 @@ def collect_mail(self, sort: bool = True) -> list[Mail]: # disconnect from Email-Server acc.close() - + # sort mails if sort: mail_list.sort(key = lambda m: m.datetime, reverse = True) - + return mail_list @@ -129,18 +259,18 @@ def _get_header( ) -> tuple[tuple[str, str], str, int, str]: # Get sender sender = ('', '') - + try: content = self._get_header_field(msg_dict, 'From') # get the two parts of the sender addr = email.utils.parseaddr(content) - + if len(addr) != 2: - logging.warning('Malformed sender field in message.') + _LOGGER.warning('Malformed sender field in message.') else: sender_real = self._convert(addr[0]) sender_addr = self._convert(addr[1]) - + sender = (sender_real, sender_addr) except: pass @@ -151,41 +281,41 @@ def _get_header( subject = self._convert(content) except: subject = _('No subject') - + # Get date try: content = self._get_header_field(msg_dict, 'Date') - + # make a 10-tupel (UTC) parsed_date = email.utils.parsedate_tz(content) if parsed_date is not None: # convert 10-tupel to seconds incl. timezone shift datetime = email.utils.mktime_tz(parsed_date) else: - logging.warning('Email date set to zero.') + _LOGGER.warning('Email date set to zero.') datetime = 0 except: - logging.warning('Email date set to zero.') + _LOGGER.warning('Email date set to zero.') datetime = 0 - + # Get message id try: msgid = self._get_header_field(msg_dict, 'Message-ID') except: msgid = '' - + return (sender, subject, datetime, msgid) - - + + def _get_header_field(self, msg_dict: Message, key: str) -> str: if key in msg_dict: value = msg_dict[key] elif key.lower() in msg_dict: value = msg_dict[key.lower()] else: - logging.debug("Couldn't get %s from message." % key) + _LOGGER.debug("Couldn't get %s from message.", key) raise KeyError - + return value @@ -207,16 +337,16 @@ def _get_id( if len(msgid) > 0: id = hashlib.md5(msgid.encode('utf-8')).hexdigest() else: - # Fallback ID. - # Note: mails received on the same server, - # in the same folder with identical sender and - # subject but *no datetime* will have the same hash id, - # i.e. only the first mail is notified. + # Fallback ID. + # Note: mails received on the same server, + # in the same folder with identical sender and + # subject but *no datetime* will have the same hash id, + # i.e. only the first mail is notified. # (Should happen very rarely). id = hashlib.md5((acc.get_id() + folder + sender[1] + subject + str(datetime)) .encode('utf-8')).hexdigest() - + return id @@ -228,21 +358,21 @@ def __init__(self, cfg: RawConfigParser): self._cfg = cfg self._mails_by_account: dict[str, dict[str, Mail]] = {} self._mail_list: list[Mail] = [] - - + + def sync(self, accounts: list["Account"]) -> list[Mail]: needs_rebuild = False - + # collect mails from given accounts rcv_lst = MailCollector(self._cfg, accounts).collect_mail(sort = False) - + # group received mails by account tmp: dict[str, dict[str, Mail]] = {} for acc in accounts: tmp[acc.get_id()] = {} for mail in rcv_lst: tmp[mail.account.get_id()][mail.id] = mail - + # compare current mails against received mails # and remove those that are gone (probably opened in mail client). for acc_id in self._mails_by_account.keys(): @@ -254,7 +384,7 @@ def sync(self, accounts: list["Account"]) -> list[Mail]: needs_rebuild = True for mail_id in del_ids: del self._mails_by_account[acc_id][mail_id] - + # compare received mails against current mails # and add new mails. for acc_id in tmp: @@ -264,7 +394,7 @@ def sync(self, accounts: list["Account"]) -> list[Mail]: if not (mail_id in self._mails_by_account[acc_id]): self._mails_by_account[acc_id][mail_id] = tmp[acc_id][mail_id] needs_rebuild = True - + # rebuild and sort mail list if needs_rebuild: self._mail_list = [] @@ -272,7 +402,7 @@ def sync(self, accounts: list["Account"]) -> list[Mail]: for mail_id in self._mails_by_account[acc_id]: self._mail_list.append(self._mails_by_account[acc_id][mail_id]) self._mail_list.sort(key = lambda m: m.datetime, reverse = True) - + return self._mail_list @@ -283,15 +413,15 @@ class Memorizer(dict[str, str]): def __init__(self) -> None: dict.__init__(self) self._changed: bool = False - - + + def load(self) -> None: self.clear() self._changed = False - + # load last known messages from mailnag.dat dat_file = os.path.join(cfg_folder, 'mailnag.dat') - + if os.path.exists(dat_file): with open(dat_file, 'r') as f: for line in f: @@ -302,15 +432,15 @@ def load(self) -> None: # add to dict [id : flag] self[pair[0]] = pair[1] - + # save mail ids to a file def save(self, force: bool = False) -> None: if (not self._changed) and (not force): return - + if not os.path.exists(cfg_folder): os.makedirs(cfg_folder) - + dat_file = os.path.join(cfg_folder, 'mailnag.dat') with open(dat_file, 'w') as f: for id, seen_flag in list(self.items()): @@ -326,10 +456,10 @@ def sync(self, mail_list: list[Mail]) -> None: # new mail is not yet known to the memorizer self[m.id] = '0' self._changed = True - + for id in list(self.keys()): found = False - for m in mail_list: + for m in mail_list: if id == m.id: found = True # break inner for loop @@ -337,8 +467,8 @@ def sync(self, mail_list: list[Mail]) -> None: if not found: del self[id] self._changed = True - - + + # check if mail id is in the memorizer list def contains(self, id: str): return (id in self) diff --git a/Mailnag/plugins/libnotifyplugin.py b/Mailnag/plugins/libnotifyplugin.py index 502890f..79b8654 100644 --- a/Mailnag/plugins/libnotifyplugin.py +++ b/Mailnag/plugins/libnotifyplugin.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2024 Timo Kankare # Copyright 2013 - 2020 Patrick Ulbrich # Copyright 2020 Dan Christensen @@ -28,25 +29,51 @@ import os import dbus import threading +import re +from subprocess import Popen, PIPE, TimeoutExpired +import logging +import csv +import copy from collections.abc import Callable from typing import Any, Optional -from gi.repository import Notify, Gio, Gtk +from gi.repository import Notify, Gio, Gtk, Gdk, GLib +from Mailnag.common.dist_cfg import PACKAGE_NAME from Mailnag.common.plugins import Plugin, HookTypes from Mailnag.common.i18n import _ from Mailnag.common.subproc import start_subprocess from Mailnag.common.exceptions import InvalidOperationException from Mailnag.daemon.mails import Mail +from Mailnag.common.utils import dbgindent +from Mailnag.common.config import cfg_folder +from mailnagger.resources import get_resource_text +import Mailnag.plugins NOTIFICATION_MODE_COUNT = '0' NOTIFICATION_MODE_SHORT_SUMMARY = '3' NOTIFICATION_MODE_SUMMARY = '1' NOTIFICATION_MODE_SINGLE = '2' +NOTIFICATION_MODE_SILENT = '4' -plugin_defaults = { +_LOGGER = logging.getLogger(__name__) + +DESKTOP_ENV_VARS_FOR_SUPPORT_TEST = ('XDG_CURRENT_DESKTOP', 'GDMSESSION') +SUPPORTED_DESKTOP_ENVIRONMENTS = ("gnome", "cinnamon") + +RE_CODE = r'\b(?P\d{4,8})\b' + +cfg_2fa_providers_file = os.path.join(cfg_folder, '2fa_providers.tsv') + +plugin_defaults = { 'notification_mode' : NOTIFICATION_MODE_SHORT_SUMMARY, - 'max_visible_mails' : '10' + 'max_visible_mails' : '10', + '2fa_notifications' : True, } +_2fa_providers_keys = ('enabled', 'provider', 'subject', 'text_re') +default_2fa_providers = [ + (True, 'Garmin', 'Your Security Passcode', r'Use this one-time code for your account\n{code}\n'), + (True, 'Garmin', _('Your Security Passcode'), r'voici votre code de sécurité.\n{code}\n'), +] class LibNotifyPlugin(Plugin): def __init__(self) -> None: @@ -56,63 +83,68 @@ def __init__(self) -> None: self._lock = threading.Lock() self._notification_server_wait_event = threading.Event() self._notification_server_ready = False - self._is_gnome = False + self._is_supported_env = False self._mails_added_hook: Optional[Callable[[list[Mail], list[Mail]], None]] = None - - + self._copy_commands = [ # wl-copy (Wayland), xsel (X11 alternatif), xclip (X11 standard) + ['wl-copy'], + ['xclip', '-selection', 'c'], + ['xsel', '--clipboard', '--input'] + ] + + def enable(self) -> None: self._max_mails = int(self.get_config()['max_visible_mails']) self._notification_server_wait_event.clear() self._notification_server_ready = False self._notifications = {} - + # initialize Notification if not self._initialized: Notify.init("Mailnagger") - self._is_gnome = self._is_gnome_environment(['XDG_CURRENT_DESKTOP', 'GDMSESSION']) + self._is_supported_env = self._is_supported_environment() self._initialized = True - + def mails_added_hook(new_mails: list[Mail], all_mails: list[Mail]) -> None: self._notify_async(new_mails, all_mails) - + self._mails_added_hook = mails_added_hook - + def mails_removed_hook(remaining_mails: list[Mail]) -> None: self._notify_async([], remaining_mails) - + self._mails_removed_hook: Optional[Callable[[list[Mail]], None]] = mails_removed_hook - + controller = self.get_mailnag_controller() hooks = controller.get_hooks() - - hooks.register_hook_func(HookTypes.MAILS_ADDED, + + hooks.register_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) - + hooks.register_hook_func(HookTypes.MAILS_REMOVED, self._mails_removed_hook) - + def disable(self) -> None: controller = self.get_mailnag_controller() hooks = controller.get_hooks() - + if self._mails_added_hook is not None: hooks.unregister_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) self._mails_added_hook = None - + if self._mails_removed_hook is not None: hooks.unregister_hook_func(HookTypes.MAILS_REMOVED, self._mails_removed_hook) self._mails_removed_hook = None - + # Abort possible notification server wait self._notification_server_wait_event.set() - # Close all open notifications - # (must be called after _notification_server_wait_event.set() + # Close all open notifications + # (must be called after _notification_server_wait_event.set() # to prevent a possible deadlock) self._close_notifications() - + def get_manifest(self) -> tuple[str, str, str, str]: return (_("LibNotify Notifications"), _("Shows a popup when new mails arrive."), @@ -122,76 +154,251 @@ def get_manifest(self) -> tuple[str, str, str, str]: def get_default_config(self) -> dict[str, Any]: return plugin_defaults - - + + def has_config_ui(self) -> bool: return True - - - def get_config_ui(self) -> Gtk.Box: - radio_mapping = [ - (NOTIFICATION_MODE_COUNT, Gtk.RadioButton(label = _('Count of new mails'))), - (NOTIFICATION_MODE_SHORT_SUMMARY, Gtk.RadioButton(label = _('Short summary of new mails'))), - (NOTIFICATION_MODE_SUMMARY, Gtk.RadioButton(label = _('Detailed summary of new mails'))), - (NOTIFICATION_MODE_SINGLE, Gtk.RadioButton(label = _('One notification per new mail'))) - ] - box = Gtk.Box() - box.set_spacing(12) - box.set_orientation(Gtk.Orientation.VERTICAL) - - label = Gtk.Label() - label.set_markup('%s' % _('Notification mode:')) - label.set_alignment(0.0, 0.0) - box.pack_start(label, False, False, 0) - - inner_box = Gtk.Box() - inner_box.set_spacing(6) - inner_box.set_orientation(Gtk.Orientation.VERTICAL) - - last_radio = None - for m, r in radio_mapping: - if last_radio != None: - r.join_group(last_radio) - inner_box.pack_start(r, False, False, 0) - last_radio = r - - alignment = Gtk.Alignment() - alignment.set_padding(0, 6, 18, 0) - alignment.add(inner_box) - box.pack_start(alignment, False, False, 0) - + def get_config_ui(self) -> Gtk.Box: + libnotifyplugin_ui = get_resource_text( + Mailnag.plugins, + "libnotifyplugin.ui" + ) + builder = Gtk.Builder() + builder.set_translation_domain(PACKAGE_NAME) + + builder.add_from_string(libnotifyplugin_ui) + + radio_id_mapping = { + NOTIFICATION_MODE_COUNT: 'notification_mode_count', + NOTIFICATION_MODE_SHORT_SUMMARY: 'notification_mode_short_summary', + NOTIFICATION_MODE_SUMMARY: 'notification_mode_summary', + NOTIFICATION_MODE_SINGLE: 'notification_mode_single', + NOTIFICATION_MODE_SILENT: 'notification_2FA_only', + } + + radio_mapping = [] + + for mode in [NOTIFICATION_MODE_COUNT, + NOTIFICATION_MODE_SHORT_SUMMARY, + NOTIFICATION_MODE_SUMMARY, + NOTIFICATION_MODE_SINGLE, + NOTIFICATION_MODE_SILENT, + ]: + radio_btn = builder.get_object(radio_id_mapping[mode]) + radio_mapping.append((mode, radio_btn)) + + label = builder.get_object('notification_modes') + label.set_markup(f'{_('Notification mode:')}') + label = builder.get_object('2fa_providers') + label.set_markup(f'{_('2FA providers')}') + + builder.connect_signals({ + 'close': self._on_close, + 'btn_cancel_clicked': self._on_btn_cancel_clicked, + 'btn_ok_clicked': self._on_btn_ok_clicked, + 'btn_add_provider_clicked': self._on_btn_add_provider_clicked, + 'btn_remove_provider_clicked': self._on_btn_remove_provider_clicked, + 'btn_edit_provider_clicked': self._on_btn_edit_provider_clicked, + 'provider_toggled': self._on_provider_toggled, + 'provider_row_activated': self._on_provider_row_activated, + 'provider_sel_changed': self._on_provider_sel_changed, + 'expander_2fa_providers_expanded': self._on_expander_2fa_providers_expanded, + 'info_response': self._on_info_response, + }) + + self._builder = builder self._radio_mapping = radio_mapping - - return box - - + self._dialog = builder.get_object('edit_2FA_provider_dialog') + self._switch_2FA_notifications = builder.get_object('switch_2FA_notifications') + self._liststore_2FA_providers = builder.get_object('liststore_2FA_providers') + self._treeview_2FA_providers = builder.get_object('treeview_2FA_providers') + self._infobar_info = builder.get_object('info') + self._label_info = builder.get_object('label_info') + + self._scrolled_window = builder.get_object('scrolledwindow1') + self._scrolled_window.set_min_content_height(120) + self._scrolled_window.set_min_content_width(348) + self._scrolled_window.set_propagate_natural_height(True) + self._scrolled_window.set_propagate_natural_width(True) + self._scrolled_window.set_max_content_height(348) + + return builder.get_object('box1') + + + @staticmethod + def _eval_2fa_providers(providers: list|str) -> list: + assert isinstance(providers,list), f'Oops! config still have invalid providers (type={type(providers).__name__})' + return providers + + def _check_2fa_provider_pattern(self, sender: str, subject: str, pattern: str) -> bool: + if not '{code}' in subject and not '{code}' in pattern: + + _LOGGER.debug('Missing "code" group pattern: {code}...\n' + 'sender: %s, subject: %s\npattern:\n%s', + sender, subject, + pattern) + self._alert_message(_('Missing "code" group pattern: {code}'), + msg_type=Gtk.MessageType.ERROR, duration_s=5) + return False + + def check_regexp(name: str, msg_name: str, regexp_p: str) -> bool: + if '{code}' not in regexp_p: + return True + try: + compiled_re = regexp_p.replace('{code}', RE_CODE).strip() + _cre = re.compile(compiled_re) + return True + except (re.error, AttributeError) as e: + posi = '' + pos = getattr(e, 'pos', None) + if pos is not None: + posi = "\n" + (" " * pos) + "^" + _LOGGER.exception('%s is incorrect regexp: %s\nregex: %s\n%s%s', + name, str(e), regexp_p, + compiled_re, posi) + self._alert_message(_('%s is incorrect regexp'), msg_name, + msg_type=Gtk.MessageType.ERROR, duration_s=5) + return False + + if not check_regexp('subject', _('Subject'), re.escape(subject)): + return False + + if not check_regexp('pattern', _('Pattern'), pattern): + return False + + return True + + def get_config(self): + config = super().get_config() + config['2fa_providers'] = self._load_2fa_providers_from_config() + return config + + def _load_2fa_providers_from_config(self): + def check_regexp(name: str, regexp_p: str) -> bool: + if '{code}' not in regexp_p: + return True + try: + compiled_re = regexp_p.replace('{code}', RE_CODE).strip() + _cre = re.compile(compiled_re) + return True + except (re.error, AttributeError) as e: + posi = '' + pos = getattr(e, 'pos', None) + if pos is not None: + posi = "\n" + (" " * pos) + "^" + _LOGGER.exception('%s is incorrect regexp: %s\nregex: %s\n%s%s', + name, str(e), regexp_p, + compiled_re, posi) + return False + + + lv = None + try: + with open(cfg_2fa_providers_file, 'r', encoding='utf-8') as fin: + next(fin) + lv = list(csv.DictReader(fin, fieldnames=_2fa_providers_keys, delimiter='\t')) + except (FileNotFoundError, StopIteration): + pass + except Exception as e: + _LOGGER.exception('Failed to read 2FA providers file: %s\n%s', + os.path.basename(cfg_2fa_providers_file), + str(e)) + + if lv is not None: + providers = [] + for l, v in enumerate(lv, start=2): + if not isinstance(v, dict): + _LOGGER.debug('Line %d invalid in: %s', + os.path.basename(cfg_2fa_providers_file)) + continue + + values = [] + regexps_invalid = [] + is_enabled = str(v.get('enabled', '')).lower() in ('y', 'yes', 'true', 'on') + + for k in _2fa_providers_keys: + if k == 'enabled': + continue + + val = v.get(k, "") + if val: + if k == 'subject': + if not check_regexp(k, re.escape(v[k])): + regexps_invalid.append(f'line: {l}, field: {k}, value: {v[k]}') + elif k =='text_re': + if not check_regexp(k, v[k]): + regexps_invalid.append(f'line: {l}, field: {k}, value: {v[k]}') + values.append(val) + + if regexps_invalid: + _LOGGER.debug('Regexp invalid in: %s\n %s', + os.path.basename(cfg_2fa_providers_file), + '\n '.join(regexps_invalid)) + + values.insert(0, is_enabled and not bool(regexps_invalid)) + + providers.append(values) + return providers + + return copy.deepcopy(default_2fa_providers) + def load_ui_from_config(self, config_ui: Gtk.Widget) -> None: - config = self.get_config() + config = self.get_config() radio = [r for m, r in self._radio_mapping if m == config['notification_mode']][0] radio.set_active(True) - - + self._switch_2FA_notifications.set_active(config['2fa_notifications']) + providers = config['2fa_providers'] + for (_enabled, _sender, _subject, _pattern) in providers: + if _enabled and not self._check_2fa_provider_pattern(_sender, _subject, _pattern): + _enabled = False + self._liststore_2FA_providers.append([_enabled, _sender, _subject, _pattern]) + + def _save_2fa_providers_to_config(self, providers): + named_providers = [] + for v in providers: + nv = dict() + for i in range(len(_2fa_providers_keys)): + k = _2fa_providers_keys[i] + nv[k] = v[i] + named_providers.append(nv) + with open(cfg_2fa_providers_file, 'wt', encoding='utf-8') as fout: + w = csv.DictWriter(fout, fieldnames=_2fa_providers_keys, delimiter='\t') + w.writeheader() + w.writerows(named_providers) + + def save_ui_to_config(self, config_ui: Gtk.Widget) -> None: config = self.get_config() mode = [m for m, r in self._radio_mapping if r.get_active()][0] config['notification_mode'] = mode + config['2fa_notifications'] = self._switch_2FA_notifications.get_active() + providers = [] + for row in self._liststore_2FA_providers: + providers.append(tuple(row)) + self._save_2fa_providers_to_config(providers) + if '2fa_providers' in config: + del config['2fa_providers'] def _notify_async(self, new_mails: list[Mail], all_mails: list[Mail]) -> None: def thread() -> None: with self._lock: - # The desktop session may have started Mailnag + # The desktop session may have started Mailnag # before the libnotify dbus daemon. if not self._notification_server_ready: if not self._wait_for_notification_server(): return self._notification_server_ready = True - + config = self.get_config() - if config['notification_mode'] == NOTIFICATION_MODE_SINGLE: + + if config['notification_mode'] == NOTIFICATION_MODE_SILENT: + self._notify_2FA_attempts(new_mails, all_mails) + elif config['notification_mode'] == NOTIFICATION_MODE_SINGLE: self._notify_single(new_mails, all_mails) else: + self._notify_2FA_attempts(new_mails, all_mails) if len(all_mails) == 0: if '0' in self._notifications: # The user may have closed the notification: @@ -204,21 +411,21 @@ def thread() -> None: self._notify_short_summary(new_mails, all_mails) elif config['notification_mode'] == NOTIFICATION_MODE_SUMMARY: self._notify_summary(new_mails, all_mails) - + t = threading.Thread(target = thread) t.start() - - + + def _notify_short_summary(self, new_mails: list[Mail], all_mails: list[Mail]) -> None: summary = "" body = "" lst = [] mails = self._prepend_new_mails(new_mails, all_mails) mail_count = len(mails) - - if len(self._notifications) == 0: + + if '0' not in self._notifications: self._notifications['0'] = self._get_notification(" ", None, None) # empty string will emit a gtk warning - + i = 0 n = 0 while (n < 3) and (i < mail_count): @@ -227,12 +434,11 @@ def _notify_short_summary(self, new_mails: list[Mail], all_mails: list[Mail]) -> lst.append(s) n += 1 i += 1 - - if self._is_gnome: - senders = "%s" % ", ".join(lst) - else: - senders = ", ".join(lst) - + + senders = ', '.join(lst) + if self._is_supported_env: + senders = f'{senders}' + if mail_count > 1: summary = _("{0} new mails").format(str(mail_count)) if (mail_count - i) > 1: @@ -242,32 +448,34 @@ def _notify_short_summary(self, new_mails: list[Mail], all_mails: list[Mail]) -> else: summary = _("New mail") body = _("from {0}.").format(senders) - + self._notifications['0'].update(summary, body, "mail-unread") self._notifications['0'].show() - - + + def _notify_summary(self, new_mails: list[Mail], all_mails: list[Mail]) -> None: summary = "" body = "" mails = self._prepend_new_mails(new_mails, all_mails) - - if len(self._notifications) == 0: + + if '0' not in self._notifications: self._notifications['0'] = self._get_notification(" ", None, None) # empty string will emit a gtk warning ubound = len(mails) if len(mails) <= self._max_mails else self._max_mails for i in range(ubound): - if self._is_gnome: - body += "%s:\n%s\n\n" % (self._get_sender(mails[i]), mails[i].subject) + m = mails[i] + sender = self._get_sender(m) + subject = m.subject + if self._is_supported_env: + body += f'{sender}:\n{subject}\n\n' else: - body += "%s - %s\n" % (ellipsize(self._get_sender(mails[i]), 20), ellipsize(mails[i].subject, 20)) + body += f'{ellipsize(sender, 20)} - {ellipsize(subject, 20)}\n' if len(mails) > self._max_mails: - if self._is_gnome: - body += "%s" % _("(and {0} more)").format(str(len(mails) - self._max_mails)) - else: - body += _("(and {0} more)").format(str(len(mails) - self._max_mails)) + fragment = _("(and {0} more)").format(str(len(mails) - self._max_mails)) + if self._is_supported_env: + body += f'{fragment}' if len(mails) > 1: # multiple new emails summary = _("{0} new mails").format(str(len(mails))) @@ -276,9 +484,112 @@ def _notify_summary(self, new_mails: list[Mail], all_mails: list[Mail]) -> None: self._notifications['0'].update(summary, body, "mail-unread") self._notifications['0'].show() - - - def _notify_single(self, new_mails, all_mails): + + + def _notify_2FA_attempts(self, new_mails: List[Mail], all_mails: List[Mail]) -> None: + self._cleanup_notifications_not_in(all_mails) + + # In single notification mode new mails are + # added to the *bottom* of the notification list. + new_mails.sort(key = lambda m: m.datetime, reverse = False) + + config = self.get_config() + + if not config['2fa_notifications']: + return + + providers = self._eval_2fa_providers(config['2fa_providers']) + if not len(providers): + return + + for mail in new_mails: + self._notify_2FA_attempt(mail, providers) + + + def _notify_2FA_attempt(self, mail, providers) -> bool: + sender = self._get_sender(mail) + subject = mail.subject + body = None + code = None + + for (_enabled, _sender, _subject, _text_re) in providers: + if not _enabled or sender != _sender: + continue + + if '{code}' in _subject: + _pattern = re.escape(_subject).replace(r'\{code\}', RE_CODE).strip() + m = re.match(_pattern, subject) + + if m: + code = m.group('code') + _LOGGER.debug("2FA matched code %s: sender=%s, subject=%s", + code, + sender, subject) + break + else: + continue + else: + if subject != _subject: + continue + + _LOGGER.debug("2FA pre-matched : sender=%s, subject=%s", + sender, subject) + + # fetch the body text only when sender and subject match + # but only once (different patterns may need to be tested) + if body is None: + body = mail.fetch_text() + + if body is None: + _LOGGER.warning("2FA match not achievable: sender=%s, subject=%s\nBody not available.", + sender, subject) + return False + + m = re.search(_text_re.replace('{code}', RE_CODE).strip(), body) + if m: + code = m.group('code') + _LOGGER.debug("2FA matched code %s: sender=%s, subject=%s, body:\n%s", + code, + sender, subject, + dbgindent(body)) + break + else: + _LOGGER.debug("2FA not matched : sender=%s, subject=%s, body:\n%s", + sender, subject, + #dbgindent(body)) + ' '+'\n '.join(str(body).splitlines())) + + return False + + assert code is not None + + _summary = f"🔑 {code} — {sender}" + _body = f'\t\t{subject}' if self._is_supported_env else f'\t\t{subject}' + + n = self._get_notification(_summary, + _body, + "security-medium") + + n.set_timeout(Notify.EXPIRES_NEVER) + n.set_urgency(Notify.Urgency.CRITICAL) + + notification_id = str(id(n)) + if self._is_supported_env: + n.add_action("copy-code", f'📋 {_("Copy code:")} {code}', + self._notification_action_handler, (mail, notification_id, code)) + n.show() + self._record_mail_notification(mail, n) + return True + + + def _record_mail_notification(self, mail: Mail, n: Notify.Notification) -> None: + # Remember the associated message, so we know when to remove the notification: + n.mail = mail + notification_id = str(id(n)) + self._notifications[notification_id] = n + + + def _cleanup_notifications_not_in(self, all_mails: List[Mail]) -> None: # Remove notifications for messages not in all_mails: for k, n in list(self._notifications.items()): if hasattr(n, 'mail') and not (n.mail in all_mails): @@ -286,42 +597,54 @@ def _notify_single(self, new_mails, all_mails): try_close(n) del self._notifications[k] + + def _notify_single(self, new_mails: List[Mail], all_mails: List[Mail]) -> None: + self._cleanup_notifications_not_in(all_mails) + # In single notification mode new mails are # added to the *bottom* of the notification list. new_mails.sort(key = lambda m: m.datetime, reverse = False) - + + config = self.get_config() + providers = None + + if config['2fa_notifications']: + providers = self._eval_2fa_providers(config['2fa_providers']) + for mail in new_mails: + if (providers is not None and + self._notify_2FA_attempt(mail, providers)): + continue + n = self._get_notification(self._get_sender(mail), mail.subject, "mail-unread") - # Remember the associated message, so we know when to remove the notification: - n.mail = mail notification_id = str(id(n)) - if self._is_gnome: - n.add_action("mark-as-read", _("Mark as read"), - self._notification_action_handler, (mail, notification_id)) + if self._is_supported_env: + n.add_action("mark-as-read", _("Mark as read"), + self._notification_action_handler, (mail, notification_id)) n.show() - self._notifications[notification_id] = n + self._record_mail_notification(mail, n) def _notify_count(self, count: int) -> None: - if len(self._notifications) == 0: + if '0' not in self._notifications: self._notifications['0'] = self._get_notification(" ", None, None) # empty string will emit a gtk warning - + if count > 1: # multiple new emails summary = _("{0} new mails").format(str(count)) else: summary = _("New mail") - + self._notifications['0'].update(summary, None, "mail-unread") self._notifications['0'].show() - - + + def _close_notifications(self) -> None: with self._lock: for n in self._notifications.values(): try_close(n) self._notifications = {} - - + + def _get_notification( self, summary: str, @@ -331,13 +654,13 @@ def _get_notification( n = Notify.Notification.new(summary, body, icon) n.set_category("email") n.set_hint_string("desktop-entry", "mailnagger") - - if self._is_gnome: + + if self._is_supported_env: n.add_action("default", "default", self._notification_action_handler, None) return n - - + + def _wait_for_notification_server(self) -> bool: bus = dbus.SessionBus() while not bus.name_has_owner('org.freedesktop.Notifications'): @@ -346,7 +669,33 @@ def _wait_for_notification_server(self) -> bool: return False return True - + def _copy_to_clipboard(self, text: str) -> None: + """Copie le texte dans le presse-papier en supportant Wayland et X11.""" + # On encode le texte une seule fois + encoded_text = text.encode('utf-8') + + for i, cmd in enumerate(list(self._copy_commands)): + try: + # On tente d'exécuter la commande + pipe = Popen(cmd, stdin=PIPE, close_fds=True) + pipe.communicate(input=encoded_text, timeout=2) + + if pipe.returncode == 0: + _LOGGER.debug("Code copy succeeded with %s", cmd[0]) + successful_cmd = self._copy_commands.pop(i) + self._copy_commands.insert(0, successful_cmd) + break + except TimeoutExpired: + _LOGGER.warning("Timeout expired with %s.", cmd[0]) + if pipe: + pipe.kill() # Important : kill the blocking process + pipe.wait() + except Exception as e: + _LOGGER.error("Copy failed with %s: %s", cmd[0], str(e)) + + else: + _LOGGER.error("Copy to clipboard failed (install wl-clipboard or xclip).") + def _notification_action_handler( self, n: Notify.Notification, @@ -355,12 +704,12 @@ def _notification_action_handler( ) -> None: with self._lock: if action == "default": - mailclient = get_default_mail_reader() + mailclient = Gio.AppInfo.get_default_for_type("x-scheme-handler/mailto", False) if mailclient is not None: - start_subprocess(mailclient) + Gio.AppInfo.launch(mailclient) # clicking the notification bubble has closed all notifications - # so clear the reference array as well. + # so clear the reference array as well. self._notifications = {} elif action == "mark-as-read": controller = self.get_mailnag_controller() @@ -368,46 +717,281 @@ def _notification_action_handler( controller.mark_mail_as_read(user_data[0].id) except InvalidOperationException: pass - + # clicking the action has closed the notification # so remove its reference. del self._notifications[user_data[1]] - + elif action == "copy-code": + controller = self.get_mailnag_controller() + try: + code = user_data[2] + self._copy_to_clipboard(code) + controller.mark_mail_as_read(user_data[0].id) + except InvalidOperationException: + pass + + # clicking the action has closed the notification + # so remove its reference. + if user_data[1] in self._notifications: + del self._notifications[user_data[1]] - def _get_sender(self, mail: Mail) -> str: + @staticmethod + def _get_sender(mail: Mail) -> str: name, addr = mail.sender if len(name) > 0: return name else: return addr - - - def _prepend_new_mails(self, new_mails: list[Mail], all_mails: list[Mail]) -> list[Mail]: - # The mail list (all_mails) is sorted by date (mails with most recent - # date on top). New mails with no date or older mails that come in - # delayed won't be listed on top. So if a mail with no or an older date - # arrives, it gives the impression that the top most mail (i.e. the mail + + @staticmethod + def _prepend_new_mails(new_mails: list[Mail], all_mails: list[Mail]) -> list[Mail]: + # The mail list (all_mails) is sorted by date (mails with most recent + # date on top). New mails with no date or older mails that come in + # delayed won't be listed on top. So if a mail with no or an older date + # arrives, it gives the impression that the top most mail (i.e. the mail # with the most recent date) is re-notified. - # To fix that, simply put new mails on top explicitly. + # To fix that, simply put new mails on top explicitly. return new_mails + [m for m in all_mails if m not in new_mails] - - def _is_gnome_environment(self, env_vars: list[str]) -> bool: - for var in env_vars: - if 'gnome' in os.environ.get(var, '').lower().split(':'): - return True + @staticmethod + def _is_supported_environment() -> bool: + for var in DESKTOP_ENV_VARS_FOR_SUPPORT_TEST: + desktop_env = os.environ.get(var, '').lower().split(':') + for env in SUPPORTED_DESKTOP_ENVIRONMENTS: + if env in desktop_env: + return True return False -def get_default_mail_reader() -> Optional[str]: - mail_reader: Optional[str] = None - app_info = Gio.AppInfo.get_default_for_type("x-scheme-handler/mailto", False) + def _on_close(self, widget: Gtk.Dialog) -> None: + _LOGGER.debug('on_close') + self._dialog.hide() + self._dialog.response(Gtk.ResponseType.CLOSE) + + + def _on_btn_cancel_clicked(self, widget: Gtk.Button) -> None: + _LOGGER.debug('on_btn_cancel_clicked') + self._dialog.hide() + self._dialog.response(Gtk.ResponseType.CANCEL) + + + def _on_btn_ok_clicked(self, widget: Gtk.Button) -> None: + _LOGGER.debug('on_btn_ok_clicked') + self._dialog.hide() + self._dialog.response(Gtk.ResponseType.OK) + + + def _on_btn_add_provider_clicked(self, widget: Gtk.ToolButton) -> None: + _LOGGER.debug('on_btn_add_provider_clicked') + b = self._builder + d = self._dialog + + b.get_object('enable').set_active(False) + b.get_object('sender').set_text('') + b.get_object('subject').set_text('') + b.get_object('pattern_text_buffer').set_text('') + + if d.run() != Gtk.ResponseType.OK: + return + + _enable = b.get_object('enable').get_active() + _sender = b.get_object('sender').get_text() + _subject = b.get_object('subject').get_text() + start = b.get_object('pattern_text_buffer').get_start_iter() + end = b.get_object('pattern_text_buffer').get_end_iter() + _pattern = b.get_object('pattern_text_buffer').get_text(start, end, False) + + if not self._check_2fa_provider_pattern(_sender, _subject, _pattern) and _enable: + _enable = False + + row = [_enable, _sender, _subject, _pattern] + + iter = self._liststore_2FA_providers.append(row) + model = self._treeview_2FA_providers.get_model() + path = model.get_path(iter) + self._treeview_2FA_providers.set_cursor(path, None, False) + self._treeview_2FA_providers.grab_focus() + + + def _show_confirmation_dialog(self, text: str) -> None: + message = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, + Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, text) + resp = message.run() + message.destroy() + if resp == Gtk.ResponseType.YES: return True + else: return False + + + def _on_btn_remove_provider_clicked(self, widget: Gtk.ToolButton) -> None: + _LOGGER.debug('on_btn_remove_provider_clicked') + + treeselection = self._treeview_2FA_providers.get_selection() + model, iter = treeselection.get_selected() + + if iter is None: + return + + _sender = model.get_value(iter, 1) + _subject = model.get_value(iter, 2) + _pattern = model.get_value(iter, 3) + + if not self._show_confirmation_dialog( + _('Delete this provider:') + '\n' + + '\n ' + _("Sender:") + _sender + + '\n ' + _("Subject:") + _subject + + '\n ' + _("Pattern:") + + '\n ' + _pattern): + return + + # select prev/next account + p = model.get_path(iter) + if not p.prev(): + p.next() + + treeselection = self._treeview_2FA_providers.get_selection() + treeselection.select_path(p) + self._treeview_2FA_providers.grab_focus() + + # remove from treeview + model.remove(iter) + + + def _edit_provider(self, model, iter) -> None: + if iter is None: + return + + _enabled = model.get_value(iter, 0) + _sender = model.get_value(iter, 1) + _subject = model.get_value(iter, 2) + _pattern = model.get_value(iter, 3) + + b = self._builder + d = self._dialog + + b.get_object('enable').set_active(_enabled) + b.get_object('sender').set_text(_sender) + b.get_object('subject').set_text(_subject) + b.get_object('pattern_text_buffer').set_text(_pattern) + + if d.run() != Gtk.ResponseType.OK: + return + + _enabled = b.get_object('enable').get_active() + _sender = b.get_object('sender').get_text() + _subject = b.get_object('subject').get_text() + start = b.get_object('pattern_text_buffer').get_start_iter() + end = b.get_object('pattern_text_buffer').get_end_iter() + _pattern = b.get_object('pattern_text_buffer').get_text(start, end, False) + + if not self._check_2fa_provider_pattern(_sender, _subject, _pattern) and _enabled: + _enabled = False + + model.set_value(iter, 0, _enabled) + model.set_value(iter, 1, _sender) + model.set_value(iter, 2, _subject) + model.set_value(iter, 3, _pattern) + + + def _on_btn_edit_provider_clicked(self, widget: Gtk.ToolButton) -> None: + _LOGGER.debug('on_btn_edit_provider_clicked') + treeselection = self._treeview_2FA_providers.get_selection() + model, iter = treeselection.get_selected() + + self._edit_provider(model, iter) + + + def _on_provider_toggled(self, cell: Gtk.CellRendererToggle, path: Gtk.TreePath) -> None: + _LOGGER.debug('on_provider_toggled') + model = self._liststore_2FA_providers + iter = model.get_iter(path) + + _enabled = not model.get_value(iter, 0) + _sender = model.get_value(iter, 1) + _subject = model.get_value(iter, 2) + _pattern = model.get_value(iter, 3) + + if _enabled and not self._check_2fa_provider_pattern(_sender, _subject, _pattern): + return + + self._liststore_2FA_providers.set_value(iter, 0, _enabled) + + + def _on_provider_row_activated(self, view: Gtk.TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn) -> None: + _LOGGER.debug('on_provider_row_activated') + + event = Gtk.get_current_event() + + _LOGGER.debug('event.type = %s', event.type.value_name) + + if column.get_name() != 'col_enabled': + model = view.get_model() + iter = model.get_iter(path) + self._edit_provider(model, iter) + + + def _on_expander_2fa_providers_expanded(self, expander, pspec) -> None: + wnd = expander.get_toplevel() + + w = wnd.get_size().width + wnd.resize(w, 1) + + + def _on_provider_sel_changed(self, selection: Gtk.TreeSelection) -> None: + model, iter = selection.get_selected() + sensitive = (iter is not None) + for id in ('btn_remove_2FA_provider', 'btn_edit_2FA_provider'): + self._builder.get_object(id).set_sensitive(sensitive) + + @staticmethod + def _stop_infobar_timeout(infobar: Gtk.InfoBar) -> None: + timeout_id = getattr(infobar, "timeout_id", None) + if timeout_id is not None: + GLib.source_remove(timeout_id) + infobar.timeout_id = None + + @staticmethod + def _start_infobar_timeout(infobar: Gtk.InfoBar, duration_s: int) -> None: + LibNotifyPlugin._stop_infobar_timeout(infobar) + + def timeout_reached(): + infobar.hide() + infobar.timeout_id = None + return False + + infobar.timeout_id = GLib.timeout_add(duration_s*1000, timeout_reached) + + + def _alert_message(self, msg_format:str, *args, msg_type: Gtk.MessageType = Gtk.MessageType.INFO, duration_s:int = None) -> None: + self._stop_infobar_timeout(self._infobar_info) + + # 1. Formatage sécurisé du message + try: + msg = msg_format % args if args else msg_format + except TypeError as e: + msg = msg_format + log_msg = f"Erreur de formatage message: {msg_format} (args: {args})" + _LOGGER.exception("Format Error: %s\n%s", str(e), log_msg) + + # 2. Correspondance Logging et Affichage + # On définit le niveau de log en fonction du type GTK passé + if msg_type == Gtk.MessageType.ERROR: + _LOGGER.error(msg) + elif msg_type == Gtk.MessageType.WARNING: + _LOGGER.warning(msg) + elif msg_type == Gtk.MessageType.QUESTION: + _LOGGER.info("QUESTION: %s", msg) + else: # INFO ou OTHER + _LOGGER.info(msg) - if app_info is not None: - executable = Gio.AppInfo.get_executable(app_info) + self._label_info.set_text(msg) + self._infobar_info.set_message_type(msg_type) + self._infobar_info.show() - if (executable != None) and (len(executable) > 0): - mail_reader = executable + if duration_s is not None: + self._start_infobar_timeout(self._infobar_info, duration_s) - return mail_reader + def _on_info_response(self, infobar: Gtk.InfoBar, response_id: int) -> None: + if response_id in (Gtk.ResponseType.CLOSE, Gtk.ResponseType.OK): + self._stop_infobar_timeout(infobar) + infobar.hide() def ellipsize(str: str, max_len: int) -> str: diff --git a/Mailnag/plugins/libnotifyplugin.ui b/Mailnag/plugins/libnotifyplugin.ui new file mode 100644 index 0000000..b48d5f3 --- /dev/null +++ b/Mailnag/plugins/libnotifyplugin.ui @@ -0,0 +1,623 @@ + + + + + + + + + + + + + + + + + + 480 + True + False + vertical + 12 + + + True + False + start + <b>Notification modes:</b> + True + + + False + True + 0 + + + + + True + False + 18 + 6 + vertical + 6 + + + Count of new mails + False + False + False + True + + + False + True + 0 + + + + + Short summary of new mails + False + False + True + notification_mode_count + + + False + True + 1 + + + + + Detailed summary of new mails + False + False + True + notification_mode_count + + + False + True + 2 + + + + + One notification per new mail + False + False + True + notification_mode_count + + + False + True + 3 + + + + + Only 2FA notification, when enabled + False + False + True + notification_mode_count + + + False + True + 4 + + + + + False + True + 1 + + + + + -1 + True + False + True + True + + + + True + False + True + vertical + + + True + True + in + + + True + True + False + True + True + liststore_2FA_providers + False + False + 1 + + + + + + + + + Enabled + + + + + + 0 + + + + + + + True + autosize + Sender + + + + 1 + + + + + + + True + Subject + + + + 2 + + + + + + + True + autosize + Pattern + + + + 3 + + + + + + + + + True + True + 0 + + + + + True + False + + + True + False + icons + False + 1 + + + True + False + Add 2FA Provider + list-add-symbolic + + + + False + True + + + + + True + False + False + Remove 2FA Provider + list-remove-symbolic + + + + False + True + + + + + True + False + False + Edit 2FA Provider + text-editor-symbolic + + + + False + True + + + + + + False + True + 0 + + + + + False + True + vertical + True + + + + False + 6 + end + + + + + + True + True + 0 + + + + + False + 16 + + + True + False + True + information + True + 50 + + + True + True + 0 + + + + + True + True + 0 + + + + + True + True + 5 + 2 + + + + + True + False + + + True + True + 6 + 6 + 2FA notifications + right + + + False + False + 1 + + + + + True + True + Enable/disable libnotify 2FA processing + end + center + 3 + 3 + + + False + False + 2 + + + + + False + False + end + 6 + + + + + False + True + end + 1 + + + + + + + False + <b>2FA providers</b> + True + + + + + True + True + 2 + + + + + Pattern regexp with {code} for capture. + + + 480 + False + True + True + text-editor-symbolic + dialog + + + + False + 6 + 6 + 12 + True + True + vertical + 2 + + + True + False + end + + + gtk-cancel + True + False + False + True + + + + True + True + 0 + + + + + gtk-ok + True + False + True + False + True + + + + True + True + 1 + + + + + False + False + 3 + + + + + + True + False + 12 + 12 + 11 + 6 + + + True + False + end + Sender + + + 0 + 0 + + + + + True + True + Display name of a mail sender, +if display name is not available, +address spec can be used. + 6 + 6 + True + Sender + + + 1 + 0 + + + + + True + False + end + Subject + + + 0 + 1 + + + + + True + True + This field is not a regular expression, +however it can contain a code which +is materialised within braces, like +this: {code}. + +Ex: Your security code: {code} + 6 + 6 + True + Subject (not a regexp but {code} for capture possible) + + + 1 + 1 + + + + + False + True + 0 + + + + + 50 + True + False + 48 + 7 + 6 + 6 + 0.05000000074505806 + out + + + 120 + True + True + This field can be a regular expression +where the code is materialised within +braces, like this: {code}. + +Ex: Your security code is:\s?{code} + pattern_text_buffer + + + + + True + False + Text + + + + + True + True + 1 + 1 + + + + + True + False + 6 + 6 + + + False + True + 3 + + + + + + button1 + button2 + + + + True + False + Edit 2FA provider + + + switch_enable_provider + True + True + Enable provider + + + + + + button1 + button2 + + + diff --git a/NEWS b/NEWS index 3c3a83a..8b2c5db 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,7 @@ Mailnagger news =============== -Version 2.4.0.dev0 (not released) +Version 2.4.0.dev1 (not released) ================================= Fixes @@ -12,6 +12,28 @@ New features ------------ +Version 2.4.0.aau0 (not released) +================================= + +Fixes +----- +* Improved IMAP/POP3 connection reliability and socket handling. +* Fixed missing UI assets in distribution packages. +* Enhanced translation build process with explicit error logging. + +New features +------------ +* Added 2FA code extraction from emails with "Copy" button in notifications. +* Integrated support for wl-copy, xclip, and xsel. +* Migrated configuration to XDG standard path (~/.config/mailnag/). +* Improved GOA integration with automatic OAuth2 token refresh. +* Added granular per-module log level configuration. +* Updated backend API (ABC classes, UID tracking, mandatory fetch_text). + +NOTES: +* Binaries are renamed to mailnagger and mailnagger-config. [cite: 7] +* Manual migration of mailnag.cfg from ~/.mailnag/ to ~/.config/mailnag/ is required. + Version 2.3.1 (2024-12-06) ========================== diff --git a/README.md b/README.md index ca0a98a..bb4d6dc 100644 --- a/README.md +++ b/README.md @@ -18,35 +18,137 @@ If you like Mailnagger, please help to keep it going by [contributing code](http or [writing docs](https://github.com/tikank/mailnagger/tree/master/docs). -## Installation +## 🚀 Advanced Features (This Fork) -Easiest way to install Mailnagger from -[PyPI](https://pypi.org/project/mailnagger/) is to use -[pipx](https://pypi.org/project/pipx/). +This version of Mailnagger introduces several high-productivity enhancements and core engine improvements: + +### 🔑 Intelligent 2FA Detection & Power Summary +* **Automatic Extraction**: The `libnotify` plugin scans incoming emails to detect Two-Factor Authentication (2FA) codes using customizable regex patterns. +* **HTML Body Parsing**: Smart extraction of plain text from HTML emails to ensure 2FA patterns are matched accurately even in complex layouts. +* **Urgency Handling**: 2FA notifications are treated with higher priority, ensuring they stay visible while the code is valid. +* **Instant Visibility**: Extracted codes are injected directly into the notification title (e.g., `🔑 123456 — Garmin`). +* **One-Click Copy**: A "Copy code" button is integrated into the notification, supporting **Wayland** (`wl-copy`) and **X11** (`xclip`, `xsel`). +* **On-Demand Fetching**: Uses the new `fetch_text()` backend method to retrieve only the necessary data for parsing, saving bandwidth. +* **Independent Notifications**: Each incoming mail generates a unique notification instance. Copying a 2FA code from one specific notification won't interfere with others, even during simultaneous bursts. +* **Non-Blocking Fetch**: The 2FA extraction engine works asynchronously. Slow network responses from one mail server won't delay notifications from other accounts. +* **Clipboard Persistence**: Copy actions are delegated to system-level utilities, ensuring codes remain available in your clipboard even after notifications are dismissed. + +### 🛡️ Robust GOA & OAuth2 Management +* **Native GNOME Integration**: Deeply integrated with **GNOME Online Accounts (GOA)**. +* **Automatic Token Refresh**: Implements `refresh_goa_token` to handle OAuth2 access token updates in the background, preventing connection drops for Gmail/Outlook. + +### 📊 Professional Logging System +* **Granular Control**: Define log levels per module via the `[logger_levels]` section in `mailnag.cfg`. +* **Safe Configuration**: Uses `dictConfig` with predefined `VALID_LEVELS` to ensure system stability even with custom log settings. + +--- + +## ⚙️ Configuration + +### 1. Granular Logging Control (`mailnag.cfg`) + +You can now define the logging verbosity per module by adding a `[logger_levels]` section to your `~/.config/mailnag/mailnag.cfg`. + +```ini +[logger_levels] +# Set the global log level (DEBUG, INFO, WARNING, ERROR, or CRITICAL) +root = INFO + +# Specific level for the 2FA extraction and GOA utilities +Mailnag.common.utils = DEBUG + +# Specific level for the libnotify plugin (useful for regex debugging) +Mailnag.plugins.libnotifyplugin = DEBUG -Run -``` - pipx install mailnagger ``` -though make sure the requirements stated below are met. +* **Dynamic Loading**: The daemon validates these levels against a predefined list (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`) and applies them using `dictConfig`. +* **Dual Output**: Logs are automatically routed to both standard output and the system journal via `/dev/log`. + +--- + +### 2. 2FA Providers (`2fa_providers.tsv`) + +The extraction of security codes from incoming emails is managed via a Tab-Separated Values (TSV) file located at `~/.config/mailnag/2fa_providers.tsv`. + +| Column | Description | +| --- | --- | +| **enabled** | `True` or `False` to toggle detection for this service. | +| **provider** | The display name of the service (e.g., Garmin, Microsoft). | +| **subject** | The email subject pattern to match (supports the `{code}` placeholder). | +| **text_re** | The Regex used to find the code in the email body (use `{code}` for the target). | + +--- + +### 3. GNOME Online Accounts (GOA) Integration -### Requirements +This fork provides deep integration with GNOME Online Accounts for robust authentication. -* python (>= 3.9) -* pygobject -* gir-notify (>= 0.7.6) -* gir-gtk-3.0 -* gir-gdkpixbuf-2.0 -* gir-glib-2.0 -* gir-gst-plugins-base-1.0 -* python-dbus -* pyxdg -* gettext -* gir1.2-secret-1 (optional) +* **Account Linking**: The system automatically maps your email and username to the corresponding GOA identity using `get_goa_account_id`. +* **Automatic OAuth2 Refresh**: If a connection fails due to an expired session, `refresh_goa_token` silently fetches a new access token from the system, preventing manual re-authentication prompts. +--- + +## 🛠️ Developers & "Under the Hood" + +* **Architecture**: Transitioned to **Abstract Base Classes (ABC)** for backends, ensuring a strict and stable interface. +* **Reliability**: All backends now support **UID-based tracking** and the `fetch_text()` method. +* **Standards**: Configuration management now follows **XDG Base Directory** specifications using `pathlib`. +* **Resilience**: Improved socket error handling and auto-retry logic for IMAP/POP3 connections. +* **Code Style**: Unified codebase using `_LOGGER` and strict **tab-indentation** (optimized for `tabsize 8`). + +If you are developing custom plugins or backends, please note the following core changes: + +* **Backend Interface**: The `list_messages()` method has been updated to yield a 4-tuple including the message **UID**: `(folder, message, uid, flags)`. +* **Mandatory Method**: All backends must now implement `fetch_text()` to allow on-demand retrieval of the email body. + +## 🏗️ Build & Setup Improvements +* **Robust Localization**: Improved `BuildData` class with explicit error handling and logging during translation compilation. +* **Asset Integrity**: Fixed missing UI resources in the distribution package, ensuring `libnotifyplugin.ui` is correctly installed. +* **Modern Packaging**: Updated `setup.py` to support high-resolution icons and standardized XDG desktop file locations. + +--- + +## ⚠️ Technical Incompatibilities + +**Important:** This fork modifies the Mailnagger core. Its `libnotify` is **not compatible** with backends from the original `titank` repository due to the following changes: + +* **`fetch_text()` Method**: All mail backends now require a mandatory `fetch_text()` function. +* **Modified Signatures**: Backend methods like `list_messages` now return additional data (such as UIDs), making them incompatible with older core versions. + +--- + +## 🔄 Migration Note (Standard Compliance) + +This fork now follows the **XDG Base Directory Specification**. +Config files have moved from `~/.mailnag` to `~/.config/mailnag`. + +If you are upgrading from an older version, you can migrate your settings manually: + +```bash +mkdir -p ~/.config/mailnag +cp ~/.mailnag/mailnag.cfg ~/.config/mailnag/ +# Optional: Move your custom rules/scripts if you have any +``` +## 📋 Requirements & Installation + +### Core Dependencies +* **Python** (>= 3.10) - *Mandatory for modern type syntax.* +* **pyxdg** - *Mandatory for XDG directory support.* +* **PyGObject / GLib / gir-notify** - *For system notifications and UI.* +* **dbus-python** - *For communication with GOA and desktop services.* + +### Optional (Feature-Specific) +* **2FA Clipboard**: `wl-clipboard` (Wayland) or `xclip/xsel` (X11). +* **Secure Storage**: `libsecret` / `gir1.2-secret-1`. +* **Translations**: `gettext` (only for building from source). + +### Installation +```bash +pipx install mailnagger +``` -## Configuration +## Configuration Run `mailnagger-config` to setup Mailnagger. diff --git a/VERSION b/VERSION index 2bf1c1c..b2e466d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3.1 +2.4.0.aau0 diff --git a/gen_locales b/gen_locales index a9613e2..d804799 100755 --- a/gen_locales +++ b/gen_locales @@ -1,4 +1,4 @@ -#/bin/bash +#!/bin/bash # # script to generate mailnagger locales diff --git a/gen_po_template b/gen_po_template index e4908f7..fc01288 100755 --- a/gen_po_template +++ b/gen_po_template @@ -3,6 +3,7 @@ # generates a gettext .pot template. glade_dir=./Mailnag/configuration/ui +plugins_dir=./Mailnag/plugins python_dir=./Mailnag pot_file=./po/mailnagger.pot @@ -15,13 +16,13 @@ if [ -f $pot_file ]; then fi # generate string headers of all glade files -for f in $glade_dir/*.ui ; do +for f in $glade_dir/*.ui $plugins_dir/*.ui ; do intltool-extract --type=gettext/glade $f done # write template files pyfiles=`find $python_dir -iname "*.py" -printf "%p "` -xgettext $pyfiles $glade_dir/*.h --keyword=_ --keyword=N_ --add-comments="TRANSLATORS:" --from-code=UTF-8 --copyright-holder="2024 Timo Kankare " --package-name="Mailnagger" --package-version="2.4.0.dev0" --msgid-bugs-address="https://github.com/tikank/mailnagger/issues" --output=$pot_file +xgettext $pyfiles $glade_dir/*.h $plugins_dir/*.h --keyword=_ --keyword=N_ --add-comments="TRANSLATORS:" --from-code=UTF-8 --copyright-holder="2024 Timo Kankare " --package-name="Mailnagger" --package-version="2.4.0.dev0" --msgid-bugs-address="https://github.com/tikank/mailnagger/issues" --output=$pot_file # clean up -rm $glade_dir/*.h +rm $glade_dir/*.h $plugins_dir/*.h diff --git a/mailnagger/config/__init__.py b/mailnagger/config/__init__.py index 23c20ff..dfecd37 100755 --- a/mailnagger/config/__init__.py +++ b/mailnagger/config/__init__.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2024 Timo Kankare # Copyright 2011 - 2019 Patrick Ulbrich # @@ -34,7 +35,7 @@ from Mailnag.configuration.configwindow import ConfigWindow from mailnagger.resources import get_icon_paths -LOG_LEVEL = logging.DEBUG +LOG_LEVEL = logging.WARNING class App(Gtk.Application): @@ -48,8 +49,8 @@ def __init__(self): def do_startup(self) -> None: Gtk.Application.do_startup(self) - # Add icons in alternative data paths (e.g. ./data/icons) - # to the icon search path in case Mailnag is launched + # Add icons in alternative data paths (e.g. ./data/icons) + # to the icon search path in case Mailnag is launched # from a local directory (without installing). icon_theme = Gtk.IconTheme.get_default() for path in get_icon_paths(): @@ -68,10 +69,13 @@ def do_shutdown(self) -> None: if self.win.get_daemon_enabled(): try: - # the launched daemon shuts down + # the launched daemon shuts down # an already running daemon print("Launching Mailnagger daemon.") - subprocess.Popen(os.path.join(BIN_DIR, "mailnagger")) + try: + subprocess.Popen(os.path.join(BIN_DIR, "mailnagger")) + except: + subprocess.Popen(['/usr/bin/env', 'python3', '-m', 'mailnagger']) except Exception as e: print(f"ERROR: Failed to launch Mailnagger daemon: {str(e)}") else: @@ -84,7 +88,7 @@ def main() -> int: set_procname("mailnagger-config") init_logging(enable_stdout = True, enable_syslog = False, log_level = LOG_LEVEL) + app = App() app.run(None) return os.EX_OK - diff --git a/mailnagger/daemon/__init__.py b/mailnagger/daemon/__init__.py index 50d11c7..d1b4901 100755 --- a/mailnagger/daemon/__init__.py +++ b/mailnagger/daemon/__init__.py @@ -42,8 +42,10 @@ from Mailnag.common.exceptions import InvalidOperationException from Mailnag.daemon.mailnagdaemon import MailnagDaemon +_LOGGER = logging.getLogger(__name__) + PROGNAME = 'mailnagger' -LOG_LEVEL = logging.DEBUG +LOG_LEVEL = logging.WARNING def cleanup(daemon: Optional[MailnagDaemon]) -> None: @@ -62,11 +64,11 @@ def thread() -> None: event.wait(10.0) if not event.is_set(): - logging.warning('Cleanup takes too long. Enforcing termination.') + _LOGGER.warning('Cleanup takes too long. Enforcing termination.') os._exit(os.EX_SOFTWARE) if threading.active_count() > 1: - logging.warning('There are still active threads. Enforcing termination.') + _LOGGER.warning('There are still active threads. Enforcing termination.') os._exit(os.EX_SOFTWARE) @@ -122,7 +124,7 @@ def main() -> int: try: if not cfg_exists(): - logging.critical( + _LOGGER.critical( "Cannot find configuration file. " + "Please run mailnagger-config first.") exit(1) @@ -150,7 +152,7 @@ def shutdown_request_hdlr() -> None: except KeyboardInterrupt: pass # ctrl+c pressed finally: - logging.info('Shutting down...') + _LOGGER.info('Shutting down...') cleanup(daemon) return os.EX_OK diff --git a/mailnagger/resources.py b/mailnagger/resources.py index 92dd765..891e5ea 100644 --- a/mailnagger/resources.py +++ b/mailnagger/resources.py @@ -1,3 +1,4 @@ +# Copyright 2025 André Auzi # Copyright 2024 Timo Kankare # # This program is free software; you can redistribute it and/or modify @@ -82,13 +83,16 @@ def get_plugin_paths() -> list[Union[Path, Traversable]]: def get_locale_path() -> Path: """Returns path to translation files.""" + if Path('./Mailnag').exists() and Path('./locale').exists(): + return Path('./locale') + for p in [LOCALE_DIR, Path("./locale")]: if p.exists(): return p + return LOCALE_DIR def get_resource_text(module: ModuleType, resource: str) -> str: """Returns resource text from module.""" return files(module).joinpath(resource).read_text() - diff --git a/po/bg.po b/po/bg.po index ef68d22..f5124d1 100644 --- a/po/bg.po +++ b/po/bg.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Bulgarian \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Без тема" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Пощенски акаунт" @@ -27,14 +31,15 @@ msgid "optional" msgstr "незадължително" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Включено" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Име" @@ -58,164 +63,195 @@ msgstr "" msgid "Connection failed." msgstr "Връзката се разпадна." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " spacy01 https://launchpad.net/~spacy00001" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Изтриване на акаунта:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Настройки на приставките" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Без тема" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Потребителски скрипт" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Спам филтър" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "При пристигане на имейл стартира скрипт дефиниран от потребителя" -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Филтър за нежелана поща." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "акаунт" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "изпращач" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "тема" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag ще игнорира писма съдържащи поне една от \n" -"следните думи в темата или изпращача." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Звукови известия" +"Следния скрипт ще бъде изпълнен при пристигането на нов имейл.\n" +"Mailnag ще предава общия брой имейли на този скрипт,\n" +"последвано от %s поредици." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Изпълнява звук при пристигане на имейл." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "LibNotify известия" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Показва изкачащ прозорец при пристигане на имейл." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Брой на нови писма" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Режим на известяване:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Кратка извадка от новите писма" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Подробна извадка от новите писма" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Едно известие за имейл" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Режим на известяване:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "тема" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} нови писма" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "от {0} и други." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "от {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Ново писмо" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "( и {0} други)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Отбележи като прочетено" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Потребителски скрипт" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "При пристигане на имейл стартира скрипт дефиниран от потребителя" - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "акаунт" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Изтриване на акаунта:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "изпращач" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "тема" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Спам филтър" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Филтър за нежелана поща." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"Следния скрипт ще бъде изпълнен при пристигането на нов имейл.\n" -"Mailnag ще предава общия брой имейли на този скрипт,\n" -"последвано от %s поредици." +"Mailnag ще игнорира писма съдържащи поне една от \n" +"следните думи в темата или изпращача." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Звукови известия" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Изпълнява звук при пристигане на имейл." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -299,6 +335,112 @@ msgstr "Плъгини" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Брой на нови писма" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Кратка извадка от новите писма" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Подробна извадка от новите писма" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Едно известие за имейл" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Едно известие за имейл" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "изпращач" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Звукови известия" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Включено" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "LibNotify известия" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Показва изкачащ прозорец при пристигане на имейл." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Максимален брой видими имейли." diff --git a/po/ca.po b/po/ca.po index 9ce262a..efed926 100644 --- a/po/ca.po +++ b/po/ca.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2020-04-05 12:44+0000\n" "Last-Translator: Marc Riera Irigoyen \n" "Language-Team: Catalan \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Sense assumpte" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Compte de correu" @@ -27,14 +31,15 @@ msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Habilitat" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nom" @@ -58,164 +63,195 @@ msgstr "Maildir (personalitzat)" msgid "Connection failed." msgstr "No s'ha pogut connectar." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "Un dimoni de notificacions de correu extensible." -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "Lloc web" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " Marc Riera Irigoyen https://launchpad.net/~marcriera" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Suprimeix aquest compte:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Configuració del connector" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Sense assumpte" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Script de l'usuari" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Filtre de correu brossa" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Executa un script definit per l'usuari quan arriba correu nou." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Exclou correus no desitjats." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "compte" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "remitent" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "assumpte" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"El Mailnag ignorarà els correus que continguin com a mínim \n" -"una de les paraules següents a l'assumpte o el remitent." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Notificacions sonores" +"S'executarà l'script següent quan arribin correus nous.\n" +"El Mailnag passa el nombre total de correus nous a aquest script,\n" +"seguit de seqüències %s." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Reprodueix un so quan arriben correus nous." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Notificacions del LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Mostra una finestra emergent quan arriben correus nous." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Recompte de correus nous" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Mode de notificació:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Resum breu dels correus nous" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Resum detallat dels correus nous" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Una notificació per correu nou" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Mode de notificació:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "assumpte" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} correus nous" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "de {0} i altres." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "de {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Correu nou" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(i {0} més)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Marca com a llegit" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Script de l'usuari" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Executa un script definit per l'usuari quan arriba correu nou." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "compte" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Suprimeix aquest compte:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "remitent" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "assumpte" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Filtre de correu brossa" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Exclou correus no desitjats." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"S'executarà l'script següent quan arribin correus nous.\n" -"El Mailnag passa el nombre total de correus nous a aquest script,\n" -"seguit de seqüències %s." +"El Mailnag ignorarà els correus que continguin com a mínim \n" +"una de les paraules següents a l'assumpte o el remitent." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Notificacions sonores" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Reprodueix un so quan arriben correus nous." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -300,6 +336,112 @@ msgstr "Connectors" msgid "Info" msgstr "Informació" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Recompte de correus nous" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Resum breu dels correus nous" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Resum detallat dels correus nous" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Una notificació per correu nou" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Una notificació per correu nou" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "remitent" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Notificacions sonores" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Habilitat" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Notificacions del LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Mostra una finestra emergent quan arriben correus nous." + #, python-format #~ msgid "About %s" #~ msgstr "Quant al %s" diff --git a/po/cs.po b/po/cs.po index 48fc377..255f6d8 100644 --- a/po/cs.po +++ b/po/cs.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Czech \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Žádný předmět" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Poštovní účet" @@ -27,14 +31,15 @@ msgid "optional" msgstr "volitelně" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Povoleno" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Název" @@ -58,30 +63,30 @@ msgstr "" msgid "Connection failed." msgstr "Spojení selhalo." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -89,135 +94,166 @@ msgstr "" " Patrick Ulbrich https://launchpad.net/~pulb\n" " Radek Otáhal https://launchpad.net/~radek-otahal" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Odstranit tento účet:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Konfigurace pluginu" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Žádný předmět" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Uživatelský skript" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Spam filtr" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Spuštění uživatelem definovaného skriptu při přijetí e-mailu." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Filtruje nevyžádané e-maily." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "účet" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "odesílatel" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "předmět" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag bude ignorovat e-maily obsahující aspoň jedno z \n" -"následujících slov v poli předmět nebo odesílatel." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Zvukové notifikace" +"Následující skript bude spuštěn při novém e-mailu.\n" +"Mailnag předá celkové počet nových e-mailů tomuto skriptu,\n" +"následovaný %s sekvencemi." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Přehraje zvuk při novém e-mailu." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "LibNotify notifikace" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Zobrazí popup okno při novém e-mailu." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Počet nových e-mailů" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Notifikační mód:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Krátký přehled nových e-mailů" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Detailní přehled nových e-mailů" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Jedna notifikace pro nový e-mail" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Notifikační mód:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "předmět" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} nových e-mailů" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "z {0} a další." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "z {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Nový mail" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(a {0} dalších)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Označit jako přečtený" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Uživatelský skript" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Spuštění uživatelem definovaného skriptu při přijetí e-mailu." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "účet" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Odstranit tento účet:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "odesílatel" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "předmět" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Spam filtr" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Filtruje nevyžádané e-maily." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"Následující skript bude spuštěn při novém e-mailu.\n" -"Mailnag předá celkové počet nových e-mailů tomuto skriptu,\n" -"následovaný %s sekvencemi." +"Mailnag bude ignorovat e-maily obsahující aspoň jedno z \n" +"následujících slov v poli předmět nebo odesílatel." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Zvukové notifikace" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Přehraje zvuk při novém e-mailu." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -301,6 +337,112 @@ msgstr "Pluginy" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Počet nových e-mailů" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Krátký přehled nových e-mailů" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Detailní přehled nových e-mailů" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Jedna notifikace pro nový e-mail" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Jedna notifikace pro nový e-mail" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "odesílatel" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Zvukové notifikace" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Povoleno" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "LibNotify notifikace" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Zobrazí popup okno při novém e-mailu." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Maximální počet viditelných e-mailů:" diff --git a/po/de.po b/po/de.po index 9a03477..c027d9d 100644 --- a/po/de.po +++ b/po/de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2020-10-24 19:26+0000\n" "Last-Translator: J. Lavoie \n" "Language-Team: German \n" "Language-Team: Spanish \n" "Language-Team: \n" @@ -18,6 +18,10 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.5\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Ei otsikkoa" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Sähköpostitili" @@ -27,14 +31,15 @@ msgid "optional" msgstr "valinnainen" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Käytössä" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nimi" @@ -58,161 +63,192 @@ msgstr "Maildir" msgid "Connection failed." msgstr "Yhteydenotto epäonnistui." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "Tietoja Mailnaggerista" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "Laajennettava sähköpostihuomautin." -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "Copyright (c) {years} {author} ja muut tekijät." -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "Kotisivu" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "ylläpitäjä" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "Timo Kankare" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Poista tämä tili:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Lisäosan konfigurointi" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Ei otsikkoa" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Käyttäjän skripti" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Roskapostisuodatin" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Ajaa käyttäjän määrittelemän skriptin, kun uusi sähköposti saapuu." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Suodattaa pois ei-toivottuja sähköposteja." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "tili" -#: Mailnag/plugins/spamfilterplugin.py:87 +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "lähettäjä" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "otsikko" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnagger suodattaa pois postit, joissa on otsikossa\n" -"tai lähettäjäkentässä ainakin yksi seuraavista sanoista." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Äänimerkki" +"Seuraava skripti suoritetaan, kun uusi sähköposti saapuu.\n" +"Mailnagger välittää skriptille uusien postien kokonaismäärän\n" +"ja sen perään %s-sarjan." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Soittaa äänimerkin, kun uusi sähköposti saapuu." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "LibNotify-huomautin" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Näyttää huomautuksen, kun uusi sähköposti saapuu." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Uusien sähköpostien lukumäärä" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Huomautusvalinta:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Lyhyt yhteenveto uusista sähköposteista" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Yksityiskohtainen yhteenveto uusista sähköposteista" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Oma huomautus kustakin uudesta sähköpostista" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Huomautusvalinta:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "otsikko" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} uutta sähköpostia" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "lähettäjältä {0} ja muilta." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "lähettäjältä {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Uusi sähköposti" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(ja {0} muuta)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Merkitse luetuksi" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Käyttäjän skripti" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Ajaa käyttäjän määrittelemän skriptin, kun uusi sähköposti saapuu." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "tili" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Poista tämä tili:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "lähettäjä" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "otsikko" -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Roskapostisuodatin" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Suodattaa pois ei-toivottuja sähköposteja." + +#: Mailnag/plugins/spamfilterplugin.py:87 msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"Seuraava skripti suoritetaan, kun uusi sähköposti saapuu.\n" -"Mailnagger välittää skriptille uusien postien kokonaismäärän\n" -"ja sen perään %s-sarjan." +"Mailnagger suodattaa pois postit, joissa on otsikossa\n" +"tai lähettäjäkentässä ainakin yksi seuraavista sanoista." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Äänimerkki" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Soittaa äänimerkin, kun uusi sähköposti saapuu." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -297,3 +333,109 @@ msgstr "Lisäosat" #: Mailnag/configuration/ui/config_window.ui.h:8 msgid "Info" msgstr "Tietoja" + +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Uusien sähköpostien lukumäärä" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Lyhyt yhteenveto uusista sähköposteista" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Yksityiskohtainen yhteenveto uusista sähköposteista" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Oma huomautus kustakin uudesta sähköpostista" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Oma huomautus kustakin uudesta sähköpostista" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "lähettäjä" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Äänimerkki" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Käytössä" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "LibNotify-huomautin" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Näyttää huomautuksen, kun uusi sähköposti saapuu." diff --git a/po/fr.po b/po/fr.po index ed57ad3..ec1e7c0 100644 --- a/po/fr.po +++ b/po/fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2020-10-15 17:26+0000\n" "Last-Translator: J. Lavoie \n" "Language-Team: French \n" "Language-Team: Galician \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Sen asunto" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Conta de correo-e" @@ -27,14 +31,15 @@ msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Activado" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nome" @@ -58,30 +63,30 @@ msgstr "" msgid "Connection failed." msgstr "" -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -89,132 +94,163 @@ msgstr "" " Marcos Lans https://launchpad.net/~markooss\n" " Patrick Ulbrich https://launchpad.net/~pulb" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Eliminar esta conta:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Configuración do complemento" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Sen asunto" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Script do usuario" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Filtro de spam" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Executa un script do usuario ao recibir algún correo." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Filtra os correos non desexados." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "remitente" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "asunto" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag ignorará os correos que conteñan alomenos \n" -"unha das seguintes palabras no asunto ou no remitente." -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Notificacións con son" - -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Reproduce un son ao recibir un novo correo-e" +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Notificacións LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Mostra unha xanela emerxente ao recibir correos novos." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Total de novos correos" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Modo Notificación:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Unha notificación por cada novo correo" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Modo Notificación:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "asunto" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} novos correos" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Correo novo" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(e {0} máis)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Marcar como lido" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Script do usuario" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Eliminar esta conta:" -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Executa un script do usuario ao recibir algún correo." +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" +msgstr "remitente" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" +msgstr "asunto" + +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" -msgstr "remitente" +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Filtro de spam" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" -msgstr "asunto" +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Filtra os correos non desexados." -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" +"Mailnag ignorará os correos que conteñan alomenos \n" +"unha das seguintes palabras no asunto ou no remitente." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Notificacións con son" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Reproduce un son ao recibir un novo correo-e" #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -298,5 +334,111 @@ msgstr "Complementos" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Total de novos correos" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Unha notificación por cada novo correo" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Unha notificación por cada novo correo" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "remitente" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Notificacións con son" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Activado" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Notificacións LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Mostra unha xanela emerxente ao recibir correos novos." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Número máximo de mensaxes visibles:" diff --git a/po/hr.po b/po/hr.po index ae20d71..c1bdd67 100644 --- a/po/hr.po +++ b/po/hr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2021-01-18 00:35+0000\n" "Last-Translator: Milo Ivir \n" "Language-Team: Croatian \n" "Language-Team: Indonesian \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Tidak ada subyek" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Akun Surat" @@ -27,14 +31,15 @@ msgid "optional" msgstr "opsional" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Aktifkan" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nama" @@ -58,161 +63,192 @@ msgstr "" msgid "Connection failed." msgstr "Koneksi gagal." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " zmni https://launchpad.net/~zmni-deactivatedaccount" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Hapus akun ini:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Konfigurasi Plugin" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Tidak ada subyek" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Skrip Pengguna" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Penyaring Spam" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Jalankan skrip tentuan pengguna saat surat datang." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Saring surat yang tidak diinginkan." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "pengirim" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "subyek" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag akan mengabaikan surat yang memuat sedikitnya \n" -"satu dari kata-kata berikut di dalam subyek atau pengirim." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Notifikasi Suara" -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Putar suara ketika surat baru datang." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Notifikasi LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Tampilkan jendela munculan ketika surat baru datang." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Jumlah surat baru" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Mode notifikasi:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Ringkasan singkat surel baru" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Ringkasan detail surel baru" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Satu notifikasi per surat baru" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Mode notifikasi:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "subyek" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} surat baru" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "dari {0} dan lainnya." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "dari {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Surat baru" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(dan {0} lainnya)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Tandai sudah dibaca" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Skrip Pengguna" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Hapus akun ini:" -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Jalankan skrip tentuan pengguna saat surat datang." +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" +msgstr "pengirim" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" +msgstr "subyek" + +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" -msgstr "pengirim" +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Penyaring Spam" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" -msgstr "subyek" +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Saring surat yang tidak diinginkan." -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" +"Mailnag akan mengabaikan surat yang memuat sedikitnya \n" +"satu dari kata-kata berikut di dalam subyek atau pengirim." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Notifikasi Suara" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Putar suara ketika surat baru datang." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -296,6 +332,112 @@ msgstr "Plugin" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Jumlah surat baru" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Ringkasan singkat surel baru" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Ringkasan detail surel baru" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Satu notifikasi per surat baru" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Satu notifikasi per surat baru" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "pengirim" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Notifikasi Suara" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Aktifkan" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Notifikasi LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Tampilkan jendela munculan ketika surat baru datang." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Jumlah maksimum surat terlihat:" diff --git a/po/it.po b/po/it.po index 09c3073..5e2d4f5 100644 --- a/po/it.po +++ b/po/it.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2020-10-15 17:26+0000\n" "Last-Translator: J. Lavoie \n" "Language-Team: Italian \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,10 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "" @@ -26,14 +30,15 @@ msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "" @@ -57,155 +62,182 @@ msgstr "" msgid "Connection failed." msgstr "" -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:87 -msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" msgstr "" -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" msgstr "" -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format +msgid "" +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +msgid "Subject" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" +#: Mailnag/plugins/libnotifyplugin.py:837 +msgid "Delete this provider:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." +#: Mailnag/plugins/libnotifyplugin.py:838 +msgid "Sender:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" +#: Mailnag/plugins/libnotifyplugin.py:839 +msgid "Subject:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:87 msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." +msgstr "" + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/configuration/ui/account_widget.ui.h:1 @@ -289,3 +321,97 @@ msgstr "" #: Mailnag/configuration/ui/config_window.ui.h:8 msgid "Info" msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +msgid "Only 2FA notification, when enabled" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +msgid "Sender" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +msgid "2FA notifications" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +msgid "Enable provider" +msgstr "" diff --git a/po/pl.po b/po/pl.po index 8f329f8..e6b4ca6 100644 --- a/po/pl.po +++ b/po/pl.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Polish \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Brak tematu" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Konto pocztowe" @@ -27,14 +31,15 @@ msgid "optional" msgstr "opcjonalny" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Włączone" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nazwa" @@ -58,30 +63,30 @@ msgstr "" msgid "Connection failed." msgstr "Połączenie nie powiodło się." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -92,130 +97,161 @@ msgstr "" " Szymon Nieznański https://launchpad.net/~isamu715\n" " vbert https://launchpad.net/~wsobczak" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Usuń to konto:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Konfiguracja wtyczki" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Brak tematu" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Skrypt użytkownika" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Filtr spamowy" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Uruchom skrypt użytkownika gdy nadejdzie nowa poczta." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Filtruje niechciane wiadomości." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "konto" -#: Mailnag/plugins/spamfilterplugin.py:87 +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "nadawca" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "temat" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Dżwięk powiadomienia" - -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Odtwarzaj dźwięk kiedy przychodzi nowa poczta." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Powiadomienia LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Pokaż okno kiedy przychodzi nowa poczta." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Liczba nowych wiadomości" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Tryb powiadamiania:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Krótkie podsumowanie nowych wiadomości e-mail" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Szczegółowe podsumowanie nowych wiadomości e-mail" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Jedno powiadomienie dla jednej wiadomości" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Tryb powiadamiania:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "temat" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} nowych wiadomości" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "od {0} i innych." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "od {0}" -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Nowa poczta" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(i {0} więcej)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Oznacz jako przeczytana" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Skrypt użytkownika" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Uruchom skrypt użytkownika gdy nadejdzie nowa poczta." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "konto" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Usuń to konto:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "nadawca" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "temat" -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Filtr spamowy" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Filtruje niechciane wiadomości." + +#: Mailnag/plugins/spamfilterplugin.py:87 msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Dżwięk powiadomienia" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Odtwarzaj dźwięk kiedy przychodzi nowa poczta." + #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" "You may need to create an application-specific password for Gmail.\n" @@ -298,6 +334,112 @@ msgstr "Wtyczki" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Liczba nowych wiadomości" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Krótkie podsumowanie nowych wiadomości e-mail" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Szczegółowe podsumowanie nowych wiadomości e-mail" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Jedno powiadomienie dla jednej wiadomości" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Jedno powiadomienie dla jednej wiadomości" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "nadawca" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Dżwięk powiadomienia" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Włączone" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Powiadomienia LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Pokaż okno kiedy przychodzi nowa poczta." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Maksymalna ilość widocznych wiadomości:" diff --git a/po/pt.po b/po/pt.po index 98fd45a..52f00f9 100644 --- a/po/pt.po +++ b/po/pt.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Portuguese \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Sem assunto" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Conta de mail" @@ -27,14 +31,15 @@ msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Ativado" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nome" @@ -58,30 +63,30 @@ msgstr "" msgid "Connection failed." msgstr "A ligação falhou." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -90,135 +95,166 @@ msgstr "" " Pedro Beja https://launchpad.net/~althaser\n" " Rafael Neri https://launchpad.net/~rafepel" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Apagar esta conta:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Configuração de Plugin" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Sem assunto" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Script de Utilizador" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Filtro Spam" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Executa um script definido pelo utilizador na receção de mails." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Filtra mails indesejados." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "conta" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "remetente" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "assunto" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"O Mailnag irá ignorar mails que contenham pelo menos uma \n" -"das palavras seguintes no assunto ou remetente." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Notificações de Som" +"O script seguinte vai ser executado sempre que cheguem novos mails.\n" +"O Mailnag passa a quantidade total de mails novos para este script,\n" +"seguido por sequências de %s." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Toca um som quando chegam novos mails." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Notificações da LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Mostra um popup quando chegam novos mails." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Quantidade de mails novos" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Modo de notificação:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Resumo curto de mails novos" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Resumo detalhado de mails novos" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Uma notificação por novo mail" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Modo de notificação:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "assunto" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} novos mails" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "de {0} e outros." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "de {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Novo mail" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(e mais {0})" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Marcar como lido" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Script de Utilizador" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Executa um script definido pelo utilizador na receção de mails." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "conta" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Apagar esta conta:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "remetente" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "assunto" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Filtro Spam" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Filtra mails indesejados." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"O script seguinte vai ser executado sempre que cheguem novos mails.\n" -"O Mailnag passa a quantidade total de mails novos para este script,\n" -"seguido por sequências de %s." +"O Mailnag irá ignorar mails que contenham pelo menos uma \n" +"das palavras seguintes no assunto ou remetente." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Notificações de Som" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Toca um som quando chegam novos mails." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -302,6 +338,112 @@ msgstr "Plugins" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Quantidade de mails novos" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Resumo curto de mails novos" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Resumo detalhado de mails novos" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Uma notificação por novo mail" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Uma notificação por novo mail" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "remetente" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Notificações de Som" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Ativado" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Notificações da LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Mostra um popup quando chegam novos mails." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Número máximo de mails visíveis:" diff --git a/po/pt_BR.po b/po/pt_BR.po index 60d5a38..4645f6e 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-12-25 19:25+0000\n" "Last-Translator: Relaxeaza \n" "Language-Team: Brazilian Portuguese \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Sem assunto" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Conta de mail" @@ -27,14 +31,15 @@ msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Ativado" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Nome" @@ -58,30 +63,30 @@ msgstr "" msgid "Connection failed." msgstr "A ligação falhou." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -90,135 +95,166 @@ msgstr "" " Rafael Neri https://launchpad.net/~rafepel\n" " Relaxeaza https://launchpad.net/~relaxeaza" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Apagar esta conta:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Configuração de Plugin" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Sem assunto" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Script de Usuário" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Filtro de Spam" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Executa um script definido pelo usuário na chegada de mensagens." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Filtra mensagens indesejadas." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "conta" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "remetente" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "assunto" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag ignorará mensagens que contenham pelo menos uma \n" -"das palavras seguintes no assunto ou remetente." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Notificações de Som" +"O script seguinte será executado sempre que cheguarem novas mensagens.\n" +"Mailnag passa o total novas mensagens para o script,\n" +"seguido por sequências de %s." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Toca um som quando novas mensagens chegarem." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Notificações da LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Mostra um popup quando chegam novas mensagens." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Quantidade de mensagens novas" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Modo de notificação:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Resumo curto de mensagens novas" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Resumo detalhado de mensagens novas" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Uma notificação por mensagem nova" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Modo de notificação:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "assunto" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} novas mensagens" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "de {0} e outros." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "de {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Novo mail" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(e mais {0})" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Marcar como lido" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Script de Usuário" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Executa um script definido pelo usuário na chegada de mensagens." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "conta" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Apagar esta conta:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "remetente" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "assunto" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Filtro de Spam" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Filtra mensagens indesejadas." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"O script seguinte será executado sempre que cheguarem novas mensagens.\n" -"Mailnag passa o total novas mensagens para o script,\n" -"seguido por sequências de %s." +"Mailnag ignorará mensagens que contenham pelo menos uma \n" +"das palavras seguintes no assunto ou remetente." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Notificações de Som" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Toca um som quando novas mensagens chegarem." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -302,6 +338,112 @@ msgstr "Plugins" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Quantidade de mensagens novas" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Resumo curto de mensagens novas" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Resumo detalhado de mensagens novas" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Uma notificação por mensagem nova" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Uma notificação por mensagem nova" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "remetente" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Notificações de Som" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Ativado" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Notificações da LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Mostra um popup quando chegam novas mensagens." + #~ msgid "MessagingMenu" #~ msgstr "MessagingMenu" diff --git a/po/ru.po b/po/ru.po index 41bb5b9..cef7297 100644 --- a/po/ru.po +++ b/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Без темы" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Учетная запись" @@ -26,14 +30,15 @@ msgid "optional" msgstr "необязательно" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Включено" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Имя" @@ -57,30 +62,30 @@ msgstr "" msgid "Connection failed." msgstr "Ошибка соединения." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -91,135 +96,166 @@ msgstr "" " Vyacheslav Sharmanov https://launchpad.net/~vsharmanov\n" " u-t https://launchpad.net/~fenoform" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Удалить учетную запись:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Настройка плагина" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Без темы" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Пользовательский скрипт" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Спам фильтр" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Запустить пользовательский скрипт при получении почты" -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Фильтрует нежелательные сообщения." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "учетная запись" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "отправитель" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "тема" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag проигнорирует письмо, если имя отправителя\n" -"или тема содержит одно из следующих слов" - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Звуковые уведомления" +"Следующий скрипт будет выполнен при появлении новых сообщений.\n" +"Скрипту передаётся количество сообщений,\n" +"за которым следуют: %s" -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Проигрывает мелодию при появлении нового сообщения." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Уведомления LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Отображение всплывающего уведомления при получении новых писем" -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Количество новых писем" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Тип уведомлений:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Краткие сведения о письмах" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Подробные сведения о письмах" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Одно уведомление на письмо" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Тип уведомлений:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "тема" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "сообщений: {0}" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "от {0} и других." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "от {0}." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Новое сообщение" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(и еще {0})" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Отметить как прочитанное" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Пользовательский скрипт" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Запустить пользовательский скрипт при получении почты" - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "учетная запись" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Удалить учетную запись:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "отправитель" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "тема" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Спам фильтр" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Фильтрует нежелательные сообщения." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"Следующий скрипт будет выполнен при появлении новых сообщений.\n" -"Скрипту передаётся количество сообщений,\n" -"за которым следуют: %s" +"Mailnag проигнорирует письмо, если имя отправителя\n" +"или тема содержит одно из следующих слов" + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Звуковые уведомления" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Проигрывает мелодию при появлении нового сообщения." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -303,5 +339,111 @@ msgstr "Плагины" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Количество новых писем" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Краткие сведения о письмах" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Подробные сведения о письмах" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Одно уведомление на письмо" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Одно уведомление на письмо" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "отправитель" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Звуковые уведомления" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Включено" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Уведомления LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Отображение всплывающего уведомления при получении новых писем" + #~ msgid "Maximum number of visible mails:" #~ msgstr "Максимальное количество отображаемых писем:" diff --git a/po/sr.po b/po/sr.po index d4fa24b..e77093d 100644 --- a/po/sr.po +++ b/po/sr.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2021-01-07 11:29+0000\n" "Last-Translator: Burek \n" "Language-Team: Serbian \n" "Language-Team: Swedish \n" "Language-Team: Turkish " -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Bu hesabı sil:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Eklenti Yapılandırması" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Konu yok" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Kullanıcı Betiği" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Spam Filtresi" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "" +"E-posta geldiğinde kullanıcı tarafından tanımlanan bir betik çalıştırır." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "İstenmeyen postaları filtreler." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "hesap" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "gönderen" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "konu" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, fuzzy, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag, konu veya gönderen kısmında aşağıdaki sözcüklerden\n" -"en az birini içeren e-postaları dikkate almayacaktır." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Sesli Bildirimler" +"Yeni e-posta geldiğinde aşağıdaki betik çalıştırılacaktır.\n" +"Mailnag, yeni e-postaların toplam sayısını, ve ardından\n" +"%s dizisini bu betiğe iletir." -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Yeni e-posta geldiğinde bir ses çalar." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "LibNotify Bildirimleri" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Yeni e-posta geldiğinde bir açılır pencere gösterir." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Yeni e-posta sayısı" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Bildirim modu:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" -msgstr "Yeni e-postaların kısa özeti" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" -msgstr "Yeni e-postaların ayrıntılı özeti" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Her yeni e-posta için bir bildirim" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Bildirim modu:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "konu" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} yeni e-posta" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "{0} ve diğerlerinden." -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "{0}'den." -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Yeni e-posta" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(ve {0} tane daha)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Okundu olarak işaretle" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Kullanıcı Betiği" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "" -"E-posta geldiğinde kullanıcı tarafından tanımlanan bir betik çalıştırır." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "hesap" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Bu hesabı sil:" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "gönderen" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "konu" -#: Mailnag/plugins/userscriptplugin.py:81 -#, fuzzy, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Spam Filtresi" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "İstenmeyen postaları filtreler." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" -"Yeni e-posta geldiğinde aşağıdaki betik çalıştırılacaktır.\n" -"Mailnag, yeni e-postaların toplam sayısını, ve ardından\n" -"%s dizisini bu betiğe iletir." +"Mailnag, konu veya gönderen kısmında aşağıdaki sözcüklerden\n" +"en az birini içeren e-postaları dikkate almayacaktır." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Sesli Bildirimler" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Yeni e-posta geldiğinde bir ses çalar." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -303,6 +339,112 @@ msgstr "Eklentiler" msgid "Info" msgstr "Bilgi" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Yeni e-posta sayısı" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "Yeni e-postaların kısa özeti" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "Yeni e-postaların ayrıntılı özeti" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Her yeni e-posta için bir bildirim" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Her yeni e-posta için bir bildirim" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "gönderen" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Sesli Bildirimler" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Etkin" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "LibNotify Bildirimleri" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Yeni e-posta geldiğinde bir açılır pencere gösterir." + #, python-format #~ msgid "About %s" #~ msgstr "%s hakkında" diff --git a/po/uk.po b/po/uk.po index 3251490..b7761ab 100644 --- a/po/uk.po +++ b/po/uk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Ukrainian \n" @@ -17,6 +17,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "Без теми" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "Обліковий запис пошти" @@ -26,14 +30,15 @@ msgid "optional" msgstr "(необов’язково)" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "Задіяно" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "Назва" @@ -57,30 +62,30 @@ msgstr "" msgid "Connection failed." msgstr "Не вдалося встановити з'єднання." -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -90,132 +95,163 @@ msgstr "" " Patrick Ulbrich https://launchpad.net/~pulb\n" " Rax https://launchpad.net/~r-a-x" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "Видалити обліковий запис" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Налаштування додатка" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "Без теми" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "Користувацький скріпт" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "Фільтр спаму" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "Виконувати скріпт користувача при отриманні листа." -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "Відфільтровує небажані листи." +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "обліківка" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "відправник" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "тема" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -"Mailnag буде нехтувати листи, що містять в темі\n" -"чи імени відправника хоча б одне слово з переліку ." - -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "Звукові сповіщення" -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "Супроводжує нові листи звуковим сповіщенням." +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "Сповіщення LibNotify" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "Сповіщає про нові листи за допомогою виринаючих повідомлень." -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "Кількість нових листів" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "Метод сповіщення:" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "Про кожен лист окремо" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "Метод сповіщення:" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "тема" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} листів" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "Новий лист" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "(і ще {0})" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "Позначити як прочитане" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "Користувацький скріпт" - -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "Виконувати скріпт користувача при отриманні листа." - -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" -msgstr "обліківка" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "Видалити обліковий запис" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" msgstr "відправник" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" msgstr "тема" -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" +msgstr "" + +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "Фільтр спаму" + +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "Відфільтровує небажані листи." + +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" +"Mailnag буде нехтувати листи, що містять в темі\n" +"чи імени відправника хоча б одне слово з переліку ." + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "Звукові сповіщення" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "Супроводжує нові листи звуковим сповіщенням." #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -299,6 +335,112 @@ msgstr "Додатки" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "Кількість нових листів" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "Про кожен лист окремо" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "Про кожен лист окремо" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "відправник" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "Звукові сповіщення" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "Задіяно" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "Сповіщення LibNotify" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "Сповіщає про нові листи за допомогою виринаючих повідомлень." + #~ msgid "Maximum number of visible mails:" #~ msgstr "Максимальна к-сть листів у списку:" diff --git a/po/zh_CN.po b/po/zh_CN.po index f08cba6..80c324c 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Chinese (Simplified) \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "无主题" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "邮件账户" @@ -27,14 +31,15 @@ msgid "optional" msgstr "可选" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "已启用" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "名称" @@ -58,30 +63,30 @@ msgstr "" msgid "Connection failed." msgstr "" -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" @@ -89,130 +94,161 @@ msgstr "" " Patrick Ulbrich https://launchpad.net/~pulb\n" " 朱涛 https://launchpad.net/~bill-zt" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "删除该帐户:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "无主题" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "用户脚本" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "垃圾邮件过滤器" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "运行在邮件到达时的用户自定义脚本。" -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "过滤掉不想要的邮件。" +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:87 +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "寄件人" + +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "主题" + +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." msgstr "" -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "声音通知" - -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "当新邮件到达时播放声音。" +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "弹出窗口通知" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "当新邮件到达时弹出。" -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "新邮件数量" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "通知模式" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "每封新邮件通知一次" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "通知模式" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "主题" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "( 还有 {0} 条)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "标记为已读" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "用户脚本" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "删除该帐户:" -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "运行在邮件到达时的用户自定义脚本。" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" +msgstr "寄件人" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" +msgstr "主题" + +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" -msgstr "寄件人" +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "垃圾邮件过滤器" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" -msgstr "主题" +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "过滤掉不想要的邮件。" -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/spamfilterplugin.py:87 msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." msgstr "" +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "声音通知" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "当新邮件到达时播放声音。" + #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" "You may need to create an application-specific password for Gmail.\n" @@ -295,5 +331,111 @@ msgstr "" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "新邮件数量" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "每封新邮件通知一次" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "每封新邮件通知一次" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "寄件人" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "声音通知" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "已启用" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "弹出窗口通知" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "当新邮件到达时弹出。" + #~ msgid "Maximum number of visible mails:" #~ msgstr "可显示邮件的最大数量" diff --git a/po/zh_TW.po b/po/zh_TW.po index fd41afb..ad5c57a 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: https://github.com/tikank/mailnagger/issues\n" -"POT-Creation-Date: 2024-11-27 20:16+0200\n" +"POT-Creation-Date: 2026-02-15 02:03+0100\n" "PO-Revision-Date: 2019-03-16 14:48+0000\n" "Last-Translator: Launchpad Translations Administrators \n" "Language-Team: Chinese (Traditional) \n" @@ -18,6 +18,10 @@ msgstr "" "X-Launchpad-Export-Date: 2020-06-11 14:44+0000\n" "X-Generator: Launchpad (build b190cebbf563f89e480a8b57f641753c8196bda0)\n" +#: Mailnag/daemon/mails.py:283 +msgid "No subject" +msgstr "無主題" + #: Mailnag/configuration/accountdialog.py:77 msgid "Mail Account" msgstr "郵件賬戶" @@ -27,14 +31,15 @@ msgid "optional" msgstr "可選" #: Mailnag/configuration/accountdialog.py:123 -#: Mailnag/configuration/configwindow.py:88 -#: Mailnag/configuration/configwindow.py:108 +#: Mailnag/configuration/configwindow.py:90 +#: Mailnag/configuration/configwindow.py:110 +#: Mailnag/plugins/libnotifyplugin.ui.h:6 msgid "Enabled" msgstr "已啓用" #: Mailnag/configuration/accountdialog.py:129 -#: Mailnag/configuration/configwindow.py:94 -#: Mailnag/configuration/configwindow.py:114 +#: Mailnag/configuration/configwindow.py:96 +#: Mailnag/configuration/configwindow.py:116 msgid "Name" msgstr "名稱" @@ -58,159 +63,190 @@ msgstr "" msgid "Connection failed." msgstr "" -#: Mailnag/configuration/configwindow.py:278 +#: Mailnag/configuration/configwindow.py:280 msgid "About Mailnagger" msgstr "" -#: Mailnag/configuration/configwindow.py:281 +#: Mailnag/configuration/configwindow.py:283 msgid "An extensible mail notification daemon." msgstr "" -#: Mailnag/configuration/configwindow.py:283 +#: Mailnag/configuration/configwindow.py:285 #, python-brace-format msgid "Copyright (c) {years} {author} and contributors." msgstr "" -#: Mailnag/configuration/configwindow.py:290 +#: Mailnag/configuration/configwindow.py:292 msgid "Homepage" msgstr "" -#: Mailnag/configuration/configwindow.py:293 +#: Mailnag/configuration/configwindow.py:295 msgid "maintainer" msgstr "" #. TRANSLATORS: Translate `translator-credits` to the list of names #. of translators, or team, or something like that. -#: Mailnag/configuration/configwindow.py:313 +#: Mailnag/configuration/configwindow.py:315 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " elleryq https://launchpad.net/~elleryq" -#: Mailnag/configuration/configwindow.py:353 +#: Mailnag/configuration/configwindow.py:355 msgid "Delete this account:" msgstr "刪除該帳戶:" -#: Mailnag/configuration/plugindialog.py:30 +#: Mailnag/configuration/plugindialog.py:35 msgid "Plugin Configuration" msgstr "Plugin 配置" -#: Mailnag/daemon/mails.py:135 -msgid "No subject" -msgstr "無主題" +#: Mailnag/plugins/userscriptplugin.py:60 +msgid "User Script" +msgstr "用戶腳本" -#: Mailnag/plugins/spamfilterplugin.py:67 -msgid "Spam Filter" -msgstr "垃圾郵件過濾器" +#: Mailnag/plugins/userscriptplugin.py:61 +msgid "Runs an user defined script on mail arrival." +msgstr "運行在郵件到達時的用戶自定義腳本。" -#: Mailnag/plugins/spamfilterplugin.py:68 -msgid "Filters out unwanted mails." -msgstr "過濾掉不想要的郵件。" +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "account" +msgstr "" -#: Mailnag/plugins/spamfilterplugin.py:87 -#, fuzzy -msgid "" -"Mailnagger will ignore mails containing at least one of \n" -"the following words in subject or sender." -msgstr "Mailnag 將會忽略主旨或寄件者裡帶有這些字詞的郵件。" +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "sender" +msgstr "寄件人" -#: Mailnag/plugins/soundplugin.py:64 -msgid "Sound Notifications" -msgstr "聲音通知" +#: Mailnag/plugins/userscriptplugin.py:80 +msgid "subject" +msgstr "主題" -#: Mailnag/plugins/soundplugin.py:65 -msgid "Plays a sound when new mails arrive." -msgstr "當新郵件到達時播放聲音。" +#: Mailnag/plugins/userscriptplugin.py:81 +#, python-format +msgid "" +"The following script will be executed whenever new mails arrive.\n" +"Mailnagger passes the total count of new mails to this script,\n" +"followed by %s sequences." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:75 +msgid "Your Security Passcode" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:114 +#: Mailnag/plugins/libnotifyplugin.py:149 msgid "LibNotify Notifications" msgstr "彈出視窗通知" -#: Mailnag/plugins/libnotifyplugin.py:115 +#: Mailnag/plugins/libnotifyplugin.py:150 msgid "Shows a popup when new mails arrive." msgstr "當新郵件到達時彈出訊息視窗。" -#: Mailnag/plugins/libnotifyplugin.py:130 -msgid "Count of new mails" -msgstr "新郵件數量" +#: Mailnag/plugins/libnotifyplugin.py:192 +msgid "Notification mode:" +msgstr "通知模式" -#: Mailnag/plugins/libnotifyplugin.py:131 -msgid "Short summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:194 +msgid "2FA providers" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:132 -msgid "Detailed summary of new mails" +#: Mailnag/plugins/libnotifyplugin.py:241 +#, python-brace-format +msgid "Missing \"code\" group pattern: {code}" msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:133 -msgid "One notification per new mail" -msgstr "每封新郵件通知一次" +#: Mailnag/plugins/libnotifyplugin.py:260 +#, python-format +msgid "%s is incorrect regexp" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:141 -msgid "Notification mode:" -msgstr "通知模式" +#: Mailnag/plugins/libnotifyplugin.py:264 +#: Mailnag/plugins/libnotifyplugin.ui.h:8 +#, fuzzy +msgid "Subject" +msgstr "主題" + +#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.ui.h:9 +msgid "Pattern" +msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:234 -#: Mailnag/plugins/libnotifyplugin.py:270 -#: Mailnag/plugins/libnotifyplugin.py:307 +#: Mailnag/plugins/libnotifyplugin.py:443 +#: Mailnag/plugins/libnotifyplugin.py:481 +#: Mailnag/plugins/libnotifyplugin.py:633 #, python-brace-format msgid "{0} new mails" msgstr "{0} 封新郵件" -#: Mailnag/plugins/libnotifyplugin.py:236 +#: Mailnag/plugins/libnotifyplugin.py:445 #, python-brace-format msgid "from {0} and others." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:238 -#: Mailnag/plugins/libnotifyplugin.py:241 +#: Mailnag/plugins/libnotifyplugin.py:447 +#: Mailnag/plugins/libnotifyplugin.py:450 #, python-brace-format msgid "from {0}." msgstr "" -#: Mailnag/plugins/libnotifyplugin.py:240 -#: Mailnag/plugins/libnotifyplugin.py:272 -#: Mailnag/plugins/libnotifyplugin.py:309 +#: Mailnag/plugins/libnotifyplugin.py:449 +#: Mailnag/plugins/libnotifyplugin.py:483 +#: Mailnag/plugins/libnotifyplugin.py:635 msgid "New mail" msgstr "新郵件" -#: Mailnag/plugins/libnotifyplugin.py:265 -#: Mailnag/plugins/libnotifyplugin.py:267 +#: Mailnag/plugins/libnotifyplugin.py:476 #, python-brace-format msgid "(and {0} more)" msgstr "( 還有 {0} 條)" -#: Mailnag/plugins/libnotifyplugin.py:296 +#: Mailnag/plugins/libnotifyplugin.py:578 +msgid "Copy code:" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.py:622 msgid "Mark as read" msgstr "標記爲已讀" -#: Mailnag/plugins/userscriptplugin.py:60 -msgid "User Script" -msgstr "用戶腳本" +#: Mailnag/plugins/libnotifyplugin.py:837 +#, fuzzy +msgid "Delete this provider:" +msgstr "刪除該帳戶:" -#: Mailnag/plugins/userscriptplugin.py:61 -msgid "Runs an user defined script on mail arrival." -msgstr "運行在郵件到達時的用戶自定義腳本。" +#: Mailnag/plugins/libnotifyplugin.py:838 +#, fuzzy +msgid "Sender:" +msgstr "寄件人" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "account" +#: Mailnag/plugins/libnotifyplugin.py:839 +#, fuzzy +msgid "Subject:" +msgstr "主題" + +#: Mailnag/plugins/libnotifyplugin.py:840 +msgid "Pattern:" msgstr "" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "sender" -msgstr "寄件人" +#: Mailnag/plugins/spamfilterplugin.py:67 +msgid "Spam Filter" +msgstr "垃圾郵件過濾器" -#: Mailnag/plugins/userscriptplugin.py:80 -msgid "subject" -msgstr "主題" +#: Mailnag/plugins/spamfilterplugin.py:68 +msgid "Filters out unwanted mails." +msgstr "過濾掉不想要的郵件。" -#: Mailnag/plugins/userscriptplugin.py:81 -#, python-format +#: Mailnag/plugins/spamfilterplugin.py:87 +#, fuzzy msgid "" -"The following script will be executed whenever new mails arrive.\n" -"Mailnagger passes the total count of new mails to this script,\n" -"followed by %s sequences." -msgstr "" +"Mailnagger will ignore mails containing at least one of \n" +"the following words in subject or sender." +msgstr "Mailnag 將會忽略主旨或寄件者裡帶有這些字詞的郵件。" + +#: Mailnag/plugins/soundplugin.py:66 +msgid "Sound Notifications" +msgstr "聲音通知" + +#: Mailnag/plugins/soundplugin.py:67 +msgid "Plays a sound when new mails arrive." +msgstr "當新郵件到達時播放聲音。" #: Mailnag/configuration/ui/account_widget.ui.h:1 msgid "" @@ -294,5 +330,111 @@ msgstr "" msgid "Info" msgstr "" +#: Mailnag/plugins/libnotifyplugin.ui.h:1 +msgid "Count of new mails" +msgstr "新郵件數量" + +#: Mailnag/plugins/libnotifyplugin.ui.h:2 +msgid "Short summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:3 +msgid "Detailed summary of new mails" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:4 +msgid "One notification per new mail" +msgstr "每封新郵件通知一次" + +#: Mailnag/plugins/libnotifyplugin.ui.h:5 +#, fuzzy +msgid "Only 2FA notification, when enabled" +msgstr "每封新郵件通知一次" + +#: Mailnag/plugins/libnotifyplugin.ui.h:7 +#, fuzzy +msgid "Sender" +msgstr "寄件人" + +#: Mailnag/plugins/libnotifyplugin.ui.h:10 +msgid "Add 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:11 +msgid "Remove 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:12 +msgid "Edit 2FA Provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:13 +msgid "information" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:14 +#, fuzzy +msgid "2FA notifications" +msgstr "聲音通知" + +#: Mailnag/plugins/libnotifyplugin.ui.h:15 +msgid "Enable/disable libnotify 2FA processing" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:16 +msgid "Pattern regexp with {code} for capture." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:17 +msgid "" +"Display name of a mail sender, \n" +"if display name is not available, \n" +"address spec can be used." +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:21 +msgid "" +"This field is not a regular expression, \n" +"however it can contain a code which \n" +"is materialised within braces, like \n" +"this: {code}.\n" +"\n" +"Ex: Your security code: {code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:27 +msgid "Subject (not a regexp but {code} for capture possible)" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:29 +msgid "" +"This field can be a regular expression \n" +"where the code is materialised within \n" +"braces, like this: {code}.\n" +"\n" +"Ex: Your security code is:\\s?{code}" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:34 +msgid "Text" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:35 +msgid "Edit 2FA provider" +msgstr "" + +#: Mailnag/plugins/libnotifyplugin.ui.h:36 +#, fuzzy +msgid "Enable provider" +msgstr "已啓用" + +#, fuzzy +#~ msgid "Garmin 2FA LibNotify Notifications" +#~ msgstr "彈出視窗通知" + +#, fuzzy +#~ msgid "Shows a popup when Garmin 2FA mails arrive." +#~ msgstr "當新郵件到達時彈出訊息視窗。" + #~ msgid "Maximum number of visible mails:" #~ msgstr "可顯示郵件的最大數量" diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index a149345..0a7d6e5 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ long_description = (this_directory / "README.md").read_text() -logger = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) # TODO : This hack won't work with --user and --home options PREFIX = sysconfig.get_path('data') @@ -56,8 +56,8 @@ def run(self): else: err = "UNKNOWN_ERR" raise Warning("gen_locales returned %d (%s)" % (rc, err)) except Exception as e: - logger.error("Building locales failed.") - logger.error("Error: %s" % str(e)) + _LOGGER.error("Building locales failed.") + _LOGGER.error("Error: %s", str(e)) sys.exit(1) # remove patch dir (if existing) @@ -169,6 +169,9 @@ def _add_icon_data(self): 'Mailnag.configuration.desktop': [ 'mailnagger.desktop', ], + 'Mailnag.plugins': [ + 'libnotifyplugin.ui', + ] }, entry_points={ "console_scripts": [ diff --git a/tests/test_account.py b/tests/test_account.py index 6a97d0c..f2e227a 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -59,7 +59,7 @@ def test_account_should_keep_configuration(): imap=True, idle=True, folders=['a', 'b'], - mailbox_type='mybox' + mailbox_type='mybox', ) config = account.get_config() expected_config = {