mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2024-12-10 10:40:15 +02:00
Merge pull request #4808 from mailcow/feature/language-change
Backport Language Changer (+ Chinese Translation) to BS5
This commit is contained in:
commit
6ce25f38e1
@ -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
.github/workflows/image_builds.yml
vendored
3
.github/workflows/image_builds.yml
vendored
@ -5,6 +5,9 @@ on:
|
||||
branches: [ "master", "staging" ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
docker_image_builds:
|
||||
strategy:
|
||||
|
3
.github/workflows/integration_tests.yml
vendored
3
.github/workflows/integration_tests.yml
vendored
@ -5,6 +5,9 @@ on:
|
||||
branches: [ "master", "staging" ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
integration_tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -307,6 +307,7 @@ namespace {
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
cat <<EOF > /etc/dovecot/sogo_trusted_ip.conf
|
||||
# Autogenerated by mailcow
|
||||
remote ${IPV4_NETWORK}.248 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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&tlsVerifyMode=none</string>
|
||||
<key>SOGoSieveServer</key>
|
||||
<string>sieve://${IPV4_NETWORK}.250:4190/?TLS=YES&tlsVerifyMode=none</string>
|
||||
<key>SOGoSMTPServer</key>
|
||||
<string>smtp://${IPV4_NETWORK}.253:588/?TLS=YES&tlsVerifyMode=none</string>
|
||||
<key>SOGoTrustProxyAuthentication</key>
|
||||
<string>YES</string>
|
||||
<key>SOGoEncryptionKey</key>
|
||||
|
@ -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
|
||||
|
@ -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
data/web/api/index.css
Normal file
16
data/web/api/index.css
Normal file
@ -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;
|
||||
}
|
@ -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>
|
||||
|
@ -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
data/web/api/swagger-initializer.js
Normal file
19
data/web/api/swagger-initializer.js
Normal file
@ -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;
|
||||
};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
data/web/css/build/007-languages.min.css
vendored
2
data/web/css/build/007-languages.min.css
vendored
File diff suppressed because one or more lines are too long
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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
|
||||
|
@ -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čí",
|
@ -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": "✓",
|
||||
"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",
|
@ -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>",
|
@ -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ă)",
|
86
data/web/lang/lang.tr.json
Normal file
86
data/web/lang/lang.tr.json
Normal file
@ -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)"
|
||||
}
|
||||
}
|
1187
data/web/lang/lang.zh-tw.json
Normal file
1187
data/web/lang/lang.zh-tw.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -45,12 +45,12 @@
|
||||
</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>
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-expanded="false"><span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span></a>
|
||||
<ul class="dropdown-menu" role="menu "aria-labelledby="languageDropdown">
|
||||
{% 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 }}
|
||||
<span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -49,13 +49,13 @@
|
||||
</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>
|
||||
<span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></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 }}
|
||||
<span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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,6 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
MASTER="en"
|
||||
MASTER="en-gb"
|
||||
|
||||
DIR = "#{__dir__}/.."
|
||||
|
||||
|
12
update.sh
12
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"
|
||||
|
Loading…
Reference in New Issue
Block a user