2
0
Эх сурвалжийг харах

Merge PR 4657 into language-change

DerLinkman 2 жил өмнө
parent
commit
a334f33b35
60 өөрчлөгдсөн 1498 нэмэгдсэн , 143 устгасан
  1. 1 1
      .github/workflows/close_old_issues_and_prs.yml
  2. 3 0
      .github/workflows/image_builds.yml
  3. 3 0
      .github/workflows/integration_tests.yml
  4. 1 0
      data/Dockerfiles/dovecot/docker-entrypoint.sh
  5. 2 2
      data/Dockerfiles/dovecot/quarantine_notify.py
  6. 14 2
      data/Dockerfiles/postfix/postfix.sh
  7. 3 3
      data/Dockerfiles/sogo/Dockerfile
  8. 4 0
      data/Dockerfiles/sogo/bootstrap-sogo.sh
  9. 1 0
      data/conf/dovecot/dovecot.conf
  10. 0 2
      data/conf/sogo/sogo.conf
  11. 16 0
      data/web/api/index.css
  12. 2 43
      data/web/api/index.html
  13. 10 6
      data/web/api/oauth2-redirect.html
  14. 19 0
      data/web/api/swagger-initializer.js
  15. 0 0
      data/web/api/swagger-ui-bundle.js
  16. 0 0
      data/web/api/swagger-ui-bundle.js.map
  17. 0 0
      data/web/api/swagger-ui-es-bundle-core.js
  18. 0 0
      data/web/api/swagger-ui-es-bundle-core.js.map
  19. 0 0
      data/web/api/swagger-ui-es-bundle.js
  20. 0 0
      data/web/api/swagger-ui-es-bundle.js.map
  21. 0 0
      data/web/api/swagger-ui-standalone-preset.js
  22. 0 0
      data/web/api/swagger-ui-standalone-preset.js.map
  23. 0 1
      data/web/api/swagger-ui.css
  24. 0 0
      data/web/api/swagger-ui.css.map
  25. 0 1
      data/web/api/swagger-ui.js
  26. 0 0
      data/web/api/swagger-ui.js.map
  27. 0 0
      data/web/css/build/007-languages.min.css
  28. 21 20
      data/web/inc/functions.inc.php
  29. 60 4
      data/web/inc/prerequisites.inc.php
  30. 25 22
      data/web/inc/vars.inc.php
  31. 0 0
      data/web/lang/lang.ca-es.json
  32. 1 1
      data/web/lang/lang.cs-cz.json
  33. 0 0
      data/web/lang/lang.da-dk.json
  34. 0 0
      data/web/lang/lang.de-de.json
  35. 0 0
      data/web/lang/lang.en-gb.json
  36. 0 0
      data/web/lang/lang.es-es.json
  37. 0 0
      data/web/lang/lang.fi-fi.json
  38. 5 2
      data/web/lang/lang.fr-fr.json
  39. 0 0
      data/web/lang/lang.hu-hu.json
  40. 2 1
      data/web/lang/lang.it-it.json
  41. 0 0
      data/web/lang/lang.ko-kr.json
  42. 0 0
      data/web/lang/lang.lv-lv.json
  43. 0 0
      data/web/lang/lang.nl-nl.json
  44. 0 0
      data/web/lang/lang.pl-pl.json
  45. 0 0
      data/web/lang/lang.pt-pt.json
  46. 3 2
      data/web/lang/lang.ro-ro.json
  47. 0 0
      data/web/lang/lang.ru-ru.json
  48. 0 0
      data/web/lang/lang.sk-sk.json
  49. 0 0
      data/web/lang/lang.sv-se.json
  50. 86 0
      data/web/lang/lang.tr.json
  51. 0 0
      data/web/lang/lang.uk-ua.json
  52. 0 0
      data/web/lang/lang.zh-cn.json
  53. 1187 0
      data/web/lang/lang.zh-tw.json
  54. 6 6
      data/web/templates/base.twig
  55. 5 5
      data/web/templates/index.twig
  56. 3 3
      docker-compose.yml
  57. 11 4
      generate_config.sh
  58. 1 1
      helper-scripts/add-new-lang-keys.php
  59. 1 1
      helper-scripts/check_translations.rb
  60. 2 10
      update.sh

+ 1 - 1
.github/workflows/close_old_issues_and_prs.yml

@@ -14,7 +14,7 @@ jobs:
       pull-requests: write
     steps:
       - name: Mark/Close Stale Issues and Pull Requests 🗑️
-        uses: actions/stale@v5.1.1
+        uses: actions/stale@v6.0.0
         with:
           repo-token: ${{ secrets.STALE_ACTION_PAT }}
           days-before-stale: 60

+ 3 - 0
.github/workflows/image_builds.yml

@@ -5,6 +5,9 @@ on:
     branches: [ "master", "staging" ]
   workflow_dispatch:
 
+permissions:
+  contents: read # to fetch code (actions/checkout)
+
 jobs:
   docker_image_builds:
     strategy:

+ 3 - 0
.github/workflows/integration_tests.yml

@@ -5,6 +5,9 @@ on:
     branches: [ "master", "staging" ]
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 jobs:
   integration_tests:
     runs-on: ubuntu-latest

+ 1 - 0
data/Dockerfiles/dovecot/docker-entrypoint.sh

@@ -307,6 +307,7 @@ namespace {
 }
 EOF
 
+
 cat <<EOF > /etc/dovecot/sogo_trusted_ip.conf
 # Autogenerated by mailcow
 remote ${IPV4_NETWORK}.248 {

+ 2 - 2
data/Dockerfiles/dovecot/quarantine_notify.py

@@ -50,7 +50,7 @@ try:
   def query_mysql(query, headers = True, update = False):
     while True:
       try:
-        cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8")
+        cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
       except Exception as ex:
         print('%s - trying again...'  % (ex))
         time.sleep(3)
@@ -166,4 +166,4 @@ try:
       notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category'])
 
 finally:
-  os.unlink(pidfile)
+  os.unlink(pidfile)

+ 14 - 2
data/Dockerfiles/postfix/postfix.sh

@@ -323,7 +323,19 @@ hosts = unix:/var/run/mysqld/mysqld.sock
 dbname = ${DBNAME}
 # First select queries domain and alias_domain to determine if domains are active.
 query = SELECT goto FROM alias
-  WHERE address='%s'
+  WHERE id IN (
+      SELECT COALESCE (
+        (
+          SELECT id FROM alias
+            WHERE address='%s'
+            AND (active='1' OR active='2')
+        ), (
+          SELECT id FROM alias
+            WHERE address='@%d'
+            AND (active='1' OR active='2')
+        )
+      )
+    )
     AND active='1'
     AND (domain IN
       (SELECT domain FROM domain
@@ -354,7 +366,7 @@ query = SELECT goto FROM alias
     WHERE alias_domain.alias_domain = '%d'
       AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain)
       AND (mailbox.active = '1' OR mailbox.active ='2')
-      AND alias_domain.active='1'
+      AND alias_domain.active='1';
 EOF
 
 # MX based routing

+ 3 - 3
data/Dockerfiles/sogo/Dockerfile

@@ -2,7 +2,7 @@ FROM debian:bullseye-slim
 LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
 
 ARG DEBIAN_FRONTEND=noninteractive
-ARG SOGO_DEBIAN_REPOSITORY=http://packages.inverse.ca/SOGo/nightly/5/debian/
+ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/
 ENV LC_ALL C
 ENV GOSU_VERSION 1.14
 
@@ -30,7 +30,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
   && gosu nobody true \
   && mkdir /usr/share/doc/sogo \
   && touch /usr/share/doc/sogo/empty.sh \
-  && apt-key adv --keyserver keyserver.ubuntu.com --recv-key 0x810273C4 \
+  && apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \
   && echo "deb ${SOGO_DEBIAN_REPOSITORY} bullseye bullseye" > /etc/apt/sources.list.d/sogo.list \
   && apt-get update && apt-get install -y --no-install-recommends \
     sogo \
@@ -52,4 +52,4 @@ RUN chmod +x /bootstrap-sogo.sh \
 
 ENTRYPOINT ["/docker-entrypoint.sh"]
 
-CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
+CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf

+ 4 - 0
data/Dockerfiles/sogo/bootstrap-sogo.sh

@@ -142,6 +142,10 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
     <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl</string>
     <key>SOGoIMAPServer</key>
     <string>imap://${IPV4_NETWORK}.250:143/?TLS=YES&amp;tlsVerifyMode=none</string>
+    <key>SOGoSieveServer</key>
+    <string>sieve://${IPV4_NETWORK}.250:4190/?TLS=YES&amp;tlsVerifyMode=none</string>
+    <key>SOGoSMTPServer</key>
+    <string>smtp://${IPV4_NETWORK}.253:588/?TLS=YES&amp;tlsVerifyMode=none</string>
     <key>SOGoTrustProxyAuthentication</key>
     <string>YES</string>
     <key>SOGoEncryptionKey</key>

+ 1 - 0
data/conf/dovecot/dovecot.conf

@@ -194,6 +194,7 @@ plugin {
   fts_solr = url=http://solr:8983/solr/dovecot-fts/
   quota = dict:Userquota::proxy::sqlquota
   quota_rule2 = Trash:storage=+100%%
+  sieve = /var/vmail/sieve/%u.sieve
   sieve_plugins = sieve_imapsieve sieve_extprograms
   sieve_vacation_send_from_recipient = yes
   sieve_redirect_envelope_from = recipient

+ 0 - 2
data/conf/sogo/sogo.conf

@@ -32,8 +32,6 @@
     // );
 
     // self-signed is not trusted anymore
-    SOGoSieveServer = "sieve://dovecot:4190/?TLS=YES&tlsVerifyMode=none";
-    SOGoSMTPServer = "smtp://postfix:588/?TLS=YES&tlsVerifyMode=none";
     WOPort = "0.0.0.0:20000";
     SOGoMemcachedHost = "memcached";
 

+ 16 - 0
data/web/api/index.css

@@ -0,0 +1,16 @@
+html {
+    box-sizing: border-box;
+    overflow: -moz-scrollbars-vertical;
+    overflow-y: scroll;
+}
+
+*,
+*:before,
+*:after {
+    box-sizing: inherit;
+}
+
+body {
+    margin: 0;
+    background: #fafafa;
+}

+ 2 - 43
data/web/api/index.html

@@ -5,56 +5,15 @@
     <meta charset="UTF-8">
     <title>Swagger UI</title>
     <link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
+    <link rel="stylesheet" type="text/css" href="index.css" />
     <link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
     <link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
-    <style>
-      html
-      {
-        box-sizing: border-box;
-        overflow: -moz-scrollbars-vertical;
-        overflow-y: scroll;
-      }
-
-      *,
-      *:before,
-      *:after
-      {
-        box-sizing: inherit;
-      }
-
-      body
-      {
-        margin:0;
-        background: #fafafa;
-      }
-    </style>
   </head>
 
   <body>
     <div id="swagger-ui"></div>
-
     <script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
     <script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
-    <script>
-    window.onload = function() {
-      // Begin Swagger UI call region
-      const ui = SwaggerUIBundle({
-        urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
-        dom_id: '#swagger-ui',
-        deepLinking: true,
-        presets: [
-          SwaggerUIBundle.presets.apis,
-          SwaggerUIStandalonePreset
-        ],
-        plugins: [
-          SwaggerUIBundle.plugins.DownloadUrl
-        ],
-        layout: "StandaloneLayout"
-      });
-      // End Swagger UI call region
-
-      window.ui = ui;
-    };
-  </script>
+    <script src="./swagger-initializer.js" charset="UTF-8"> </script>
   </body>
 </html>

+ 10 - 6
data/web/api/oauth2-redirect.html

@@ -13,7 +13,7 @@
         var isValid, qp, arr;
 
         if (/code|token|error/.test(window.location.hash)) {
-            qp = window.location.hash.substring(1);
+            qp = window.location.hash.substring(1).replace('?', '&');
         } else {
             qp = location.search.substring(1);
         }
@@ -38,7 +38,7 @@
                     authId: oauth2.auth.name,
                     source: "auth",
                     level: "warning",
-                    message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
+                    message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
                 });
             }
 
@@ -58,7 +58,7 @@
                     authId: oauth2.auth.name,
                     source: "auth",
                     level: "error",
-                    message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
+                    message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
                 });
             }
         } else {
@@ -67,9 +67,13 @@
         window.close();
     }
 
-    window.addEventListener('DOMContentLoaded', function () {
-      run();
-    });
+    if (document.readyState !== 'loading') {
+        run();
+    } else {
+        document.addEventListener('DOMContentLoaded', function () {
+            run();
+        });
+    }
 </script>
 </body>
 </html>

+ 19 - 0
data/web/api/swagger-initializer.js

@@ -0,0 +1,19 @@
+window.onload = function() {
+  // Begin Swagger UI call region
+  const ui = SwaggerUIBundle({
+    urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
+    dom_id: '#swagger-ui',
+    deepLinking: true,
+    presets: [
+      SwaggerUIBundle.presets.apis,
+      SwaggerUIStandalonePreset
+    ],
+    plugins: [
+      SwaggerUIBundle.plugins.DownloadUrl
+    ],
+    layout: "StandaloneLayout"
+  });
+  // End Swagger UI call region
+
+  window.ui = ui;
+};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-bundle.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-bundle.js.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-es-bundle-core.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-es-bundle-core.js.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-es-bundle.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-es-bundle.js.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-standalone-preset.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui-standalone-preset.js.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1
data/web/api/swagger-ui.css


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui.css.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1
data/web/api/swagger-ui.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/api/swagger-ui.js.map


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
data/web/css/build/007-languages.min.css


+ 21 - 20
data/web/inc/functions.inc.php

@@ -938,13 +938,16 @@ function check_login($user, $pass, $app_passwd_data = false) {
     $stmt->execute(array(':user' => $user));
     $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
   }
