diff --git a/src/main/java/de/srsoftware/widerhall/Configuration.java b/src/main/java/de/srsoftware/widerhall/Configuration.java index 9fe0a16..51eaff4 100644 --- a/src/main/java/de/srsoftware/widerhall/Configuration.java +++ b/src/main/java/de/srsoftware/widerhall/Configuration.java @@ -17,6 +17,45 @@ public class Configuration { private JSONObject data; private File file; + public File archiveDir() { + var locations = locations(); + if (!locations.containsKey(ARCHIVE)) locations.put(ARCHIVE, String.join(File.separator,baseDir(),"archive")); + return new File((String) locations.get(ARCHIVE)); + } + + public String baseDir() { + var locations = locations(); + if (!locations.containsKey(BASE)) locations.put(BASE,System.getProperty("user.dir")); + return (String) locations.get(BASE); + } + + public String baseUrl() { + if (!data.containsKey(BASE_URL)) data.put(BASE_URL,"http://localhost"); + return (String) data.get(BASE_URL); + } + + public File configFile() { + var locations = locations(); + if (!locations.containsKey(CONFIG)) locations.put(CONFIG, String.join(File.separator,baseDir(),"config","config.json")); + return new File((String) locations.get(CONFIG)); + } + + public File dbFile() { + var locations = locations(); + if (!locations.containsKey(DB)) locations.put(DB, String.join(File.separator,baseDir(),"db","db.sqlite3")); + return new File((String) locations.get(DB)); + } + + public Configuration dbFile(File dbFile){ + var locations = locations(); + locations.put(DB,dbFile.toString()); + return this; + } + + public File file() { + return file; + } + public static Configuration instance() { if (singleton == null) singleton = new Configuration().setDefaults(); return singleton; @@ -37,18 +76,24 @@ public class Configuration { var newVals = (JSONObject) new JSONParser().parse(Files.readString(file.toPath())); data.putAll(newVals); } catch (ParseException | IOException e){ - LOG.warn("Konnte Konfiguration nicht aus {} laden:",file,e); + LOG.warn("Was not able to load configuration from {}:",file,e); } return this; } + private JSONObject locations() { + Object o = data.get(LOCATIONS); + if (!(o instanceof JSONObject)) data.put(LOCATIONS,o = new JSONObject()); + return (JSONObject) o; + } + public Configuration save(File file) throws IOException { this.file = file; return save(); } public Configuration save() throws IOException { - if (file == null) throw new NullPointerException("Konnte Konfiguration nicht speichern: Datei ist null!"); + if (file == null) throw new NullPointerException("Cannot save configuration: file is null!"); file.getParentFile().mkdirs(); Files.writeString(file.toPath(),data.toJSONString()); return this; @@ -64,57 +109,9 @@ public class Configuration { return this; } - private JSONObject locations() { - Object o = data.get(LOCATIONS); - if (!(o instanceof JSONObject)) data.put(LOCATIONS,o = new JSONObject()); - return (JSONObject) o; - } - - public String baseDir() { - var locations = locations(); - if (!locations.containsKey(BASE)) locations.put(BASE,System.getProperty("user.dir")); - return (String) locations.get(BASE); - } - - public File configFile() { - var locations = locations(); - if (!locations.containsKey(CONFIG)) locations.put(CONFIG, String.join(File.separator,baseDir(),"config","config.json")); - return new File((String) locations.get(CONFIG)); - } - - public File dbFile() { - var locations = locations(); - if (!locations.containsKey(DB)) locations.put(DB, String.join(File.separator,baseDir(),"db","db.sqlite3")); - return new File((String) locations.get(DB)); - } - - public Configuration dbFile(File dbFile){ - var locations = locations(); - locations.put(DB,dbFile.toString()); - return this; - } - - public File archiveDir() { - var locations = locations(); - if (!locations.containsKey(ARCHIVE)) locations.put(ARCHIVE, String.join(File.separator,baseDir(),"archive")); - return new File((String) locations.get(ARCHIVE)); - } - - public int serverPort() { if (!data.containsKey(PORT)) data.put(PORT,80L); var o = data.get(PORT); return (int) (long) o; } - - public String baseUrl() { - if (!data.containsKey(BASE_URL)) data.put(BASE_URL,"http://localhost"); - return (String) data.get(BASE_URL); - } - - - public File file() { - return file; - } - } diff --git a/src/main/java/de/srsoftware/widerhall/Util.java b/src/main/java/de/srsoftware/widerhall/Util.java index eaa48fc..c49ce75 100644 --- a/src/main/java/de/srsoftware/widerhall/Util.java +++ b/src/main/java/de/srsoftware/widerhall/Util.java @@ -28,19 +28,33 @@ public class Util { private static final MessageDigest SHA256 = getSha256(); private static final String EMAIL_PATTERN = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"; - public static String urlEncode(Map data) { - String params = data.entrySet() - .stream() - .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) - .collect(Collectors.joining("&")); - return params; + public static String dropEmail(String tx) { + return tx.replaceAll( "[.\\-\\w]+@[.\\-\\w]+", "[email_removed]"); } private static String encode(Object value) { return URLEncoder.encode(value.toString(), StandardCharsets.UTF_8); } + public static boolean getCheckbox(HttpServletRequest req, String key) { + return "on".equals(req.getParameter(key)); + } + + public static MailingList getMailingList(HttpServletRequest req) { + var listEmail = req.getParameter(LIST); + if (listEmail == null || listEmail.isBlank()) return null; + return MailingList.load(listEmail); + } + + public static T getNullable(ResultSet rs, String colName) throws SQLException { + final T val = (T) rs.getObject(colName); + return rs.wasNull() ? null : val; + } + public static String getPath(HttpServletRequest req) { + var path = req.getPathInfo(); + return path == null ? INDEX : path.substring(1); + } public static MessageDigest getSha256() { try { return MessageDigest.getInstance("SHA-256"); @@ -50,9 +64,43 @@ public class Util { } } - public static String sha256(String s) { - byte[] bytes = SHA256.digest(s.getBytes(StandardCharsets.UTF_8)); - return hex(bytes); + /** + * Return the primary text content of the message. + */ + public static String getText(Part p) throws MessagingException, IOException { + // https://javaee.github.io/javamail/FAQ + if (p.isMimeType("text/*")) return (String)p.getContent(); + + if (p.isMimeType("multipart/alternative")) { + // prefer html text over plain text + Multipart mp = (Multipart)p.getContent(); + String text = null; + for (int i = 0; i < mp.getCount(); i++) { + Part bp = mp.getBodyPart(i); + if (bp.isMimeType("text/plain")) { + if (text == null) text = getText(bp); + continue; + } else if (bp.isMimeType("text/html")) { + String s = getText(bp); + if (s != null) return s; + } else { + return getText(bp); + } + } + return text; + } else if (p.isMimeType("multipart/*")) { + Multipart mp = (Multipart)p.getContent(); + for (int i = 0; i < mp.getCount(); i++) { + String s = getText(mp.getBodyPart(i)); + if (s != null) return s; + } + } + return null; + } + + public static User getUser(HttpServletRequest req) { + var o = req.getSession().getAttribute(USER); + return o instanceof User ? (User) o : null; } private static String hex(byte[] bytes) { @@ -67,14 +115,15 @@ public class Util { return (char)(upper < 10 ? '0'+upper : 'A'+upper-10)+""+(char)(lower < 10 ? '0'+lower : 'A'+lower-10); } - public static String t(String tx, Object ... fills){ - return Translation.get(Application.class,tx,fills); - } - public static boolean isEmail(String email) { return email.matches(EMAIL_PATTERN); } + public static String sha256(String s) { + byte[] bytes = SHA256.digest(s.getBytes(StandardCharsets.UTF_8)); + return hex(bytes); + } + public static boolean simplePassword(String pass) { if (pass.length() < 6) return true; if (pass.length() < 8){ @@ -98,75 +147,23 @@ public class Util { return false; } - public static int unset(int value, int...flags) { - for (int flag : flags){ - if ((value & flag) > 0) value ^= flag; - } - return value; - } - - public static User getUser(HttpServletRequest req) { - var o = req.getSession().getAttribute(USER); - return o instanceof User ? (User) o : null; - } - - public static String getPath(HttpServletRequest req) { - var path = req.getPathInfo(); - return path == null ? INDEX : path.substring(1); - - } - - public static MailingList getMailingList(HttpServletRequest req) { - var listEmail = req.getParameter(LIST); - if (listEmail == null || listEmail.isBlank()) return null; - return MailingList.load(listEmail); + public static String t(String tx, Object ... fills){ + return Translation.get(Application.class,tx,fills); } - public static boolean getCheckbox(HttpServletRequest req, String key) { - return "on".equals(req.getParameter(key)); + public static String urlEncode(Map data) { + String params = data.entrySet() + .stream() + .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) + .collect(Collectors.joining("&")); + return params; } - /** - * Return the primary text content of the message. - */ - public static String getText(Part p) throws MessagingException, IOException { - // https://javaee.github.io/javamail/FAQ - if (p.isMimeType("text/*")) return (String)p.getContent(); - if (p.isMimeType("multipart/alternative")) { - // prefer html text over plain text - Multipart mp = (Multipart)p.getContent(); - String text = null; - for (int i = 0; i < mp.getCount(); i++) { - Part bp = mp.getBodyPart(i); - if (bp.isMimeType("text/plain")) { - if (text == null) text = getText(bp); - continue; - } else if (bp.isMimeType("text/html")) { - String s = getText(bp); - if (s != null) return s; - } else { - return getText(bp); - } - } - return text; - } else if (p.isMimeType("multipart/*")) { - Multipart mp = (Multipart)p.getContent(); - for (int i = 0; i < mp.getCount(); i++) { - String s = getText(mp.getBodyPart(i)); - if (s != null) return s; - } + public static int unset(int value, int...flags) { + for (int flag : flags){ + if ((value & flag) > 0) value ^= flag; } - - return null; - } - - public static String dropEmail(String tx) { - return tx.replaceAll( "[.\\-\\w]+@[.\\-\\w]+", "[email_removed]"); - } - - public static T getNullable(ResultSet rs, String colName) throws SQLException { - final T val = (T) rs.getObject(colName); - return rs.wasNull() ? null : val; + return value; } } diff --git a/src/main/java/de/srsoftware/widerhall/data/Database.java b/src/main/java/de/srsoftware/widerhall/data/Database.java index ae90a9b..8318d8a 100644 --- a/src/main/java/de/srsoftware/widerhall/data/Database.java +++ b/src/main/java/de/srsoftware/widerhall/data/Database.java @@ -46,13 +46,13 @@ public class Database { * @throws SQLException */ public ResultSet exec() throws SQLException { - LOG.debug("Führe {} aus",this); + LOG.debug("Executing {}",this); try { var stmt = conn.prepareStatement(sql); for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i)); return stmt.executeQuery(); } catch (SQLException sqle) { // add sql to the exception - throw new SQLException(t("Query '{}' fehlgeschlagen:",this),sqle); + throw new SQLException(t("Query '{}' failed:",this),sqle); } } @@ -61,13 +61,13 @@ public class Database { * @throws SQLException */ public void run() throws SQLException { - LOG.debug("Führe {} aus",this); + LOG.debug("Running {}",this); try { var stmt = conn.prepareStatement(sql); for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i)); stmt.execute(); } catch (SQLException sqle) { - throw new SQLException(t("Query '{}' fehlgeschlagen:",this),sqle); + throw new SQLException(t("Query '{}' failed:",this),sqle); } } @@ -193,8 +193,6 @@ public class Database { return new CompiledRequest(sql.toString(),args); } - - public Request groupBy(String column) { groupBy = column; return this; @@ -354,7 +352,7 @@ public class Database { Configuration config = Configuration.instance(); var dbFile = config.dbFile(); String url = "jdbc:sqlite:"+dbFile; - LOG.debug("Öffne {}",url); + LOG.debug("Opening {}",url); dbFile.getParentFile().mkdirs(); try { singleton = new Database(DriverManager.getConnection(url)); @@ -410,7 +408,7 @@ public class Database { rs.close(); return val > 0; } catch (SQLException e) { - throw new SQLException(t("Konnte Existenz der Tabelle {} nicht prüfen!",tbName),e); + throw new SQLException(t("Was not able to check existence of table {}!",tbName),e); } } diff --git a/src/main/java/de/srsoftware/widerhall/data/ListMember.java b/src/main/java/de/srsoftware/widerhall/data/ListMember.java index eb593df..ce2134e 100644 --- a/src/main/java/de/srsoftware/widerhall/data/ListMember.java +++ b/src/main/java/de/srsoftware/widerhall/data/ListMember.java @@ -50,22 +50,22 @@ public class ListMember { } public String addNewModerator(String userEmail) { - if (!isAllowedToEditMods()) return t("Es ist dir nicht gestattet, neue Moderatoren für {} zu ernennen!",list.email()); + if (!isAllowedToEditMods()) return t("You are not allowed to nominate new mods for {}",list.email()); User moderator = null; try { moderator = User.load(userEmail); } catch (SQLException e) { - LOG.warn("Laden des Nutzers zu {} fehlgeschlagen",userEmail,e); - return t("Laden des Nutzers zu {} fehlgeschlagen",userEmail); + LOG.warn("Failed to load user for {}",userEmail,e); + return t("Failed to load user for {}",userEmail); } - if (moderator == null) return t("Kein solcher Nutzer: {}",userEmail); + if (moderator == null) return t("No such user: {}",userEmail); ListMember member = null; try { member = ListMember.load(list,moderator); } catch (SQLException e) { - LOG.warn("Laden der Mitgliedschaft zu {}/{} fehlgeschlagen",moderator.email(),list.email(),e); - return t("Laden der Mitgliedschaft zu {}/{} fehlgeschlagen",moderator.email(),list.email()); + LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e); + return t("Failed to load list member for {}/{}",moderator.email(),list.email()); } try { if (member == null) { @@ -74,8 +74,8 @@ public class ListMember { member.setState(Util.unset(member.state|STATE_MODERATOR,STATE_AWAITING_CONFIRMATION)); } } catch (SQLException e) { - LOG.warn("Ernennen von {} zum Moderator von {} fehlgeschlagen",moderator.email(),list.email(),e); - return t("Ernennen von {} zum Moderator von {} fehlgeschlagen",moderator.email(),list.email()); + LOG.warn("Failed to make {} a moderator of {}",moderator.email(),list.email(),e); + return t("Failed to make {} a moderator of {}",moderator.email(),list.email()); } return null; } @@ -112,20 +112,6 @@ public class ListMember { return null; } - public void sendConfirmationMail(ST template) throws SQLException, MessagingException { - var subject = t("[{}] Abonnement abgeschlossen!",list.name()); - var data = new HashMap(); - data.put(USER,user.safeMap()); - data.put(LIST,list.minimalMap()); - data.put(URL,Configuration.instance().baseUrl()+"/web/index"); - if (list.isOpenForSubscribers()) data.put("open_list",true); - var text = template.add("data",data).render(); - try { - list.smtp().send(list.email(),list.name(),user.email(),subject,text); - } catch (UnsupportedEncodingException e) { - LOG.warn("Failed to send list subscription confirmation!",e); - } - } /** * Create a new list member entry in the database. * If the member has the state AWAITING_CONFIRMATION, a token is assigned with the member, too. @@ -160,44 +146,44 @@ public class ListMember { } public String dropMember(String userEmail) { - if (!isModerator()) return t("Es ist dir nicht erlaubt, Mitglieder von {} zu entfernen.",list.email()); + if (!isModerator()) return t("You are not allowed to remove members of {}",list.email()); User user = null; try { user = User.load(userEmail); } catch (SQLException e) { - LOG.warn("Laden des Nutzers zu {} fehlgeschlagen",userEmail,e); - return t("Laden des Nutzers zu {} fehlgeschlagen",userEmail); + LOG.warn("Failed to load user for {}",userEmail,e); + return t("Failed to load user for {}",userEmail); } - if (user == null) return t("Kein solcher Nutzer: {}",userEmail); + if (user == null) return t("No such user: {}",userEmail); ListMember member = null; try { member = ListMember.load(list,user); } catch (SQLException e) { - LOG.warn("Laden der Mitgliedschaft zu {}/{} fehlgeschlagen",user.email(),list.email(),e); - return t("Laden der Mitgliedschaft zu {}/{} fehlgeschlagen",user.email(),list.email()); + LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e); + return t("Failed to load list member for {}/{}",user.email(),list.email()); } - if (member == null) return t("{} it nicht Mitglied von {}",user.email(),list.email()); - if (member.isOwner()) return t("Du kannst den Listen-Besitzer nicht entfernen!"); + if (member == null) return t("{} is no member of {}",user.email(),list.email()); + if (member.isOwner()) return t("You are not allowed to remvoe the list owner!"); try { member.unsubscribe(); } catch (SQLException e) { - LOG.warn("Abbestellen von {} / {} nicht fehlgeschlagen",user.email(),list.email(),e); - return t("Abbestellen von {} / {} nicht fehlgeschlagen",user.email(),list.email()); + LOG.warn("Failed to un-subscribe {} from {}",user.email(),list.email(),e); + return t("Failed to un-subscribe {} from {}",user.email(),list.email()); } return null; } public String dropModerator(String userEmail) { - if (!isAllowedToEditMods()) return t("Es ist dir nicht gestattet, die Moderatoren von {} zu verändern",list.email()); + if (!isAllowedToEditMods()) return t("You are not allowed to edit mods of {}",list.email()); User moderator = null; try { moderator = User.load(userEmail); } catch (SQLException e) { - LOG.warn("Laden des Nutzers zu {} fehlgeschlagen",userEmail,e); - return t("Laden des Nutzers zu {} fehlgeschlagen",userEmail); + LOG.warn("Failed to load user for {}",userEmail,e); + return t("Failed to load user for {}",userEmail); } if (moderator == null) return t("No such user: {}",userEmail); @@ -205,8 +191,8 @@ public class ListMember { try { member = ListMember.load(list,moderator); } catch (SQLException e) { - LOG.warn("Laden der Mitgliedschaft zu {}/{} fehlgeschlagen",moderator.email(),list.email(),e); - return t("Laden der Mitgliedschaft zu {}/{} fehlgeschlagen",moderator.email(),list.email()); + LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e); + return t("Failed to load list member for {}/{}",moderator.email(),list.email()); } try { if (member == null) { @@ -215,8 +201,8 @@ public class ListMember { member.setState(Util.unset(member.state,STATE_MODERATOR)); } } catch (SQLException e) { - LOG.warn("Ernennen von {} zum normalen Abonnenten von {} fehlgeschlagen",moderator.email(),list.email(),e); - return t("Ernennen von {} zum normalen Abonnenten von {} fehlgeschlagen",moderator.email(),list.email()); + LOG.warn("Failed to make {} a subscriber of {}",moderator.email(),list.email(),e); + return t("Failed to make {} a subscriber of {}",moderator.email(),list.email()); } return null; } @@ -283,7 +269,7 @@ public class ListMember { var rs = request.compile().exec(); while (rs.next()) listEmails.add(rs.getString(LIST_EMAIL)); } catch (SQLException e) { - LOG.warn("Sammeln der Listen von {} fehlgeschlagen: ",user.email(),e); + LOG.warn("Collecting lists of {} failed: ",user.email(),e); } var lists = MailingList.loadAll(listEmails); var result = new HashSet(); @@ -309,7 +295,7 @@ public class ListMember { var rs = request.compile().exec(); while (rs.next()) list.add(rs.getString(LIST_EMAIL)); } catch (SQLException e) { - LOG.warn("Sammeln der Listen von {} fehlgeschlagen: ",user.email(),e); + LOG.warn("Collecting lists of {} failed: ",user.email(),e); } return list; } @@ -374,6 +360,21 @@ public class ListMember { return this; } + public void sendConfirmationMail(ST template) throws SQLException, MessagingException { + var subject = t("[{}] Subscription complete!",list.name()); + var data = new HashMap(); + data.put(USER,user.safeMap()); + data.put(LIST,list.minimalMap()); + data.put(URL,Configuration.instance().baseUrl()+"/web/index"); + if (list.isOpenForSubscribers()) data.put("open_list",true); + var text = template.add("data",data).render(); + try { + list.smtp().send(list.email(),list.name(),user.email(),subject,text); + } catch (UnsupportedEncodingException e) { + LOG.warn("Failed to send list subscription confirmation!",e); + } + } + public ListMember setState(int newState) throws SQLException { Database.open() .update(TABLE_NAME) @@ -391,10 +392,10 @@ public class ListMember { */ public String stateText() { var words = new ArrayList(); - if (isSubscriber()) words.add("Abonniert"); - if (isOwner()) words.add("Besitzer"); - if (isAwaiting()) words.add("erwartet Bestätigung"); - if (isModerator()) words.add("Moderator"); + if (isAwaiting()) words.add("awaiting confirmation"); + if (isModerator()) words.add("moderator"); + if (isOwner()) words.add("owner"); + if (isSubscriber()) words.add("subscriber"); return String.join(", ",words); } diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java index ffe7afb..dd1a90f 100644 --- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java +++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java @@ -270,7 +270,7 @@ public class MailingList implements MessageHandler, ProblemListener { var member = ListMember.load(this,user); return member.hasState(ListMember.STATE_OWNER|ListMember.STATE_SUBSCRIBER); // owners may subscribe their own mailing lists } catch (SQLException e) { - LOG.warn("Konnte Listen-Mitglied nicht laden: ",e); + LOG.warn("Was not able to load ListMember: ",e); return false; } } @@ -303,7 +303,7 @@ public class MailingList implements MessageHandler, ProblemListener { ml.lastError = rs.getString(LAST_ERROR); } } catch (SQLException e) { - LOG.debug("Konnte Mailing-Liste nicht laden: ",e); + LOG.debug("Failed to load MailingList: ",e); } return ml; } @@ -325,7 +325,7 @@ public class MailingList implements MessageHandler, ProblemListener { .compile().exec(); while (rs.next()) list.add(MailingList.from(rs)); } catch (SQLException e) { - LOG.debug("Konnte Mailing-Listen nicht laden: ",e); + LOG.debug("Failed to load MailingLists: ",e); } return list; } @@ -336,7 +336,7 @@ public class MailingList implements MessageHandler, ProblemListener { try { if (ListMember.load(this,user).isModerator()) return true; } catch (SQLException e) { - LOG.debug("Fehler beim Laden des Listenmitglieds für ({}, {})",user.email(),email()); + LOG.debug("Error loading list member for ({}, {})",user.email(),email()); } return false; } @@ -346,7 +346,7 @@ public class MailingList implements MessageHandler, ProblemListener { try { if (ListMember.load(this,user).isOwner()) return true; } catch (SQLException e) { - LOG.debug("Fehler beim Laden des Listenmitglieds für ({}, {})",user.email(),email()); + LOG.debug("Error loading list member for ({}, {})",user.email(),email()); } return false; } @@ -360,7 +360,7 @@ public class MailingList implements MessageHandler, ProblemListener { try { if (ListMember.load(this,user).isOwner()) return true; } catch (SQLException e) { - LOG.debug("Fehler beim Laden des Listenmitglieds für ({}, {})",user.email(),email()); + LOG.debug("Error loading list member for ({}, {})",user.email(),email()); } return false; } @@ -422,7 +422,7 @@ public class MailingList implements MessageHandler, ProblemListener { @Override public void onMessageReceived(Message message) throws MessagingException { - LOG.info("Nachricht empfangen: {}",message.getFrom()); + LOG.info("Message received: {}",message.getFrom()); String subject = message.getSubject(); try { @@ -487,7 +487,7 @@ public class MailingList implements MessageHandler, ProblemListener { while (rs.next()) list.add(MailingList.from(rs)); rs.close(); } catch (SQLException e) { - LOG.warn("Auflisten der Mailinglisten fehlgeschlagen: ", e); + LOG.warn("Listing mailing lists failed: ", e); } return list; } @@ -574,13 +574,13 @@ public class MailingList implements MessageHandler, ProblemListener { .map(ListMember::user) .map(User::email) .collect(Collectors.joining(", ")); - var subject = t("Liste '{}' erfordert Aufmerksamkeit!",name()); - var text = t("Diese Liste hat eine E-Mail von {} empfangen. Der Absender ist nicht Mitglied der Liste.\nDie Email wurde in den '{}'-Ordner verschoben.\nSie können die Nachricht manuell weiterleiten oder verwerfen.",senderEmail,RETAINED_FOLDER); + var subject = t("List '{}' requires attention!",name()); + var text = t("This list received an email from {}, who is not member of the list.\nThe email has been moved to the '{}' folder.\nYou may manually forward this message or drop it.",senderEmail,RETAINED_FOLDER); smtp.send(email(), name(), receivers,subject,text); - subject = t("Ihre Nachricht an {} wurde zurückgewiesen!",email()); - text = t("Sie haben versucht, eine Nachricht an die Liste '{}' zu senden. Das wurde verweigert, da Sie kein Mitglied der Liste (mit entsprechenden Berechtigungen) sind.\n",name()); - if (hasState(STATE_PUBLIC)) text += t("Sie können zu {} gehen und die Liste abonnieren. Versuchen Sie es danach erneut.",Configuration.instance().baseUrl()); + subject = t("Your message to {} was rejected!",email()); + text = t("You have tried to send a message to the list '{}', which failed. This is because you are not a (privileged) member of this list.\n",name()); + if (hasState(STATE_PUBLIC)) text += t("You may go to {} and subscribe to the list, then try again.",Configuration.instance().baseUrl()); smtp.send(email(), name(), senderEmail,subject,text); } catch (SQLException e){ LOG.error("Failed to load list of owners of mailing list. Retention notification was not sent to owners of {}",email(),e); @@ -646,7 +646,7 @@ public class MailingList implements MessageHandler, ProblemListener { rs.close(); return result; } catch (SQLException e) { - LOG.warn("Lesen der abbonnierbaren Mailinglisten von {} fehlgeschlagen.",user,e); + LOG.warn("Failed to read subscribable mailinglists for {}",user,e); return Set.of(); } } @@ -671,11 +671,11 @@ public class MailingList implements MessageHandler, ProblemListener { try { var config = Configuration.instance(); var url = new StringBuilder(config.baseUrl()).append("/web/confirm?token=").append(member.token()).toString(); - var subject = t("[{}] Bitte bestätigen Sie Ihr Listen-Abonnement",name()); + var subject = t("[{}] Please confirm your list subscription",name()); var text = template.add(URL,url).add(LIST_NAME,name()).render(); smtp.send(email(),name(),user.email(),subject,text); } catch (UnsupportedEncodingException e) { - throw new MessagingException(t("Senden der Email an {} fehlgeschlagen",user.email()),e); + throw new MessagingException(t("Failed to send email to {}",user.email()),e); } } @@ -711,8 +711,8 @@ public class MailingList implements MessageHandler, ProblemListener { * @throws UnsupportedEncodingException */ public void test(User user) throws MessagingException, UnsupportedEncodingException { - var subject = t("[{}] Test-Mail",name()); - var text = "Wenn Sie diese Nachricht empfangen haben, sind die SMTP-Einstellungen Ihrer Mailing-Liste korrekt."; + var subject = t("[{}]: test mail",name()); + var text = "If you received this mail, the SMTP settings of your mailing list are correct."; smtp.login().send(email(),name(),user.email(),subject,text); } @@ -720,7 +720,7 @@ public class MailingList implements MessageHandler, ProblemListener { try { return Arrays.asList(InternetAddress.parse(email)).stream(); } catch (AddressException e) { - LOG.debug("Parsen von {} fehlgeschlagen",email,e); + LOG.debug("Was not able to parse {}",email,e); return new ArrayList().stream(); } } diff --git a/src/main/java/de/srsoftware/widerhall/data/Post.java b/src/main/java/de/srsoftware/widerhall/data/Post.java index 282ac49..288c891 100644 --- a/src/main/java/de/srsoftware/widerhall/data/Post.java +++ b/src/main/java/de/srsoftware/widerhall/data/Post.java @@ -96,7 +96,24 @@ public class Post { return new File(filename); } - public static ArrayList find(MailingList list, String month, List allowedSenders) throws SQLException { + public static ArrayList find(MailingList list, String month) throws SQLException { + var rs = Database.open() + .select(TABLE_NAME,"*","strftime('%Y-%m',date/1000,'unixepoch') as month") + .where(LIST,list.email()) + .where(MONTH,month) + .sort(DATE) + .compile() + .exec(); + try { + var result = new ArrayList(); + while (rs.next()) result.add(Post.from(rs)); + return result; + } finally { + rs.close(); + } + } + + public static ArrayList find(MailingList list, String month, List allowedSenders) throws SQLException { var query = Database.open() .select(TABLE_NAME,"*","strftime('%Y-%m',date/1000,'unixepoch') as month") .where(LIST,list.email()) diff --git a/src/main/java/de/srsoftware/widerhall/data/User.java b/src/main/java/de/srsoftware/widerhall/data/User.java index 3c60b18..eae649e 100644 --- a/src/main/java/de/srsoftware/widerhall/data/User.java +++ b/src/main/java/de/srsoftware/widerhall/data/User.java @@ -268,7 +268,7 @@ public class User { * @return */ public Map safeMap(){ - return Map.of(NAME,name,EMAIL,email,PERMISSIONS,permissionList(),PASSWORD,hashedPassword() == null ? "nein" : "ja"); + return Map.of(NAME,name,EMAIL,email,PERMISSIONS,permissionList(),PASSWORD,hashedPassword() == null ? "no" : "yes"); } /** diff --git a/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java b/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java index 40afbec..1b30638 100644 --- a/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java +++ b/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java @@ -33,44 +33,20 @@ public class ImapClient { return this; } - public ListeningThread dropListeners() { - listeners.clear(); + public ListeningThread doStop() { + stopped = true; return this; } - @Override - public void run() { - while (!stopped) { - try { - sleep(5000); - } catch (InterruptedException interruptedException) { - interruptedException.printStackTrace(); - } - try { - openInbox(); - } catch (MessagingException e){ - LOG.warn("Verbindung-Problem:",e); - problemListener.onImapException(e); - } - } + public ListeningThread dropListeners() { + listeners.clear(); + return this; } - private void openInbox() throws MessagingException { - LOG.debug("Verbinden und Einloggen…"); - Properties props = imapProps(); - session = Session.getInstance(props); - Store store = session.getStore(Constants.IMAPS); - store.connect(host,username,password); - LOG.debug("Verbunden. Öffne {}:",folderName); - inbox = (IMAPFolder)store.getFolder(folderName); - inbox.open(IMAPFolder.READ_WRITE); - while (!stopped){ - handleMessages(); - problemListener.clearProblems(); - LOG.debug("Warte."); - inbox.idle(true); - } + private void handle(Message message) throws MessagingException { + LOG.debug("Handling {}",message.getSubject()); + for (MessageHandler listener : listeners) listener.onMessageReceived(message); } private void handleMessages() throws MessagingException { @@ -85,35 +61,64 @@ public class ImapClient { } } - private void handle(Message message) throws MessagingException { - LOG.debug("Verarbeite {}",message.getSubject()); - for (MessageHandler listener : listeners) listener.onMessageReceived(message); - } - private Properties imapProps() { Properties props = new Properties(); props.put(Constants.PROTOCOL,Constants.IMAPS); return props; } - public ListeningThread doStop() { - stopped = true; - return this; + private void openInbox() throws MessagingException { + LOG.debug("Connecting and logging in…"); + Properties props = imapProps(); + session = Session.getInstance(props); + Store store = session.getStore(Constants.IMAPS); + store.connect(host,username,password); + LOG.debug("Connected, opening {}:",folderName); + inbox = (IMAPFolder)store.getFolder(folderName); + inbox.open(IMAPFolder.READ_WRITE); + + while (!stopped){ + handleMessages(); + problemListener.clearProblems(); + LOG.debug("Idling."); + inbox.idle(true); + } + } + + @Override + public void run() { + while (!stopped) { + try { + sleep(5000); + } catch (InterruptedException interruptedException) { + interruptedException.printStackTrace(); + } + try { + openInbox(); + } catch (MessagingException e){ + LOG.warn("Connection problem:",e); + problemListener.onImapException(e); + } + } } } private class Heartbeat extends Thread{ - private static final Logger LOG = LoggerFactory.getLogger(Heartbeat.class); private boolean stopped = false; + public Heartbeat doStop() { + stopped = true; + return this; + } + @Override public void run() { while (!stopped){ try { sleep(300034); if (inbox != null) continue; - LOG.debug("Sende NOOP"); + LOG.debug("sending NOOP"); inbox.doCommand(protocol -> { protocol.simpleCommand("NOOP",null); return null; @@ -123,11 +128,6 @@ public class ImapClient { } } } - - public Heartbeat doStop() { - stopped = true; - return this; - } } public ImapClient(String host, int port, String username, String password, String folderName,ProblemListener listener) { @@ -148,7 +148,7 @@ public class ImapClient { public void dropMailsOlderThan(Integer holdTime) throws MessagingException { var now = new Date(); if (holdTime == null) return; - LOG.info("Removing mails older than {} days:",holdTime); + LOG.debug("Removing mails older than {} days:",holdTime); if (!inbox.isOpen()) inbox.open(IMAPFolder.READ_WRITE); for (Message message : inbox.getMessages()){ Date receivedDate = message.getReceivedDate(); @@ -165,25 +165,13 @@ public class ImapClient { inbox.expunge(); } - - public String host(){ - return host; - } - - public String username(){ - return username; - } - - public String password(){ - return password; + public String folderName(){ + return folderName; } - public int port(){ - return port; - } - public String folderName(){ - return folderName; + public String host(){ + return host; } public ImapClient move(Message message, String destinationFolder) throws MessagingException { @@ -200,14 +188,22 @@ public class ImapClient { return this; } + public String password(){ + return password; + } + + public int port(){ + return port; + } + public ImapClient start() { stop(); - LOG.info("Erzeuge ListeningThread für {}…",username); + LOG.info("Creating ListeningThread for {}…",username); (listeningThread = new ListeningThread()).start(); - LOG.info("Erzeuge Heartbeat für {}…",username); + LOG.info("Creating Heartbeat for {}…",username); (heartbeat = new Heartbeat()).start(); return this; @@ -215,7 +211,7 @@ public class ImapClient { public ImapClient stop(){ if (listeningThread != null) { - LOG.info("Stoppe alten ListeningThread für {}…",username); + LOG.info("Stopping old ListeningThread for {}…",username); listeningThread.dropListeners().doStop(); listeningThread = null; } @@ -225,4 +221,8 @@ public class ImapClient { } return this; } + + public String username(){ + return username; + } } diff --git a/src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java b/src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java index 6ca3471..0a7fb20 100644 --- a/src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java +++ b/src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java @@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory; import javax.mail.*; import javax.mail.internet.*; -import javax.ws.rs.HEAD; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.List; @@ -63,7 +62,7 @@ public class SmtpClient { MimeMultipart multipart = new MimeMultipart(); if (forwardAsAttachment){ MimeBodyPart bodyPart = new MimeBodyPart(); - bodyPart.setText("Die weitergeleitete Nachricht findest du im Anhang dieser E-Mail!\n"); + bodyPart.setText("Find the forwarded message in the attachment(s)!\n"); multipart.addBodyPart(bodyPart); // create another body part to contain the message to be forwarded @@ -82,6 +81,9 @@ public class SmtpClient { send(forward); } + public String host() { + return host; + } public SmtpClient login(){ if (session == null) { @@ -93,11 +95,25 @@ public class SmtpClient { props.put(ENVELOPE_FROM,from); session = Session.getInstance(props); - LOG.debug("Neue Session erzeugt: {}", session); + LOG.debug("Created new session: {}", session); } return this; } + public String password() { + return password; + } + + public int port() { + return port; + } + + public void send(Message message) throws MessagingException { + LOG.debug("Versende Mail…"); + Transport.send(message,username,password); + LOG.debug("…versendet"); + } + public void send(String senderAdress, String senderName, String receivers, String subject, String content) throws MessagingException, UnsupportedEncodingException { login(); MimeMessage message = new MimeMessage(session); @@ -114,27 +130,7 @@ public class SmtpClient { send(message); } - public void send(Message message) throws MessagingException { - LOG.debug("Versende Mail…"); - Transport.send(message,username,password); - LOG.debug("…versendet"); - - } - - public String host() { - return host; - } - - public int port() { - return port; - } - public String username() { return username; } - - public String password() { - return password; - } - } diff --git a/src/main/java/de/srsoftware/widerhall/web/Rest.java b/src/main/java/de/srsoftware/widerhall/web/Rest.java index 6b7987a..6ccfc42 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Rest.java +++ b/src/main/java/de/srsoftware/widerhall/web/Rest.java @@ -15,7 +15,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.SQLException; -import java.time.Month; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,22 +47,21 @@ public class Rest extends HttpServlet { private static final String SUCCESS = "success"; private Map addPermission(String userEmail, String permissions) { - if (userEmail == null || userEmail.isBlank()) return Map.of(ERROR,"E-Mail-Adresse des Listenmitglieds nicht angegeben!"); + if (userEmail == null || userEmail.isBlank()) return Map.of(ERROR,"missing user email address!"); try { int perm = Integer.parseInt(permissions); var user = User.loadAll(List.of(userEmail)).stream().findAny().orElse(null); - if (user == null) return Map.of(ERROR,t("Laden des Nutzers für die Adresse {} fehlgeschlagen",userEmail)); + if (user == null) return Map.of(ERROR,t("Failed to load user for address {}",userEmail)); user.addPermission(perm); } catch (NumberFormatException nfe){ - return Map.of(ERROR,"Keine gültigen Berechtigungen übergeben!"); + return Map.of(ERROR,"no valid permissions provided!"); } catch (SQLException e) { - LOG.debug("Laden des Nutzers für die Adresse {} fehlgeschlagen",userEmail,e); - return Map.of(ERROR,t("Laden des Nutzers für die Adresse {} fehlgeschlagen",userEmail)); + LOG.debug("Failed to load user for address {}",userEmail,e); + return Map.of(ERROR,t("Failed to load user for address {}",userEmail)); } - return Map.of(SUCCESS,"Nutzer-Berechtigungen aktualisiert"); + return Map.of(SUCCESS,"Updated user permissions"); } - private Map archive(HttpServletRequest req, User user) throws SQLException { var list = Util.getMailingList(req); if (list == null) throw new IllegalArgumentException(t("You are trying to access a non-existing list!")); @@ -100,7 +98,7 @@ public class Rest extends HttpServlet { } catch (SQLException e) { LOG.warn("Was not able to load listmember for {}/{}",list.email(),user.email(),e); } - if (!allowed) return Map.of(ERROR,"Es ist dir nicht gestattet, diese Liste zu löschen!"); + if (!allowed) return Map.of(ERROR,"You are not allowed to remove this list!"); try { list.hide(true).enable(false).openForGuests(false).openForSubscribers(false); list.members().forEach(listMember -> { @@ -114,7 +112,7 @@ public class Rest extends HttpServlet { } catch (Exception e) { LOG.debug("Disabling and hiding of {} failed",list.email(),e); } - return Map.of(SUCCESS,t("Liste {} deaktiviert, Abonnement gesperrt, Liste de-publiziert. Mitglieder wurden entfernt.",list.email())); + return Map.of(SUCCESS,t("List {} disabled, closed for subscribers and hidden. Members have been removed.",list.email())); } @Override @@ -139,30 +137,30 @@ public class Rest extends HttpServlet { } private Map dropPermission(String userEmail, String permissions) { - if (userEmail == null || userEmail.isBlank()) return Map.of(ERROR,"Nutzer-Emailadresse fehlt!"); + if (userEmail == null || userEmail.isBlank()) return Map.of(ERROR,"missing user email address!"); try { int perm = Integer.parseInt(permissions); var user = User.loadAll(List.of(userEmail)).stream().findAny().orElse(null); - if (user == null) return Map.of(ERROR,t("Laden des Nutzers für die Adresse {} fehlgeschlagen",userEmail)); + if (user == null) return Map.of(ERROR,t("Failed to load user for address {}",userEmail)); user.dropPermission(perm); } catch (NumberFormatException nfe){ - return Map.of(ERROR,"Keine gültigen Berechtigungen übergeben!"); + return Map.of(ERROR,"no valid permissions provided!"); } catch (SQLException e) { - LOG.debug("Laden des Nutzers für die Adresse {} fehlgeschlagen",userEmail,e); - return Map.of(ERROR,t("Laden des Nutzers für die Adresse {} fehlgeschlagen",userEmail)); + LOG.debug("Failed to load user for address {}",userEmail,e); + return Map.of(ERROR,t("Failed to load user for address {}",userEmail)); } - return Map.of(SUCCESS,"Nutzer-Berechtigungen aktualisiert"); + return Map.of(SUCCESS,t("Updated user permissions")); } private Map enableList(MailingList list, User user, boolean enable) { - if (list == null) return Map.of(ERROR,"Keine Listen-Email übertragen!"); - if (!list.mayBeAlteredBy(user)) Map.of(ERROR,t("Du bist nicht berechtigt, '{}' zu bearbeiten!",list.email())); + if (list == null) return Map.of(ERROR,"no list email provided!"); + if (!list.mayBeAlteredBy(user)) Map.of(ERROR,t("You are not allowed to edit '{}'",list.email())); try { list.enable(enable); - return Map.of(SUCCESS,t("Mailing-Liste '{}' wurde {}!",list.email(),enable ? "aktiviert" : "inaktiviert")); + return Map.of(SUCCESS,t("Mailing list '{}' was {}!",list.email(),enable ? "enabled" : "disabled")); } catch (SQLException e) { - LOG.error("Aktivieren/Inaktivieren der Mailing-Liste fehlgeschlagen: ",e); - return Map.of(ERROR,t("Aktualisieren der Liste '{}' fehlgeschlagen",list.email())); + LOG.error("Failed to enable/disable mailing list: ",e); + return Map.of(ERROR,t("Failed to update list '{}'",list.email())); } } @@ -182,8 +180,8 @@ public class Rest extends HttpServlet { try { json.put("users", (user.hashPermission(User.PERMISSION_ADMIN) ? User.loadAll() : List.of(user)).stream().map(User::safeMap).toList()); } catch (SQLException e) { - LOG.debug("Laden der Nutzerliste fehlgeschlagen:",e); - json.put(ERROR,"Laden der Nutzerliste fehlgeschlagen"); + LOG.debug("Failed to load user list:",e); + json.put(ERROR,"failed to load user list"); } break; case LIST_MODERATED: @@ -193,7 +191,7 @@ public class Rest extends HttpServlet { json.put("lists", MailingList.subscribable(user).stream().map(MailingList::minimalMap).toList()); break; default: - json.put(ERROR,t("Kein Handler für den Pfad '{}'!",path)); + json.put(ERROR,t("No handler for path '{}'!",path)); break; } } else { @@ -205,7 +203,7 @@ public class Rest extends HttpServlet { json.put("lists", MailingList.subscribable().stream().map(MailingList::minimalMap).toList()); break; default: - json.put(ERROR,"Nicht eingeloggt!"); + json.put(ERROR,"Not logged in!"); } } try { @@ -213,7 +211,7 @@ public class Rest extends HttpServlet { resp.getWriter().println(json.toJSONString()); return null; } catch (IOException e) { - return t("Konnte Anfrage nicht verarbeiten: {}",e.getMessage()); + return t("Failed to handle request: {}",e.getMessage()); } } @@ -272,38 +270,38 @@ public class Rest extends HttpServlet { case USER_ADD_PERMISSION: if (user.hashPermission(User.PERMISSION_ADMIN)){ json.putAll(addPermission(userEmail,permissions)); - } else json.put(ERROR,"Sie haben nicht die Berechtigung, um Berechtigungen zu ändern!"); + } else json.put(ERROR,"You are not allowed to alter user permissions!"); break; case USER_DROP_PERMISSION: if (user.hashPermission(User.PERMISSION_ADMIN)){ json.putAll(dropPermission(userEmail,permissions)); - } else json.put(ERROR,"Sie haben nicht die Berechtigung, um Berechtigungen zu ändern!"); + } else json.put(ERROR,"You are not allowed to alter user permissions!"); break; default: - json.put(ERROR,t("Kein Handler für den Pfad '{}'!",path)); + json.put(ERROR,t("No handler for path '{}'!",path)); break; } } else { - json.put(ERROR,"Nicht eingeloggt!"); + json.put(ERROR,"Not logged in!"); } try { resp.setContentType("application/json"); resp.getWriter().println(json.toJSONString()); return null; } catch (IOException e) { - return t("Konnte Anfrage nicht verarbeiten: {}",e.getMessage()); + return t("Failed to handle request: {}",e.getMessage()); } } private Map hideList(MailingList list, User user, boolean hide) { - if (list == null) return Map.of(ERROR,"Keine Listen-Email übertragen!"); + if (list == null) return Map.of(ERROR,"no list email provided!"); if (!list.mayBeAlteredBy(user)) Map.of(ERROR,t("You are not allowed to edit '{}'",list.email())); try { list.hide(hide); - return Map.of(SUCCESS,t("Mailing-Liste '{}' wurde {}!",list.email(),hide ? "versteckt" : "veröffentlicht")); + return Map.of(SUCCESS,t("Mailing list '{}' was {}!",list.email(),hide ? "hidden" : "made public")); } catch (SQLException e) { - LOG.error("Veröffentlichen/Verstekcen der Mailing-Liste fehlgeschlagen: ",e); - return Map.of(ERROR,t("Aktualisieren der Liste '{}' fehlgeschlagen",list.email())); + LOG.error("Failed to (un)hide mailing list: ",e); + return Map.of(ERROR,t("Failed to update list '{}'",list.email())); } } @@ -323,7 +321,7 @@ public class Rest extends HttpServlet { } private Map listDetail(MailingList list, User user) { - if (list == null) return Map.of(ERROR,"Keine Listen-Email übertragen!"); + if (list == null) return Map.of(ERROR,"no list email provided!"); var map = new HashMap<>(); if (list.hasState(MailingList.STATE_FORWARD_FROM)) map.put(KEY_FORWARD_FROM,true); if (list.hasState(MailingList.STATE_FORWARD_ATTACHED)) map.put(KEY_FORWARD_ATTACHED,true); @@ -368,8 +366,8 @@ public class Rest extends HttpServlet { } private Map listMembers(MailingList list, User user) { - if (list == null) return Map.of(ERROR,"Keine Listen-Email übertragen!"); - if (!list.membersMayBeListedBy(user)) Map.of(ERROR,t("Es ist dir nicht gestattet, die Mitglieder von '{}' aufzulisten",list.email())); + if (list == null) return Map.of(ERROR,"no list email provided!"); + if (!list.membersMayBeListedBy(user)) Map.of(ERROR,t("You are not allowed to list members of '{}'",list.email())); try { var members = list.members() .sorted((m1,m2)->m1.user().name().compareTo(m2.user().name())) @@ -377,20 +375,20 @@ public class Rest extends HttpServlet { .toList(); return Map.of(MEMBERS,members,LIST,list.minimalMap()); } catch (SQLException e) { - LOG.error("Laden der Mitglieder-Liste fehlgeschlagen: ",e); - return Map.of(ERROR,t("Laden der Mitglieder-Liste von '{}' fehlgeschlagen!",list.email())); + LOG.error("Failed to load member list: ",e); + return Map.of(ERROR,t("Failed to load member list '{}'",list.email())); } } private Map testList(MailingList list, User user) { - if (list == null) return Map.of(ERROR,"Keine Listen-Email übertragen!"); - if (!list.mayBeTestedBy(user)) Map.of(ERROR,t("Es ist dir nicht gestattet, '{}' zu testen",list.email())); + if (list == null) return Map.of(ERROR,"no list email provided!"); + if (!list.mayBeTestedBy(user)) Map.of(ERROR,t("You are not allowed to test '{}'",list.email())); try { list.test(user); - return Map.of(SUCCESS,t("Test-Email an {} versendet",user.email())); + return Map.of(SUCCESS,t("Sent test email to {}",user.email())); } catch (Exception e) { - LOG.warn("Senden der Test-Email fehlgeschlagen",e); - return Map.of(ERROR,t("Senden der Test-Email an {} fehlgeschlagen",user.email())); + LOG.warn("Failed to send test email",e); + return Map.of(ERROR,t("Failed to send test email to {}",user.email())); } } } diff --git a/src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java b/src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java index e11d530..bd55559 100644 --- a/src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java +++ b/src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java @@ -31,14 +31,14 @@ public abstract class TemplateServlet extends HttpServlet { protected String loadFile(String filename, HttpServletResponse resp) { var path = String.join(File.separator,baseDir,"static",filename); var file = new File(path); - if (!file.exists()) return t("Datei {} existiert nicht!",filename); + if (!file.exists()) return t("File {} does not exist!",filename); try { var content = Files.readAllBytes(file.toPath()); var out = resp.getOutputStream(); out.write(content); out.flush(); } catch (IOException e) { - return t("Ladend der Datei '{}' fehlgeschlagen!",filename); + return t("Failed to load file '{}'!",filename); } return null; } @@ -52,10 +52,10 @@ public abstract class TemplateServlet extends HttpServlet { resp.getWriter().println(template.render()); return null; } catch (IOException e) { - return t("Laden der Vorlage '{}' fehlgeschlagen",path); + return t("Failed to load template '{}'",path); } } - return t("Keine Vorlage für den Pfad '{}' vorhanden!",path); + return t("No template for path '{}'!",path); } protected void loadTemplates() { diff --git a/src/main/java/de/srsoftware/widerhall/web/Web.java b/src/main/java/de/srsoftware/widerhall/web/Web.java index f6d9ec6..4ee6b10 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Web.java +++ b/src/main/java/de/srsoftware/widerhall/web/Web.java @@ -11,7 +11,6 @@ import org.slf4j.LoggerFactory; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.HEAD; import java.io.IOException; import java.nio.file.Files; import java.security.InvalidKeyException; @@ -57,7 +56,7 @@ public class Web extends TemplateServlet { data.put(USER, user.safeMap()); if (!user.hashPermission(User.PERMISSION_CREATE_LISTS)){ - data.put(ERROR,t("Ihnen ist es nicht gestattet, neue Mailinglisten anzulegen!")); + data.put(ERROR,t("You are not allowed to create new mailing lists!")); return loadTemplate(ADMIN,data,resp); } @@ -89,17 +88,17 @@ public class Web extends TemplateServlet { data.put(SMTP_PORT, smtpPort); if (name == null || name.isBlank() || email == null || email.isBlank()) { - data.put(ERROR, "Name und Adresse der Liste sind notwendige Felder!"); + data.put(ERROR, "List name and address are required!"); return loadTemplate(ADD_LIST, data, resp); } if (!Util.isEmail(email)) { - data.put(ERROR, t("Listen-E-Mail-Adresse ({}) ist keine gültige E-Mail-Adresse!", email)); + data.put(ERROR, t("List email ({}) is not a valid email address!", email)); return loadTemplate(ADD_LIST, data, resp); } if (imapHost == null || imapHost.isBlank() || imapUser == null || imapUser.isBlank() || imapPass == null || imapPass.isBlank()) { - data.put(ERROR, "IMAP-Zugangsdaten sind erforderlich!"); + data.put(ERROR, "IMAP credentials are required!"); return loadTemplate(ADD_LIST, data, resp); } @@ -108,12 +107,12 @@ public class Web extends TemplateServlet { imapPort = Integer.parseInt(req.getParameter(IMAP_PORT)); data.put(IMAP_PORT, imapPort); } catch (NumberFormatException nfe) { - data.put(ERROR, t("'{}' ist keine gültige Port-Nummer!", req.getParameter(IMAP_PORT))); + data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(IMAP_PORT))); return loadTemplate(ADD_LIST, data, resp); } if (smtpHost == null || smtpHost.isBlank() || smtpUser == null || smtpUser.isBlank() || smtpPass == null || smtpPass.isBlank()) { - data.put(ERROR, "SMTP-Zugangsdaten sind erforderlich!"); + data.put(ERROR, "SMTP credentials are required!"); return loadTemplate(ADD_LIST, data, resp); } @@ -121,7 +120,7 @@ public class Web extends TemplateServlet { smtpPort = Integer.parseInt(req.getParameter(SMTP_PORT)); data.put(SMTP_PORT, smtpPort); } catch (NumberFormatException nfe) { - data.put(ERROR, t("'{}' ist keine gültige Port-Nummer!", req.getParameter(SMTP_PORT))); + data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(SMTP_PORT))); return loadTemplate(ADD_LIST, data, resp); } @@ -130,7 +129,7 @@ public class Web extends TemplateServlet { ListMember.create(list, user, ListMember.STATE_OWNER); return redirectTo(ADMIN, resp); } catch (SQLException e) { - return t("Erzeugen der Liste '{}' fehlgeschlagen: {}", name, e.getMessage()); + return t("Failed to create list '{}': {}", name, e.getMessage()); } } @@ -154,17 +153,17 @@ public class Web extends TemplateServlet { private String confirm(HttpServletRequest req, HttpServletResponse resp) { try { var token = req.getParameter(TOKEN); - if (token== null || token.isBlank()) return t("Ungültiger oder fehlender Token!"); + if (token== null || token.isBlank()) return t("Invalid or missing token!"); var listMember = ListMember.confirm(token); if (listMember != null) { listMember.sendConfirmationMail(getTemplate("confirmation_mail")); - return loadTemplate(INDEX,Map.of(USER,listMember.user().safeMap(),NOTES,"Listen-Mitgliedschaft bestätigt!"),resp); + return loadTemplate(INDEX,Map.of(USER,listMember.user().safeMap(),NOTES,"Confirmed list subscription!"),resp); } - return t("Nutzer oder Token unbekannt"); + return t("Unknown user or token"); } catch (Exception e) { - LOG.debug("Bestätigung des Listen-Abonnements fehlgeschlagen:",e); - return t("Bestätigung des Listen-Abonnements fehlgeschlagen!"); + LOG.debug("Failed to confirm list membership:",e); + return t("Confirmation of list membership failed!"); } } @@ -221,17 +220,17 @@ public class Web extends TemplateServlet { data.put(SMTP_PORT, smtpPort); if (name == null || name.isBlank() || email == null || email.isBlank()) { - data.put(ERROR, "Listen-Name und -adresse sind erforderlich!"); + data.put(ERROR, "List name and address are required!"); return loadTemplate(EDIT_LIST, data, resp); } if (!Util.isEmail(email)) { - data.put(ERROR, t("Listen-E-Mail ({}) ist keine gültige Mailadresse!", email)); + data.put(ERROR, t("List email ({}) is not a valid email address!", email)); return loadTemplate(EDIT_LIST, data, resp); } if (imapHost == null || imapHost.isBlank() || imapUser == null || imapUser.isBlank() || imapPass == null || imapPass.isBlank()) { - data.put(ERROR, "IMAP-Zugangsdaten sind erforderlich!"); + data.put(ERROR, "IMAP credentials are required!"); return loadTemplate(EDIT_LIST, data, resp); } @@ -240,12 +239,12 @@ public class Web extends TemplateServlet { imapPort = Integer.parseInt(req.getParameter(IMAP_PORT)); data.put(IMAP_PORT, imapPort); } catch (NumberFormatException nfe) { - data.put(ERROR, t("'{}' ist keine gültige Port-Nummer!", req.getParameter(IMAP_PORT))); + data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(IMAP_PORT))); return loadTemplate(EDIT_LIST, data, resp); } if (smtpHost == null || smtpHost.isBlank() || smtpUser == null || smtpUser.isBlank() || smtpPass == null || smtpPass.isBlank()) { - data.put(ERROR, "SMTP-Zugangsdaten sind erforderlich!"); + data.put(ERROR, "SMTP credentials are required!"); return loadTemplate(EDIT_LIST, data, resp); } @@ -253,7 +252,7 @@ public class Web extends TemplateServlet { smtpPort = Integer.parseInt(req.getParameter(SMTP_PORT)); data.put(SMTP_PORT, smtpPort); } catch (NumberFormatException nfe) { - data.put(ERROR, t("'{}' ist keine gültige Port-Nummer!", req.getParameter(SMTP_PORT))); + data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(SMTP_PORT))); return loadTemplate(EDIT_LIST, data, resp); } @@ -261,7 +260,7 @@ public class Web extends TemplateServlet { return loadTemplate(ADMIN,data,resp); } catch (SQLException e) { LOG.warn("Editing list {} by {} failed",list.email(),user.email(),e); - return t("Bearbeiten der Liste {} durch {} fehlgeschlagen",list.email(),user.email()); + return t("Editing list {} by {} failed",list.email(),user.email()); } } @@ -295,7 +294,7 @@ public class Web extends TemplateServlet { return post(req,resp); case RELOAD: loadTemplates(); - data.put(NOTES,t("Vorlagen wurden neu geladen")); + data.put(NOTES,t("Templates have been reloaded")); path = INDEX; case CSS: case INDEX: @@ -305,16 +304,16 @@ public class Web extends TemplateServlet { data.put(LIST,list.email()); return loadTemplate(path, data, resp); } - return t("Es ist ihnen nicht gestattet, '{}' zu abonnieren!",list.email()); + return t("You are not allowed to subscribe to '{}'!",list.email()); case "js": resp.setContentType("text/javascript"); return loadTemplate(path,data,resp); case LOGIN: try { - if (User.noUsers()) return loadTemplate(REGISTER, Map.of(NOTES,t("Nutzer-Datenbank ist leer. Admin-Nutzer wird hiermit angelegt:")), resp); + if (User.noUsers()) return loadTemplate(REGISTER, Map.of(NOTES,t("User database is empty. Create admin user first:")), resp); return loadTemplate(path,null,resp); } catch (SQLException e) { - return "Fehler beim Lesen der Datenbank!"; + return "Error reading user database!"; } case LOGOUT: req.getSession().invalidate(); @@ -347,8 +346,8 @@ public class Web extends TemplateServlet { private String handleLogin(HttpServletRequest req, HttpServletResponse resp) { var email = req.getParameter("email"); var pass = req.getParameter("pass"); - if (email == null || pass == null) return loadTemplate("login", Map.of("error",t("Nutzername oder Passwort fehlen!")), resp); - if (!Util.isEmail(email)) return loadTemplate("login", Map.of("error",t("'{}' ist keine gültige Mailadresse!",email)), resp); + if (email == null || pass == null) return loadTemplate("login", Map.of("error",t("Missing username or password!")), resp); + if (!Util.isEmail(email)) return loadTemplate("login", Map.of("error",t("'{}' is not a valid email address!",email)), resp); try { var user = User.loadUser(email,pass); req.getSession().setAttribute("user",user); @@ -356,10 +355,10 @@ public class Web extends TemplateServlet { resp.sendRedirect(String.join("/",WEB_ROOT,"admin")); } catch (Exception e) { try { - LOG.warn("Static.handleLogin fehlgeschlagen:",e); + LOG.warn("Static.handleLogin failed:",e); Thread.sleep(10000); } finally { - return loadTemplate("login", Map.of(ERROR,t("Ungültiger Nutzername oder ungültiges Passwort!"),EMAIL,email), resp); + return loadTemplate("login", Map.of(ERROR,t("Invalid username/password"),EMAIL,email), resp); } } return null; @@ -385,7 +384,7 @@ public class Web extends TemplateServlet { return unsubscribe(req,resp); } - return t("Kein Handler für den Pfad '{}'!",path); + return t("No handler for path {}!",path); } private String inspect(HttpServletRequest req, HttpServletResponse resp) { @@ -398,12 +397,12 @@ public class Web extends TemplateServlet { var list = Util.getMailingList(req); if (list == null) { error = true; - data.put(ERROR, t("Keine gültige MailingListe übermittelt!")); + data.put(ERROR, t("No valid mailing list provided!")); } else data.put(LIST, list.email()); if (!error && !list.mayBeAlteredBy(user)) { error = true; - data.put(ERROR,t("Es ist Ihnen nicht gestattet, die Einselltungen dieser Mailingliste zu verändern!")); + data.put(ERROR,t("You are not alter settings of this list!")); } if (!error){ @@ -417,10 +416,10 @@ public class Web extends TemplateServlet { .openForSubscribers(Util.getCheckbox(req,KEY_OPEN_FOR_SUBSCRIBERS)) .archive(Util.getCheckbox(req,KEY_ARCHIVE)) .deleteMessages(Util.getCheckbox(req,KEY_DELETE_MESSAGES),req.getParameter(HOLD_TIME)); - data.put(NOTES,t("Mailing-Liste aktualisiert!")); + data.put(NOTES,t("Sucessfully updated MailingList!")); } catch (SQLException e){ - LOG.warn("Aktualisierung der Mailing-Liste fehlgeschlagen:",e); - data.put(ERROR,t("Aktualisierung der Mailing-Liste fehlgeschlagen!")); + LOG.warn("Failed to update MailingList:",e); + data.put(ERROR,t("Failed to update MailingList!")); } } @@ -439,7 +438,7 @@ public class Web extends TemplateServlet { return loadTemplate("post",map,resp); } catch (SQLException | IOException e) { LOG.debug("Failed to load post from file!",e); - return t("Laden der Nachricht aus Datei fehlgeschlagen!"); + return t("Failed to load post from file!"); } } @@ -448,7 +447,7 @@ public class Web extends TemplateServlet { resp.sendRedirect(String.join("/",WEB_ROOT,page)); return null; } catch (IOException e) { - return t("Weiterleitung nach {} fehlgeschlagen: {}", page, e.getMessage()); + return t("Was not able to redirect to {} page: {}", page, e.getMessage()); } } @@ -464,15 +463,15 @@ public class Web extends TemplateServlet { if (email == null || email.isBlank() || name == null || name.isBlank() || pass == null || pass.isBlank() || - pass_repeat == null || pass_repeat.isBlank()) return loadTemplate(REGISTER,Map.of(ERROR,"Bitte alle Felder ausfüllen!",NAME,name,EMAIL,email),resp); - if (!pass.equals(pass_repeat)) return loadTemplate(REGISTER,Map.of(ERROR,"Passworte stimmen nicht überein!",NAME,name,EMAIL,email),resp); - if (Util.simplePassword(pass)) return loadTemplate(REGISTER,Map.of(ERROR,"Passwort zu kurz oder zu einfach!",NAME,name,EMAIL,email),resp); + pass_repeat == null || pass_repeat.isBlank()) return loadTemplate(REGISTER,Map.of(ERROR,"Fill all fields, please!",NAME,name,EMAIL,email),resp); + if (!pass.equals(pass_repeat)) return loadTemplate(REGISTER,Map.of(ERROR,"Passwords do not match!",NAME,name,EMAIL,email),resp); + if (Util.simplePassword(pass)) return loadTemplate(REGISTER,Map.of(ERROR,"Password to short or to simple!",NAME,name,EMAIL,email),resp); var firstUser = false; try { firstUser = User.noUsers(); } catch (SQLException e) { - return t("Fehler beim Zugriff auf die Nutzer-Datenbank: {}",e.getMessage()); + return t("Failed to access user database: {}",e.getMessage()); } @@ -482,8 +481,8 @@ public class Web extends TemplateServlet { req.getSession().setAttribute("user",user); return redirectTo(INDEX,resp); } catch (SQLException e) { - LOG.warn("Erzeugen des neuen Nutzers fehlgeschlagen:",e); - return t("Erzeugen des neuen Nutzers fehlgeschlagen: {}",e.getMessage()); + LOG.warn("Failed to create new user:",e); + return t("Failed to create new user: {}",e.getMessage()); } } @@ -500,12 +499,12 @@ public class Web extends TemplateServlet { if (list == null){ - data.put(ERROR,"Formular-Daten enthalten keine Liste!"); + data.put(ERROR,"No list provided by form data!"); return loadTemplate(SUBSCRIBE,data,resp); } if (name == null || name.isBlank() || email == null || email.isBlank()){ - data.put(ERROR,"Name und E-Mail-Adresse sind für das Abonnieren der Mailingliste erforderlich!"); + data.put(ERROR,"Name and email are required fields for list subscription!"); return loadTemplate(SUBSCRIBE,data,resp); } if (pass != null && pass.isBlank()) pass = null; @@ -522,36 +521,36 @@ public class Web extends TemplateServlet { // success → subscribe } catch (InvalidKeyException | SQLException e) { // invalid credentials - data.put(ERROR,t("'{}' gibt es schon in der Datenbank, hat dort aber ein anderes Passwort!",email)); + data.put(ERROR,t("'{}' already in database, but with different password!",email)); return loadTemplate(SUBSCRIBE,data,resp); } } data.put(USER,user.safeMap()); if (!list.isOpenFor(user)){ - data.put(ERROR,t("Ihnen ist es nicht gestattet, '{}' zu abonnieren!",list.email())); + data.put(ERROR,t("You are not allowed to join {}!",list.email())); return loadTemplate(SUBSCRIBE,data,resp); } try { list.requestSubscription(user,skipConfirmation,getTemplate("subscribe_mail")); if (skipConfirmation) { - data.put(NOTES, t("'{}' hat die Mailingliste '{}' erfolgreich abonniert.", user.email(), list.email())); + data.put(NOTES, t("Successfully subscribed '{}' to '{}'.", user.email(), list.email())); } else { - data.put(NOTES, t("Bestätigungs-Email wurde an '{} versendet.", user.email())); + data.put(NOTES, t("Sent confirmation mail to '{}.", user.email())); } return loadTemplate(INDEX,data,resp); } catch (SQLException sqle) { - LOG.debug("Abonnieren der Liste fehlgeschlagen: ",sqle); + LOG.debug("List subscription failed: ",sqle); var cause = getCausingException(sqle); int code = cause.getErrorCode(); if (code == PRIMARY_KEY_CONSTRAINT) {// user already exists - data.put(ERROR,t("Sie haben diese Liste bereits abonniert!",sqle.getMessage())); - } else data.put(ERROR,t("Abonnieren der Liste fehlgeschlagen: {}",sqle.getMessage())); + data.put(ERROR,t("You already are member of this list!",sqle.getMessage())); + } else data.put(ERROR,t("Subscription failed: {}",sqle.getMessage())); return loadTemplate(SUBSCRIBE,data,resp); } catch (MessagingException e) { - LOG.warn("Senden der Bestätigungs-Email fehlgeschlagen:",e); - data.put(ERROR,t("Senden der Bestätigungs-Email fehlgeschlagen: {}",e.getMessage())); + LOG.warn("Failed to send request confirmation email:",e); + data.put(ERROR,t("Failed to send request confirmation email: {}",e.getMessage())); return loadTemplate(SUBSCRIBE,data,resp); } } @@ -566,12 +565,12 @@ public class Web extends TemplateServlet { data.put(EMAIL,email); if (user != null) data.put(USER,user.safeMap()); if (list == null){ - data.put(ERROR,"Keine Mailin-Liste in den Formular-Daten übermittelt!!"); + data.put(ERROR,"No list provided by form data!"); return loadTemplate(UNSUBSCRIBE,data,resp); } else data.put(LIST,list.email()); if (user == null) { if (email == null || email.isBlank()) { - data.put(ERROR, "Für das Abbestellen ist eine E-Mail-Adresse erforderlich!"); + data.put(ERROR, "Email is required for list un-subscription!"); return loadTemplate(UNSUBSCRIBE, data, resp); } var pass = req.getParameter(PASSWORD); @@ -582,7 +581,7 @@ public class Web extends TemplateServlet { req.getSession().setAttribute(USER,user); data.put(USER,user.safeMap()); } catch (InvalidKeyException | SQLException e) { - data.put(ERROR,"Ungültige E-Mail-/Passwort-Kombination!"); + data.put(ERROR,"Invalid email/password combination!"); return loadTemplate(UNSUBSCRIBE,data,resp); } } @@ -591,23 +590,22 @@ public class Web extends TemplateServlet { try { member = ListMember.load(list,user); } catch (SQLException e) { - LOG.debug("Laden des Listenmitglieds für {}/{} fehlgeschlagen",user.email(),list.email(),e); - data.put(ERROR, t("Laden des Listenmitglieds für {}/{} fehlgeschlagen",user.email(),list.email())); + LOG.debug("Failed to load list member for {}/{}",user.email(),list.email(),e); + data.put(ERROR, t("Failed to load list member for {}/{}",user.email(),list.email())); return loadTemplate(UNSUBSCRIBE,data,resp); } if (member == null){ - data.put(ERROR, t("{} ist kein Mitglied von {}",user.email(),list.email())); + data.put(ERROR, t("{} is no member of {}",user.email(),list.email())); return loadTemplate(UNSUBSCRIBE,data,resp); } // if we get here, we should have a valid member object try { member.unsubscribe(); - data.put(NOTES,t("'{}' erfolgreich abbestellt.",list.email())); - + data.put(NOTES,t("Sucessfully un-subscribed from '{}'.",list.email())); return loadTemplate(INDEX,data,resp); } catch (SQLException e) { - LOG.warn("Es ist ein Problem beim Entfernen von {} aus der Liste {} aufgetreten:",user.email(),list.email(),e); - data.put(ERROR,"Abbestellen der Mailin-Liste fehlgeschlagen!"); + LOG.warn("Problem during unscubsription of {} from {}:",user.email(),list.email(),e); + data.put(ERROR,"Failed to unsubscribe!"); return loadTemplate(UNSUBSCRIBE,data,resp); } diff --git a/src/main/resources/Application.de.translation b/src/main/resources/Application.de.translation index 0cc2bb4..c94240d 100644 --- a/src/main/resources/Application.de.translation +++ b/src/main/resources/Application.de.translation @@ -1,9 +1,112 @@ +[{}] Please confirm your list subscription : [{}] Bitte bestätigen Sie Ihr Listen-Abonnement +[{}] Subscription complete! : [{}] Abonnement abgeschlossen! +[{}]\: test mail : [{}] Test-Mail +'{}' already in database, but with different password! : '{}' gibt es schon in der Datenbank, hat dort aber ein anderes Passwort! +{} is no member of {} : {} ist nicht Mitglied von {}! +{} is not a member of {} : {} ist nicht Mitglied von {}! +'{}' is not a proper port number! : '{}' ist keine gültige Port-Nummer! +'{}' is not a valid email address! : '{}' ist keine gültige Mailadresse! +{} is now a moderator of {} : {} ist nun ein Moderator der Liste {} archive : Archiv +awaiting confirmation : erwartet Bestätigung +Confirmation of list membership failed! : Bestätigung des Listen-Abonnements fehlgeschlagen! +Confirmed list subscription! : Listen-Mitgliedschaft bestätigt! enabled : aktiviert +disabled : deaktiviert +Editing list {} by {} failed : Bearbeiten der Liste {} durch {} fehlgeschlagen! +Email is required for list un-subscription! : Für das Abbestellen ist eine E-Mail-Adresse erforderlich! +Error reading user database! : Fehler beim Lesen der Nutzer-Datenbank! +Failed to access user database\: {} : Fehler beim Zugriff auf die Nutzer-Datenbank: {} +Failed to create list '{}'\: {} : Erzeugen der Liste '{}' fehlgeschlagen: {} +Failed to create new user\: {} : Erzeugen des neuen Nutzers fehlgeschlagen: {} +Failed to handle request\: {} : Konnte Anfrage nicht verarbeiten: {} +Failed to load file '{}'! : Laden der Datei '{}' fehlgeschlagen! +Failed to load list member for {}/{} : Laden der Mitgliedschaft zu {}/{} fehlgeschlagen! +Failed to load member list '{}' : Laden der Mitglieder-Liste von '{}' fehlgeschlagen! +Failed to load post from file! : Laden der Nachricht aus Datei fehlgeschlagen! +Failed to load template '{}' : Laden der Vorlage '{}' fehlgeschlagen! +Failed to load user for {} : Laden des Nutzers zu {} fehlgeschlagen! +Failed to load user for address {} : Laden des Nutzers für die Adresse {} fehlgeschlagen! +Failed to make {} a moderator of {} : Ernennen von {} zum Moderator von {} fehlgeschlagen! +Failed to make {} a subscriber of {} : Ernennen von {} zum regulären Abonnenten von {} fehlgeschlagen! +Failed to send email to {} : Senden der Email an {} fehlgeschlagen! +Failed to send request confirmation email\: {} : Senden der Bestätigungs-Email fehlgeschlagen: {} +Failed to send test email to {} : Senden der Test-Email an {} fehlgeschlagen! +Failed to (un)hide mailing list\: : Veröffentlichen/Verstekcen der Mailing-Liste fehlgeschlagen: +Failed to un-subscribe {} from {} : Abbestellen von {} / {} fehlgeschlagen! +Failed to unsubscribe! : Abbestellen der Mailin-Liste fehlgeschlagen! +Failed to update list '{}' : Aktualisieren der Liste '{}' fehlgeschlagen! +Failed to update MailingList! : Aktualisierung der Mailing-Liste fehlgeschlagen! +Fill all fields, please! : Bitte alle Felder ausfüllen! +File {} does not exist! : Datei {} existiert nicht! +Find the forwarded message in the attachment(s)!\n : Die weitergeleitete Nachricht findest du im Anhang dieser E-Mail!\n forward_attached : als Anhang weiterleiten +hidden : versteckt hide_receivers : Empfänger verbergen +If you received this mail, the SMTP settings of your mailing list are correct. : Wenn Sie diese Nachricht empfangen haben, sind die SMTP-Einstellungen Ihrer Mailing-Liste korrekt. +IMAP credentials are required! : IMAP-Zugangsdaten sind erforderlich! +Invalid or missing token : Ungültiger oder fehlender Token! +Invalid username/password : Ungültiger Nutzername oder ungültiges Passwort! +Invalid email/password combination! : Ungültige E-Mail-/Passwort-Kombination! +List '{}' requires attention! : Liste '{}' erfordert Aufmerksamkeit! +List {} disabled, closed for subscribers and hidden. Members have been removed. : Liste {} deaktiviert, Abonnement gesperrt, Liste de-publiziert. Mitglieder wurden entfernt. +List email ({}) is not a valid email address! : Listen-E-Mail-Adresse ({}) ist keine gültige E-Mail-Adresse! +List name and address are required! : Name und Adresse der Liste sind notwendige Felder! +made public : veröffentlicht +Mailing list '{}' was {}! : Mailing-Liste '{}' wurde {}! +missing user email address! : E-Mail-Adresse des Listenmitglieds nicht angegeben! +Missing username or password! : Nutzername oder Passwort fehlen! +moderator : Moderator +Name and email are required fields for list subscription! : Name und E-Mail-Adresse sind für das Abonnieren der Mailingliste erforderlich! +No handler for path {}! : Kein Handler für den Pfad '{}'! +no list email provided! : Keine Listen-Email übertragen! +No list provided by form data! : Formular-Daten enthalten keine Liste! +No such user\: {} : Kein solcher Nutzer: {} +No template for path '{}'! : Keine Vorlage für den Pfad '{}' vorhanden! +No valid mailing list provided! : Keine gültige MailingListe übermittelt! +no valid permissions provided! : Keine gültigen Berechtigungen übergeben! +Not logged in! : Nicht eingeloggt! open_for_guests : Gästbeiträge erlaubt open_for_subscribers : Selbstregistrierung original_from : ursprünglicher Absender +owner : Besitzer +Passwords do not match! : Passworte stimmen nicht überein! +Password to short or to simple! : Passwort zu kurz oder zu einfach! +Problem during unscubsription of {} from {}\: : Es ist ein Problem beim Austragen von {} aus der Liste {} aufgetreten: public : öffentlich +Query '{}' failed\: : Query '{}' fehlgeschlagen: reply_to_list : Antwort an Liste +Sent confirmation mail to '{}. : Bestätigungs-Email wurde an '{} versendet. +SMTP credentials are required! : SMTP-Zugangsdaten sind erforderlich! +subscriber : Abonniert +Subscription failed\: {} : Abonnieren der Liste fehlgeschlagen: {} +Sucessfully updated MailingList! : Mailing-Liste aktualisiert! +Successfully subscribed '{}' to '{}'. : '{}' hat die Mailingliste '{}' erfolgreich abonniert. +Sucessfully un-subscribed from '{}'. : '{}' erfolgreich abbestellt. +Templates have been reloaded : Vorlagen wurden neu geladen! +The mailing list you are trying to view does not exist! : Die Mailingliste, auf die Sie zugreifen wollen, gibt es nicht! +This list received an email from {}, who is not member of the list.\nThe email has been moved to the '{}' folder.\nYou may manually forward this message or drop it. : Diese Liste hat eine E-Mail von {} empfangen. Der Absender ist nicht Mitglied der Liste.\nDie Email wurde in den '{}'-Ordner verschoben.\nSie können die Nachricht manuell weiterleiten oder verwerfen. +Unknown user or token : Nutzer oder Token unbekannt! +Updated user permissions : Nutzer-Berechtigungen aktualisiert +User database is empty. Create admin user first\: : Nutzer-Datenbank ist leer. Admin-Nutzer wird hiermit angelegt: +Was not able to check existence of table {}! : Konnte Existenz der Tabelle {} nicht prüfen! +Was not able to redirect to {} page\: {} : Weiterleitung nach {} fehlgeschlagen: {} +You already are member of this list! : Sie haben diese Liste bereits abonniert! +You are not allowed to access the archive of this list! : Du hast keine Berechtigung, das Archiv dieser Liste anzusehen! +You are not allowed to alter user permissions! : Sie haben nicht die Berechtigung, um Berechtigungen zu ändern! +You are not allowed to create new mailing lists! : Ihnen ist es nicht gestattet, neue Mailinglisten anzulegen! +You are not allowed to edit '{}' : Du bist nicht berechtigt, '{}' zu bearbeiten! +You are not allowed to edit mods of {} : Es ist dir nicht gestattet, die Moderatoren von {} zu verändern! +You are not allowed to join {}! : Ihnen ist es nicht gestattet, '{}' zu abonnieren! +You are not allowed to list members of '{}' : Es ist dir nicht gestattet, die Mitglieder von '{}' aufzulisten! +You are not allowed to nominate new mods for {} : Es ist dir nicht gestattet, neue Moderatoren für {} zu ernennen! +You are not allowed to remove members of {} : Es ist dir nicht erlaubt, Mitglieder von {} zu entfernen! +You are not allowed to remove the list owner! : Du kannst den Listen-Besitzer nicht entfernen! +You are not allowed to remove this list! : Es ist dir nicht gestattet, diese Liste zu löschen! +You are not allowed to subscribe to '{}'! : Es ist ihnen nicht gestattet, '{}' zu abonnieren! +You are not allowed to test '{}' : Es ist dir nicht gestattet, '{}' zu testen +You are not alter settings of this list! : Es ist Ihnen nicht gestattet, die Einselltungen dieser Mailingliste zu verändern! +You are trying to access a non-existing list! : Du versuchst auf eine nicht existierende Liste zuzugreifen! +You have tried to send a message to the list '{}', which failed. This is because you are not a (privileged) member of this list.\n : Sie haben versucht, eine Nachricht an die Liste '{}' zu senden. Das wurde verweigert, da Sie kein Mitglied der Liste (mit entsprechenden Berechtigungen) sind.\n +You may go to {} and subscribe to the list, then try again. : Sie können zu {} gehen und die Liste abonnieren. Versuchen Sie es danach erneut. +Your message to {} was rejected! : Ihre Nachricht an {} wurde zurückgewiesen!