-  foreach ($rows as $row) {
+  foreach ($rows as $row) { 
     // verify password
-    if ($app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true){
-      if (verify_hash($row['password'], $pass) !== false) {
+    if (verify_hash($row['password'], $pass) !== false) {
+      if (!array_key_exists("app_passwd_id", $row)){ 
+        // password is not a app password
         // check for tfa authenticators
         $authenticators = get_tfa($user);
-        if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
+        if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&
+            $app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) {
+          // authenticators found, init TFA flow
           $_SESSION['pending_mailcow_cc_username'] = $user;
           $_SESSION['pending_mailcow_cc_role'] = "user";
           $_SESSION['pending_tfa_methods'] = $authenticators['additional'];
@@ -955,7 +958,8 @@ function check_login($user, $pass, $app_passwd_data = false) {
             'msg' => array('logged_in_as', $user)
           );
           return "pending";
-        } else {
+        } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
+          // no authenticators found, login successfull
           // Reactivate TFA if it was set to "deactivate TFA for next login"
           $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
           $stmt->execute(array(':user' => $user));
@@ -963,22 +967,19 @@ function check_login($user, $pass, $app_passwd_data = false) {
           unset($_SESSION['ldelay']);
           return "user";
         }
-      }
-    } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
-      if (array_key_exists("app_passwd_id", $row)){
-        if (verify_hash($row['password'], $pass) !== false) {
-          $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';
-          $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
-          $stmt->execute(array(
-            ':service' => $service,
-            ':app_id' => $row['app_passwd_id'],
-            ':username' => $user,
-            ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
-          ));
+      } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
+        // password is a app password
+        $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';
+        $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
+        $stmt->execute(array(
+          ':service' => $service,
+          ':app_id' => $row['app_passwd_id'],
+          ':username' => $user,
+          ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
+        ));
 
-          unset($_SESSION['ldelay']);
-          return "user";
-        }
+        unset($_SESSION['ldelay']);
+        return "user";
       }
     }
   }

+ 60 - 4
data/web/inc/prerequisites.inc.php

@@ -180,9 +180,65 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
 // Set language
 if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
   if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
-    $header_lang = strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
-    if (array_key_exists($header_lang, $AVAILABLE_LANGUAGES)) {
-      $_SESSION['mailcow_locale'] = $header_lang;
+    // regex inspired from @GabrielAnderson on http://stackoverflow.com/questions/6038236/http-accept-language
+    preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})*)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
+
+    $langs = $lang_parse[1];
+    $ranks = $lang_parse[4];
+
+    // (create an associative array 'language' => 'preference')
+    $lang2pref = array();
+    for ($i=0; $i<count($langs); $i++) {
+      $lang2pref[strtolower($langs[$i])] = (float) (!empty($ranks[$i]) ? $ranks[$i] : 1);
+    }
+
+    // (comparison function for uksort)
+    $cmpLangs = function ($a, $b) use ($lang2pref) {
+      if ($lang2pref[$a] > $lang2pref[$b])
+        return -1;
+      elseif ($lang2pref[$a] < $lang2pref[$b])
+        return 1;
+      elseif (strlen($a) > strlen($b))
+        return -1;
+      elseif (strlen($a) < strlen($b))
+        return 1;
+      else
+        return 0;
+    };
+
+    // sort the languages by prefered language and by the most specific region
+    uksort($lang2pref, $cmpLangs);
+
+    // generate language array without the region part
+    $AVAILABLE_BASE_LANGUAGES=array();
+    foreach ($AVAILABLE_LANGUAGES as $code => $lang) {
+      $base_code = substr($code, 0, 2);
+      if (!array_key_exists($base_code, $AVAILABLE_BASE_LANGUAGES)) {
+        $AVAILABLE_BASE_LANGUAGES[$base_code] = $code;
+      }
+    }
+
+    // Find a perfect match or partial match
+    // Match en-gb or en
+    foreach ($lang2pref as $lang => $q) {
+      if (array_key_exists($lang, $AVAILABLE_LANGUAGES)) {
+        $_SESSION['mailcow_locale'] = $lang;
+        break;
+      } elseif (array_key_exists($lang, $AVAILABLE_BASE_LANGUAGES)) {
+        $_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[$lang];
+        break;
+      }
+    }
+
+    // Try suggest match
+    // e.g. suggest en-gb when only en-us is provided
+    if (!isset($_COOKIE['mailcow_locale'])) {
+      foreach ($lang2pref as $lang => $q) {
+        if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) {
+          $_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)];
+          break;
+        }
+      }
     }
   }
   else {
@@ -200,7 +256,7 @@ if (isset($_GET['lang']) && array_key_exists($_GET['lang'], $AVAILABLE_LANGUAGES
 /*
  * load language
  */
-$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.json'), true);
+$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en-gb.json'), true);
 
 $langFile = $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.json';
 if(file_exists($langFile)) {

+ 25 - 22
data/web/inc/vars.inc.php

@@ -76,32 +76,35 @@ $autodiscover_config = array(
 $DETECT_LANGUAGE = true;
 
 // Change default language
-$DEFAULT_LANG = 'en';
+$DEFAULT_LANG = 'en-gb';
 
 // Available languages
 // https://www.iso.org/obp/ui/#search
-// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+// https://en.wikipedia.org/wiki/IETF_language_tag
 $AVAILABLE_LANGUAGES = array(
-  'cs' => 'Čeština (Czech)',
-  'da' => 'Danish (Dansk)',
-  'de' => 'Deutsch (German)',
-  'en' => 'English',
-  'es' => 'Español (Spanish)',
-  'fi' => 'Suomi (Finish)',
-  'fr' => 'Français (French)',
-  'hu' => 'Magyar (Hungarian)',
-  'it' => 'Italiano (Italian)',
-  'ko' => '한국어 (Korean)',
-  'lv' => 'latviešu (Latvian)',
-  'nl' => 'Nederlands (Dutch)',
-  'pl' => 'Język Polski (Polish)',
-  'pt' => 'Português (Portuguese)',
-  'ro' => 'Română (Romanian)',
-  'ru' => 'Pусский (Russian)',
-  'sk' => 'Slovenčina (Slovak)',
-  'sv' => 'Svenska (Swedish)',
-  'uk' => 'Українська (Ukrainian)',
-  'zh' => '中文 (Chinese)'
+  // 'ca-es' => 'Català (Catalan)',
+  'cs-cz' => 'Čeština (Czech)',
+  'da-dk' => 'Danish (Dansk)',
+  'de-de' => 'Deutsch (German)',
+  'en-gb' => 'English',
+  'es-es' => 'Español (Spanish)',
+  'fi-fi' => 'Suomi (Finish)',
+  'fr-fr' => 'Français (French)',
+  'hu-hu' => 'Magyar (Hungarian)',
+  'it-it' => 'Italiano (Italian)',
+  'ko-kr' => '한국어 (Korean)',
+  'lv-lv' => 'latviešu (Latvian)',
+  'nl-nl' => 'Nederlands (Dutch)',
+  'pl-pl' => 'Język Polski (Polish)',
+  'pt-pt' => 'Português (Portuguese)',
+  'ro-ro' => 'Română (Romanian)',
+  'ru-ru' => 'Pусский (Russian)',
+  'sk-sk' => 'Slovenčina (Slovak)',
+  'sv-se' => 'Svenska (Swedish)',
+  'tr-tr' => 'Türkçe (Turkish)',
+  'uk-ua' => 'Українська (Ukrainian)',
+  'zh-cn' => '简体中文 (Simplified Chinese)',
+  'zh-tw' => '繁體中文 (Traditional Chinese)',
 );
 
 // default theme is lumen

+ 0 - 0
data/web/lang/lang.ca.json → data/web/lang/lang.ca-es.json


+ 1 - 1
data/web/lang/lang.cs.json → data/web/lang/lang.cs-cz.json

@@ -830,7 +830,7 @@
         "confirm_delete": "Potvrdit smazání prvku.",
         "danger": "Nebezpečí",
         "deliver_inbox": "Doručit do schránky",
-        "disabled_by_config": "Funkce karanténa je momentálně vypnuta v nastavení systému.",
+        "disabled_by_config": "Funkce karanténa je momentálně vypnuta v nastavení systému. Nastavte, prosím, prvkům karantény hodnoty \"počet zadržených zpráv\" a \"maximální velikost\".",
         "download_eml": "Stáhnout (.eml)",
         "empty": "Žádné výsledky",
         "high_danger": "Vysoké nebezpečí",

+ 0 - 0
data/web/lang/lang.da.json → data/web/lang/lang.da-dk.json


+ 0 - 0
data/web/lang/lang.de.json → data/web/lang/lang.de-de.json


+ 0 - 0
data/web/lang/lang.en.json → data/web/lang/lang.en-gb.json


+ 0 - 0
data/web/lang/lang.es.json → data/web/lang/lang.es-es.json


+ 0 - 0
data/web/lang/lang.fi.json → data/web/lang/lang.fi-fi.json


+ 5 - 2
data/web/lang/lang.fr.json → data/web/lang/lang.fr-fr.json

@@ -102,7 +102,8 @@
         "timeout2": "Délai d'expiration pour la connexion à l'hôte local",
         "username": "Nom d'utilisateur",
         "validate": "Valider",
-        "validation_success": "Validation réussie"
+        "validation_success": "Validation réussie",
+        "bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici."
     },
     "admin": {
         "access": "Accès",
@@ -313,7 +314,9 @@
         "yes": "&#10003;",
         "api_read_write": "Accès Lecture-Écriture",
         "oauth2_add_client": "Ajouter un client OAuth2",
-        "password_policy": "Politique de mots de passe"
+        "password_policy": "Politique de mots de passe",
+        "admins": "Administrateurs",
+        "api_read_only": "Accès lecture-seule"
     },
     "danger": {
         "access_denied": "Accès refusé ou données de formulaire non valides",

+ 0 - 0
data/web/lang/lang.hu.json → data/web/lang/lang.hu-hu.json


+ 2 - 1
data/web/lang/lang.it.json → data/web/lang/lang.it-it.json

@@ -975,7 +975,8 @@
         "verified_fido2_login": "Verified FIDO2 login",
         "verified_totp_login": "Verified TOTP login",
         "verified_webauthn_login": "Verified WebAuthn login",
-        "verified_yotp_login": "Verified Yubico OTP login"
+        "verified_yotp_login": "Verified Yubico OTP login",
+        "domain_add_dkim_available": "Esisteva già una chiave DKIM"
     },
     "tfa": {
         "api_register": "%s usa le API Yubico Cloud. Richiedi una chiave API <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">qui</a>",

+ 0 - 0
data/web/lang/lang.ko.json → data/web/lang/lang.ko-kr.json


+ 0 - 0
data/web/lang/lang.lv.json → data/web/lang/lang.lv-lv.json


+ 0 - 0
data/web/lang/lang.nl.json → data/web/lang/lang.nl-nl.json


+ 0 - 0
data/web/lang/lang.pl.json → data/web/lang/lang.pl-pl.json


+ 0 - 0
data/web/lang/lang.pt.json → data/web/lang/lang.pt-pt.json


+ 3 - 2
data/web/lang/lang.ro.json → data/web/lang/lang.ro-ro.json

@@ -981,7 +981,8 @@
         "verified_totp_login": "Autentificarea TOTP verificată",
         "verified_webauthn_login": "Autentificarea WebAuthn verificată",
         "verified_fido2_login": "Conectare FIDO2 verificată",
-        "verified_yotp_login": "Autentificarea Yubico OTP verificată"
+        "verified_yotp_login": "Autentificarea Yubico OTP verificată",
+        "domain_add_dkim_available": "O cheie DKIM deja a existat"
     },
     "tfa": {
         "api_register": "%s utilizează API-ul Yubico Cloud. Obțineți o cheie API pentru cheia dvs. de <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">aici</a>",
@@ -992,7 +993,7 @@
         "enter_qr_code": "Codul tău TOTP dacă dispozitivul tău nu poate scana codurile QR",
         "error_code": "Cod de eroare",
         "init_webauthn": "Inițializare, vă rugăm așteptați...",
-        "key_id": "Un identificator pentru YubiKey",
+        "key_id": "Un identificator pentru dispozitiv",
         "key_id_totp": "Un identificator pentru cheia ta",
         "none": "Dezactivează",
         "reload_retry": "- (reîncărcați browserul dacă eroarea persistă)",

+ 0 - 0
data/web/lang/lang.ru.json → data/web/lang/lang.ru-ru.json


+ 0 - 0
data/web/lang/lang.sk.json → data/web/lang/lang.sk-sk.json


+ 0 - 0
data/web/lang/lang.sv.json → data/web/lang/lang.sv-se.json


+ 86 - 0
data/web/lang/lang.tr.json

@@ -0,0 +1,86 @@
+{
+    "acl": {
+        "alias_domains": "Takma alan adı ekle",
+        "app_passwds": "Uygulama şifrelerini yönet",
+        "delimiter_action": "Sınırlama işlemi",
+        "domain_relayhost": "Bir alan adı için relayhost sunucusunu değiştir",
+        "eas_reset": "EAS cihazlarını sıfırla",
+        "mailbox_relayhost": "Bir posta kutusunun relayhost sunucularını değiştir",
+        "pushover": "Bildirim",
+        "quarantine": "Karantina işlemleri",
+        "quarantine_attachments": "Ekleri karantinaya al",
+        "quarantine_notification": "Karantina bildirimlerini değiştir",
+        "smtp_ip_access": "SMTP sunucularının değiştirilmesine izin ver",
+        "sogo_access": "SOGo erişiminin yönetilmesine izin ver",
+        "domain_desc": "Alan adı açıklamasını değiştir",
+        "extend_sender_acl": "Gönderenin acl'sini harici adreslere göre genişletmeye izin ver",
+        "spam_policy": "Engellenenler / İzin verilenler",
+        "filters": "Fitreler"
+    },
+    "add": {
+        "activate_filter_warn": "Aktif edilirse diğer tüm filtreler devre dışı bırakılacak.",
+        "add_domain_only": "Sadece alan adı ekle",
+        "alias_address": "Takma ad adres(leri)",
+        "alias_domain": "Takma alan adı",
+        "alias_domain_info": "<small>Sadece geçerli alan adları (virgülle ayırın).</small>",
+        "backup_mx_options": "İletme ayarları",
+        "delete2": "Kaynakta olmayan hedefteki mesajları sil",
+        "delete2duplicates": "Hedefteki kopyaları sil",
+        "disable_login": "Giriş yapmaya izin verme ( Gelen mailler yine de kabul edilir)",
+        "domain": "Alan adı",
+        "domain_matches_hostname": "Alan adı %s ana bilgisayar adıyla eşleşiyor",
+        "add_domain_restart": "Alan adı ekleyin ve SOGo'yu yeniden başlatın",
+        "alias_address_info": "<small>Bir alan adına ilişkin tüm iletileri yakalamak için tam e-posta adresi veya @example.com olacak şeklinde girin (virgülle ayırın).<b>sadece mailcow alan adları</b>.</small>",
+        "domain_quota_m": "Toplam alan adı kotası (MiB)",
+        "generate": "oluştur",
+        "goto_ham": "Ham olarak<span class=\"text-success\"><b>işaretle</b></span>",
+        "goto_null": "Postaları sessizce çöpe at",
+        "goto_spam": "Spam olarak<span class=\"text-danger\"><b>işaretle</b></span>",
+        "hostname": "Ana sunucu",
+        "kind": "Tür",
+        "mailbox_quota_m": "Posta kutusu başına maksimum kota (MiB)",
+        "max_aliases": "Maksimum olası takma adı",
+        "max_mailboxes": "Maksimum olası posta kutusu",
+        "nexthop": "Sonraki atlama",
+        "port": "Port",
+        "public_comment": "Genel yorum",
+        "relay_all": "Tüm alıcılara ilet",
+        "relay_all_info": "Eğer <b>hiçbir</b> alıcıya iletilmemesini seçerseniz, aktarılması gereken her alıcı için bir (\"kör\") posta kutusu eklemeniz gerekecektir.",
+        "relay_domain": "Bu alan adını ilet",
+        "relay_transport_info": "<div class=\"label label-info\">Bilgi</div> Bu etki alanı için özel bir hedef için aktarım eşlemeleri tanımlayabilirsiniz. Ayarlanmazsa, bir MX araması yapılacaktır.",
+        "relay_unknown_only": "Yalnızca mevcut olmayan posta kutularını ilet. Mevcut posta kutuları yerel olarak teslim edilecektir.",
+        "relayhost_wrapped_tls_info": "Lütfen TLS ile örtülmüş portları <b> kullanmayın</b> (çoğu 465 portunda çalışır).<br>\nÖrtülmemiş port kullan ve STARTTLS üzerinden yayınla. TLS'yi zorlamak için bir TLS ilkesi \"TLS ilke eşlemeleri\" sayfası içinde oluşturulabilir.",
+        "skipcrossduplicates": "Klasörler arasında yinelenen mesajları atlayın (ilk mesaj seçilir)",
+        "target_address": "Adreslere git",
+        "target_address_info": "<small>Tam e-posta adres(leri) girin ( virgülle ayırın).</small>",
+        "target_domain": "Hedef alan adı",
+        "timeout1": "Uzak ana bilgisayara bağlantısı zaman aşımına uğradı",
+        "timeout2": "Yerel ana bilgisayara bağlantı zaman aşımına uğradı"
+    },
+    "admin": {
+        "action": "İşlem",
+        "add_forwarding_host": "Yönlendirme sunucusu ekle",
+        "add_transport": "İletim ekle",
+        "admin_details": "Yönetici detaylarını düzenle",
+        "admin_domains": "Alan adı atamaları",
+        "add_domain_admin": "Alan adı yöneticisi ekle",
+        "api_info": "API üzerinde çalışmalar devam etmektedir. Belgeler <a href=\"/api\">/api</a>adresinde bulunabilir",
+        "apps_name": "\"mailcow Uygulamaları\" adı",
+        "authed_user": "Yetkili kullanıcı",
+        "ban_list_info": "Aşağıdaki yasaklı IP'lerin listesine bakın: <b>ağ (kalan yasak süresi) - [işlemler]</b>.<br />Yasağı kaldırılmak üzere sıraya alınan IP'ler birkaç saniye içinde aktif yasak listesinden kaldırılacaktır.<br />Kırmızı etiketler, kara listeye alınarak aktif kalıcı yasakları gösterir.",
+        "configuration": "Yapılandırma",
+        "dkim_from_title": "Verilerin kopyalanacağı kaynak alan adı",
+        "dkim_to": "Kime",
+        "dkim_to_title": "Hedef alan ad(ları) üzerinde yazılacak",
+        "dkim_domains_wo_keys": "Eksik anahtarları olan alan adlarını seçin",
+        "domain": "Alan adı",
+        "domain_admin": "Alan adı yöneticisi",
+        "domain_admins": "Alan adı yöneticileri",
+        "domain_s": "Alan ad(ları)",
+        "duplicate": "Çift",
+        "duplicate_dkim": "Çift DKIM kayıtları",
+        "f2b_ban_time": "Yasaklama süresi (saniye)",
+        "f2b_max_attempts": "Maksimum giriş denemesi",
+        "f2b_retry_window": "Maksimum girişim için deneme pencere(leri)"
+    }
+}

+ 0 - 0
data/web/lang/lang.uk.json → data/web/lang/lang.uk-ua.json


+ 0 - 0
data/web/lang/lang.zh.json → data/web/lang/lang.zh-cn.json


+ 1187 - 0
data/web/lang/lang.zh-tw.json

@@ -0,0 +1,1187 @@
+{
+    "acl": {
+        "alias_domains": "新增域名別名",
+        "app_passwds": "管理應用程式密碼",
+        "bcc_maps": "密件副本規則表",
+        "delimiter_action": "郵件地址標籤選項",
+        "domain_desc": "更改域名描述",
+        "domain_relayhost": "更改域名的中繼主機",
+        "eas_reset": "重設 Exchange ActiveSync 裝置",
+        "extend_sender_acl": "可使用外部地址擴充存取權限表",
+        "filters": "過濾器",
+        "login_as": "以信箱使用者登入",
+        "mailbox_relayhost": "更改信箱的中繼主機",
+        "prohibited": "存取權限表禁止這項操作",
+        "protocol_access": "更改存取協定",
+        "pushover": "Pushover",
+        "quarantine": "隔離選項",
+        "quarantine_attachments": "隔離附件",
+        "quarantine_category": "更改隔離通知類別",
+        "quarantine_notification": "更改隔離通知",
+        "ratelimit": "速率限制",
+        "recipient_maps": "收件人規則表",
+        "smtp_ip_access": "更改允許 SMTP 的主機",
+        "sogo_access": "管理 SOGo 存取權",
+        "sogo_profile_reset": "重設 SOGo 個人資料",
+        "spam_alias": "臨時別名",
+        "spam_policy": "黑名單/白名單",
+        "spam_score": "垃圾郵件分數",
+        "syncjobs": "同步任務",
+        "tls_policy": "TLS 規則",
+        "unlimited_quota": "信箱容量配額無上限"
+    },
+    "add": {
+        "activate_filter_warn": "當\"啟用\"被勾選,所有其他過濾器都會被消除。",
+        "active": "啟用",
+        "add": "新增",
+        "add_domain_only": "只新增域名",
+        "add_domain_restart": "新增域名並重新啟動",
+        "alias_address": "地址別名",
+        "alias_address_info": "<small>完整郵件地址,或使用 @example.com 來收取此域名下所有郵件地址的信件 (以英文逗號分隔多個地址)。 <b>只允許此 mailcow 中的域名</b>。</small>",
+        "alias_domain": "域名別名",
+        "alias_domain_info": "<small>僅允許有效的域名 (以英文逗號分隔多個地址)</small>",
+        "app_name": "應用程式名稱",
+        "app_password": "新增應用程式密碼",
+        "app_passwd_protocols": "此應用程式密碼所允許的協定",
+        "automap": "自動歸類資料夾規則表 (如:\"已發送\", \"Sent\" => \"Sent\" 等...)",
+        "backup_mx_options": "中繼選項",
+        "bcc_dest_format": "密件副本地址必須是單一的有效地址。<br>如果你需要傳送到多個地址,請幫它們建立一個別名然後設定於此欄。",
+        "comment_info": "隱密備註不會被使用者看到,公開備註則會在使用者游標懸停於概述頁時顯示於提示框",
+        "custom_params": "訂製參數",
+        "custom_params_hint": "正確的用法:--param=xy,錯誤的用法:--param xy",
+        "delete1": "完成後將來源郵件刪除",
+        "delete2": "刪除目的地信箱中存在但來源信箱中不存在的郵件",
+        "delete2duplicates": "刪除目的地信箱中的重複郵件",
+        "description": "描述",
+        "destination": "目的地信箱",
+        "disable_login": "不允許登入 (但郵件仍然會正常接收)",
+        "domain": "域名",
+        "domain_matches_hostname": "域名 %s 與主機名稱匹配",
+        "domain_quota_m": "域名總容量配額 (MiB)",
+        "enc_method": "加密方式",
+        "exclude": "拒絕物件 (regex)",
+        "full_name": "全名",
+        "gal": "全域地址清單",
+        "gal_info": "<b>全域地址清單</b>包含了域名下所有的物件,且它們不能被使用者更改。如果關閉,使用者的 閒置/忙碌中 狀態將無法在 SOGo 中顯示。 <b>需重新啟動 SOGo 來更改。</b>",
+        "generate": "生成",
+        "goto_ham": "歸類為 <span class=\"text-success\"><b>非垃圾郵件</b></span>",
+        "goto_null": "無聲的刪除郵件",
+        "goto_spam": "歸類為 <span class=\"text-danger\"><b>垃圾郵件</b></span>",
+        "hostname": "主機",
+        "inactive": "停用",
+        "kind": "種類",
+        "mailbox_quota_def": "預設信箱容量配額",
+        "mailbox_quota_m": "信箱容量配額上限 (MiB)",
+        "mailbox_username": "使用者名稱 (郵件地址的左側部分)",
+        "max_aliases": "地址別名上限",
+        "max_mailboxes": "信箱數量上限",
+        "mins_interval": "輪詢間隔 (分鐘)",
+        "multiple_bookings": "允許重複預約",
+        "nexthop": "下一個跳躍點",
+        "password": "密碼",
+        "password_repeat": "密碼確認 (重復輸入)",
+        "port": "通訊埠",
+        "post_domain_add": "在新增新域名後 SOGo 的容器 \"sogo-mailcow\" 需要重新啟動。<br><br>另外,請檢查域名的 DNS 設定。當 DNS 設定生效,重新啟動 \"acme-mailcow\" 容器來自動幫你的新域名生成憑證 (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;)。<br>可省略此步驟,憑證生成的機制每 24 小時會自動執行。",
+        "private_comment": "隱密備註",
+        "public_comment": "公開備註",
+        "quota_mb": "容量配額 (MiB)",
+        "relay_all": "中繼所有收件人",
+        "relay_all_info": "↪ 如果選擇<b>不</b>中繼所有收件人,你會需要幫每個應該中繼的收件人新增一個 (\"盲\") 信箱。",
+        "relay_domain": "中繼此域名",
+        "relay_transport_info": "<div class=\"label label-info\">資訊</div> 你可以為此域名定義傳輸規則以自訂目的地,如果留空則會遵照 MX 紀錄。",
+        "relay_unknown_only": "只為不存在的信箱地址中繼。已存在的信箱地址則在本區域遞送。",
+        "relayhost_wrapped_tls_info": "請 <b>不要</b> 使用\"已包裝 TLS\"的通訊埠 (大多為通訊埠 465).<br>\r\n使用其他\"未包裝\"的通訊埠發起 STARTTLS. 你可以在\"TLS 規則表\"中新增強制使用 TLS 的規則。",
+        "select": "請選擇...",
+        "select_domain": "請先選擇一個域名",
+        "sieve_desc": "簡短描述",
+        "sieve_type": "過濾器類型",
+        "skipcrossduplicates": "跳過在其他資料夾中重複的郵件 (優先使用先找到的郵件)",
+        "subscribeall": "訂閱所有資料夾",
+        "syncjob": "新增同步任務",
+        "syncjob_hint": "請注意此密碼會以明文儲存!",
+        "tags": "標籤",
+        "target_address": "目標地址",
+        "target_address_info": "<small>完整的信箱地址 (以英文逗號分隔多個地址)。</small>",
+        "target_domain": "目標域名",
+        "timeout1": "遠端主機連線逾時時間",
+        "timeout2": "本地主機連線逾時時間",
+        "username": "使用者名稱",
+        "validate": "驗證",
+        "validation_success": "驗證成功"
+    },
+    "admin": {
+        "access": "存取",
+        "action": "動作",
+        "activate_api": "啟用 API",
+        "activate_send": "啟用發送按鈕",
+        "active": "啟用",
+        "active_rspamd_settings_map": "啟用設定規則",
+        "add": "新增",
+        "add_admin": "新增管理員",
+        "add_domain_admin": "新增域名管理員",
+        "add_forwarding_host": "新增轉發主機",
+        "add_relayhost": "新增中繼傳輸主機",
+        "add_relayhost_hint": "請注意,任何認證資料都將以明文儲存。",
+        "add_row": "新增列",
+        "add_settings_rule": "新增設定規則",
+        "add_transport": "新增傳輸規則",
+        "add_transports_hint": "請注意,任何認證資料都將以明文儲存。",
+        "additional_rows": "額外列已新增",
+        "admin": "管理員",
+        "admin_details": "編輯管理員詳情",
+        "admin_domains": "分配域名",
+        "admins": "管理員",
+        "admins_ldap": "LDAP 管理員",
+        "advanced_settings": "進階設定",
+        "api_allow_from": "允許來自這些 IP/CIDR 網路的 API 存取",
+        "api_info": "API 功能仍在實作中。你可以在<a href=\"/api\">/api</a>找到 API 文檔",
+        "api_key": "API 金鑰",
+        "api_read_only": "僅限讀取",
+        "api_read_write": "可讀可寫",
+        "api_skip_ip_check": "在 API 中跳過 IP 檢查",
+        "app_links": "應用程式連結",
+        "app_name": "應用程式名稱",
+        "apps_name": "\"mailcow 應用程式\" 名稱",
+        "arrival_time": "送達時間 (伺服器)",
+        "authed_user": "已認證使用者",
+        "ays": "確定操作?",
+        "ban_list_info": "以下為封鎖的 IP 清單: <b>網路 (剩餘解除封鎖時間) - [動作]</b>。<br />解除封鎖的 IP 將會在幾秒之內從封鎖清單中移除<br />紅色標籤代表因出現在黑名單上而永久封鎖",
+        "change_logo": "更改 logo",
+        "configuration": "組態",
+        "convert_html_to_text": "將 HTML 轉換為純文字",
+        "credentials_transport_warning": "<b>警告</b>: 新增新的傳輸規則會同時更新所有\"下一跳\"欄中符合項目的認證憑證。",
+        "customer_id": "客戶 ID",
+        "customize": "自訂",
+        "delete_queue": "刪除全部",
+        "destination": "目標地址",
+        "dkim_add_key": "新增 ARC/DKIM 金鑰",
+        "dkim_domains_selector": "選擇器",
+        "dkim_domains_wo_keys": "選擇沒有金鑰的域名",
+        "dkim_from": "從",
+        "dkim_from_title": "獲取資料的來源域名",
+        "dkim_key_length": "DKIM 金鑰長度 (bits)",
+        "dkim_key_missing": "金鑰遺失",
+        "dkim_key_unused": "金鑰未使用",
+        "dkim_key_valid": "有效的金鑰",
+        "dkim_keys": "ARC/DKIM 金鑰",
+        "dkim_overwrite_key": "覆蓋既有的 DKIM 金鑰",
+        "dkim_private_key": "私密金鑰",
+        "dkim_to": "到",
+        "dkim_to_title": "要覆蓋的目標域名",
+        "domain": "域名",
+        "domain_admin": "域名管理員",
+        "domain_admins": "域名管理員",
+        "domain_s": "域名",
+        "duplicate": "複製",
+        "duplicate_dkim": "複製 DKIM 紀錄",
+        "edit": "編輯",
+        "empty": "沒有結果",
+        "excludes": "排除這些收件者",
+        "f2b_ban_time": "封鎖時間 (秒)",
+        "f2b_blacklist": "網路/主機黑名單",
+        "f2b_filter": "正規表示式過濾器",
+        "f2b_list_info": "黑名單優先於白名單。 <b>清單更新需要幾秒才能套用。</b>",
+        "f2b_max_attempts": "嘗試次數上限",
+        "f2b_netban_ipv4": "封鎖的 IPv4 子網路大小 (8-32)",
+        "f2b_netban_ipv6": "封鎖的 IPv6 子網路大小 (8-128)",
+        "f2b_parameters": "Fail2ban 參數",
+        "f2b_regex_info": "納入過濾參考的應用程式紀錄: SOGo、Postfix、Dovecot、PHP-FPM。",
+        "f2b_retry_window": "重試次數的時間區間大小 (秒)",
+        "f2b_whitelist": "網路/主機白名單",
+        "filter_table": "篩選表格",
+        "flush_queue": "清空佇列",
+        "forwarding_hosts": "轉發主機",
+        "forwarding_hosts_add_hint": "你可以指定 IPv4/IPv6 地址、CIDR 表示的網路、主機名稱 (會被自動解析為 IP 地址),或者信箱域名 (會自動查詢 SPF 紀錄或 MX 紀錄並解析為 IP 地址)。",
+        "forwarding_hosts_hint": "來自此處所列的主機的郵件會被無條件接收。而且這些主機不會經過 DNSBL 或灰名單的檢查。來自它們的垃圾郵件不會被拒絕,但可以設定被歸入垃圾郵件資料夾。這個欄位經常用於設定轉寄信件至 mailcow 伺服器的郵件伺服器。",
+        "from": "來自",
+        "generate": "生成",
+        "guid": "GUID - 唯一實例 ID",
+        "guid_and_license": "GUID 和許可證",
+        "hash_remove_info": "移除速率限制雜湊 (如果存在的話) 會重設它的計數器。<br>\r\n每個雜湊會有各自的顏色表示。",
+        "help_text": "覆蓋登入面板下的幫助文字 (可使用 HTML)",
+        "host": "主機",
+        "html": "HTML",
+        "import": "匯入",
+        "import_private_key": "匯入私鑰",
+        "in_use_by": "被...使用中",
+        "inactive": "停用",
+        "include_exclude": "包括/排除",
+        "include_exclude_info": "如果沒有選擇時,預設包括<b>所有信箱</b>",
+        "includes": "包括這些收件人",
+        "is_mx_based": "MX 為準的",
+        "last_applied": "最後套用的",
+        "license_info": "證書不是強制的,但購買證書可以幫助此專案的後續開發。<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">在這裡註冊你的 GUID</a> 或者也可以 <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">為你的 mailcow 購買支援服務。</a>",
+        "link": "連結",
+        "loading": "請稍等...",
+        "login_time": "登入時間",
+        "logo_info": "你的起始頁面圖片會在頂部導覽列的限制下被縮放為 40px 高,以及最大 250px 高度。強烈推薦使用能較好縮放的圖片。",
+        "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
+        "main_name": "\"mailcow UI\" 名稱",
+        "merged_vars_hint": "灰色列來自 <code>vars.(local.)inc.php</code> 並且不能修改。",
+        "message": "訊息",
+        "message_size": "訊息大小",
+        "nexthop": "下一跳",
+        "no": "&#10005;",
+        "no_active_bans": "沒有啟用中的封鎖項目",
+        "no_new_rows": "沒有更多資料了",
+        "no_record": "沒有紀錄",
+        "oauth2_apps": "OAuth2 應用程式",
+        "oauth2_add_client": "新增 OAuth2 用戶端",
+        "oauth2_client_id": "用戶端 ID",
+        "oauth2_client_secret": "用戶端密碼",
+        "oauth2_info": "本 OAuth2 機制支援 \"Authorization Code\" 的授權方式並且會生成 refresh token。<br>\r\n並且伺服器會自動在 refresh token 被使用後再生成一個新的 refresh token。<br><br>\r\n&#8226; 預設的授權範圍是<i>基本資料頁面</i>。只有信箱使用者能夠使用 OAuth2 來認證。如果省略授權範圍參數則會使用<i>基本資料頁面</i>。<br>\r\n&#8226; 用戶端必須在認證請求中包含<i>state</i>參數。<br><br>\r\nOAuth2 API 路徑:<br>\r\n<ul>\r\n  <li>Authorization endpoint: <code>/oauth/authorize</code></li>\r\n<li>Token endpoint: <code>/oauth/token</code></li>\r\n<li>Resource page:  <code>/oauth/profile</code></li>\r\n</ul>\r\n重新生成用戶端密碼不會使已存在的 authorization code 過期,但是會在它們換新權杖時失敗。<br><br>\r\n撤銷用戶端權杖則會立即終止所有連線階段。所有的用戶端都需要重新認證。",
+        "oauth2_redirect_uri": "重新導向 URI",
+        "oauth2_renew_secret": "生成新的用戶端密碼",
+        "oauth2_revoke_tokens": "撤銷所有用戶端 token",
+        "optional": "可不選",
+        "password": "密碼",
+        "password_length": "密碼長度",
+        "password_policy": "密碼規範",
+        "password_policy_chars": "必須包含至少一個英文字母",
+        "password_policy_length": "密碼長度至少要有 %d 個字元",
+        "password_policy_lowerupper": "必須包含英文大小寫字母",
+        "password_policy_numbers": "必須包含至少一個數字",
+        "password_policy_special_chars": "必須包含特殊符號",
+        "password_repeat": "密碼確認 (再輸入一次)",
+        "priority": "權重",
+        "private_key": "私鑰",
+        "quarantine": "隔離",
+        "quarantine_bcc": "發送一份包含所有郵件的副本 (以密件副本的方式) 給這個收件人:<br><small>留空以停用此功能。<b>此郵件不會簽名也不會檢查,故只應該在內部遞送。</b></small>",
+        "quarantine_exclude_domains": "排除的域名和域名別名",
+        "quarantine_max_age": "最長保留天數<br><small>必須大於或等於 1 天。</small>",
+        "quarantine_max_score": "如果評分高於分數就不通知:<br><small>預設為 9999.0</small>",
+        "quarantine_max_size": "附件大小上限 (單位 MiB,超過會被丟棄):<br><small>0 <b>不</b>表示無上限。</small>",
+        "quarantine_notification_html": "通知信模版:<br><small>留空來重設為預設模版。</small>",
+        "quarantine_notification_sender": "通知信寄件人",
+        "quarantine_notification_subject": "通知信主旨",
+        "quarantine_redirect": "<b>轉發所有通知信</b>給這個收件人:<br><small>留空以停用此功能。<b>此郵件不會簽名也不會檢查,故只應該在內部遞送。</b></small>",
+        "quarantine_release_format": "放行項目的格式",
+        "quarantine_release_format_att": "如附件",
+        "quarantine_release_format_raw": "未修改的原始信件",
+        "quarantine_retention_size": "每個信箱的隔離保留上限:<br><small>0 表示 <b>停用</b>。</small>",
+        "queue_ays": "請確定是否要刪除目前佇列中的所有項目。",
+        "queue_deliver_mail": "遞送",
+        "queue_hold_mail": "暫停",
+        "queue_manager": "佇列管理",
+        "queue_show_message": "顯示訊息",
+        "queue_unban": "佇列解除封鎖",
+        "queue_unhold_mail": "繼續",
+        "quota_notification_html": "通知信模版:<br><small>留空來重設為預設模版。</small>",
+        "quota_notification_sender": "通知信寄件人",
+        "quota_notification_subject": "通知信主旨",
+        "quota_notifications": "容量配額通知",
+        "quota_notifications_info": "容量配額通知會在使用者使用超過 80% 和 95% 時各發送一次。",
+        "quota_notifications_vars": "{{percent}} 表示目前使用者的用量<br>{{username}} 為信箱名稱",
+        "r_active": "啟用的限制規則",
+        "r_inactive": "停用的限制規則",
+        "r_info": "啟用的限制規則清單中灰色的/停用的項目是 mailcow 無法識別且不可移動的。未知的限制規則將會被設定為原來的順序。<br>你可以在 <code>inc/vars.local.inc.php</code> 中新增新元素然後勾選它們。",
+        "rate_name": "頻率名稱",
+        "recipients": "收件人",
+        "refresh": "重新整理",
+        "regen_api_key": "重新生成 API 金鑰",
+        "regex_maps": "正規表示式規則",
+        "relay_from": "\"發送自:\" 地址",
+        "relay_rcpt": "\"發送到:\" 地址",
+        "relay_run": "測試",
+        "relayhosts": "中繼傳輸",
+        "relayhosts_hint": "定義的中繼傳輸可以在域名設定中選擇。<br>\r\n中繼傳輸服務固定使用 \"smtp:\" 並且會在可以時嘗試 TLS。不支援包裝的 TLS (SMTPS)。使用者的連外 TLS 規則會被納入。<br>\r\n對選中的域名和域名別名都有效。",
+        "remove": "刪除",
+        "remove_row": "刪除列",
+        "reset_default": "重設為預設值",
+        "reset_limit": "移除雜湊",
+        "routing": "路由",
+        "rsetting_add_rule": "新增規則",
+        "rsetting_content": "規則內容",
+        "rsetting_desc": "簡短描述",
+        "rsetting_no_selection": "請選擇一個規則",
+        "rsetting_none": "沒有可用的規則",
+        "rsettings_insert_preset": "插入範例預設 \"%s\"",
+        "rsettings_preset_1": "為已認證使用者關閉 DKIM 和速率限制外的所有規則",
+        "rsettings_preset_2": "管理員 (postmaster) 想要垃圾郵件",
+        "rsettings_preset_3": "只允許指定的寄件人 (如只允許內部信箱)",
+        "rsettings_preset_4": "為域名停用 Rspamd",
+        "rspamd_com_settings": "設定名稱會被自動生成,請看下方的預設範例。查看 <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a> 以瞭解更多細節。",
+        "rspamd_global_filters": "全域過濾規則",
+        "rspamd_global_filters_agree": "我會小心謹慎的!",
+        "rspamd_global_filters_info": "全域過濾規則包含了不同種類的全域黑名單和白名單。",
+        "rspamd_global_filters_regex": "用途如其名稱。所有內容都必須是 \"/pattern/options\" 格式的有效表達式 (如 <code>/.+@domain\\.tld/i</code>)。<br>\r\n雖然系統會對正規表示式執行初步檢查,但 Rspamd 的功能仍可能壞掉,如果他沒有運作,請再檢查正規表示式。<br>\r\n  Rspamd 會在規則更改後讀取其內容。如果你遇到了問題,<a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">重新啟動 Rspamd</a> 來強制重新載入規則。<br>黑名單中的項目會被排除在隔離系統外。",
+        "rspamd_settings_map": "Rspamd 設定規則",
+        "sal_level": "Moo 等級",
+        "save": "儲存更改",
+        "search_domain_da": "搜尋域名",
+        "send": "發送",
+        "sender": "寄件人",
+        "service": "服務",
+        "service_id": "服務 ID",
+        "source": "來源",
+        "spamfilter": "垃圾郵件過濾器",
+        "subject": "主旨",
+        "success": "成功",
+        "sys_mails": "系統郵件",
+        "text": "文本",
+        "time": "時間",
+        "title": "標題",
+        "title_name": "\"mailcow UI\" 網站標題",
+        "to_top": "返回頂部",
+        "transport_dest_format": "正規表示式格式: example.org, .example.org, *, box@example.org (多個地址可用英文逗號分隔表示)",
+        "transport_maps": "傳輸規則",
+        "transport_test_rcpt_info": "&#8226; 使用 null@hosted.mailcow.de 來測試發送到外部地址的中繼傳輸。",
+        "transports_hint": "&#8226; 傳輸規則<b>優先於</b>中繼傳輸規則</b>。<br>\r\n&#8226; MX 的傳輸規則會優先被使用。<br>\r\n&#8226; 使用者個別的連外 TLS 規則設定會被忽略,只會執行域名的 TLS 規則。<br>\r\n&#8226; 傳輸服務固定使用 \"smtp:\" 並且會在可以時嘗試 TLS。不支援包裝的 TLS (SMTPS)。\r\n&#8226; 匹配 \"/localhost$/\" 的地址會透過 \"local:\" 傳輸,但是 \"*\" 不會匹配這些本地地址。<br>\r\n&#8226; 為了決定下一跳 \"[host]:25\" 的認證資訊,因此 Postfix <b>會</b>優先查詢 \"host\" 而不是 \"[host]:25\"。也因此 \"host\" 和 \"[host]:25\" 不能同時使用。",
+        "ui_footer": "頁面註腳 (可使用 HTML)",
+        "ui_header_announcement": "公告",
+        "ui_header_announcement_active": "啟用公告",
+        "ui_header_announcement_content": "文本 (可使用 HTML)",
+        "ui_header_announcement_help": "公告會顯示於 UI 登入畫面和使用者登入後頁面顯示。",
+        "ui_header_announcement_select": "選擇公告類型",
+        "ui_header_announcement_type": "類型",
+        "ui_header_announcement_type_danger": "非常重要",
+        "ui_header_announcement_type_info": "訊息",
+        "ui_header_announcement_type_warning": "重要",
+        "ui_texts": "UI 標籤和文本",
+        "unban_pending": "等待解除封鎖",
+        "unchanged_if_empty": "如果不更改請留空",
+        "upload": "上傳",
+        "username": "使用者名稱",
+        "validate_license_now": "與證書伺服器驗證 GUID",
+        "verify": "驗證",
+        "yes": "&#10003;"
+    },
+    "danger": {
+        "access_denied": "存取拒絕或表單資料有誤",
+        "alias_domain_invalid": "無效的域名別名 %s",
+        "alias_empty": "地址別名為必填",
+        "alias_goto_identical": "別名不能和目的地地址相同",
+        "alias_invalid": "無效的地址別名 %s",
+        "aliasd_targetd_identical": "域名別名不能和目標域名相同: %s",
+        "aliases_in_use": "最大別名數必須大於等於 %d",
+        "app_name_empty": "應用程式名稱為必填",
+        "app_passwd_id_invalid": "無效的應用程式密碼 ID %s",
+        "bcc_empty": "密件副本目標地址為必填",
+        "bcc_exists": "%s 類型的密件副本規則表 %s 已存在",
+        "bcc_must_be_email": "密件副本目標地址 %s 不是有效的信箱地址",
+        "comment_too_long": "備註僅允許 160 個字元",
+        "defquota_empty": "每個信箱的預設容量配額不得為 0。",
+        "description_invalid": "%s 的資源描述無效",
+        "dkim_domain_or_sel_exists": "\"%s\"的 DKIM 金鑰已存在,因此不會被覆蓋",
+        "dkim_domain_or_sel_invalid": "無效的 DKIM 域名或選擇器: %s",
+        "domain_cannot_match_hostname": "域名與主機名稱不合",
+        "domain_exists": "域名 %s 已存在",
+        "domain_invalid": "無效的域名地址或未填",
+        "domain_not_empty": "不能刪除不是空的的域名 %s",
+        "domain_not_found": "找不到域名 %s",
+        "domain_quota_m_in_use": "域名容量配額必須大於等於 %s MiB",
+        "extra_acl_invalid": "無效的外部寄件人地址 \"%s\"",
+        "extra_acl_invalid_domain": "外部寄件人地址 \"%s\" 使用了無效的域名",
+        "fido2_verification_failed": "FIDO2 驗證失敗: %s",
+        "file_open_error": "檔案無法開啟以供寫入",
+        "filter_type": "過濾器類型錯誤",
+        "from_invalid": "寄件人地址為必填",
+        "global_filter_write_error": "不能寫入過濾器檔案: %s",
+        "global_map_invalid": "無效的全域規則 ID %s",
+        "global_map_write_error": "無法寫入全域規則 ID %s: %s",
+        "goto_empty": "一個別名地址必須包含至少一個有效的目標地址",
+        "goto_invalid": "無效的目標地址 %s",
+        "ham_learn_error": "學習非垃圾信件時發生錯誤: %s",
+        "imagick_exception": "錯誤: 讀取圖片時 Imagick 發生了意外",
+        "img_invalid": "不能驗證圖片檔案",
+        "img_tmp_missing": "不能驗證圖片檔案: 找不到臨時檔案",
+        "invalid_bcc_map_type": "無效的密件副本規則表類型",
+        "invalid_destination": "無效的目的地址 \"%s\"",
+        "invalid_filter_type": "無效的過濾器類型",
+        "invalid_host": "無效的主機: %s",
+        "invalid_mime_type": "無效的 mime 類型",
+        "invalid_nexthop": "無效的下一跳格式",
+        "invalid_nexthop_authenticated": "下一跳的主機使用了不同的憑證,請先更改下一跳的憑證。",
+        "invalid_recipient_map_new": "無效的新收件人地址: %s",
+        "invalid_recipient_map_old": "無效的原收件人地址: %s",
+        "ip_list_empty": "IP 允許清單為必填",
+        "is_alias": "%s 已經被作為別名地址使用",
+        "is_alias_or_mailbox": "%s 已經被作為別名地址、信箱地址或域名別名擴展出的別名地址使用。",
+        "is_spam_alias": "%s 已經被作為臨時別名地址使用 (垃圾郵件別名地址)",
+        "last_key": "最後一個金鑰不能被刪除,請先停用兩步驟驗證。",
+        "login_failed": "登入失敗",
+        "mailbox_defquota_exceeds_mailbox_maxquota": "預設容量配額大於上限",
+        "mailbox_invalid": "無效的信箱名稱",
+        "mailbox_quota_exceeded": "容量配額超出域名容量上限 (最大 %d MiB)",
+        "mailbox_quota_exceeds_domain_quota": "最大容量配額超出域名容量上限",
+        "mailbox_quota_left_exceeded": "空間不足 (剩餘空間: %d MiB)",
+        "mailboxes_in_use": "最大信箱數必須大於等於 %d",
+        "malformed_username": "無效的使用者名稱",
+        "map_content_empty": "規則內容為必填",
+        "max_alias_exceeded": "別名數已達上限",
+        "max_mailbox_exceeded": "超出最大信箱數 (%d / %d)",
+        "max_quota_in_use": "信箱容量上限必須大於等於 %d MiB",
+        "maxquota_empty": "每個信箱最大容量配額不可為 0",
+        "mysql_error": "MySQL 錯誤: %s",
+        "network_host_invalid": "無效的網路或主機: %s",
+        "next_hop_interferes": "%s 與下一跳 %s 衝突",
+        "next_hop_interferes_any": "一個已存在的下一跳設定與 %s 衝突",
+        "nginx_reload_failed": "Nginx 重新載入失敗: %s",
+        "no_user_defined": "未定義使用者",
+        "object_exists": "物件 %s 已存在",
+        "object_is_not_numeric": "%s 不是數字",
+        "password_complexity": "密碼不符合規則",
+        "password_empty": "密碼不得為空",
+        "password_mismatch": "密碼確認不合",
+        "policy_list_from_exists": "指定的名稱紀錄已存在",
+        "policy_list_from_invalid": "無效的紀錄格式",
+        "private_key_error": "私鑰錯誤: %s",
+        "pushover_credentials_missing": "Pushover 權杖或金鑰遺失",
+        "pushover_key": "Pushover 金鑰格式錯誤",
+        "pushover_token": "Pushover 權杖格式錯誤",
+        "quota_not_0_not_numeric": "容量配額必須為數值且 >= 0",
+        "recipient_map_entry_exists": "收件人規則條目 \"%s\" 已存在",
+        "redis_error": "Redis 錯誤: %s",
+        "relayhost_invalid": "無效的中繼主機規則 %s",
+        "release_send_failed": "郵件無法被釋放: %s",
+        "reset_f2b_regex": "暫時無法重設正規表示式過濾器,請重試或在稍後重新載入網頁。",
+        "resource_invalid": "無效的資源名稱 %s",
+        "rl_timeframe": "速率限制間隔時間不正確",
+        "rspamd_ui_pw_length": "Rspamd UI 密碼至少要 6 個字元",
+        "script_empty": "腳本為必填",
+        "sender_acl_invalid": "無效的寄件人 ACL 值 %s",
+        "set_acl_failed": "ACL 設定失敗",
+        "settings_map_invalid": "無效的設定規則 ID %s",
+        "sieve_error": "sieve 解析器錯誤: %s",
+        "spam_learn_error": "學習垃圾郵件時發成錯誤: %s",
+        "subject_empty": "主旨為必填",
+        "target_domain_invalid": "無效的目標域名 %s",
+        "targetd_not_found": "找不到目標域名 %s",
+        "targetd_relay_domain": "目標域名 %s 是中繼域名",
+        "temp_error": "暫時性錯誤",
+        "text_empty": "文本為必填",
+        "tfa_token_invalid": "無效的 TFA 權杖",
+        "tls_policy_map_dest_invalid": "無效的目標規則",
+        "tls_policy_map_entry_exists": "TLS 規則 \"%s\" 已存在",
+        "tls_policy_map_parameter_invalid": "無效的規則參數",
+        "totp_verification_failed": "TOTP 認證失敗",
+        "transport_dest_exists": "傳輸目標 \"%s\" 已存在",
+        "webauthn_verification_failed": "WebAuthn 認證失敗: %s",
+        "unknown": "發生未知錯誤",
+        "unknown_tfa_method": "未知 TFA 方法",
+        "unlimited_quota_acl": "ACL 設定禁止了無限容量配額",
+        "username_invalid": "使用者名稱 %s 無法使用",
+        "validity_missing": "請設定有效期",
+        "value_missing": "請填入所有欄位",
+        "yotp_verification_failed": "Yubico OTP 認證失敗: %s"
+    },
+    "debug": {
+        "chart_this_server": "圖表 (此伺服器)",
+        "containers_info": "容器資訊",
+        "disk_usage": "硬碟使用量",
+        "docs": "文件",
+        "external_logs": "外部紀錄",
+        "history_all_servers": "歷史 (所有伺服器)",
+        "in_memory_logs": "記憶體紀錄",
+        "jvm_memory_solr": "JVM 記憶體使用量",
+        "last_modified": "上次修改時間",
+        "log_info": "<p>mailcow 的<b>記憶體紀錄</b>會被收集到 Redis 清單中並且每分鐘自動縮減到 LOG_LINES (%d) 以避免重複撞擊 (hammering) 造成的大量記錄。\r\n<br>記憶體紀錄並不會永久保存。所有記錄到記憶體的應用程式也會同時透過預設紀錄的驅動程式寫入紀錄到 Docker 常駐程式中。\r\n<br>記憶體紀錄是設計用來為容器中的小問題除錯的。</p>\r\n<p><b>外部紀錄</b>透過應用程式提供的 API 收集。</p>\r\n<p><b>靜態紀錄</b>大多為不寫入到 Dockerd,但仍然需要被保存的活動紀錄 (API 紀錄除外)。</p>",
+        "login_time": "時間",
+        "logs": "紀錄",
+        "online_users": "在線使用者",
+        "restart_container": "重新啟動",
+        "service": "服務",
+        "size": "大小",
+        "solr_dead": "Solr 正在啟動、停用或已停止運行",
+        "solr_status": "Solr 狀態",
+        "started_at": "啟動於",
+        "started_on": "啟動於",
+        "static_logs": "靜態紀錄",
+        "success": "成功",
+        "system_containers": "系統和容器",
+        "uptime": "運行時間",
+        "username": "使用者名稱"
+    },
+    "diagnostics": {
+        "cname_from_a": "由 A/AAAA 紀錄獲取。只要紀錄指向正確的資源,此功能就會持續運作。",
+        "dns_records": "DNS 紀錄",
+        "dns_records_24hours": "請注意 DNS 紀錄的更改可能需要 24 小時才能正確顯示於此頁面。此頁面的目的是為了讓你可以輕鬆的了解如何設定 DNS 紀錄並檢查 DNS 是否設定正確。",
+        "dns_records_data": "正確值",
+        "dns_records_docs": "請同時另外查看 <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">文件</a>.",
+        "dns_records_name": "名稱",
+        "dns_records_status": "目前狀態",
+        "dns_records_type": "類型",
+        "optional": "此項紀錄可不填。"
+    },
+    "edit": {
+        "acl": "ACL (權限)",
+        "active": "啟用",
+        "admin": "編輯管理員",
+        "advanced_settings": "進階設定",
+        "alias": "編輯別名",
+        "allow_from_smtp": "只允許這些 IP 使用 <b>SMTP</b>",
+        "allow_from_smtp_info": "留空將允許所有寄件人<br>IPv4/IPv6 地址或網路",
+        "allowed_protocols": "允許的協定",
+        "app_name": "應用程式名稱",
+        "app_passwd": "應用程式密碼",
+        "app_passwd_protocols": "應用程式密碼允許的協定",
+        "automap": "自動分類資料夾規則 (如:\"已發送\", \"寄件備份\" => \"Sent\")",
+        "backup_mx_options": "中繼選項",
+        "bcc_dest_format": "密件副本地址必須是單一的有效地址。<br>如果你需要傳送到多個地址,請幫它們建立一個別名然後設定於此欄。",
+        "client_id": "用戶端 ID",
+        "client_secret": "用戶端金鑰",
+        "comment_info": "隱密備註不會被使用者看到,公開備註則會在使用者游標懸停於概述頁時顯示於提示框",
+        "delete1": "完成後將來源郵件刪除",
+        "delete2": "刪除目的地信箱中存在但來源信箱中不存在的郵件",
+        "delete2duplicates": "刪除目的地信箱中的重複郵件",
+        "delete_ays": "確定要刪除?",
+        "description": "描述",
+        "disable_login": "不允許登入 (但郵件仍然會正常接收)",
+        "domain": "編輯域名",
+        "domain_admin": "編輯域名管理員",
+        "domain_quota": "域名容量配額",
+        "domains": "域名",
+        "dont_check_sender_acl": "關閉域名 %s (及其域名別名) 的寄件人檢查",
+        "edit_alias_domain": "編輯域名別名",
+        "encryption": "加密",
+        "exclude": "拒絕物件 (正規表示式)",
+        "extended_sender_acl": "外部寄件人地址",
+        "extended_sender_acl_info": "如果可以的話,請匯入 DKIM 域名金鑰。<br>\r\n別忘記將此伺服器新增到相應的 SPF TXT 中。<br>\r\n當域名或域名別名被新增時,若其與此外部寄件人地址交疊,則外部寄件人地址會被移除。<br>\r\n填入 @domain.tld 以允許作為 *@domain.tld 發送郵件。",
+        "force_pw_update": "在下一次登入時強制要求更新密碼",
+        "force_pw_update_info": "此使用者只能登入至 %s。應用程式密碼仍可正常使用",
+        "full_name": "全名",
+        "gal": "全域聯絡人清單",
+        "gal_info": "<b>全域聯絡人清單</b>包含了域名下的所有物件,且使用者不可編輯。如果關閉,使用者的 空閒/繁忙 訊息將不能在 SOGo 中顯示。<b>重新啟動 SOGo 以應用更改。</b>",
+        "generate": "生成",
+        "grant_types": "授權類型",
+        "hostname": "主機名稱",
+        "inactive": "停用",
+        "kind": "種類",
+        "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
+        "mailbox": "編輯信箱",
+        "mailbox_quota_def": "預設信箱容量配額",
+        "mailbox_relayhost_info": "只會套用於信箱和直接別名,不會覆寫域名中繼主機。",
+        "max_aliases": "最大允許地址別名數",
+        "max_mailboxes": "最大允許信箱數",
+        "max_quota": "每個信箱的最大容量配額 (MiB)",
+        "maxage": "從遠端拉取消息的最大消息年齡限制<br><small>(0 表示忽略)</small>",
+        "maxbytespersecond": "最大速率 (Bytes/s) <br><small>(0 表示不限)</small>",
+        "mbox_rl_info": "此頻率限制應用於 SASL 登入名,它會匹配任何郵件的\"寄件人\"地址。信箱的頻率限制設定會覆蓋域名的速率限制值設定。",
+        "mins_interval": "輪詢間隔 (分鐘)",
+        "multiple_bookings": "多重登記限制",
+        "none_inherit": "無 / 繼承",
+        "nexthop": "下一跳",
+        "password": "密碼",
+        "password_repeat": "確認密碼 (重復輸入)",
+        "previous": "上一頁",
+        "private_comment": "隱密備註",
+        "public_comment": "公開備註",
+        "pushover": "Pushover",
+        "pushover_evaluate_x_prio": "急件發送高優先級郵件 [<code>X-Priority: 1</code>]",
+        "pushover_info": "推送通知設定會應用到所有遞送到 <b>%s</b> 的非垃圾郵件 (包括其共用、非公用、標註的別名)。",
+        "pushover_only_x_prio": "只發送高優先級郵件 [<code>X-Priority: 1</code>]",
+        "pushover_sender_array": "只發送以下寄件人 <small>(英文逗號分隔)</small>",
+        "pushover_sender_regex": "只發送匹配下列正規表示式的寄件人",
+        "pushover_text": "通知內文",
+        "pushover_title": "通知標題",
+        "pushover_vars": "如果沒有定義寄件人過濾器則會為所有郵件開啟通知推送。<br>正規表示式過濾器和寄件人清單可以個別設定,並且會按順序匹配,它們互不相關。<br>在文本和標題中可用的變數 (請注意資料保護政策)",
+        "pushover_verify": "驗證憑證",
+        "quota_mb": "容量配額 (MiB)",
+        "quota_warning_bcc": "密件副本容量警告",
+        "quota_warning_bcc_info": "警告會以個別的方式發送給這些收件人。使用者名稱會以中括號後綴於主旨欄,如:<code>Quota warning (user@example.com)</code>。",
+        "ratelimit": "速率限制",
+        "redirect_uri": "重新導向/回呼 URL",
+        "relay_all": "中繼所有收件人",
+        "relay_all_info": "↪ 如果選擇<b>不</b>中繼所有收件人,你會需要幫每個應該中繼的收件人新增一個 (\"盲\") 信箱。",
+        "relay_domain": "中繼這個域名",
+        "relay_transport_info": "<div class=\"label label-info\">資訊</div> 你可以為此域名定義傳輸規則以自訂目的地,如果留空則會遵照 MX 紀錄。",
+        "relay_unknown_only": "只為不存在的信箱地址中繼。已存在的信箱地址則在本區域遞送。",
+        "relayhost": "中繼傳輸",
+        "remove": "移除",
+        "resource": "資源",
+        "save": "儲存更改",
+        "scope": "範圍",
+        "sender_acl": "允許發送為",
+        "sender_acl_disabled": "<span class=\"label label-danger\">寄件人檢查已關閉</span>",
+        "sender_acl_info": "如果信箱使用者 A 被允許以信箱使用者 B 發送郵件,該寄件人地址不會出現在 SOGo 中\"發送自\"的下拉選項中。<br>\r\n信箱使用者 B 需要新增授權以允許信箱使用者 A 選擇 B 的地址作為寄件人;授權方法為,在 SOGo 中點擊右上方信箱名稱左邊的選項按鈕(三個點)並授權。此行為不會套用於信箱別名。",
+        "sieve_desc": "簡短描述",
+        "sieve_type": "過濾器類型",
+        "skipcrossduplicates": "跳過在其他資料夾中重複的郵件 (優先使用先找到的郵件)",
+        "sogo_access": "授權 SOGo 的直接存取權",
+        "sogo_access_info": "mail UI 內的 SSO 仍可以運作。此設定既不會影響到存取其他服務的權限,也不會刪除或更改使用者既有的個人檔案。",
+        "sogo_visible": "別名會在 SOGo 中顯示",
+        "sogo_visible_info": "此選項只會影響可以顯示於 SOGo 上物件 (指向至少一個本地信箱的共享或非共享別名地址)。如果設為隱藏,別名地址將不會顯示於寄件人下拉選項中。",
+        "spam_alias": "新增或更改臨時別名地址",
+        "spam_filter": "垃圾郵件過濾器",
+        "spam_policy": "將項目新增到白/黑名單或從其中移除",
+        "spam_score": "自訂垃圾郵件分數",
+        "subfolder2": "同步到目標信箱子資料夾<br><small>(留空表示不使用子資料夾)</small>",
+        "syncjob": "編輯同步任務",
+        "target_address": "目標地址 <small>(以英文逗號分隔多個地址)</small>",
+        "target_domain": "目標域名",
+        "timeout1": "遠端主機連接超時時間",
+        "timeout2": "本地主機連接超時時間",
+        "title": "編輯物件",
+        "unchanged_if_empty": "如果不更改則留空",
+        "username": "使用者名稱",
+        "validate_save": "驗證並儲存"
+    },
+    "fido2": {
+        "confirm": "確認",
+        "fido2_auth": "使用 FIDO2 登入",
+        "fido2_success": "裝置成功註冊",
+        "fido2_validation_failed": "驗證失敗",
+        "fn": "Friendly name",
+        "known_ids": "已知 ID",
+        "none": "常用名稱",
+        "register_status": "註冊狀態",
+        "rename": "重新命名",
+        "set_fido2": "註冊 FIDO2 裝置",
+        "set_fido2_touchid": "註冊 Apple M1 上的 Touch ID",
+        "set_fn": "設定常用名稱",
+        "start_fido2_validation": "開始 FIDO2 驗證"
+    },
+    "footer": {
+        "cancel": "取消",
+        "confirm_delete": "確認刪除",
+        "delete_now": "立即刪除",
+        "delete_these_items": "請確認對以下 id 物件的更改",
+        "hibp_check": "透過 haveibeenpwned.com 進行檢查",
+        "hibp_nok": "找到了!這是一個有潛在危險的密碼!",
+        "hibp_ok": "沒有找到任何密碼。",
+        "loading": "請稍等...",
+        "nothing_selected": "未選擇任何項目",
+        "restart_container": "重新啟動容器",
+        "restart_container_info": "<b>注意:</b> 平穩重新啟動可能需要一些時間,請稍等。",
+        "restart_now": "立即重新啟動",
+        "restarting_container": "容器重新啟動中,這可能需要一些時間"
+    },
+    "header": {
+        "administration": "設定和管理",
+        "apps": "應用程式",
+        "debug": "系統訊息",
+        "mailboxes": "信箱設定",
+        "mailcow_settings": "設定",
+        "quarantine": "隔離",
+        "restart_netfilter": "重新啟動 netfilter",
+        "restart_sogo": "重新啟動 SOGo",
+        "user_settings": "使用者設定"
+    },
+    "info": {
+        "awaiting_tfa_confirmation": "等待 TFA 確認",
+        "no_action": "沒有適用的操作選項",
+        "session_expires": "連線階段將在 15 秒後到期"
+    },
+    "login": {
+        "delayed": "請在 %s 秒後重新登入。",
+        "fido2_webauthn": "FIDO2/WebAuthn",
+        "login": "登入",
+        "mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。",
+        "other_logins": "金鑰登入",
+        "password": "密碼",
+        "username": "使用者名稱"
+    },
+    "mailbox": {
+        "action": "操作",
+        "activate": "啟用",
+        "active": "啟用",
+        "add": "新增",
+        "add_alias": "新增別名",
+        "add_alias_expand": "擴展信箱別名至域名別名",
+        "add_bcc_entry": "新增密件副本規則表",
+        "add_domain": "新增域名",
+        "add_domain_alias": "新增域名別名",
+        "add_domain_record_first": "請先新增一個域名",
+        "add_filter": "新增過濾器",
+        "add_mailbox": "新增信箱",
+        "add_recipient_map_entry": "新增收件人規則表",
+        "add_resource": "新增資源",
+        "add_tls_policy_map": "新增 TLS 規則",
+        "address_rewriting": "地址重寫",
+        "alias": "別名",
+        "alias_domain_alias_hint": "信箱別名<b>不會</b>自動對映到域名別名。信箱別名地址 <code>my-alias@domain</code> <b>不會</b> 對映到 <code>my-alias@alias-domain</code> (假設 \"alias-domain\" 是 \"domain\" 的域名別名)。<br>若需要將郵件轉發到外部信箱,請使用 sieve 過濾器 (查看標籤頁 \"過濾器\" 或者使用 SOGo -> 轉發器)。使用\"擴展信箱別名至域名別名\"來自動新增缺失的別名。",
+        "alias_domain_backupmx": "域名別名在中繼域名下不啟用",
+        "aliases": "別名",
+        "all_domains": "所有域名",
+        "allow_from_smtp": "只允許這些 IP 使用<b>SMTP</b>",
+        "allow_from_smtp_info": "留空以允許所有發送者<br>IPv4/IPv6 地址或網路",
+        "allowed_protocols": "使用者直接存取時允許的協定 (不影響應用程式密碼所能使用的協定)",
+        "backup_mx": "中繼域名",
+        "bcc": "密件副本",
+        "bcc_destination": "密件副本目標地址",
+        "bcc_destinations": "密件副本目標地址",
+        "bcc_info": "密件副本規則表用於悄悄的將郵件轉發到另一個地址。當目標地址為本地地址時則會會使用收件人規則表條目。寄件人規則表遵循同樣的原則。<br/>\r\n遞送失敗時不會通知本地目標地址。",
+        "bcc_local_dest": "本地目標地址",
+        "bcc_map": "密件副本規則表",
+        "bcc_map_type": "密件副本類型",
+        "bcc_maps": "密件副本規則表",
+        "bcc_rcpt_map": "收件人規則表",
+        "bcc_sender_map": "寄件人規則表",
+        "bcc_to_rcpt": "切換到收件人規則表",
+        "bcc_to_sender": "切換到寄件人規則表",
+        "bcc_type": "密件副本類型",
+        "booking_null": "永遠顯示為空閒",
+        "booking_0_short": "永遠空閒",
+        "booking_custom": "嚴格限制登記數",
+        "booking_custom_short": "嚴格限制",
+        "booking_ltnull": "不限制登記數,但會在被登記後顯示為繁忙",
+        "booking_lt0_short": "寬鬆限制",
+        "catch_all": "全部接收",
+        "daily": "每天",
+        "deactivate": "停用",
+        "description": "描述",
+        "disable_login": "不允許登入 (仍然會接收郵件)",
+        "disable_x": "關閉",
+        "domain": "域名",
+        "domain_admins": "域名管理員",
+        "domain_aliases": "域名別名",
+        "domain_quota": "容量配額",
+        "domains": "域名",
+        "edit": "編輯",
+        "empty": "沒有結果",
+        "enable_x": "開啟",
+        "excludes": "排除",
+        "filter_table": "篩選表格",
+        "filters": "過濾器",
+        "fname": "全名",
+        "goto_ham": "學習為<b>非垃圾郵件</b>",
+        "goto_spam": "學習為<b>垃圾郵件</b>",
+        "hourly": "每小時",
+        "in_use": "已使用 (%)",
+        "inactive": "停用",
+        "insert_preset": "插入範例預設 \"%s\"",
+        "kind": "種類",
+        "last_mail_login": "上一次信箱登入",
+        "last_pw_change": "上一次密碼更改",
+        "last_run": "上一次執行",
+        "last_run_reset": "下一次執行",
+        "mailbox": "信箱",
+        "mailbox_defaults": "預設設定",
+        "mailbox_defaults_info": "定義新信箱的預設設定",
+        "mailbox_defquota": "預設信箱大小",
+        "mailbox_quota": "最大信箱大小",
+        "mailboxes": "信箱",
+        "mins_interval": "間隔 (分鐘)",
+        "msg_num": "訊息 #",
+        "multiple_bookings": "重複登記",
+        "never": "永不",
+        "no": "&#10005;",
+        "no_record": "沒有關於物件 %s 的紀錄",
+        "no_record_single": "沒有紀錄",
+        "open_logs": "開啟日誌",
+        "owner": "所有者",
+        "private_comment": "私密備註",
+        "public_comment": "公開備註",
+        "q_add_header": "當移到垃圾信箱時",
+        "q_all": " 當移到垃圾信箱和拒絕時",
+        "q_reject": "當拒絕時",
+        "quarantine_category": "隔離通知分類",
+        "quarantine_notification": "隔離通知",
+        "quick_actions": "快速動作",
+        "recipient": "收件人",
+        "recipient_map": "收件人規則表",
+        "recipient_map_info": "收件人規則表是用來在郵件被遞送前替換收件人地址。",
+        "recipient_map_new": "新收件人",
+        "recipient_map_new_info": "新收件人必須為有效的郵件地址",
+        "recipient_map_old": "原收件人",
+        "recipient_map_old_info": "原收件人必須為有效的郵件地址",
+        "recipient_maps": "收件人規則表",
+        "remove": "刪除",
+        "resources": "資源",
+        "running": "執行中",
+        "sender": "寄件人",
+        "set_postfilter": "標記為 postfilter",
+        "set_prefilter": "標記為 prefilter",
+        "sieve_info": "你可以為每個使用者儲存多個過濾器,但只能同時啟用一個 prefilter 和一個 postfilter。<br>\r\n過濾器將按清單中的順序依次執行,下一個腳本不會因為上一個腳本失敗或\"keep;\"而停止。更改全域 sieve 腳本會重新啟動 Dovecot。<br><br>全域 sieve prefilter → Prefilter → 使用者腳本 → Postfilter → 全域 sieve postfilter",
+        "sieve_preset_1": "丟棄含有潛在危險檔案格式的信件",
+        "sieve_preset_2": "永遠標記來自指定寄件人的郵件為已讀",
+        "sieve_preset_3": "無聲刪除,並停止運行後續的 sieve 腳本",
+        "sieve_preset_4": "移動到收件夾,並停止運行後續的 sieve 腳本",
+        "sieve_preset_5": "自動回覆 (休假)",
+        "sieve_preset_6": "拒絕郵件並回應",
+        "sieve_preset_7": "重新導向並保留/刪除",
+        "sieve_preset_8": "刪除寄件人發送給包含自己別名地址的郵件",
+        "sieve_preset_header": "請看下方的範例預設。 查看 <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedia</a> 以瞭解更多細節。",
+        "sogo_visible": "別名會顯示於 SOGo",
+        "sogo_visible_n": "在 SOGo 中隱藏別名",
+        "sogo_visible_y": "在 SOGo 中顯示別名",
+        "spam_aliases": "臨時別名",
+        "stats": "統計",
+        "status": "狀態",
+        "sync_jobs": "同步任務",
+        "syncjob_check_log": "檢查日誌",
+        "syncjob_last_run_result": "上次執行結果",
+        "syncjob_EX_OK": "成功",
+        "syncjob_EXIT_CONNECTION_FAILURE": "連線問題",
+        "syncjob_EXIT_TLS_FAILURE": "加密連線問題",
+        "syncjob_EXIT_AUTHENTICATION_FAILURE": "身分驗證問題",
+        "syncjob_EXIT_OVERQUOTA": "目標信箱容量配額已滿",
+        "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "無法連接至遠端伺服器",
+        "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "錯誤的使用者名稱或密碼",
+        "table_size": "表格尺寸",
+        "table_size_show_n": "顯示 %s 個項目",
+        "target_address": "目標地址",
+        "target_domain": "目標域名",
+        "tls_enforce_in": "強制入站 TLS",
+        "tls_enforce_out": "強制出站 TLS",
+        "tls_map_dest": "目的地",
+        "tls_map_dest_info": "範例: example.org, .example.org, [mail.example.org]:25",
+        "tls_map_parameters": "參數",
+        "tls_map_parameters_info": "留空或填入參數,比如: protocols=!SSLv2 ciphers=medium exclude=3DES",
+        "tls_map_policy": "規則",
+        "tls_policy_maps": "TLS 規則表",
+        "tls_policy_maps_enforced_tls": "這些規則同時會影響信箱使用者的出站 TLS 連接行為。如果在下方沒有任何規則,則使用者會應用 <code>smtp_tls_mandatory_protocols</code> 和 <code>smtp_tls_mandatory_ciphers</code> 指定的預設值",
+        "tls_policy_maps_info": "此規則表會覆蓋使用者的出站 TLS 規則設定。<br>\r\n查看 <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">\"smtp_tls_policy_maps\"文檔</a> 以瞭解更多資訊。",
+        "tls_policy_maps_long": "出站 TLS 規則重寫",
+        "toggle_all": "選擇/取消所有",
+        "username": "使用者名稱",
+        "waiting": "等待中",
+        "weekly": "每週",
+        "yes": "&#10003;"
+    },
+    "oauth2": {
+        "access_denied": "請使用信箱使用者登入來進行 OAuth2 授權",
+        "authorize_app": "授權應用程式",
+        "deny": "拒絕",
+        "permit": "授權應用程式",
+        "profile": "個人資料",
+        "profile_desc": "查看個人資訊: 使用者名稱、全名,創建時間,修改時間,啟用狀態",
+        "scope_ask_permission": "應用程式請求了這些權限"
+    },
+    "quarantine": {
+        "action": "操作",
+        "atts": "附件",
+        "check_hash": "搜尋檔案雜湊 @ VT",
+        "confirm": "確認",
+        "confirm_delete": "確認刪除此元素。",
+        "danger": "危險性",
+        "deliver_inbox": "發送到收件箱",
+        "disabled_by_config": "目前系統設定關閉了隔離功能,請設定 \"每個信箱保留隔離項目數\" 和 \"最大檔案大小\" 以開啟隔離。",
+        "download_eml": "下載 (.eml)",
+        "empty": "沒有結果",
+        "high_danger": "高危險",
+        "info": "資訊",
+        "junk_folder": "垃圾郵件資料夾",
+        "learn_spam_delete": "學習為垃圾郵件並刪除",
+        "low_danger": "低危險",
+        "medium_danger": "中危險",
+        "neutral_danger": "中性",
+        "notified": "已通知",
+        "qhandler_success": "請求已成功向系統發送,您可以關閉此視窗。",
+        "qid": "Rspamd QID",
+        "qinfo": "隔離系統會將被拒絕的郵件以及發送到垃圾郵件資料夾的副本郵件儲存到資料庫中 (但寄件人<em>不</em>會被告知)。\r\n  <br>\"學習為垃圾並刪除\" 會根據貝氏定理將郵件分類為垃圾郵件並計算其模糊特徵以在未來拒絕相似的郵件。\r\n  <br>請注意,取決於你的系統資源,學習多個郵件可能會花費大量的時間。<br>黑名單中的項目不會被隔離系統考慮。",
+        "qitem": "隔離項目",
+        "quarantine": "隔離",
+        "quick_actions": "操作",
+        "quick_delete_link": "打開快速刪除連結",
+        "quick_info_link": "打開詳情連結",
+        "quick_release_link": "打開快速釋放連結",
+        "rcpt": "收件人",
+        "received": "已接收",
+        "recipients": "收件人",
+        "refresh": "重新整理",
+        "rejected": "已拒絕",
+        "release": "釋放",
+        "release_body": "我們已在此郵件中將你的郵件附檔為 eml 檔案",
+        "release_subject": "有風險的隔離項目 %s",
+        "remove": "刪除",
+        "rewrite_subject": "改寫主旨",
+        "rspamd_result": "Rspamd 結果",
+        "sender": "寄件人 (SMTP)",
+        "sender_header": "寄件人 (\"From\" 標頭)",
+        "settings_info": "隔離項目數量上限:%s<br>信件大小上限:%s MiB",
+        "show_item": "顯示項目",
+        "spam": "垃圾郵件",
+        "spam_score": "分數",
+        "subj": "主旨",
+        "table_size": "表格尺寸",
+        "table_size_show_n": "顯示 %s 個項目",
+        "text_from_html_content": "內容 (轉換為 html)",
+        "text_plain_content": "內容 (text/plain)",
+        "toggle_all": "選擇/取消選擇全部",
+        "type": "類型"
+    },
+    "ratelimit": {
+      "disabled": "停用",
+      "second": "訊息 / 秒",
+      "minute": "訊息 / 分鐘",
+      "hour": "訊息 / 小時",
+      "day": "訊息 / 天"
+    },
+    "start": {
+        "help": "顯示/隱藏 幫助面板",
+        "imap_smtp_server_auth_info": "請使用完整的信箱地址及明文認證 (PLAIN) 來登入。<br>\r\n你的登入資訊會在伺服器端被加密。",
+        "mailcow_apps_detail": "使用 mailcow 應用程式來存取你的郵件、行事曆、聯絡資訊及其他更多功能。",
+        "mailcow_panel_detail": "<b>域名管理員</b> 可以創建、修改、刪除信箱別名,以及讀取並修改被指定他們名下的域名。<br>\r\n<b>信箱使用者</b> 可以創建臨時別名 (垃圾郵件別名),更改密碼和垃圾郵件過濾器設定。"
+    },
+    "success": {
+        "acl_saved": "%s 的存取權限表已儲存",
+        "admin_added": "管理員 %s 已新增",
+        "admin_api_modified": "API 的更改已儲存",
+        "admin_modified": "管理員更改已儲存",
+        "admin_removed": "管理員 %s 已刪除",
+        "alias_added": "別名地址 %s (%d) 已新增",
+        "alias_domain_removed": "域名別名 %s 已刪除",
+        "alias_modified": "別名地址更改已儲存",
+        "alias_removed": "別名 %s 已刪除",
+        "aliasd_added": "域名別名 %s 已新增",
+        "aliasd_modified": "域名別名 %s 更改已儲存",
+        "app_links": "應用程式連結更改已儲存",
+        "app_passwd_added": "新的應用密碼已新增",
+        "app_passwd_removed": "應用程式密碼已刪除,ID:%s",
+        "bcc_deleted": "密件副本規則表條目 %s 已刪除",
+        "bcc_edited": "密件副本規則表條目 %s 已編輯",
+        "bcc_saved": "密件副本規則表條目已儲存",
+        "db_init_complete": "資料庫初始化完成",
+        "delete_filter": "過濾器 ID %s 已刪除",
+        "delete_filters": "過濾器 %s 已刪除",
+        "deleted_syncjob": "同步任務 ID %s 已刪除",
+        "deleted_syncjobs": "同步任務 %s 已刪除",
+        "dkim_added": " DKIM 金鑰 %s 已儲存",
+        "domain_add_dkim_available": "DKIM 金鑰已存在",
+        "dkim_duplicated": "域名的 DKIM 金鑰 %s 已複製到 %s",
+        "dkim_removed": " DKIM 金鑰 %s 已刪除",
+        "domain_added": "域名 %s 已新增",
+        "domain_admin_added": "域名管理員 %s 已新增",
+        "domain_admin_modified": "域名管理員 %s 更改已儲存",
+        "domain_admin_removed": "域名管理員 %s 已刪除",
+        "domain_modified": "域名 %s 更改已儲存",
+        "domain_removed": "域名 %s 已刪除",
+        "dovecot_restart_success": "Dovecot 成功重新啟動",
+        "eas_reset": "使用者 %s 的 ActiveSync 裝置已重設",
+        "f2b_modified": "Fail2ban 參數更改已儲存",
+        "forwarding_host_added": "轉發主機 %s 已新增",
+        "forwarding_host_removed": "轉發主機 %s 已刪除",
+        "global_filter_written": "過濾器已成功寫入到檔案",
+        "hash_deleted": "特徵雜湊已刪除",
+        "item_deleted": "項目 %s 已刪除",
+        "item_released": "項目 %s 已釋放",
+        "items_deleted": "項目 %s 已刪除",
+        "items_released": "選取的項目已釋放",
+        "learned_ham": "郵件 ID %s 已成功學習為非垃圾郵件",
+        "license_modified": "許可證更改已儲存",
+        "logged_in_as": "以 %s 登入",
+        "mailbox_added": "信箱 %s 已新增",
+        "mailbox_modified": "信箱 %s 更改已儲存",
+        "mailbox_removed": "信箱 %s 已刪除",
+        "nginx_reloaded": "Nginx 已重新載入",
+        "object_modified": "%s 的更改已儲存",
+        "password_policy_saved": "密碼政策已成功儲存",
+        "pushover_settings_edited": "成功設定 Pushover 設定,請驗證憑證",
+        "qlearn_spam": "郵件 ID %s 已被學習為垃圾郵件並刪除",
+        "queue_command_success": "成功完成佇列命令",
+        "recipient_map_entry_deleted": "收件人規則表 ID %s 已刪除",
+        "recipient_map_entry_saved": "收件人規則表條目 \"%s\" 已儲存",
+        "relayhost_added": "規則表條目 %s 已新增",
+        "relayhost_removed": "規則表條目 %s 已刪除",
+        "reset_main_logo": "重設為預設 logo",
+        "resource_added": "資源 %s 已新增",
+        "resource_modified": "資源 %s 更改已儲存",
+        "resource_removed": "資源 %s 已刪除",
+        "rl_saved": "%s 的速率限制設定已儲存",
+        "rspamd_ui_pw_set": "成功設定 Rspamd UI 密碼",
+        "saved_settings": "設定已儲存",
+        "settings_map_added": "設定規則已新增",
+        "settings_map_removed": "設定規則 ID %s 已刪除",
+        "sogo_profile_reset": "使用者 %s 的 SOGo 個人頁面已重設",
+        "tls_policy_map_entry_deleted": " TLS 規則 ID %s 已刪除",
+        "tls_policy_map_entry_saved": "TLS 規則 \"%s\" 已儲存",
+        "ui_texts": "UI 內文更改已儲存",
+        "upload_success": "檔案已成功上傳",
+        "verified_fido2_login": "FIDO2 登入驗證成功",
+        "verified_totp_login": "TOTP 登入驗證成功",
+        "verified_webauthn_login": "WebAuthn 登入驗證成功",
+        "verified_yotp_login": "Yubico OTP 登入驗證成功"
+    },
+    "tfa": {
+        "api_register": "%s 使用 Yubico Cloud API,請在<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">這裡</a>為這把金鑰獲取 API 金鑰",
+        "confirm": "確認",
+        "confirm_totp_token": "請輸入生成的權杖來確認更改",
+        "delete_tfa": "關閉兩步驟驗證",
+        "disable_tfa": "在下一次成功登入前關閉兩步驟驗證",
+        "enter_qr_code": "如果你的裝置不能掃描 QR 碼,輸入此 TOTP 碼",
+        "error_code": "錯誤碼",
+        "init_webauthn": "初始化中,請稍等...",
+        "key_id": "這個裝置的標籤",
+        "key_id_totp": "這把金鑰的標識",
+        "none": "停用",
+        "reload_retry": "- (如果錯誤持續出現,請重新啟動瀏覽器)",
+        "scan_qr_code": "請使用驗證應用程式掃描或手動輸入下面的條碼。",
+        "select": "請選擇",
+        "set_tfa": "設定兩步驟驗證方法",
+        "start_webauthn_validation": "開始驗證",
+        "tfa": "兩步驟驗證 (2FA)",
+        "tfa_token_invalid": "無效的 TFA 權杖",
+        "totp": "TOTP 認證 (Google Authenticator、Authy 等)",
+        "u2f_deprecated": "這把金鑰似乎是使用已失效的舊 U2F 流程所註冊。我們會取消二步驟驗證並刪除這把金鑰。",
+        "u2f_deprecated_important": "請在管理員介面使用新的 WebAuthn 流程註冊金鑰。",
+        "webauthn": "WebAuthn 認證",
+        "waiting_usb_auth": "<i>等待 USB 裝置...</i><br><br>請觸碰 USB 裝置上的按鈕。",
+        "waiting_usb_register": "<i>等待 USB 裝置...</i><br><br>請輸入密碼並觸碰 USB 裝置上的按鈕來確認註冊。",
+        "yubi_otp": "Yubico OTP 認證"
+    },
+    "user": {
+        "action": "操作",
+        "active": "啟用",
+        "active_sieve": "啟用的過濾器",
+        "advanced_settings": "進階設定",
+        "alias": "別名",
+        "alias_create_random": "產生隨機別名",
+        "alias_extend_all": "延長臨時別名 1 小時",
+        "alias_full_date": "日.月.年, 小時:分:秒 時區",
+        "alias_remove_all": "刪除所有別名",
+        "alias_select_validity": "有效期限",
+        "alias_time_left": "剩餘時間",
+        "alias_valid_until": "有效至",
+        "aliases_also_send_as": "同時允許以使用者發送",
+        "aliases_send_as_all": "不要為下列域名及其別名域名進行寄件者權限檢查",
+        "app_hint": "應用程式密碼是用於登入 IMAP, SMTP, CalDAV, CardDAV 和 EAS 時的替代密碼,使用者名稱保持不變。應用程式密碼無法用於 SOGo",
+        "allowed_protocols": "允許的協定",
+        "app_name": "應用程式名稱",
+        "app_passwds": "應用程式密碼",
+        "apple_connection_profile": "Apple 連接描述檔案",
+        "apple_connection_profile_complete": "此連接描述檔案包括提供給 Apple 裝置的 IMAP 和 SMTP 組態參數並包括 CalDAV (日曆) 和 CardDAV (聯繫人) 存取路徑。",
+        "apple_connection_profile_mailonly": "此連接描述檔案包括提供給 Apple 裝置的 IMAP 和 SMTP 組態參數。",
+        "apple_connection_profile_with_app_password": "應用程式密碼已產生並加入到連接描述檔案中,因此裝置在設定時不需要輸入密碼。請勿分享這個檔案,因為它擁有存取信箱的所有權限。",
+        "change_password": "更改密碼",
+        "change_password_hint_app_passwords": "你的帳號有 {{number_of_app_passwords}} 個應用程式密碼不會被更動。要管理這些密碼,請至應用程式密碼分頁。",
+        "clear_recent_successful_connections": "中斷成功的連線",
+        "client_configuration": "顯示電子信箱程式和智慧型手機的設定指南",
+        "create_app_passwd": "新增應用程式密碼",
+        "create_syncjob": "新增同步任務",
+        "created_on": "建立於",
+        "daily": "每日",
+        "day": "日",
+        "delete_ays": "請確認刪除。",
+        "direct_aliases": "直接別名",
+        "direct_aliases_desc": "直接別名會受到垃圾郵件過濾器和 TLS 規則限制。",
+        "direct_protocol_access": "此信箱使用者有 <b>直接、外部存取</b> 至下列協定及應用程式。此設定是由管理者設定。應用程式密碼則可以用於授權單獨的協定及應用程式存取權限。<br>\"登入至網頁信箱\" 使用 SSO (single-sign-on) 來登入至 SOGO 且不受前面的限制。",
+        "eas_reset": "重設 ActiveSync 裝置快取",
+        "eas_reset_help": "在許多情況下,重設裝置快取可以幫助恢復錯誤的 ActiveSync 資料。<br><b>注意:</b> 所有元素都會被重新下載!",
+        "eas_reset_now": "立即重設",
+        "edit": "編輯",
+        "email": "郵件",
+        "email_and_dav": "郵件、日曆和聯絡人",
+        "empty": "沒有結果",
+        "encryption": "加密",
+        "excludes": "排除",
+        "expire_in": "過期於",
+        "fido2_webauthn": "FIDO2/WebAuthn",
+        "force_pw_update": "你<b>必須</b>設定一個新密碼以繼續使用相關服務。",
+        "from": "來自",
+        "generate": "生成",
+        "hour": "小時",
+        "hourly": "每小時",
+        "hours": "小時",
+        "in_use": "已使用",
+        "interval": "間隔",
+        "is_catch_all": "接收域名下的所有郵件",
+        "last_mail_login": "上一次信箱登入",
+        "last_pw_change": "上一次密碼更改",
+        "last_run": "上一次執行",
+        "last_ui_login": "上一次 UI 登入",
+        "loading": "載入中...",
+        "login_history": "登入紀錄",
+        "mailbox": "信箱",
+        "mailbox_details": "詳細資料",
+        "mailbox_general": "一般",
+        "mailbox_settings": "設定",
+        "messages": "訊息",
+        "month": "月",
+        "months": "月",
+        "never": "永不",
+        "new_password": "新密碼",
+        "new_password_repeat": "確認密碼 (再次輸入)",
+        "no_active_filter": "沒有啟用的過濾器",
+        "no_last_login": "沒有最後 UI 登入訊息",
+        "no_record": "沒有紀錄",
+        "open_logs": "開啟日誌",
+        "open_webmail_sso": "登入至網頁信箱",
+        "password": "密碼",
+        "password_now": "目前密碼 (確認更改)",
+        "password_repeat": "密碼 (再次輸入)",
+        "pushover_evaluate_x_prio": "急件發送高優先級郵件 [<code>X-Priority: 1</code>]",
+        "pushover_info": "推送通知設定會應用到所有遞送到 <b>%s</b> 的非垃圾郵件 (包括其共用、非公用、標註的別名)。",
+        "pushover_only_x_prio": "只發送高優先級郵件 [<code>X-Priority: 1</code>]",
+        "pushover_sender_array": "只發送以下寄件人 <small>(英文逗號分隔)</small>",
+        "pushover_sender_regex": "只發送匹配下列正規表示式的寄件人",
+        "pushover_text": "通知內文",
+        "pushover_title": "通知標題",
+        "pushover_vars": "如果沒有定義寄件人過濾器則會為所有郵件開啟通知推送。<br>正規表示式過濾器和寄件人清單可以個別設定,並且會按順序匹配,它們互不相關。<br>在文本和標題中可用的變數 (請注意資料保護政策)",
+        "pushover_verify": "驗證憑證",
+        "q_add_header": "垃圾郵件資料夾",
+        "q_all": "所有分類",
+        "q_reject": "拒絕",
+        "quarantine_category": "隔離通知分類",
+        "quarantine_category_info": "通知分類\"拒絕\"包含被拒絕接收的信件,而\"垃圾郵件資料夾\"則會通知使用者該信件被分類至垃圾信件。",
+        "quarantine_notification": "隔離通知",
+        "quarantine_notification_info": "一旦通知已被發送,它會被標記為\"已通知\"且不會被再次發送。",
+        "recent_successful_connections": "成功的連線",
+        "remove": "移除",
+        "running": "運行中",
+        "save": "儲存變更",
+        "save_changes": "儲存變更",
+        "sender_acl_disabled": "<span class=\"label label-danger\">寄件人檢查已關閉</span>",
+        "shared_aliases": "共享別名地址",
+        "shared_aliases_desc": "共用別名不會受使用者的個別設定如垃圾過濾器和加密規則等影響。共享別名地址的垃圾信件過濾只能由管理員透過域名級別規則修改。",
+        "show_sieve_filters": "顯示使用者啟用的 sieve 過濾器",
+        "sogo_profile_reset": "重設 SOGo 個人資料",
+        "sogo_profile_reset_help": "此動作會刪除使用者的 SOGo 個人檔案並<b>刪除所有聯繫人和日曆資料 (無法反悔)</b>。",
+        "sogo_profile_reset_now": "立即重設個人資料",
+        "spam_aliases": "臨時信箱別名",
+        "spam_score_reset": "重設為伺服器預設值",
+        "spamfilter": "垃圾信件過濾器",
+        "spamfilter_behavior": "評分",
+        "spamfilter_bl": "黑名單",
+        "spamfilter_bl_desc": "黑名單中地址<b>總是會</b>被標記為垃圾郵件並拒絕。被拒絕的郵件<b>不會</b>被複製至隔離區。可以使用萬用匹配字元\"*\"。此過濾器只會套用於直接別名 (只有一個目標信箱),而不會應用到\"接收所有信件\"別名和信箱地址本身。",
+        "spamfilter_default_score": "預設值",
+        "spamfilter_green": "綠色: 此訊息不是垃圾信件",
+        "spamfilter_hint": "第一個值表示\"低垃圾分數\",第二個值表示\"高垃圾分數\"。",
+        "spamfilter_red": "紅色: 此訊息是垃圾郵件且會被伺服器拒絕",
+        "spamfilter_table_action": "動作",
+        "spamfilter_table_add": "新增項目",
+        "spamfilter_table_domain_policy": "n/a (域名規則)",
+        "spamfilter_table_empty": "沒有資料可以顯示",
+        "spamfilter_table_remove": "移除",
+        "spamfilter_table_rule": "規則",
+        "spamfilter_wl": "白名單",
+        "spamfilter_wl_desc": "白名單中地址<b>永遠不會</b>被標記為垃圾郵件。可以使用萬用匹配字元\"*\"。此過濾器只會套用於直接別名 (只有一個目標信箱),而不會應用到\"接收所有信件\"別名和信箱地址本身。",
+        "spamfilter_yellow": "黃色: 此信件可能是垃圾消息,會被標記為垃圾信件並且移入垃圾信件資料夾",
+        "status": "狀態",
+        "sync_jobs": "同步任務",
+        "syncjob_check_log": "檢查日誌",
+        "syncjob_last_run_result": "上一次執行結果",
+        "syncjob_EX_OK": "成功",
+        "syncjob_EXIT_CONNECTION_FAILURE": "連線問題",
+        "syncjob_EXIT_TLS_FAILURE": "加密連線問題",
+        "syncjob_EXIT_AUTHENTICATION_FAILURE": "身分認證問題",
+        "syncjob_EXIT_OVERQUOTA": "目標信箱容量配額已滿",
+        "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "無法連接至遠端伺服器",
+        "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "錯誤的使用者名稱或密碼",
+        "tag_handling": "設定被標籤的郵件處理方式",
+        "tag_help_example": "標籤的信箱地址範例: me<b>+Facebook</b>@example.org",
+        "tag_help_explain": "置於子資料夾: 在收件夾下創建一個以標籤名命名的子資料夾 (\"INBOX/Facebook\")。<br>\r\n主旨: 標籤名會被前綴於郵件主旨, 如: \"[Facebook] 我的新聞\"。",
+        "tag_in_none": "不處理",
+        "tag_in_subfolder": "置於子資料夾",
+        "tag_in_subject": "置於主旨",
+        "text": "內文",
+        "title": "標題",
+        "tls_enforce_in": "強制連入 TLS",
+        "tls_enforce_out": "強制連外 TLS",
+        "tls_policy": "加密規則",
+        "tls_policy_warning": "<strong>警告:</strong>如果你決定強制加密信箱傳輸,你有可能會丟失郵件。<br>不支援加密的郵件系統將忽略傳入的信件。<br>此選項會套用到你的主信箱地址(登入名)、域名別名對應的地址和<b>只指向此信箱</b>(非共享)的別名地址。",
+        "user_settings": "使用者設定",
+        "username": "使用者名稱",
+        "verify": "驗證",
+        "waiting": "等待中",
+        "week": "週",
+        "weekly": "每週",
+        "weeks": "週",
+        "with_app_password": "使用應用程式密碼",
+        "year": "年",
+        "years": "年"
+    },
+    "warning": {
+        "cannot_delete_self": "不能刪除已登入的使用者",
+        "domain_added_sogo_failed": "域名已新增但是 SOGo 重新啟動失敗,請檢查紀錄檔。",
+        "dovecot_restart_failed": "Dovecot 重新啟動失敗,請檢查紀錄檔",
+        "fuzzy_learn_error": "模糊雜湊學習失敗: %s",
+        "hash_not_found": "找不到雜湊碼或已被刪除",
+        "ip_invalid": "略過不合規則的 IP: %s",
+        "is_not_primary_alias": "略過非主要的別名 %s",
+        "no_active_admin": "不能停用唯一的管理員",
+        "quota_exceeded_scope": "域名容量配額已滿: 此域名現在只能創建無限容量的信箱。",
+        "session_token": "表單驗證失敗: 驗證碼錯誤",
+        "session_ua": "表單驗證失敗: User-Agent 校驗錯誤"
+    }
+}

+ 6 - 6
data/web/templates/base.twig

@@ -44,13 +44,13 @@
           </div>
         </li>
         {% if mailcow_locale %}
-        <li class="nav-item dropdown{% if available_languages|length == 1 %}lang-link-disabled{% endif %}">
-          <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-expanded="false"><span class="flag-icon flag-icon-{{ mailcow_locale }}"></span></a>
-          <ul class="dropdown-menu" role="menu "aria-labelledby="languageDropdown">
+        <li class="dropdown{% if available_languages|length == 1 %}lang-link-disabled{% endif %}">
+          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span><span class="caret"></span></a>
+          <ul class="dropdown-menu" role="menu">
             {% for key, val in available_languages %}
-            <li>
-              <a class="dropdown-item {% if mailcow_locale == key %}active{% endif %}" href="?{{ query_string({'lang': key}) }}">
-                <span class="flag-icon flag-icon-{{ key }}"></span>{{ val }}
+            <li{% if mailcow_locale == key %} class="active"{% endif %}>
+              <a href="?{{ query_string({'lang': key}) }}">
+                <span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
               </a>
             </li>
             {% endfor %}

+ 5 - 5
data/web/templates/index.twig

@@ -48,14 +48,14 @@
               </div>
             </div>
             {% if not oauth2_request %}
-            <button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-xs-lg btn-secondary ms-auto dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-              <span class="flag-icon flag-icon-{{ mailcow_locale }}"></span>
+            <button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-xs-lg btn-default pull-right dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span> <span class="caret"></span>
             </button>
             <ul class="dropdown-menu ms-auto login">
               {% for key, val in available_languages %}
-                <li>
-                  <a class="dropdown-item {% if mailcow_locale == key %}active{% endif %}" href="?{{ query_string({'lang': key}) }}">
-                    <span class="flag-icon flag-icon-{{ key }}"></span>{{ val }}
+                <li{% if mailcow_locale == key %} class="active"{% endif %}>
+                  <a href="?{{ query_string({'lang': key}) }}">
+                    <span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
                   </a>
                 </li>
               {% endfor %}

+ 3 - 3
docker-compose.yml

@@ -168,7 +168,7 @@ services:
             - phpfpm
 
     sogo-mailcow:
-      image: mailcow/sogo:1.109
+      image: mailcow/sogo:1.111
       environment:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
@@ -215,7 +215,7 @@ services:
             - sogo
 
     dovecot-mailcow:
-      image: mailcow/dovecot:1.18
+      image: mailcow/dovecot:1.20
       depends_on:
         - mysql-mailcow
       dns:
@@ -295,7 +295,7 @@ services:
             - dovecot
 
     postfix-mailcow:
-      image: mailcow/postfix:1.67
+      image: mailcow/postfix:1.68
       depends_on:
         - mysql-mailcow
       volumes:

+ 11 - 4
generate_config.sh

@@ -141,15 +141,22 @@ echo "Available Branches:"
 echo "- master branch (stable updates) | default, recommended [1]"
 echo "- nightly branch (unstable updates, testing) | not-production ready [2]"
 sleep 1
-read -r -p  "Choose the Branch with it´s number [1/2] " branch
+
+while [ -z "${MAILCOW_BRANCH}" ]; do
+  read -r -p  "Choose the Branch with it´s number [1/2] " branch
   case $branch in
     [2])
-      git_branch="nightly"
+      MAILCOW_BRANCH="nightly"
       ;;
     *)
-      git_branch="master"
+      MAILCOW_BRANCH="master"
     ;;
   esac
+done
+
+if [ ! -z "${MAILCOW_BRANCH}" ]; then
+  git_branch=${MAILCOW_BRANCH}
+fi
 
 git fetch --all
 git checkout -f $git_branch
@@ -458,4 +465,4 @@ else
   echo '  $MAILCOW_UPDATEDAT='$(date +%s)';' >> data/web/inc/app_info.inc.php
   echo '?>' >> data/web/inc/app_info.inc.php
   echo -e "\e[33mCannot determine current git repository version...\e[0m"
-fi
+fi

+ 1 - 1
helper-scripts/add-new-lang-keys.php

@@ -26,7 +26,7 @@ if(empty($targetLang)) {
 }
 
 // load master lang
-$masterLang = file_get_contents(__DIR__.'/../data/web/lang/lang.en.json');
+$masterLang = file_get_contents(__DIR__.'/../data/web/lang/lang.en-gb.json');
 $masterLang = json_decode($masterLang, true);
 
 // load target lang

+ 1 - 1
helper-scripts/check_translations.rb

@@ -1,6 +1,6 @@
 #!/usr/bin/env ruby
 
-MASTER="en"
+MASTER="en-gb"
 
 DIR = "#{__dir__}/.."
 

+ 2 - 10
update.sh

@@ -178,7 +178,7 @@ remove_obsolete_nginx_ports() {
 detect_docker_compose_command(){
 if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
   if docker compose > /dev/null 2>&1; then
-      if docker compose version --short | grep "^2." > /dev/null 2>&1; then
+      if docker compose version --short | grep "2." > /dev/null 2>&1; then
         DOCKER_COMPOSE_VERSION=native
         COMPOSE_COMMAND="docker compose"
         echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
@@ -198,7 +198,7 @@ if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSIO
         echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
         echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
         sleep 2
-        echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.[0m"
+        echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
       else
         echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" 
         echo -e "\e[31mPlease update/install regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
@@ -362,14 +362,6 @@ if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox grep
 if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\""; exit 1; fi
 if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\""; exit 1; fi
 
-# Check if Docker Compose is older then v2 before continuing
-if ! $COMPOSE_COMMAND version --short | grep "^2." > /dev/null 2>&1; then
-  echo -e "\e[33mYour Docker Compose Version is not up to date!\e[0m"
-  echo -e "\e[33mmailcow needs Docker Compose > 2.X.X!\e[0m"
-  echo -e "\e[33mYour current installed Version: $($COMPOSE_COMMAND version --short)\e[0m"
-  exit 1
-fi
-
 CONFIG_ARRAY=(
   "SKIP_LETS_ENCRYPT"
   "SKIP_SOGO"

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно