Compare commits

..
Author SHA1 Message Date
FreddleSpl0it 55982ac74b Merge pull request #7537 from mailcow/staging
Update 2026-10
2026-10-21 19:36:04 +02:00
FreddleSpl0it 81f6f7b002 Merge pull request #7506 from mailcow/staging
Update 2026-09a
2026-10-06 12:28:36 +02:00
FreddleSpl0it ca07d8d333 Merge pull request #7477 from mailcow/staging
Update 2026-09
2026-09-21 10:30:34 +02:00
FreddleSpl0it 02552ffefd Merge pull request #7427 from mailcow/staging
Update 2026-07b
2026-08-18 09:29:10 +02:00
FreddleSpl0it 06424670fa Merge pull request #7393 from mailcow/staging
update README.md sponsors
2026-08-05 09:00:52 +02:00
FreddleSpl0it 489db90512 Merge pull request #7391 from mailcow/staging
Update 2026-07a
2026-07-30 11:17:15 +02:00
FreddleSpl0it 641ed63782 Merge pull request #7353 from mailcow/staging
update README.md sponsors
2026-07-15 08:31:16 +02:00
FreddleSpl0it 96a70652c3 Merge pull request #7328 from mailcow/staging
Update 2026-07
2026-07-13 10:42:55 +02:00
FreddleSpl0it 2ac4b1deae Merge pull request #7260 from mailcow/staging
Update 2026-05c
2026-05-26 10:50:58 +02:00
FreddleSpl0it 1eb6d2e26c Merge pull request #7243 from mailcow/staging
Update 2025-05b
2026-05-21 08:33:36 +02:00
FreddleSpl0it 384e2f6ac1 Merge pull request #7227 from mailcow/staging
Update 2026-05a
2026-05-13 10:42:14 +02:00
FreddleSpl0it 32156a337a Merge pull request #7221 from mailcow/staging
Update 2026-05
2026-05-12 08:46:25 +02:00
FreddleSpl0it 281cf93db3 Merge pull request #7174 from mailcow/staging
Update 2026-03b
2026-03-31 09:57:16 +02:00
FreddleSpl0it f399c07c85 Merge pull request #7137 from mailcow/staging
Hotfix 2026-03a
2026-03-13 14:15:04 +01:00
FreddleSpl0it a693325fe6 Merge pull request #7135 from mailcow/staging
Update 2026-03a
2026-03-13 13:16:32 +01:00
FreddleSpl0it 9ad84eee92 Merge pull request #7113 from mailcow/staging
Hotfix 2026-03
2026-03-10 13:50:33 +01:00
FreddleSpl0it b59869b720 Merge pull request #7109 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:39:43 +01:00
FreddleSpl0it 7515bef66c Merge pull request #7104 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:05:39 +01:00
FreddleSpl0it b84ba8ded1 Merge pull request #7101 from mailcow/staging
Update 2026-03
2026-03-10 08:08:29 +01:00
FreddleSpl0it 4845928e7a Merge pull request #7027 from mailcow/staging
[Hotfix] Update 2026-01
2026-01-29 10:31:23 +01:00
FreddleSpl0it 4ccfedd6b3 Merge pull request #7024 from mailcow/staging
🐄🛡️ January 2026 Update | Limited EAS/DAV Access and Restricted Alias Sending
2026-01-29 07:25:09 +01:00
Ashitaka e8d9315d4a Merge pull request #6905 from Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
2025-12-12 14:08:21 +01:00
DerLinkman d977ddb501 backup: add image prefetch function to verify latest image is used 2025-12-12 14:07:57 +01:00
DerLinkman e76f5237ed ofelia: revert fixed cron syntax for sa-rules download 2025-12-12 14:07:47 +01:00
CopilotandDerLinkman c11ed5dd1e Prevent duplicate/plaintext login announcement rendering (#6963)
* Initial plan

* Fix duplicate login announcement display

Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 14:07:36 +01:00
DerLinkman 4ef65fc382 Merge pull request #6948 from mailcow/staging
2025-12
2025-12-09 13:29:15 +01:00
115 changed files with 5462 additions and 17834 deletions

No files matched your search

-2
View File
@@ -79,5 +79,3 @@ update_diffs/
create_cold_standby.sh create_cold_standby.sh
!data/conf/nginx/mailcow_auth.conf !data/conf/nginx/mailcow_auth.conf
data/conf/postfix/postfix-tlspol data/conf/postfix/postfix-tlspol
tests/php/vendor/
tests/php/.phpunit.cache/
+2 -1
View File
@@ -68,6 +68,7 @@ RUN addgroup -g 5000 vmail \
perl-package-stash-xs \ perl-package-stash-xs \
perl-par-packer \ perl-par-packer \
perl-parse-recdescent \ perl-parse-recdescent \
perl-lockfile-simple \
libproc2 \ libproc2 \
perl-readonly \ perl-readonly \
perl-regexp-common \ perl-regexp-common \
@@ -119,7 +120,7 @@ COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
COPY imapsync /usr/local/bin/imapsync COPY imapsync /usr/local/bin/imapsync
COPY imapsync_runner.py /usr/local/bin/imapsync_runner.py COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl
COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve
COPY report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve COPY report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve
COPY rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham COPY rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham
@@ -279,7 +279,7 @@ chmod g+rw /dev/console
chown root:tty /dev/console chown root:tty /dev/console
chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \ chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \
/usr/lib/dovecot/sieve/rspamd-pipe-spam \ /usr/lib/dovecot/sieve/rspamd-pipe-spam \
/usr/local/bin/imapsync_runner.py \ /usr/local/bin/imapsync_runner.pl \
/usr/local/bin/imapsync \ /usr/local/bin/imapsync \
/usr/local/bin/trim_logs.sh \ /usr/local/bin/trim_logs.sh \
/usr/local/bin/sa-rules.sh \ /usr/local/bin/sa-rules.sh \
File diff suppressed because it is too large. Load diff
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/perl
use DBI;
use LockFile::Simple qw(lock trylock unlock);
use Proc::ProcessTable;
use Data::Dumper qw(Dumper);
use IPC::Run 'run';
use File::Temp;
use Try::Tiny;
use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT);
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
my $t = Proc::ProcessTable->new;
my $imapsync_running = grep { $_->{cmndline} =~ /imapsync\s/i } @{$t->table};
if ($imapsync_running ge 1)
{
print "imapsync is active, exiting...";
exit;
}
sub qqw($) {
my @params = ();
my @values = split(/(?=--)/, $_[0]);
foreach my $val (@values) {
my @tmpparam = split(/ /, $val, 2);
foreach my $tmpval (@tmpparam) {
if ($tmpval ne '') {
push @params, $tmpval;
}
}
}
foreach my $val (@params) {
$val=trim($val);
}
return @params;
}
$run_dir="/tmp";
$dsn = 'DBI:mysql:database=' . $ENV{'DBNAME'} . ';mysql_socket=/var/run/mysqld/mysqld.sock';
$lock_file = $run_dir . "/imapsync_busy";
$lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1);
$lockmgr->lock($lock_file) || die "can't lock ${lock_file}";
$dbh = DBI->connect($dsn, $ENV{'DBUSER'}, $ENV{'DBPASS'}, {
mysql_auto_reconnect => 1,
mysql_enable_utf8mb4 => 1
});
$dbh->do("UPDATE imapsync SET is_running = 0");
sub sig_handler {
# Send die to force exception in "run"
die "sig_handler received signal, preparing to exit...\n";
};
open my $file, '<', "/etc/sogo/sieve.creds";
my $creds = <$file>;
close $file;
my ($master_user, $master_pass) = split /:/, $creds;
my $sth = $dbh->prepare("SELECT id,
user1,
user2,
host1,
authmech1,
password1,
exclude,
port1,
enc1,
delete2duplicates,
maxage,
subfolder2,
delete1,
delete2,
automap,
skipcrossduplicates,
maxbytespersecond,
custom_params,
subscribeall,
timeout1,
timeout2,
dry
FROM imapsync
WHERE active = 1
AND is_running = 0
AND (
UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60
OR
last_run IS NULL)
ORDER BY last_run");
$sth->execute();
my $row;
while ($row = $sth->fetchrow_arrayref()) {
$id = @$row[0];
$user1 = @$row[1];
$user2 = @$row[2];
$host1 = @$row[3];
$authmech1 = @$row[4];
$password1 = @$row[5];
$exclude = @$row[6];
$port1 = @$row[7];
$enc1 = @$row[8];
$delete2duplicates = @$row[9];
$maxage = @$row[10];
$subfolder2 = @$row[11];
$delete1 = @$row[12];
$delete2 = @$row[13];
$automap = @$row[14];
$skipcrossduplicates = @$row[15];
$maxbytespersecond = @$row[16];
$custom_params = @$row[17];
$subscribeall = @$row[18];
$timeout1 = @$row[19];
$timeout2 = @$row[20];
$dry = @$row[21];
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
my $template = $run_dir . '/imapsync.XXXXXXX';
my $passfile1 = File::Temp->new(TEMPLATE => $template);
my $passfile2 = File::Temp->new(TEMPLATE => $template);
binmode( $passfile1, ":utf8" );
print $passfile1 "$password1\n";
print $passfile2 trim($master_pass) . "\n";
my @custom_params_a = qqw($custom_params);
my $custom_params_ref = \@custom_params_a;
my $generated_cmds = [ "/usr/local/bin/imapsync",
"--tmpdir", "/tmp",
"--nofoldersizes",
"--addheader",
($timeout1 le "0" ? () : ('--timeout1', $timeout1)),
($timeout2 le "0" ? () : ('--timeout2', $timeout2)),
($exclude eq "" ? () : ("--exclude", $exclude)),
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
($maxage eq "0" ? () : ('--maxage', $maxage)),
($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)),
($delete2duplicates ne "1" ? () : ('--delete2duplicates')),
($subscribeall ne "1" ? () : ('--subscribeall')),
($delete1 ne "1" ? () : ('--delete')),
($delete2 ne "1" ? () : ('--delete2')),
($automap ne "1" ? () : ('--automap')),
($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')),
(!defined($enc1) ? () : ($enc1)),
"--host1", $host1,
"--user1", $user1,
"--passfile1", $passfile1->filename,
"--port1", $port1,
"--host2", "localhost",
"--user2", $user2 . '*' . trim($master_user),
"--passfile2", $passfile2->filename,
($dry eq "1" ? ('--dry') : ()),
'--no-modulesversion',
'--noreleasecheck'];
try {
$is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1, success = NULL, exit_status = NULL WHERE id = ?");
$is_running->bind_param( 1, ${id} );
$is_running->execute();
run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout;
# check exit code and status
($exit_code, $exit_status) = ($stdout =~ m/Exiting\swith\sreturn\svalue\s(\d+)\s\(([^:)]+)/);
$success = 0;
if (defined $exit_code && $exit_code == 0) {
$success = 1;
}
$update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ? WHERE id = ?");
$update->bind_param( 1, ${stdout} );
$update->bind_param( 2, ${success} );
$update->bind_param( 3, ${exit_status} );
$update->bind_param( 4, ${id} );
$update->execute();
} catch {
$update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', success = 0 WHERE id = ?");
$update->bind_param( 1, ${id} );
$update->execute();
} finally {
$update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?");
$update->bind_param( 1, ${id} );
$update->execute();
};
}
$sth->finish();
$dbh->disconnect();
$lockmgr->unlock($lock_file);
-331
View File
@@ -1,331 +0,0 @@
#!/usr/bin/python3
import fcntl
import json
import os
import re
import signal
import subprocess
import sys
import tempfile
import time
import MySQLdb
import MySQLdb.cursors
run_dir = '/tmp'
# Max size of imapsync output stored in returned_text (the tail holds the summary)
max_output = 16 * 1024 * 1024
lock_file = run_dir + '/imapsync_busy.lock'
# An OAuth run needs a token valid this long at start (half the lifetime for short-lived tokens)
token_min_valid = 900
# Refreshed tokens are written to the token files of running jobs this often, imapsync re-reads them on reconnect
token_sync_interval = 60
# Next due job: higher prio first, never-run jobs first, then oldest run
next_sql = """SELECT i.id, i.user1, i.user2, i.password1, i.exclude, i.delete2duplicates, i.maxage,
i.subfolder2, i.delete1, i.delete2, i.automap, i.skipcrossduplicates, i.maxbytespersecond, i.custom_params,
i.subscribeall, i.timeout1, i.timeout2, i.dry, i.source_id,
s.host1, s.port1, s.enc1, s.auth_type, s.oauth_flow
FROM imapsync i JOIN imapsync_source s ON i.source_id = s.id
WHERE i.active = 1
AND s.active = 1
AND i.is_running = 0
AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(i.last_run) > i.mins_interval * 60 OR i.last_run IS NULL)
{skip}
ORDER BY i.prio DESC, i.last_run IS NOT NULL, i.last_run ASC, i.id ASC
LIMIT 1"""
# Atomic claim, a job is only started when this flips is_running 0 -> 1
claim_sql = """UPDATE imapsync SET is_running = 1, success = NULL, exit_status = NULL
WHERE id = %s AND is_running = 0 AND active = 1"""
# Access token: client_credentials share the source token, authorization_code use the token of
# source + local mailbox (user2) + remote user (user1). issued/error describe the last refresh.
source_token_sql = """SELECT oauth_access_token AS access_token, oauth_token_expires AS token_expires,
UNIX_TIMESTAMP(oauth_last_refresh_attempt) AS issued, oauth_last_refresh_error AS error
FROM imapsync_source WHERE id = %s"""
user_token_sql = """SELECT access_token, token_expires, UNIX_TIMESTAMP(oauth_last_refresh_attempt) AS issued,
last_refresh_error AS error FROM imapsync_source_oauth_token
WHERE source_id = %s AND owner = %s AND username = %s"""
conn = None
# Popen => job of running workers
running = {}
# Jobs released without a run (token not ready), skipped for the rest of this runner round
deferred = set()
stopping = False
def num(value):
try:
return int(value)
except (TypeError, ValueError):
return 0
def unlink(files):
for path in files:
try:
os.unlink(path)
except OSError:
pass
def imapsync_running():
for pid in filter(str.isdigit, os.listdir('/proc')):
try:
with open('/proc/' + pid + '/cmdline', 'rb') as f:
cmdline = f.read().replace(b'\0', b' ')
except OSError:
continue
if re.search(rb'imapsync\s', cmdline, re.I):
return True
return False
def connect():
return MySQLdb.connect(unix_socket='/var/run/mysqld/mysqld.sock', user=os.environ['DBUSER'],
passwd=os.environ['DBPASS'], db=os.environ['DBNAME'], charset='utf8mb4', autocommit=True)
# Reconnect when the server dropped the connection (syncs can outlast wait_timeout)
def query(sql, args=None):
global conn
try:
conn.ping()
except MySQLdb.Error:
conn = connect()
cur = conn.cursor(MySQLdb.cursors.DictCursor)
cur.execute(sql, args)
return cur
# Store a result and release the job
def finish_job(job_id, text, success, exit_status):
try:
query("UPDATE imapsync SET returned_text = %s, success = %s, exit_status = %s, last_run = NOW(), is_running = 0 WHERE id = %s",
(text, success, exit_status, job_id))
except MySQLdb.Error:
query("UPDATE imapsync SET success = 0, last_run = NOW(), is_running = 0 WHERE id = %s", (job_id,))
# Release a job without a run; last_run stays, so the next runner round retries it
def defer_job(job_id, text, exit_status):
query("UPDATE imapsync SET returned_text = %s, success = 0, exit_status = %s, is_running = 0 WHERE id = %s",
(text, exit_status, job_id))
deferred.add(job_id)
def oauth_token(j):
if j['oauth_flow'] == 'authorization_code':
return query(user_token_sql, (j['source_id'], j['user2'], j['user1'])).fetchone() or {}
return query(source_token_sql, (j['source_id'],)).fetchone() or {}
# Valid for token_min_valid, or half the lifetime of a short-lived token (known when its last refresh succeeded)
def token_ready(row):
token, expires, issued = row.get('access_token'), row.get('token_expires'), row.get('issued')
if not token or expires is None:
return False
need = token_min_valid
if issued and not row.get('error') and expires > int(issued):
need = min(need, (expires - int(issued)) // 2)
return expires - time.time() >= need
# Write a refreshed token over the token file of a running job (atomic, same path)
def sync_token_file(oauth):
token = oauth_token(oauth['job']).get('access_token')
if not token or token == oauth['token']:
return
fd, tmp = tempfile.mkstemp(prefix='imapsync.', dir=run_dir)
try:
with os.fdopen(fd, 'w', encoding='utf-8') as f:
f.write(token + '\n')
os.replace(tmp, oauth['file'])
oauth['token'] = token
except OSError:
unlink([tmp])
# Max concurrent imapsync processes and global per-process bandwidth cap in bytes/s (0 = off),
# re-read before every claim round so changes apply to long runs
def read_settings():
settings = {r['name']: r['value'] for r in query("SELECT name, value FROM imapsync_settings")}
return max(num(settings.get('max_parallel')), 1), max(num(settings.get('max_bytes_per_second')), 0)
# Build temp files and argv for a claimed job. Returns None when the job was finished without running imapsync.
def prepare_job(j, global_bw, files):
def tmpfile(content):
fd, path = tempfile.mkstemp(prefix='imapsync.', dir=run_dir)
files.append(path)
with os.fdopen(fd, 'w', encoding='utf-8') as f:
f.write(content)
return path
auth_type = j['auth_type'] or 'PLAIN'
oauth = None
if auth_type == 'XOAUTH2':
row = oauth_token(j)
if not token_ready(row):
defer_job(j['id'], "OAuth access token for the configured source is missing or expires too soon; refresh cron will retry.",
'OAUTH_TOKEN_NOT_READY')
return None
oauth = {'job': j, 'token': row['access_token'], 'file': tmpfile(row['access_token'] + '\n')}
auth_args = ['--authmech1', 'XOAUTH2', '--oauthaccesstoken1', oauth['file']]
else:
auth_args = ['--passfile1', tmpfile(j['password1'] + '\n')]
# imapsync auto-picks PLAIN, only pass --authmech1 for LOGIN/CRAM-MD5
if auth_type != 'PLAIN':
auth_args += ['--authmech1', auth_type]
# Custom params: JSON array of {o,v}, validated by the backend on write. One argv token per pair,
# "--opt" or "--opt=value" (value glued to its option, no shell)
custom_params = []
try:
pairs = json.loads(j['custom_params'] or '[]')
except ValueError:
pairs = []
for p in pairs if isinstance(pairs, list) else []:
if not isinstance(p, dict) or not isinstance(p.get('o'), str) or not re.fullmatch(r'[a-z0-9]+', p['o']):
continue
value = '' if p.get('v') is None else str(p['v'])
custom_params.append('--' + p['o'] if value == '' else '--' + p['o'] + '=' + value)
# Effective per-process bandwidth cap (bytes/s): per-job value, capped by the global limit
eff_bw = num(j['maxbytespersecond'])
if global_bw > 0 and (eff_bw <= 0 or eff_bw > global_bw):
eff_bw = global_bw
# --nolog: the output is stored in returned_text, imapsync's own log dir would be the (read-only) cwd
cmd = ['/usr/local/bin/imapsync', '--tmpdir', '/tmp', '--nolog', '--nofoldersizes', '--addheader']
if num(j['timeout1']) > 0:
cmd += ['--timeout1', str(j['timeout1'])]
if num(j['timeout2']) > 0:
cmd += ['--timeout2', str(j['timeout2'])]
if j['exclude']:
cmd += ['--exclude', j['exclude']]
if j['subfolder2']:
cmd += ['--subfolder2', j['subfolder2']]
if num(j['maxage']) != 0:
cmd += ['--maxage', str(j['maxage'])]
if eff_bw > 0:
cmd += ['--maxbytespersecond', str(eff_bw)]
for col, opt in (('delete2duplicates', '--delete2duplicates'), ('subscribeall', '--subscribeall'),
('delete1', '--delete1'), ('delete2', '--delete2'), ('automap', '--automap'),
('skipcrossduplicates', '--skipcrossduplicates')):
if j[col] == 1:
cmd.append(opt)
enc1 = {'TLS': '--tls1', 'SSL': '--ssl1'}.get(j['enc1'])
if enc1:
cmd.append(enc1)
cmd += ['--host1', j['host1'], '--user1', j['user1']] + auth_args + ['--port1', str(j['port1'])]
cmd += ['--host2', 'localhost', '--user2', j['user2'] + '*' + master_user, '--passfile2', tmpfile(master_pass + '\n')]
if j['dry'] == 1:
cmd.append('--dry')
cmd += ['--no-modulesversion', '--noreleasecheck'] + custom_params
return {'id': j['id'], 'files': files, 'outfile': tmpfile(''), 'cmd': cmd, 'oauth': oauth}
# Worker: imapsync with output to a file, no inherited fds (DB socket, lock)
def start_job(job):
with open(job['outfile'], 'wb') as out:
return subprocess.Popen(job['cmd'], stdin=subprocess.DEVNULL, stdout=out, stderr=subprocess.STDOUT)
# Store the result of a finished worker and remove its temp files
def reap_job(job):
stdout = b''
try:
with open(job['outfile'], 'rb') as f:
size = os.fstat(f.fileno()).st_size
if size > max_output:
f.seek(size - max_output)
stdout = b'[output truncated, last %d bytes]\n' % max_output
stdout += f.read()
except OSError:
pass
unlink(job['files'])
text = stdout.decode('utf-8', 'replace')
# Last match: the echoed command line at the top may contain user controlled values
m = re.findall(r'Exiting\swith\sreturn\svalue\s(\d+)\s\(([^:)]+)', text)
exit_code, exit_status = m[-1] if m else (None, None)
if exit_code is None:
text = 'Could not start or finish imapsync\n' + text
finish_job(job['id'], text, 1 if exit_code is not None and int(exit_code) == 0 else 0, exit_status)
# Stop claiming new jobs, pass the signal on to the running imapsync processes
def stop(signum, frame):
global stopping
stopping = True
for proc in list(running):
proc.send_signal(signal.SIGTERM)
# Released by the kernel when the process ends, a crashed run leaves no stale lock
lock_fd = os.open(lock_file, os.O_RDWR | os.O_CREAT, 0o600)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
sys.exit("can't lock " + lock_file)
if imapsync_running():
print('imapsync is active, exiting...')
sys.exit(0)
for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT):
signal.signal(sig, stop)
conn = connect()
# Stale flags from crashed runs, safe only here: under the lock and with no imapsync running
query("UPDATE imapsync SET is_running = 0")
with open('/etc/sogo/sieve.creds') as f:
master_user, master_pass = [s.strip() for s in f.readline().split(':', 1)]
# Worker pool: keep up to max_parallel workers busy, claim the next due job whenever a slot is free
# (after every worker exit and at least every 10 seconds while workers run)
last_token_sync = time.monotonic()
try:
while True:
for proc, job in list(running.items()):
if proc.poll() is not None:
del running[proc]
reap_job(job)
if time.monotonic() - last_token_sync >= token_sync_interval:
last_token_sync = time.monotonic()
for job in running.values():
if job['oauth']:
try:
sync_token_file(job['oauth'])
except MySQLdb.Error as e:
print('imapsync job %d: %s' % (job['id'], e), file=sys.stderr)
max_parallel, global_bw = read_settings()
# Deferred and finished jobs drop out of next_sql, only failed claims are capped
failed_claims = 0
while not stopping and len(running) < max_parallel and failed_claims < 100:
skip = ('AND i.id NOT IN (%s)' % ', '.join(['%s'] * len(deferred))) if deferred else ''
j = query(next_sql.format(skip=skip), tuple(deferred) or None).fetchone()
if not j:
break
if query(claim_sql, (j['id'],)).rowcount != 1:
failed_claims += 1
continue
files = []
try:
job = prepare_job(j, global_bw, files)
except Exception as e:
print('imapsync job %d: %s' % (j['id'], e), file=sys.stderr)
finish_job(j['id'], 'Could not start or finish imapsync', 0, None)
job = None
if not job:
unlink(files)
continue
try:
running[start_job(job)] = job
except OSError as e:
# Release the claim, the job is picked up again later
print('imapsync job %d: %s' % (j['id'], e), file=sys.stderr)
unlink(files)
query("UPDATE imapsync SET is_running = 0 WHERE id = %s", (j['id'],))
break
if not running:
break
# A worker exit ends the wait early
for _ in range(50):
if any(proc.poll() is not None for proc in running):
break
time.sleep(0.2)
finally:
# Never leave password/token files behind
for job in running.values():
unlink(job['files'])
@@ -1,73 +0,0 @@
<?php
require_once(__DIR__ . '/../web/inc/vars.inc.php');
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
}
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
} catch (PDOException $e) {
fwrite(STDERR, "DB connect failed: " . $e->getMessage() . "\n");
exit(1);
}
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
} else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
} catch (Exception $e) {
fwrite(STDERR, "Redis connect failed: " . $e->getMessage() . "\n");
exit(1);
}
function logMsg($priority, $message, $task = "imapsync OAuth refresh") {
global $redis;
$redis->lPush('CRON_LOG', json_encode(array(
"time" => time(),
"priority" => $priority,
"task" => $task,
"message" => $message,
)));
}
require_once __DIR__ . '/../web/inc/functions.inc.php';
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
require_once __DIR__ . '/../web/inc/sessions.inc.php';
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
// File-lock to prevent concurrent runs
$lock_file = '/tmp/imapsync-oauth-refresh.lock';
if (file_exists($lock_file)) {
$pid = (int)trim(@file_get_contents($lock_file));
if ($pid > 0 && posix_kill($pid, 0)) {
logMsg("info", "Previous refresh still running (pid=$pid), exiting");
exit(0);
}
@unlink($lock_file);
}
file_put_contents($lock_file, getmypid());
try {
// client_credentials source tokens, unused per-user tokens (1 day grace), authorization_code per-user tokens;
// failing tokens are retried 15 minutes after their last attempt (C2), errors are sanitized
$imapsync = \Mailcow\Imapsync\ImapsyncModule::create($pdo, \Mailcow\Core\Context::system(), [
'hostname' => (string)getenv('MAILCOW_HOSTNAME'),
]);
foreach ($imapsync->oauth->refreshDue() as $e) {
logMsg($e['priority'], $e['message']);
}
} finally {
@unlink($lock_file);
}
-2
View File
@@ -9,7 +9,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$js_minifier->add('/web/js/site/imapsync.js');
$js_minifier->add('/web/js/site/mailbox.js'); $js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js'); $js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js'); $js_minifier->add('/web/js/site/pwgen.js');
@@ -43,7 +42,6 @@ $template_data = [
'lang_rl' => json_encode($lang['ratelimit']), 'lang_rl' => json_encode($lang['ratelimit']),
'lang_edit' => json_encode($lang['edit']), 'lang_edit' => json_encode($lang['edit']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
]; ];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
-1
View File
@@ -118,7 +118,6 @@ $template_data = [
'ui_themes' => customize('get', 'ui_themes'), 'ui_themes' => customize('get', 'ui_themes'),
'ui_theme' => customize('get', 'ui_theme'), 'ui_theme' => customize('get', 'ui_theme'),
'password_complexity' => password_complexity('get'), 'password_complexity' => password_complexity('get'),
'imapsync_settings' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getSettings(),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings, 'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', 'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
+157 -1003
View File
File diff suppressed because it is too large. Load diff
-2
View File
@@ -9,7 +9,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$js_minifier->add('/web/js/site/imapsync.js');
$js_minifier->add('/web/js/site/mailbox.js'); $js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js'); $js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js'); $js_minifier->add('/web/js/site/pwgen.js');
@@ -43,7 +42,6 @@ $template_data = [
'lang_rl' => json_encode($lang['ratelimit']), 'lang_rl' => json_encode($lang['ratelimit']),
'lang_edit' => json_encode($lang['edit']), 'lang_edit' => json_encode($lang['edit']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
]; ];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
+1 -9
View File
@@ -204,15 +204,9 @@ if (isset($_SESSION['mailcow_cc_role'])) {
if (isset($_GET['syncjob']) && if (isset($_GET['syncjob']) &&
is_numeric($_GET['syncjob'])) { is_numeric($_GET['syncjob'])) {
$id = $_GET["syncjob"]; $id = $_GET["syncjob"];
$result = \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getJob($id); $result = mailbox('get', 'syncjob_details', $id);
$template = 'edit/syncjob.twig'; $template = 'edit/syncjob.twig';
} }
elseif (isset($_GET['syncjob_source']) &&
is_numeric($_GET['syncjob_source'])) {
$id = $_GET["syncjob_source"];
$result = \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getSource($id);
$template = 'edit/imapsync_source.twig';
}
elseif (isset($_GET['filter']) && elseif (isset($_GET['filter']) &&
is_numeric($_GET['filter'])) { is_numeric($_GET['filter'])) {
$id = $_GET["filter"]; $id = $_GET["filter"];
@@ -231,7 +225,6 @@ else {
$template_data['access_denied'] = true; $template_data['access_denied'] = true;
} }
$js_minifier->add('/web/js/site/imapsync.js');
$js_minifier->add('/web/js/site/edit.js'); $js_minifier->add('/web/js/site/edit.js');
$js_minifier->add('/web/js/site/pwgen.js'); $js_minifier->add('/web/js/site/pwgen.js');
@@ -240,6 +233,5 @@ $template_data['return_to'] = $_SESSION['return_to'];
$template_data['lang_user'] = json_encode($lang['user']); $template_data['lang_user'] = json_encode($lang['user']);
$template_data['lang_admin'] = json_encode($lang['admin']); $template_data['lang_admin'] = json_encode($lang['admin']);
$template_data['lang_datatables'] = json_encode($lang['datatables']); $template_data['lang_datatables'] = json_encode($lang['datatables']);
$template_data['imapsync_redirect_uri'] = \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri();
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
+1 -1
View File
@@ -6,7 +6,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
} }
if (isset($_GET['id']) && is_numeric($_GET['id'])) { if (isset($_GET['id']) && is_numeric($_GET['id'])) {
if ($details = \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getJob(intval($_GET['id']))) { if ($details = mailbox('get', 'syncjob_details', intval($_GET['id']))) {
echo (empty($details['log'])) ? '-' : $details['log']; echo (empty($details['log'])) ? '-' : $details['log'];
} }
} }
-1
View File
@@ -72,7 +72,6 @@ $globalVariables = [
'lang_fido2' => json_encode($lang['fido2']), 'lang_fido2' => json_encode($lang['fido2']),
'lang_success' => json_encode($lang['success']), 'lang_success' => json_encode($lang['success']),
'lang_danger' => json_encode($lang['danger']), 'lang_danger' => json_encode($lang['danger']),
'lang_syncjobs' => json_encode($lang['syncjobs']),
'docker_timeout' => $DOCKER_TIMEOUT, 'docker_timeout' => $DOCKER_TIMEOUT,
'session_lifetime' => (int)$SESSION_LIFETIME, 'session_lifetime' => (int)$SESSION_LIFETIME,
'csrf_token' => $_SESSION['CSRF']['TOKEN'], 'csrf_token' => $_SESSION['CSRF']['TOKEN'],
+1 -5
View File
@@ -103,7 +103,7 @@ function admin($_action, $_data = null) {
continue; continue;
} }
} }
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new)) || $username_new == 'API') { if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),
@@ -179,9 +179,6 @@ function admin($_action, $_data = null) {
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(':username_new' => $username_new, ':username' => $username)); $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
} }
if ($username_new != $username) {
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->accountChanged($username, $username_new);
}
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
@@ -214,7 +211,6 @@ function admin($_action, $_data = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
)); ));
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->accountChanged($username);
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
+1 -6
View File
@@ -156,7 +156,7 @@ function domain_admin($_action, $_data = null) {
} }
} }
} }
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new)) || $username_new == 'API') { if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),
@@ -183,9 +183,6 @@ function domain_admin($_action, $_data = null) {
':username_new' => $username_new, ':username_new' => $username_new,
':username' => $username ':username' => $username
)); ));
if ($username_new != $username) {
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->accountChanged($username, $username_new);
}
if (!empty($domains)) { if (!empty($domains)) {
foreach ($domains as $domain) { foreach ($domains as $domain) {
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
@@ -334,8 +331,6 @@ function domain_admin($_action, $_data = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
)); ));
// sync sources created by this account must not pass to a re-created account of the same name
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->accountChanged($username);
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),
+484 -9
View File
@@ -302,6 +302,192 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_modified', $username) 'msg' => array('mailbox_modified', $username)
); );
break; break;
case 'syncjob':
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
else {
$username = $_data['username'];
}
}
elseif ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'no_user_defined'
);
return false;
}
$active = intval($_data['active']);
$subscribeall = intval($_data['subscribeall']);
$delete2duplicates = intval($_data['delete2duplicates']);
$delete1 = intval($_data['delete1']);
$delete2 = intval($_data['delete2']);
$timeout1 = intval($_data['timeout1']);
$timeout2 = intval($_data['timeout2']);
$skipcrossduplicates = intval($_data['skipcrossduplicates']);
$automap = intval($_data['automap']);
$dry = intval($_data['dry']);
$port1 = $_data['port1'];
$host1 = strtolower($_data['host1']);
$password1 = $_data['password1'];
$exclude = $_data['exclude'];
$maxage = $_data['maxage'];
$maxbytespersecond = $_data['maxbytespersecond'];
$subfolder2 = $_data['subfolder2'];
$user1 = $_data['user1'];
$mins_interval = $_data['mins_interval'];
$enc1 = $_data['enc1'];
$custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']);
// validate custom params
foreach (explode('-', $custom_params) as $param){
if(empty($param)) continue;
// extract option
if (str_contains($param, '=')) $param = explode('=', $param)[0];
else $param = rtrim($param, ' ');
// remove first char if first char is -
if ($param[0] == '-') $param = ltrim($param, $param[0]);
if (str_contains($param, ' ')) {
// bad char
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'bad character SPACE'
);
return false;
}
// check if param is whitelisted
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
// bad option
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'bad option '. $param
);
return false;
}
}
if (empty($subfolder2)) {
$subfolder2 = "";
}
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$maxage = "0";
}
if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout1 = "600";
}
if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout2 = "600";
}
if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
$maxbytespersecond = "0";
}
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
// if (!is_valid_domain_name($host1)) {
// $_SESSION['return'][] = array(
// 'type' => 'danger',
// 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
// 'msg' => 'access_denied'
// );
// return false;
// }
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (@preg_match("/" . $exclude . "/", null) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT '1' FROM `imapsync`
WHERE `user2` = :user2 AND `user1` = :user1 AND `host1` = :host1");
$stmt->execute(array(':user1' => $user1, ':user2' => $username, ':host1' => $host1));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('object_exists', htmlspecialchars($host1 . ' / ' . $user1))
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `dry`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`)
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :dry, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)");
$stmt->execute(array(
':user2' => $username,
':custom_params' => $custom_params,
':exclude' => $exclude,
':maxage' => $maxage,
':delete1' => $delete1,
':delete2' => $delete2,
':timeout1' => $timeout1,
':timeout2' => $timeout2,
':automap' => $automap,
':skipcrossduplicates' => $skipcrossduplicates,
':maxbytespersecond' => $maxbytespersecond,
':subscribeall' => $subscribeall,
':dry' => $dry,
':subfolder2' => $subfolder2,
':host1' => $host1,
':authmech1' => 'PLAIN',
':user1' => $user1,
':password1' => $password1,
':mins_interval' => $mins_interval,
':port1' => $port1,
':enc1' => $enc1,
':delete2duplicates' => $delete2duplicates,
':active' => $active,
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
break;
case 'domain': case 'domain':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -2094,6 +2280,202 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
} }
break; break;
case 'syncjob':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($ids as $id) {
$is_now = mailbox('get', 'syncjob_details', $id, array('with_password'));
if (!empty($is_now)) {
$username = $is_now['user2'];
$user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run'];
$success = (isset($_data['success'])) ? NULL : $is_now['success'];
$delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
$subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall'];
$dry = (isset($_data['dry'])) ? intval($_data['dry']) : $is_now['dry'];
$delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
$delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
$automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap'];
$skipcrossduplicates = (isset($_data['skipcrossduplicates'])) ? intval($_data['skipcrossduplicates']) : $is_now['skipcrossduplicates'];
$port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
$password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
$host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
$subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2'];
$enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1'];
$mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
$exclude = (isset($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
$custom_params = (isset($_data['custom_params'])) ? $_data['custom_params'] : $is_now['custom_params'];
$maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
$maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond'];
$timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1'];
$timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
// validate custom params
foreach (explode('-', $custom_params) as $param){
if(empty($param)) continue;
// extract option
if (str_contains($param, '=')) $param = explode('=', $param)[0];
else $param = rtrim($param, ' ');
// remove first char if first char is -
if ($param[0] == '-') $param = ltrim($param, $param[0]);
if (str_contains($param, ' ')) {
// bad char
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'bad character SPACE'
);
return false;
}
// check if param is whitelisted
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
// bad option
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'bad option '. $param
);
return false;
}
}
if (empty($subfolder2)) {
$subfolder2 = "";
}
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$maxage = "0";
}
if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout1 = "600";
}
if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout2 = "600";
}
if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
$maxbytespersecond = "0";
}
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!is_valid_domain_name($host1)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (@preg_match("/" . $exclude . "/", null) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1,
`delete2` = :delete2,
`automap` = :automap,
`skipcrossduplicates` = :skipcrossduplicates,
`maxage` = :maxage,
`maxbytespersecond` = :maxbytespersecond,
`subfolder2` = :subfolder2,
`exclude` = :exclude,
`host1` = :host1,
`last_run` = :last_run,
`success` = :success,
`user1` = :user1,
`password1` = :password1,
`mins_interval` = :mins_interval,
`port1` = :port1,
`enc1` = :enc1,
`delete2duplicates` = :delete2duplicates,
`custom_params` = :custom_params,
`timeout1` = :timeout1,
`timeout2` = :timeout2,
`subscribeall` = :subscribeall,
`dry` = :dry,
`active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':delete1' => $delete1,
':delete2' => $delete2,
':automap' => $automap,
':skipcrossduplicates' => $skipcrossduplicates,
':id' => $id,
':exclude' => $exclude,
':maxage' => $maxage,
':maxbytespersecond' => $maxbytespersecond,
':subfolder2' => $subfolder2,
':host1' => $host1,
':user1' => $user1,
':password1' => $password1,
':last_run' => $last_run,
':success' => $success,
':mins_interval' => $mins_interval,
':port1' => $port1,
':enc1' => $enc1,
':delete2duplicates' => $delete2duplicates,
':custom_params' => $custom_params,
':timeout1' => $timeout1,
':timeout2' => $timeout2,
':subscribeall' => $subscribeall,
':dry' => $dry,
':active' => $active,
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'filter': case 'filter':
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -3270,9 +3652,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
} }
// Sync sources created by / targeting the mailbox and its OAuth tokens (owner); a PDOException rolls back below
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->sources->onAccountRenamed($old_username, $new_username);
// Update c_uid, c_name and mail in _sogo_static_view table // Update c_uid, c_name and mail in _sogo_static_view table
$pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username") $pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username")
->execute([ ->execute([
@@ -4224,6 +4603,68 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
return false; return false;
break; break;
case 'syncjob_details':
$syncjobdetails = array();
if (!is_numeric($_data)) {
return false;
}
if (isset($_extra) && in_array('no_log', $_extra)) {
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("returned_text", "password1")');
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
while($field = array_shift($fields)) {
$shown_fields[] = $field['Field'];
}
$stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ",
`active`
FROM `imapsync` WHERE id = :id");
}
elseif (isset($_extra) && in_array('with_password', $_extra)) {
$stmt = $pdo->prepare("SELECT *,
`active`
FROM `imapsync` WHERE id = :id");
}
else {
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("password1")');
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
while($field = array_shift($fields)) {
$shown_fields[] = $field['Field'];
}
$stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ",
`active`
FROM `imapsync` WHERE id = :id");
}
$stmt->execute(array(':id' => $_data));
$syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($syncjobdetails['returned_text'])) {
$syncjobdetails['log'] = $syncjobdetails['returned_text'];
}
else {
$syncjobdetails['log'] = '';
}
unset($syncjobdetails['returned_text']);
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $syncjobdetails['user2'])) {
return false;
}
return $syncjobdetails;
break;
case 'syncjobs':
$syncjobdata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$syncjobdata[] = $row['id'];
}
return $syncjobdata;
break;
case 'spam_score': case 'spam_score':
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
@@ -5053,6 +5494,46 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
break; break;
case 'delete': case 'delete':
switch ($_type) { switch ($_type) {
case 'syncjob':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($ids as $id) {
if (!is_numeric($id)) {
return false;
}
$stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` WHERE id = :id");
$stmt->execute(array(':id' => $id));
$user2 = $stmt->fetch(PDO::FETCH_ASSOC)['user2'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $user2)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('deleted_syncjob', $id)
);
}
break;
case 'filter': case 'filter':
if (!is_array($_data['id'])) { if (!is_array($_data['id'])) {
$ids = array(); $ids = array();
@@ -5283,10 +5764,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':domain' => $domain, ':domain' => $domain,
)); ));
$stmt = $pdo->prepare("DELETE FROM `imapsync_source_domain` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain"); $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain");
$stmt->execute(array( $stmt->execute(array(
':domain' => $domain, ':domain' => $domain,
@@ -5594,8 +6071,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
)); ));
// Tokens, scope targets and own sync sources of the mailbox
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->accountChanged($username);
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username"); $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username");
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
+19 -293
View File
@@ -4,7 +4,7 @@ function init_db_schema()
try { try {
global $pdo; global $pdo;
$db_version = "16102026_1200"; $db_version = "18082026_1200";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -755,153 +755,35 @@ function init_db_schema()
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
"imapsync_source" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"name" => "VARCHAR(100) NOT NULL",
"description" => "VARCHAR(255) NOT NULL DEFAULT ''",
"created_by" => "VARCHAR(255) NOT NULL DEFAULT ''",
"scope" => "ENUM('all','domain','user') NOT NULL DEFAULT 'user'",
"host1" => "VARCHAR(255) NOT NULL",
"port1" => "SMALLINT UNSIGNED NOT NULL",
"enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
"auth_type" => "ENUM('PLAIN','LOGIN','CRAM-MD5','XOAUTH2') DEFAULT 'PLAIN'",
"oauth_flow" => "ENUM('client_credentials','authorization_code') NOT NULL DEFAULT 'client_credentials'",
"oauth_token_endpoint" => "VARCHAR(500) DEFAULT NULL",
"oauth_authorize_endpoint" => "VARCHAR(500) DEFAULT NULL",
"oauth_userinfo_endpoint" => "VARCHAR(500) DEFAULT NULL",
"oauth_client_id" => "VARCHAR(500) DEFAULT NULL",
"oauth_client_secret" => "VARCHAR(500) DEFAULT NULL",
"oauth_scope" => "VARCHAR(500) DEFAULT NULL",
"oauth_extra_params" => "TEXT DEFAULT NULL",
"oauth_access_token" => "TEXT DEFAULT NULL",
"oauth_token_expires" => "BIGINT UNSIGNED DEFAULT NULL",
"oauth_last_refresh_error" => "TEXT DEFAULT NULL",
"oauth_last_refresh_attempt" => "DATETIME NULL DEFAULT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"unique" => array(
"uniq_creator_name" => array("created_by", "name")
),
"key" => array(
"idx_created_by" => array("created_by"),
"idx_scope" => array("scope")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync_source_domain" => array(
"cols" => array(
"source_id" => "INT NOT NULL",
"domain" => "VARCHAR(255) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("source_id", "domain")
),
"key" => array(
"idx_domain" => array("domain")
),
"fkey" => array(
"fk_imapsync_source_domain" => array(
"col" => "source_id",
"ref" => "imapsync_source.id",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync_source_user" => array(
"cols" => array(
"source_id" => "INT NOT NULL",
"username" => "VARCHAR(255) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("source_id", "username")
),
"key" => array(
"idx_username" => array("username")
),
"fkey" => array(
"fk_imapsync_source_user" => array(
"col" => "source_id",
"ref" => "imapsync_source.id",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync_source_oauth_token" => array(
"cols" => array(
"source_id" => "INT NOT NULL",
"owner" => "VARCHAR(255) NOT NULL",
"username" => "VARCHAR(255) NOT NULL",
"access_token" => "TEXT DEFAULT NULL",
"refresh_token" => "TEXT DEFAULT NULL",
"token_expires" => "BIGINT UNSIGNED DEFAULT NULL",
"last_refresh_error" => "TEXT DEFAULT NULL",
"oauth_last_refresh_attempt" => "DATETIME NULL DEFAULT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("source_id", "owner", "username")
),
"key" => array(
"idx_owner" => array("owner")
),
"fkey" => array(
"fk_imapsync_source_oauth_token" => array(
"col" => "source_id",
"ref" => "imapsync_source.id",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync" => array( "imapsync" => array(
"cols" => array( "cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT", "id" => "INT NOT NULL AUTO_INCREMENT",
"user2" => "VARCHAR(255) NOT NULL", "user2" => "VARCHAR(255) NOT NULL",
"source_id" => "INT NOT NULL", "host1" => "VARCHAR(255) NOT NULL",
"authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'",
"regextrans2" => "VARCHAR(255) DEFAULT ''", "regextrans2" => "VARCHAR(255) DEFAULT ''",
"authmd51" => "TINYINT(1) NOT NULL DEFAULT 0", "authmd51" => "TINYINT(1) NOT NULL DEFAULT 0",
"domain2" => "VARCHAR(255) NOT NULL DEFAULT ''", "domain2" => "VARCHAR(255) NOT NULL DEFAULT ''",
"subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''", "subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''",
"user1" => "VARCHAR(255) NOT NULL", "user1" => "VARCHAR(255) NOT NULL",
"password1" => "VARCHAR(255) NOT NULL DEFAULT ''", "password1" => "VARCHAR(255) NOT NULL",
"exclude" => "VARCHAR(500) NOT NULL DEFAULT ''", "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''",
"maxage" => "SMALLINT NOT NULL DEFAULT '0'", "maxage" => "SMALLINT NOT NULL DEFAULT '0'",
"mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'", "mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'",
"maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'", "maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'",
"port1" => "SMALLINT UNSIGNED NOT NULL",
"enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
"delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'", "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
"delete1" => "TINYINT(1) NOT NULL DEFAULT '0'", "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
"delete2" => "TINYINT(1) NOT NULL DEFAULT '0'", "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
"automap" => "TINYINT(1) NOT NULL DEFAULT '0'", "automap" => "TINYINT(1) NOT NULL DEFAULT '0'",
"skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'", "skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'",
"custom_params" => "TEXT", "custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''",
// original legacy custom_params string, only set when options were dropped during migration (never used by the runner)
"custom_params_legacy" => "TEXT DEFAULT NULL",
"timeout1" => "SMALLINT NOT NULL DEFAULT '600'", "timeout1" => "SMALLINT NOT NULL DEFAULT '600'",
"timeout2" => "SMALLINT NOT NULL DEFAULT '600'", "timeout2" => "SMALLINT NOT NULL DEFAULT '600'",
"subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'", "subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'",
"dry" => "TINYINT(1) NOT NULL DEFAULT '0'", "dry" => "TINYINT(1) NOT NULL DEFAULT '0'",
"is_running" => "TINYINT(1) NOT NULL DEFAULT '0'", "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
"prio" => "INT NOT NULL DEFAULT 0",
"returned_text" => "LONGTEXT", "returned_text" => "LONGTEXT",
"last_run" => "TIMESTAMP NULL DEFAULT NULL", "last_run" => "TIMESTAMP NULL DEFAULT NULL",
"success" => "TINYINT(1) UNSIGNED DEFAULT NULL", "success" => "TINYINT(1) UNSIGNED DEFAULT NULL",
@@ -913,32 +795,6 @@ function init_db_schema()
"keys" => array( "keys" => array(
"primary" => array( "primary" => array(
"" => array("id") "" => array("id")
),
"key" => array(
"idx_source_id" => array("source_id"),
"idx_prio" => array("prio")
),
"fkey" => array(
"fk_imapsync_source" => array(
"col" => "source_id",
"ref" => "imapsync_source.id",
"delete" => "RESTRICT",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync_settings" => array(
"cols" => array(
"name" => "VARCHAR(64) NOT NULL",
"value" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("name")
) )
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
@@ -1328,128 +1184,11 @@ function init_db_schema()
} }
} }
// Migrate legacy imapsync jobs (host/port/enc per job) to domain-scoped legacy sources
// Re-entrant: only rows with source_id IS NULL are migrated, the legacy columns are kept while any are left
if ($table == 'imapsync') {
$stmt = $pdo->query("SHOW TABLES LIKE 'imapsync'");
if (count($stmt->fetchAll(PDO::FETCH_ASSOC)) != 0) {
$imapsync_cols = array_column($pdo->query("SHOW COLUMNS FROM `imapsync`")->fetchAll(PDO::FETCH_ASSOC), 'Field', 'Field');
if (isset($imapsync_cols['host1'])) {
if (!isset($imapsync_cols['source_id'])) {
$pdo->exec("ALTER TABLE `imapsync` ADD COLUMN `source_id` INT DEFAULT NULL");
}
if (!isset($imapsync_cols['custom_params_legacy'])) {
$pdo->exec("ALTER TABLE `imapsync` ADD COLUMN `custom_params_legacy` TEXT DEFAULT NULL");
}
// Converted JSON may not fit into the old VARCHAR(512)
$pdo->exec("ALTER TABLE `imapsync` MODIFY COLUMN `custom_params` TEXT");
$imapsync_allow = \Mailcow\Imapsync\Model\CustomParams::allowlist($GLOBALS['IMAPSYNC_OPTIONS'] ?? [], $GLOBALS['IMAPSYNC_OPTIONS_DEFAULT'] ?? []);
$source_sel = $pdo->prepare("SELECT `id` FROM `imapsync_source` WHERE `created_by` = '' AND `name` = :name");
$source_ins = $pdo->prepare("INSERT IGNORE INTO `imapsync_source` (`name`, `description`, `created_by`, `scope`, `host1`, `port1`, `enc1`, `auth_type`, `active`)
VALUES (:name, '', '', 'domain', :host, :port, :enc, :auth, 1)");
$source_domain_ins = $pdo->prepare("INSERT IGNORE INTO `imapsync_source_domain` (`source_id`, `domain`) VALUES (:id, :domain)");
$job_upd = $pdo->prepare("UPDATE `imapsync` SET `source_id` = :source_id, `custom_params` = :cp, `custom_params_legacy` = :cp_legacy,
`dry` = GREATEST(`dry`, :dry) WHERE `id` = :id AND `source_id` IS NULL");
$stmt = $pdo->query("SELECT `id`, `user2`, `host1`, `port1`, `enc1`, `custom_params` FROM `imapsync` WHERE `source_id` IS NULL");
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
// Parse the legacy custom_params string like the old runner did (all tokens up to the next option are its value)
// into pairs valid under $IMAPSYNC_OPTIONS, a token after a flag is never its value.
// --authmech1 PLAIN|LOGIN|CRAM-MD5 becomes the source auth type, --dry the job's dry flag (never run a simulation for real).
// Anything else that does not fit is dropped, the original string is then kept in custom_params_legacy.
$pairs = array();
$auth = null;
$dry = false;
$dropped = false;
$tokens = preg_split('/\s+/', trim((string)$row['custom_params']), -1, PREG_SPLIT_NO_EMPTY);
for ($i = 0, $n = count($tokens); $i < $n; $i++) {
if ($tokens[$i][0] !== '-') {
// stray value without option
$dropped = true;
continue;
}
@list($opt, $val) = explode('=', ltrim($tokens[$i], '-'), 2);
$opt = strtolower($opt);
$inline = ($val !== null);
$rest = array();
while ($i + 1 < $n && $tokens[$i + 1][0] !== '-') {
$rest[] = $tokens[++$i];
}
$rule = ($opt === 'authmech1') ? true : ($imapsync_allow[$opt] ?? null);
if ($opt === 'dry' || $rule === false) {
if ($inline || !empty($rest)) $dropped = true;
if ($opt === 'dry') {
$dry = true;
} elseif (!$inline) {
$pairs[] = array('o' => $opt, 'v' => '');
}
continue;
}
// unknown option, or "--opt=a b" (the old runner cut the value at the first space)
if ($rule === null || ($inline && !empty($rest))) {
$dropped = true;
continue;
}
$val = $inline ? $val : implode(' ', $rest);
if ($opt === 'authmech1' && $auth === null && in_array(strtoupper($val), array('PLAIN', 'LOGIN', 'CRAM-MD5'), true)) {
$auth = strtoupper($val);
} elseif ($opt === 'authmech1' || trim($val) === ''
|| (is_string($rule) && preg_match('~^(?:' . str_replace('~', '\~', $rule) . ')$~D', $val) !== 1)) {
$dropped = true;
} else {
$pairs[] = array('o' => $opt, 'v' => $val);
}
}
// Old runner ignored the authmech1 column, only a custom --authmech1 was effective
$auth = $auth ?? 'PLAIN';
$host = strtolower(trim((string)$row['host1']));
$port = (intval($row['port1']) >= 1 && intval($row['port1']) <= 65535) ? intval($row['port1']) : 143;
$enc = in_array($row['enc1'], array('TLS', 'SSL', 'PLAIN'), true) ? $row['enc1'] : 'TLS';
$at = strrchr((string)$row['user2'], '@');
$domain = ($at === false) ? '' : strtolower(substr($at, 1));
// One source per (host, port, enc, auth, domain), name always unique and <= 100 chars
$name = 'legacy-' . mb_substr($host, 0, 60) . '-' . $port . '-' . substr(sha1($host . '|' . $port . '|' . $enc . '|' . $auth . '|' . $domain), 0, 8);
$source_sel->execute(array(':name' => $name));
if (($source_id = $source_sel->fetchColumn()) === false) {
$source_ins->execute(array(':name' => $name, ':host' => $host, ':port' => $port, ':enc' => $enc, ':auth' => $auth));
$source_sel->execute(array(':name' => $name));
if (($source_id = $source_sel->fetchColumn()) === false) continue;
}
if ($domain !== '') {
$source_domain_ins->execute(array(':id' => $source_id, ':domain' => $domain));
}
$job_upd->execute(array(
':source_id' => $source_id,
':cp' => json_encode($pairs, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
':cp_legacy' => $dropped ? $row['custom_params'] : null,
':dry' => $dry ? 1 : 0,
':id' => $row['id']
));
}
// Never throw on leftovers: keep source_id nullable and the legacy columns, retried with the next db_version
$pending = intval($pdo->query("SELECT COUNT(*) FROM `imapsync` WHERE `source_id` IS NULL")->fetchColumn());
if ($pending > 0) {
$properties['cols']['source_id'] = "INT DEFAULT NULL";
$legacy_col_types = array(
'host1' => "VARCHAR(255) DEFAULT NULL",
'port1' => "SMALLINT UNSIGNED DEFAULT NULL",
'enc1' => "ENUM('TLS','SSL','PLAIN') DEFAULT NULL",
'authmech1' => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT NULL"
);
foreach ($legacy_col_types as $col => $type) {
if (isset($imapsync_cols[$col])) $properties['cols'][$col] = $type;
}
error_log('mailcow: imapsync migration: ' . $pending . ' sync job(s) could not be assigned to a source and will not run');
}
}
}
}
$stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) { if ($num_results != 0) {
$stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE `', `table_schema`, '`.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE `', `table_schema`, '`.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints`
WHERE `constraint_type` = 'FOREIGN KEY' AND `table_schema` = DATABASE() AND `table_name` = :table;"); WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;");
$stmt->execute(array(':table' => $table)); $stmt->execute(array(':table' => $table));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) { while ($row = array_shift($rows)) {
@@ -1610,29 +1349,20 @@ function init_db_schema()
AND (JSON_CONTAINS_PATH(`call`, 'one', '$[2].password') AND (JSON_CONTAINS_PATH(`call`, 'one', '$[2].password')
OR JSON_CONTAINS_PATH(`call`, 'one', '$[2].password2'));"); OR JSON_CONTAINS_PATH(`call`, 'one', '$[2].password2'));");
// Mask syncjob secrets in old log entries (data and attr arguments of the logged call) // Mitigate imapsync argument injection issue
$mask_paths = array(); $pdo->query("UPDATE `imapsync` SET `custom_params` = ''
$unmasked = array(); WHERE `custom_params` LIKE '%pipemess%'
foreach (array(3, 4) as $idx) { OR custom_params LIKE '%skipmess%'
foreach (array('password1', 'password2', 'oauth_client_secret', 'oauth_extra_params') as $key) { OR custom_params LIKE '%delete2foldersonly%'
$mask_paths[] = "'$[" . $idx . "]." . $key . "'"; OR custom_params LIKE '%delete2foldersbutnot%'
// only rows still holding a value other than the string '*' (objects/arrays/numbers included), missing paths are NULL OR custom_params LIKE '%regexflag%'
$unmasked[] = "JSON_UNQUOTE(JSON_EXTRACT(`call`, '$[" . $idx . "]." . $key . "')) <> '*'"; OR custom_params LIKE '%pipemess%'
} OR custom_params LIKE '%regextrans2%'
} OR custom_params LIKE '%maxlinelengthcmd%';");
$pdo->exec("UPDATE `logs` SET `call` = JSON_REPLACE(`call`, " . implode(", '*', ", $mask_paths) . ", '*')
WHERE JSON_VALID(`call`)
AND (JSON_VALUE(`call`, '$[0]') = 'syncjob'
OR JSON_VALUE(`call`, '$[0]') LIKE 'imapsync%'
OR (JSON_VALUE(`call`, '$[0]') = 'mailbox' AND JSON_VALUE(`call`, '$[2]') = 'syncjob'))
AND (" . implode(" OR ", $unmasked) . ");");
// Migrate webauthn tfa // Migrate webauthn tfa
$stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')"); $stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')");
// Syncjobs: seed global settings
$pdo->query("INSERT IGNORE INTO `imapsync_settings` (`name`, `value`) VALUES ('max_parallel', '1')");
// Inject admin if not exists // Inject admin if not exists
$stmt = $pdo->query("SELECT NULL FROM `admin`"); $stmt = $pdo->query("SELECT NULL FROM `admin`");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -1814,10 +1544,6 @@ function init_db_schema()
} }
if (php_sapi_name() == "cli") { if (php_sapi_name() == "cli") {
include '/web/inc/vars.inc.php'; include '/web/inc/vars.inc.php';
if (file_exists('/web/inc/vars.local.inc.php')) {
include '/web/inc/vars.local.inc.php';
}
require_once '/web/inc/lib/vendor/autoload.php';
include '/web/inc/functions.inc.php'; include '/web/inc/functions.inc.php';
include '/web/inc/functions.docker.inc.php'; include '/web/inc/functions.docker.inc.php';
// $now = new DateTime(); // $now = new DateTime();
@@ -1835,7 +1561,7 @@ if (php_sapi_name() == "cli") {
//PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;",
]; ];
$pdo = new PDO($dsn, $database_user, $database_pass, $opt); $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
$stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND (TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view');"); $stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';");
$res = $stmt->fetch(PDO::FETCH_ASSOC); $res = $stmt->fetch(PDO::FETCH_ASSOC);
if (intval($res['OK_C']) === 2) { if (intval($res['OK_C']) === 2) {
// Be more precise when replacing into _sogo_static_view, col orders may change // Be more precise when replacing into _sogo_static_view, col orders may change
-5
View File
@@ -13,10 +13,5 @@
"stevenmaguire/oauth2-keycloak": "^4.0", "stevenmaguire/oauth2-keycloak": "^4.0",
"league/oauth2-client": "^2.7", "league/oauth2-client": "^2.7",
"bacon/bacon-qr-code": "^2.0" "bacon/bacon-qr-code": "^2.0"
},
"autoload": {
"psr-4": {
"Mailcow\\": "mailcow/"
}
} }
} }
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Core;
/**
* The caller may not do this (or may not see the object). Always shown as `access_denied`.
*/
class AccessDenied extends UserError
{
public function __construct()
{
parent::__construct('access_denied');
}
}
@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Core;
/**
* Collects the messages of one API call and writes them to $_SESSION['return'] in the mailcow format
* (`type`, `log`, `msg`), which json_api.php and the UI read. This is the only class that touches the session.
*
* Values of the configured secret keys are replaced by '*' inside every array of the `log` part,
* so passwords or client secrets never reach the mailcow log or an API response.
*/
class ApiResponse
{
private array $messages = [];
public function __construct(private array $secretKeys = [])
{
}
/** Returns true, so callers can `return $response->success(...)` */
public function success(string|array $msg, array $log): bool
{
$this->add('success', $msg, $log);
return true;
}
/** Returns false, so callers can `return $response->danger(...)` */
public function danger(string|array $msg, array $log): bool
{
$this->add('danger', $msg, $log);
return false;
}
public function error(UserError $error, array $log): bool
{
return $this->danger($error->msg(), $log);
}
/** Messages collected since the last flush() */
public function messages(): array
{
return $this->messages;
}
/** Appends the collected messages to $_SESSION['return'] and starts over */
public function flush(): void
{
foreach ($this->messages as $message) {
$_SESSION['return'][] = $message;
}
$this->messages = [];
}
private function add(string $type, string|array $msg, array $log): void
{
foreach ($log as $i => $part) {
if (!is_array($part)) {
continue;
}
foreach ($this->secretKeys as $key) {
if (isset($part[$key]) && $part[$key] !== '') {
$log[$i][$key] = '*';
}
}
}
$this->messages[] = ['type' => $type, 'log' => $log, 'msg' => $msg];
}
}
-76
View File
@@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Core;
/**
* Who is calling: role, username, ACL flags and the access-all exception of the session.
*
* Services get a Context instead of reading $_SESSION. Build it once per request in the entry point:
* `Context::fromSession($_SESSION)` for web requests, `Context::system()` for cron/CLI code paths
* that do no permission checks.
*/
class Context
{
/**
* @param array $acl ACL flags of the session (user_acl / da_acl, always set for admins)
* @param bool $accessAll the session may ignore object ownership (`access_all_exception`)
*/
public function __construct(
public readonly string $role,
public readonly ?string $username,
private readonly array $acl = [],
public readonly bool $accessAll = false
) {
}
public static function fromSession(array $session): self
{
$username = $session['mailcow_cc_username'] ?? null;
return new self(
(string)($session['mailcow_cc_role'] ?? ''),
is_scalar($username) ? (string)$username : null,
is_array($session['acl'] ?? null) ? $session['acl'] : [],
($session['access_all_exception'] ?? null) == '1'
);
}
/** Caller for cron and CLI code: not a logged-in account, so every permission check fails */
public static function system(): self
{
return new self('system', null);
}
public function isAdmin(): bool
{
return $this->role === 'admin';
}
public function isDomainAdmin(): bool
{
return $this->role === 'domainadmin';
}
public function isUser(): bool
{
return $this->role === 'user';
}
public function hasAcl(string $name): bool
{
return isset($this->acl[$name]) && $this->acl[$name] == '1';
}
/** Bridge to the legacy hasMailboxObjectAccess() until functions.inc.php is converted */
public function canAccessMailbox(string $mailbox): bool
{
return (bool)hasMailboxObjectAccess($this->username, $this->role, $mailbox);
}
/** Bridge to the legacy hasDomainAccess() until functions.inc.php is converted */
public function canAccessDomain(string $domain): bool
{
return (bool)hasDomainAccess($this->username, $this->role, $domain);
}
}
-35
View File
@@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Core;
/**
* Helpers for loosely typed request values. Form values arrive as strings, JSON bodies as any type,
* and 011-api.js turns JSON-looking strings into arrays, so services must not trust the PHP type.
*/
class Input
{
/**
* Scalar value as trimmed string; '' for null; null for arrays/objects (= invalid).
*/
public static function text(mixed $value): ?string
{
if ($value === null) {
return '';
}
if (!is_scalar($value)) {
return null;
}
return trim((string)$value);
}
/**
* Integer within [$min, $max] (filter_var rules: "12" and 12 pass, "12abc" and "1.5" do not), else null.
*/
public static function intInRange(mixed $value, int $min, int $max): ?int
{
$int = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]);
return ($int === false) ? null : $int;
}
}
@@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Core;
use PDO;
use PDOStatement;
use Throwable;
/**
* Base class of the repositories: the PDO plus the helpers every repository needs.
* Named parameters are passed without the colon (['id' => 5]).
*/
class Repository
{
public function __construct(protected PDO $pdo)
{
}
/** Prepares and executes one statement */
protected function query(string $sql, array $params = []): PDOStatement
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
/** Inserts one row (column => value) and returns the new id */
protected function insertRow(string $table, array $values): int
{
$columns = array_keys($values);
$this->query("INSERT INTO `$table` (`" . implode('`, `', $columns) . "`) VALUES (:" . implode(', :', $columns) . ")", $values);
return (int)$this->pdo->lastInsertId();
}
/** Updates the columns (column => value) of the row with the given `id` */
protected function updateRow(string $table, int $id, array $values): void
{
$set = implode(', ', array_map(fn(string $column): string => "`$column` = :$column", array_keys($values)));
$this->query("UPDATE `$table` SET $set WHERE `id` = :id", $values + ['id' => $id]);
}
/**
* Runs $work in a transaction and returns its result. Inside a transaction of the caller it just runs,
* so the caller keeps control over commit and rollback.
*/
protected function transaction(callable $work): mixed
{
if ($this->pdo->inTransaction()) {
return $work();
}
$this->pdo->beginTransaction();
try {
$result = $work();
$this->pdo->commit();
return $result;
} catch (Throwable $e) {
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw $e;
}
}
}
@@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Core;
use RuntimeException;
/**
* An expected error that is shown to the user (validation failed, not found, not allowed).
*
* Carries a lang key of the `danger` section plus optional message arguments, e.g.
* `throw new UserError('object_exists', [$name]);`
* Controllers turn it into an ApiResponse message; anything else that is thrown is a real failure.
*/
class UserError extends RuntimeException
{
public function __construct(public readonly string $key, public readonly array $args = [])
{
parent::__construct($key);
}
/** The `msg` value of a mailcow message: the lang key alone, or [key, arg1, ...] when there are arguments */
public function msg(): string|array
{
return empty($this->args) ? $this->key : array_merge([$this->key], array_values($this->args));
}
}
@@ -1,267 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Controller;
use Mailcow\Core\AccessDenied;
use Mailcow\Core\ApiResponse;
use Mailcow\Core\UserError;
use Mailcow\Imapsync\Model\Source;
use Mailcow\Imapsync\Service\JobService;
use Mailcow\Imapsync\Service\SourceService;
use PDOException;
/**
* HTTP API adapter for json_api.php (and the pages that show the same data).
*
* Write methods take the decoded request ($items = ids, $attr = attributes), loop over batch ids, turn
* UserError into danger messages and write all messages to $_SESSION['return'] via ApiResponse.
* They return what json_api passes to process_*_return(): false when the whole call was rejected.
* A database error stops the call with one `mysql_error` message, like the rest of mailcow.
* Read methods return the API data (an empty array when there is nothing the caller may see).
* Messages are logged as ['syncjob', action, type, data] like before the conversion.
*/
class ImapsyncApiController
{
public function __construct(private SourceService $sources, private JobService $jobs, private ApiResponse $response)
{
}
/** POST add/syncjob */
public function addJob(array $attr): bool
{
return $this->handle('add', 'job', $attr,
fn(array $log): bool => $this->response->success(['mailbox_modified', $this->jobs->create($attr)->user2], $log));
}
/** POST add/syncjob_source */
public function addSource(array $attr): bool
{
return $this->handle('add', 'source', $attr, function (array $log) use ($attr): bool {
$source = $this->sources->create($attr);
$this->tokenFailure($source, $log);
return $this->response->success(['imapsync_source_added', $source->name], $log);
});
}
/** POST edit/syncjob: one message per id; `id` in $attr wins over $items (array_merge like before) */
public function editJobs(?array $items, array $attr): bool
{
$data = array_merge(['id' => $items], $attr);
return $this->handle('edit', 'job', $data, function (array $log) use ($data): bool {
$this->jobs->requireAcl();
return $this->batch((array)($data['id'] ?? []), $log,
fn(int $id): array => ['mailbox_modified', $this->jobs->update($id, $data)->user2]);
});
}
/** POST edit/syncjob/run_next (admin): false when no job was moved */
public function runNext(?array $items): bool
{
return $this->handle('edit', 'run_next', ['id' => $items], function (array $log) use ($items): bool {
$outcome = $this->jobs->runNext(array_map('intval', array_values(array_filter((array)$items, 'is_numeric'))));
foreach ($outcome['running'] as $id) {
$this->response->danger(['imapsync_job_running', $id], $log);
}
return ($outcome['moved'] > 0) ? $this->response->success(['imapsync_run_next_done', $outcome['moved']], $log) : false;
});
}
/** POST edit/syncjob_source: one message per id, non-numeric ids are skipped */
public function editSources(?array $items, array $attr): bool
{
$data = array_merge(['id' => $items], $attr);
return $this->handle('edit', 'source', $data, function (array $log) use ($data): bool {
$this->sources->requireEditor();
return $this->batch(array_filter((array)($data['id'] ?? []), 'is_numeric'), $log, function (int $id) use ($data, $log): array {
$source = $this->sources->update($id, $data);
$this->tokenFailure($source, $log);
return ['imapsync_source_modified', $source->name];
});
});
}
/** POST edit/syncjob_source/refresh_token: the first id only */
public function refreshSourceToken(?array $items): bool
{
return $this->handle('edit', 'refresh_token', ['id' => $items], function (array $log) use ($items): bool {
$source = $this->sources->refreshToken($this->requireId(is_array($items) ? reset($items) : null));
return $this->response->success(['imapsync_source_token_refreshed', $source->name], $log);
});
}
/** POST edit/imapsync_settings (admin) */
public function editSettings(array $attr): bool
{
return $this->handle('edit', 'settings', $attr, function (array $log) use ($attr): bool {
$this->jobs->saveSettings($attr);
return $this->response->success('max_parallel_saved', $log);
});
}
/** POST delete/syncjob: one message per id (with the id as requested) */
public function deleteJobs(array $items): bool
{
return $this->handle('delete', 'job', ['id' => $items], function (array $log) use ($items): bool {
$this->jobs->requireAcl();
return $this->batch($items, $log, function (int $id, mixed $requested): array {
$this->jobs->delete($id);
return ['deleted_syncjob', $requested];
});
});
}
/** POST delete/syncjob_source: one message per id, non-numeric ids are skipped */
public function deleteSources(array $items): bool
{
return $this->handle('delete', 'source', ['id' => $items], function (array $log) use ($items): bool {
$this->sources->requireAcl();
return $this->batch(array_filter($items, 'is_numeric'), $log,
fn(int $id): array => ['imapsync_source_deleted', $this->sources->delete($id)->name]);
});
}
/** GET syncjob_options */
public function getOptions(): array
{
return $this->jobs->customParamOptions();
}
/** GET imapsync_settings (admin), also admin/system.php */
public function getSettings(): array
{
try {
return $this->jobs->getSettings()->toArray();
} catch (UserError) {
return [];
}
}
/** GET syncjob_source/all */
public function getSources(): array
{
return array_values(array_filter(array_map(fn(int $id): array => $this->getSource($id), $this->sources->visibleIds())));
}
/** GET syncjob_source/<id>, also edit.php */
public function getSource(mixed $id): array
{
$id = $this->toId($id);
$source = ($id === null) ? null : $this->sources->get($id);
return ($source === null) ? [] : $this->sources->toArray($source);
}
/**
* GET syncjobs/all. Admins get every job (incl. orphaned ones). Others get the jobs of $mailboxes,
* the mailboxes the caller manages (json_api collects them with the legacy mailbox() functions
* until functions.mailbox.inc.php is converted), in queue order.
*/
public function getAllJobs(?string $extra, array $mailboxes): array
{
if ($this->jobs->canListAll()) {
return $this->jobRows($this->jobs->allIds(), $extra);
}
$ids = [];
foreach ($mailboxes as $mailbox) {
$ids = array_merge($ids, $this->jobs->idsForMailbox((string)$mailbox));
}
$rows = $this->jobRows($ids, $extra);
// Queue order (C4): prio desc, never-run first, least recently run, id
usort($rows, fn(array $a, array $b): int => [$b['prio'], !empty($a['last_run']), (string)$a['last_run'], (int)$a['id']]
<=> [$a['prio'], !empty($b['last_run']), (string)$b['last_run'], (int)$b['id']]);
return $rows;
}
/** GET syncjobs/<mailbox>; without a valid mailbox the caller's own jobs */
public function getJobs(?string $mailbox, ?string $extra): array
{
return $this->jobRows($this->jobs->idsForMailbox($mailbox), $extra);
}
/** One job (edit.php, inc/ajax/syncjob_logs.php); [] when missing or not accessible. `no_log` in $extra skips the log. */
public function getJob(mixed $id, ?string $extra = null): array
{
$id = $this->toId($id);
$withLog = ($extra === null || !in_array('no_log', explode(',', $extra), true));
$job = ($id === null) ? null : $this->jobs->get($id, $withLog);
return ($job === null) ? [] : $job->toArray($withLog);
}
/**
* Account rename ($new) or delete ($new = null) from legacy account code that runs outside a transaction
* (admin, domain admin, mailbox delete): a database error becomes a `mysql_error` message.
* Inside a transaction (mailbox rename) call $module->sources->onAccountRenamed() directly instead,
* so the caller's catch can roll back.
*/
public function accountChanged(string $old, ?string $new = null): void
{
try {
if ($new === null) {
$this->sources->onAccountDeleted($old);
} else {
$this->sources->onAccountRenamed($old, $new);
}
} catch (PDOException $e) {
$this->response->danger(['mysql_error', $e->getMessage()], ['imapsync_account_changed', $old, $new]);
}
$this->response->flush();
}
/** The token fetched when saving a client_credentials source failed: the source is saved, report the error */
private function tokenFailure(Source $source, array $log): void
{
if ($source->usesClientCredentials() && $source->active === 1 && $source->lastRefreshError !== null) {
$this->response->danger(['imapsync_source_token_refresh_failed', $source->name . ' (' . $source->lastRefreshError . ')'], $log);
}
}
/**
* Runs one write call: $work gets the log entry and returns the result for process_*_return().
* A UserError becomes a danger message, a database error stops the call with one `mysql_error` message.
*/
private function handle(string $action, string $type, mixed $data, callable $work): bool
{
$log = ['syncjob', $action, $type, $data];
try {
$result = $work($log);
} catch (UserError $e) {
$result = $this->response->error($e, $log);
} catch (PDOException $e) {
$result = $this->response->danger(['mysql_error', $e->getMessage()], $log);
}
$this->response->flush();
return $result;
}
/** One message per id: the success message returned by $work(id, requested id), or the UserError */
private function batch(array $ids, array $log, callable $work): bool
{
foreach ($ids as $id) {
try {
$this->response->success($work($this->requireId($id), $id), $log);
} catch (UserError $e) {
$this->response->error($e, $log);
}
}
return true;
}
/** @return array[] API rows of the jobs the caller may access */
private function jobRows(array $ids, ?string $extra): array
{
return array_values(array_filter(array_map(fn(int $id): array => $this->getJob($id, $extra), $ids)));
}
/** Integer id from request input ("12", 12); null for anything else, also "1.5" (matches no row) */
private function toId(mixed $id): ?int
{
return (is_numeric($id) && (float)$id == (int)(float)$id) ? (int)(float)$id : null;
}
/** @throws AccessDenied for ids that cannot exist */
private function requireId(mixed $id): int
{
return $this->toId($id) ?? throw new AccessDenied();
}
}
@@ -1,172 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Http;
/**
* HTTPS requests to OAuth provider endpoints with an address policy against SSRF (C5).
*
* Every endpoint must be https://, without userinfo, with a valid hostname. In strict mode (endpoints of
* sources not created by an admin) every resolved address must be public; curl is then pinned to the checked
* addresses (CURLOPT_RESOLVE), so a DNS change between check and request does not help an attacker.
*
* resolve() and send() are the only I/O and protected, so tests can replace them.
*/
class OAuthClient
{
private const MAX_URL_LENGTH = 500;
private const MAX_ERROR_LENGTH = 200;
public function isAllowed(string $url, bool $strict): bool
{
return $this->pin($url, $strict) !== null;
}
/**
* Sends one request (POST form when $post is given, otherwise GET) and returns the decoded JSON object.
*
* @throws OAuthException with a sanitised message
*/
public function request(string $url, bool $strict, ?array $post = null, array $headers = []): array
{
$pin = $this->pin($url, $strict);
if ($pin === null) {
throw new OAuthException('endpoint not allowed');
}
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_PROXY => '',
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 20,
CURLOPT_MAXFILESIZE => 1048576,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HTTPHEADER => array_merge(['Accept: application/json'], $headers),
];
if (!empty($pin)) {
$options[CURLOPT_RESOLVE] = $pin;
}
if ($post !== null) {
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = http_build_query($post);
}
[$body, $code] = $this->send($options);
if ($body === false) {
throw new OAuthException('connection failed');
}
$data = json_decode($body, true);
$ok = ($code >= 200 && $code < 300);
if ($ok && is_array($data)) {
return $data;
}
throw new OAuthException($this->errorText($code, $ok, $data));
}
/**
* IPv4 and IPv6 addresses of a host.
*
* @return string[]
*/
protected function resolve(string $host): array
{
$ips = @gethostbynamel($host) ?: [];
foreach (@dns_get_record($host, DNS_AAAA) ?: [] as $record) {
$ips[] = $record['ipv6'] ?? '';
}
return $ips;
}
/**
* Runs curl with the given options.
*
* @return array{0: string|false, 1: int} response body (false on connection errors) and HTTP status
*/
protected function send(array $options): array
{
$ch = curl_init();
curl_setopt_array($ch, $options);
$body = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [is_string($body) ? $body : false, $code];
}
/**
* Applies the endpoint policy. Returns null when the URL is not allowed, otherwise the CURLOPT_RESOLVE
* pin (empty when not strict).
*/
private function pin(string $url, bool $strict): ?array
{
if (strlen($url) > self::MAX_URL_LENGTH || preg_match('/[\x00-\x20\x7f]/', $url)) {
return null;
}
$parts = parse_url($url);
if (!$parts || strtolower($parts['scheme'] ?? '') !== 'https' || isset($parts['user']) || isset($parts['pass'])) {
return null;
}
$host = strtolower($parts['host'] ?? '');
if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) || !filter_var($url, FILTER_VALIDATE_URL)) {
return null;
}
if (!$strict) {
return [];
}
$ips = array_values(array_unique($this->resolve($host)));
if (empty($ips)) {
return null;
}
$pinned = [];
foreach ($ips as $ip) {
if (!$this->isPublic($ip)) {
return null;
}
$pinned[] = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? '[' . $ip . ']' : $ip;
}
return [$host . ':' . (int)($parts['port'] ?? 443) . ':' . implode(',', $pinned)];
}
/**
* Public unicast address only: no private, loopback, link-local or reserved ranges, no CGNAT (100.64/10),
* benchmarking (198.18/15) or multicast/reserved (224/3); IPv6 only global unicast 2000::/3 without
* 6to4 (2002::/16), Teredo (2001::/32) and documentation (2001:db8::/32).
*/
private function isPublic(string $ip): bool
{
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return false;
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$long = ip2long($ip);
return ($long & 0xFFC00000) != 0x64400000
&& ($long & 0xFFFE0000) != 0xC6120000
&& ($long & 0xE0000000) != 0xE0000000;
}
$hex = bin2hex((string)inet_pton($ip));
return ($hex[0] === '2' || $hex[0] === '3')
&& substr($hex, 0, 4) !== '2002'
&& !in_array(substr($hex, 0, 8), ['20010000', '20010db8'], true);
}
/**
* Error text that is safe to store: "HTTP <code>" plus the provider's error / error_description.
*/
private function errorText(int $code, bool $ok, mixed $data): string
{
$text = 'HTTP ' . $code . ($ok ? ': malformed response' : '');
$detail = [];
foreach (['error', 'error_description'] as $key) {
if (is_array($data) && isset($data[$key]) && is_scalar($data[$key]) && (string)$data[$key] !== '') {
$detail[] = (string)$data[$key];
}
}
if (!empty($detail)) {
$text .= ': ' . implode(' - ', $detail);
}
return mb_substr(trim((string)preg_replace('/[\x00-\x1f\x7f]+/', ' ', strip_tags($text))), 0, self::MAX_ERROR_LENGTH);
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Http;
use RuntimeException;
/**
* A failed OAuth request. The message is safe to store and show: "HTTP <code>" plus the provider's
* error fields, or a fixed text; never a raw response body or curl details.
*/
class OAuthException extends RuntimeException
{
}
@@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync;
use Mailcow\Core\ApiResponse;
use Mailcow\Core\Context;
use Mailcow\Imapsync\Controller\ImapsyncApiController;
use Mailcow\Imapsync\Http\OAuthClient;
use Mailcow\Imapsync\Model\CustomParams;
use Mailcow\Imapsync\Repository\JobRepository;
use Mailcow\Imapsync\Repository\SettingsRepository;
use Mailcow\Imapsync\Repository\SourceRepository;
use Mailcow\Imapsync\Repository\TokenRepository;
use Mailcow\Imapsync\Service\JobService;
use Mailcow\Imapsync\Service\OAuthService;
use Mailcow\Imapsync\Service\SourceService;
use PDO;
/**
* Composition root of the sync job module: wires repositories, services and the API controller.
*
* $imapsync = ImapsyncModule::create($pdo, Context::fromSession($_SESSION), [
* 'allowlist' => CustomParams::allowlist($IMAPSYNC_OPTIONS, $IMAPSYNC_OPTIONS_DEFAULT),
* 'hostname' => (string)getenv('MAILCOW_HOSTNAME'),
* ]);
* process_add_return($imapsync->api->addJob($attr));
*
* Procedural legacy code (json_api.php, pages, functions.*.inc.php) uses fromGlobals() instead.
*/
class ImapsyncModule
{
/** Request keys whose values never reach a log */
private const SECRET_KEYS = ['password1', 'password2', 'oauth_client_secret', 'oauth_extra_params'];
public function __construct(
public readonly SourceService $sources,
public readonly JobService $jobs,
public readonly OAuthService $oauth,
public readonly ImapsyncApiController $api
) {
}
/**
* @param array $config 'allowlist' => effective custom param allowlist (CustomParams::allowlist()),
* 'hostname' => MAILCOW_HOSTNAME (OAuth redirect URI); both optional
*/
public static function create(PDO $pdo, Context $context, array $config = []): self
{
$sourceRepository = new SourceRepository($pdo);
$tokenRepository = new TokenRepository($pdo);
$oauth = new OAuthService($sourceRepository, $tokenRepository, new OAuthClient(), (string)($config['hostname'] ?? ''));
$sources = new SourceService($sourceRepository, $tokenRepository, $oauth, $context);
$jobs = new JobService(new JobRepository($pdo), $sources, $tokenRepository, new SettingsRepository($pdo),
$context, (array)($config['allowlist'] ?? []));
return new self($sources, $jobs, $oauth, new ImapsyncApiController($sources, $jobs, new ApiResponse(self::SECRET_KEYS)));
}
/**
* Legacy entry point for procedural callers: the one place that reads the globals ($pdo, $_SESSION,
* $IMAPSYNC_OPTIONS / $IMAPSYNC_OPTIONS_DEFAULT from vars(.local).inc.php, MAILCOW_HOSTNAME)
* and passes them to create(). $context overrides the session caller (e.g. Context::system()).
*/
public static function fromGlobals(?Context $context = null): self
{
return self::create($GLOBALS['pdo'], $context ?? Context::fromSession($_SESSION ?? []), [
'allowlist' => CustomParams::allowlist((array)($GLOBALS['IMAPSYNC_OPTIONS'] ?? []),
(array)($GLOBALS['IMAPSYNC_OPTIONS_DEFAULT'] ?? [])),
'hostname' => (string)getenv('MAILCOW_HOSTNAME'),
]);
}
}
@@ -1,97 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Model;
use Mailcow\Core\UserError;
/**
* Rules for the extra imapsync options of a sync job: a list of {o: option, v: value} pairs, stored as JSON
* (`imapsync`.`custom_params`, read by the runner). Stored values are passed through unchanged.
*
* Allowlist rules (option name lowercase, without dashes):
* true option requires a non-empty value
* false flag, must not have a value
* string PCRE (no delimiters) the whole non-empty value must match
*/
class CustomParams
{
private const MAX_PAIRS = 100;
private const MAX_VALUE_LENGTH = 1000;
private static bool $warnedOldFormat = false;
/**
* Validates request input (list of {o, v}) against the allowlist and returns the JSON to store.
* Empty input means no options.
*
* @throws UserError imapsync_custom_params_format, imapsync_custom_param_invalid, imapsync_custom_param_value
*/
public static function toJson(mixed $input, array $allowlist): string
{
$input = ($input === null || $input === '') ? [] : $input;
if (!is_array($input)) {
throw new UserError('imapsync_custom_params_format');
}
$pairs = [];
foreach ($input as $pair) {
$option = is_array($pair) ? ($pair['o'] ?? '') : null;
$value = is_array($pair) ? ($pair['v'] ?? '') : null;
if (!is_scalar($option) || !is_scalar($value)) {
throw new UserError('imapsync_custom_params_format');
}
$option = ltrim(strtolower(trim((string)$option)), '-');
$value = str_replace("\0", '', (string)$value);
if ($option === '' && trim($value) === '') {
continue; // empty UI row
}
$value = self::checkValue($option, $value, $allowlist);
if (strlen($value) > self::MAX_VALUE_LENGTH || count($pairs) >= self::MAX_PAIRS) {
throw new UserError('imapsync_custom_params_format');
}
$pairs[] = ['o' => $option, 'v' => $value];
}
return json_encode($pairs, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
/**
* The effective allowlist: $IMAPSYNC_OPTIONS (after vars.local.inc.php) without the old
* 'whitelist' key and malformed entries; the defaults when nothing valid is left.
*/
public static function allowlist(array $configured, array $defaults): array
{
$allowlist = [];
foreach ($configured as $option => $rule) {
$option = strtolower(ltrim(trim((string)$option), '-'));
if ($option !== 'whitelist' && preg_match('/^[a-z][a-z0-9]*$/', $option)
&& (is_bool($rule) || (is_string($rule) && $rule !== ''))) {
$allowlist[$option] = $rule;
}
}
if (!self::$warnedOldFormat && isset($configured['whitelist'])) {
self::$warnedOldFormat = true;
error_log("mailcow: \$IMAPSYNC_OPTIONS['whitelist'] (old format) is ignored, use 'option' => true|false|'regex' (see data/web/inc/vars.inc.php)");
}
return empty($allowlist) ? $defaults : $allowlist;
}
/** The value to store for one option; throws when the allowlist rule is violated */
private static function checkValue(string $option, string $value, array $allowlist): string
{
$rule = $allowlist[$option] ?? null;
if ($option === '' || !(is_bool($rule) || is_string($rule))) {
throw new UserError('imapsync_custom_param_invalid', [htmlspecialchars($option)]);
}
$empty = (trim($value) === '');
$valid = match (true) {
$rule === false => $empty,
$rule === true => !$empty,
default => !$empty && preg_match('~^(?:' . str_replace('~', '\~', $rule) . ')$~D', $value) === 1,
};
if (!$valid) {
throw new UserError('imapsync_custom_param_value', [$option]);
}
return ($rule === false) ? '' : $value;
}
}
@@ -1,154 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Model;
/**
* A sync job (table `imapsync`): copies the remote account user1 of a source into the local mailbox user2.
*
* Loaded jobs also carry display data of their source and of the per-user token ($source / $token,
* filled from the JOIN; no secrets). Switches default to off, because unchecked form checkboxes are not sent.
*/
class Job
{
public ?int $id = null;
public string $user2 = '';
public ?string $regextrans2 = '';
public int $authmd51 = 0;
public string $domain2 = '';
public string $subfolder2 = '';
public string $user1 = '';
/** Remote password, never returned by toArray() */
public string $password1 = '';
public string $exclude = '';
public int $maxage = 0;
public int $minsInterval = 0;
/** Bytes/s limit, stored as text */
public string $maxBytesPerSecond = '0';
public int $delete2duplicates = 0;
public int $delete1 = 0;
public int $delete2 = 0;
public int $automap = 0;
public int $skipcrossduplicates = 0;
/** JSON list of {o, v} pairs (see CustomParams), old rows may hold other text */
public ?string $customParams = '[]';
public ?string $customParamsLegacy = null;
public int $timeout1 = 600;
public int $timeout2 = 600;
public int $subscribeall = 0;
public int $dry = 0;
public int $isRunning = 0;
public ?string $returnedText = null;
public ?string $lastRun = null;
public ?int $success = null;
public ?string $exitStatus = null;
public ?string $created = null;
public ?string $modified = null;
public int $active = 0;
public int $sourceId = 0;
public int $prio = 0;
public ?Source $source = null;
public ?OAuthToken $token = null;
/**
* Builds a job from an `imapsync` row, optionally joined with `source_<column>` (columns of the source)
* and `user_token_*` columns.
*/
public static function fromRow(array $row): self
{
$job = new self();
$job->id = isset($row['id']) ? (int)$row['id'] : null;
foreach (['authmd51', 'maxage', 'delete2duplicates', 'delete1', 'delete2', 'automap', 'skipcrossduplicates', 'timeout1',
'timeout2', 'subscribeall', 'dry', 'active', 'prio'] as $column) {
$job->$column = (int)($row[$column] ?? 0);
}
foreach (['user2', 'domain2', 'subfolder2', 'user1', 'password1', 'exclude'] as $column) {
$job->$column = (string)($row[$column] ?? '');
}
$job->minsInterval = (int)($row['mins_interval'] ?? 0);
$job->maxBytesPerSecond = (string)($row['maxbytespersecond'] ?? '0');
$job->isRunning = (int)($row['is_running'] ?? 0);
$job->success = isset($row['success']) ? (int)$row['success'] : null;
// Nullable text columns (column => property)
foreach (['regextrans2' => 'regextrans2', 'custom_params' => 'customParams', 'custom_params_legacy' => 'customParamsLegacy',
'returned_text' => 'returnedText', 'last_run' => 'lastRun', 'exit_status' => 'exitStatus', 'created' => 'created',
'modified' => 'modified'] as $column => $property) {
$job->$property = isset($row[$column]) ? (string)$row[$column] : null;
}
$job->sourceId = (int)($row['source_id'] ?? 0);
if (isset($row['source_name'])) {
// source_id is the job's foreign key, i.e. the id of the source
$source = [];
foreach ($row as $key => $value) {
if (str_starts_with($key, 'source_')) {
$source[substr($key, 7)] = $value;
}
}
$job->source = Source::fromRow($source);
}
if (!empty($row['user_token_exists'])) {
$job->token = OAuthToken::fromRow(['source_id' => $job->sourceId, 'owner' => $job->user2, 'username' => $job->user1,
'token_expires' => $row['user_token_expires'] ?? null, 'last_refresh_error' => $row['user_token_error'] ?? null]);
}
return $job;
}
/**
* API representation (GET syncjobs, edit page, log view). Never contains password1.
* Without $withLog the `log` field is empty (UI tables request `no_log`).
*/
public function toArray(bool $withLog = true): array
{
$source = $this->source;
return [
'id' => $this->id,
'user2' => $this->user2,
'regextrans2' => $this->regextrans2,
'authmd51' => $this->authmd51,
'domain2' => $this->domain2,
'subfolder2' => $this->subfolder2,
'user1' => $this->user1,
'exclude' => $this->exclude,
'maxage' => $this->maxage,
'mins_interval' => $this->minsInterval,
'maxbytespersecond' => $this->maxBytesPerSecond,
'delete2duplicates' => $this->delete2duplicates,
'delete1' => $this->delete1,
'delete2' => $this->delete2,
'automap' => $this->automap,
'skipcrossduplicates' => $this->skipcrossduplicates,
'custom_params' => $this->customParams,
'timeout1' => $this->timeout1,
'timeout2' => $this->timeout2,
'subscribeall' => $this->subscribeall,
'dry' => $this->dry,
'is_running' => $this->isRunning,
'last_run' => $this->lastRun,
'success' => $this->success,
'exit_status' => $this->exitStatus,
'created' => $this->created,
'modified' => $this->modified,
'active' => $this->active,
'source_id' => $this->sourceId,
'custom_params_legacy' => $this->customParamsLegacy,
'prio' => $this->prio,
'source_name' => $source?->name,
'source_description' => $source?->description,
'source_host' => $source?->host,
'source_port' => $source?->port,
'source_enc' => $source?->enc,
'source_auth_type' => $source?->authType,
'source_created_by' => $source?->createdBy,
'source_scope' => $source?->scope,
'source_oauth_flow' => $source?->oauthFlow,
'source_active' => $source?->active,
'source_oauth_token_expires' => $source?->tokenExpires,
'source_oauth_last_refresh_error' => $source?->lastRefreshError,
'user_token_expires' => $this->token?->tokenExpires,
'user_token_error' => $this->token?->lastRefreshError,
'user_token_exists' => $this->token === null ? 0 : 1,
'log' => ($withLog && !empty($this->returnedText)) ? $this->returnedText : '',
];
}
}
@@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Model;
/**
* Per-user OAuth token of an authorization_code source (table `imapsync_source_oauth_token`).
*
* Bound to (source, owner, username): owner = local mailcow mailbox (job user2),
* username = authenticated remote identity (job user1).
*/
class OAuthToken
{
public int $sourceId = 0;
public string $owner = '';
public string $username = '';
public ?string $accessToken = null;
public ?string $refreshToken = null;
public ?int $tokenExpires = null;
public ?string $lastRefreshError = null;
/** Seconds since the last refresh attempt by the DB clock; only filled by the refresh queries */
public ?int $refreshAttemptAge = null;
public static function fromRow(array $row): self
{
$token = new self();
$token->sourceId = (int)($row['source_id'] ?? 0);
$token->owner = (string)($row['owner'] ?? '');
$token->username = (string)($row['username'] ?? '');
$token->accessToken = isset($row['access_token']) ? (string)$row['access_token'] : null;
$token->refreshToken = isset($row['refresh_token']) ? (string)$row['refresh_token'] : null;
$token->tokenExpires = isset($row['token_expires']) ? (int)$row['token_expires'] : null;
$token->lastRefreshError = isset($row['last_refresh_error']) ? (string)$row['last_refresh_error'] : null;
$token->refreshAttemptAge = isset($row['refresh_attempt_age']) ? (int)$row['refresh_attempt_age'] : null;
return $token;
}
}
@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Model;
/**
* Global sync job settings (table `imapsync_settings`), read by the runner.
*/
class Settings
{
public int $maxParallel = 1;
/** Global bandwidth limit in bytes/s, 0 = unlimited */
public int $maxBytesPerSecond = 0;
/**
* API representation (GET imapsync_settings); the UI edits the limit in KB/s.
*/
public function toArray(): array
{
return [
'max_parallel' => $this->maxParallel,
'max_bytes_per_second' => $this->maxBytesPerSecond,
'max_kb_per_second' => intdiv($this->maxBytesPerSecond, 1024),
];
}
}
@@ -1,143 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Model;
/**
* A reusable IMAP source (table `imapsync_source` plus its scope targets).
*
* Visibility is driven by `scope` and the target lists:
* 'all' visible to everyone (admin-created only)
* 'domain' visible to the listed domains (their domain admins and users)
* 'user' visible to the listed users (and their domain admins); no targets = private
* `createdBy` records the creator ('' = handed over to the admins).
*
* The object holds secrets (client secret, access token); toArray() never returns them.
*/
class Source
{
public const AUTH_XOAUTH2 = 'XOAUTH2';
public const FLOW_CLIENT_CREDENTIALS = 'client_credentials';
public const FLOW_AUTHORIZATION_CODE = 'authorization_code';
public ?int $id = null;
public string $name = '';
public string $description = '';
public string $createdBy = '';
public string $scope = 'user';
public string $host = '';
public int $port = 0;
public ?string $enc = 'TLS';
public ?string $authType = 'PLAIN';
public string $oauthFlow = self::FLOW_CLIENT_CREDENTIALS;
public ?string $tokenEndpoint = null;
public ?string $authorizeEndpoint = null;
public ?string $userinfoEndpoint = null;
public ?string $clientId = null;
public ?string $clientSecret = null;
public ?string $oauthScope = null;
/** Extra OAuth parameters (sign-in or token request, see OAuthService), JSON object text */
public ?string $extraParams = null;
/** Shared token of a client_credentials source */
public ?string $accessToken = null;
public ?int $tokenExpires = null;
public ?string $lastRefreshError = null;
public ?string $lastRefreshAttempt = null;
/** Seconds since lastRefreshAttempt by the DB clock; only filled by the refresh queries */
public ?int $refreshAttemptAge = null;
public ?string $created = null;
public ?string $modified = null;
public int $active = 1;
/** @var string[] */
public array $domains = [];
/** @var string[] */
public array $users = [];
/** Builds a source from an `imapsync_source` row (column names as in the table) */
public static function fromRow(array $row): self
{
$source = new self();
$source->id = isset($row['id']) ? (int)$row['id'] : null;
$source->name = (string)($row['name'] ?? '');
$source->description = (string)($row['description'] ?? '');
$source->createdBy = (string)($row['created_by'] ?? '');
$source->scope = (string)($row['scope'] ?? 'user');
$source->host = (string)($row['host1'] ?? '');
$source->port = (int)($row['port1'] ?? 0);
$source->oauthFlow = (string)($row['oauth_flow'] ?? self::FLOW_CLIENT_CREDENTIALS);
$source->tokenExpires = isset($row['oauth_token_expires']) ? (int)$row['oauth_token_expires'] : null;
$source->refreshAttemptAge = isset($row['refresh_attempt_age']) ? (int)$row['refresh_attempt_age'] : null;
// Nullable text columns (column => property)
foreach (['enc1' => 'enc', 'auth_type' => 'authType', 'oauth_token_endpoint' => 'tokenEndpoint',
'oauth_authorize_endpoint' => 'authorizeEndpoint', 'oauth_userinfo_endpoint' => 'userinfoEndpoint',
'oauth_client_id' => 'clientId', 'oauth_client_secret' => 'clientSecret', 'oauth_scope' => 'oauthScope',
'oauth_extra_params' => 'extraParams', 'oauth_access_token' => 'accessToken', 'oauth_last_refresh_error' => 'lastRefreshError',
'oauth_last_refresh_attempt' => 'lastRefreshAttempt', 'created' => 'created', 'modified' => 'modified'] as $column => $property) {
$source->$property = isset($row[$column]) ? (string)$row[$column] : null;
}
$source->active = (int)($row['active'] ?? 1);
return $source;
}
public function isXOAuth2(): bool
{
return $this->authType === self::AUTH_XOAUTH2;
}
/** XOAUTH2 with one app-wide token that reaches every mailbox of the tenant */
public function usesClientCredentials(): bool
{
return $this->isXOAuth2() && $this->oauthFlow === self::FLOW_CLIENT_CREDENTIALS;
}
/** XOAUTH2 with a per-user token from the browser sign-in */
public function usesAuthorizationCode(): bool
{
return $this->isXOAuth2() && $this->oauthFlow === self::FLOW_AUTHORIZATION_CODE;
}
/**
* API representation (GET syncjob_source). Secrets and tokens are replaced by has_* flags;
* extra params are only shown to callers who may edit the source.
*/
public function toArray(bool $canEdit, string $redirectUri): array
{
$data = [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'created_by' => $this->createdBy,
'scope' => $this->scope,
'host1' => $this->host,
'port1' => $this->port,
'enc1' => $this->enc,
'auth_type' => $this->authType,
'oauth_flow' => $this->oauthFlow,
'oauth_token_endpoint' => $this->tokenEndpoint,
'oauth_authorize_endpoint' => $this->authorizeEndpoint,
'oauth_userinfo_endpoint' => $this->userinfoEndpoint,
'oauth_client_id' => $this->clientId,
'oauth_scope' => $this->oauthScope,
'oauth_extra_params' => $canEdit ? $this->extraParams : null,
'oauth_token_expires' => $this->tokenExpires,
'oauth_last_refresh_error' => $this->lastRefreshError,
'oauth_last_refresh_attempt' => $this->lastRefreshAttempt,
'created' => $this->created,
'modified' => $this->modified,
'active' => $this->active,
'domains' => $this->domains,
'users' => $this->users,
'can_edit' => $canEdit,
'host' => $this->host,
'port' => $this->port,
'enc' => $this->enc,
'has_client_secret' => !empty($this->clientSecret),
'has_access_token' => !empty($this->accessToken),
];
if ($this->usesAuthorizationCode()) {
$data['oauth_redirect_uri'] = $redirectUri;
}
return $data;
}
}
@@ -1,128 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Repository;
use Mailcow\Core\Repository;
use Mailcow\Imapsync\Model\Job;
use PDO;
/**
* SQL for sync jobs (table `imapsync`).
*/
class JobRepository extends Repository
{
/** Job queue order (C4): prio desc, never-run first, then least recently run */
private const QUEUE_ORDER = "ORDER BY `prio` DESC, `last_run` IS NOT NULL, `last_run` ASC, `id` ASC";
/** Every column except returned_text (the log, loaded on demand) */
private const COLUMNS = ['id', 'user2', 'regextrans2', 'authmd51', 'domain2', 'subfolder2', 'user1', 'password1',
'exclude', 'maxage', 'mins_interval', 'maxbytespersecond', 'delete2duplicates', 'delete1', 'delete2', 'automap',
'skipcrossduplicates', 'custom_params', 'timeout1', 'timeout2', 'subscribeall', 'dry', 'is_running',
'last_run', 'success', 'exit_status', 'created', 'modified', 'active', 'source_id', 'custom_params_legacy', 'prio'];
/**
* The job with display data of its source and per-user token (C1: source, owner = user2, username = user1).
* $withLog also loads returned_text.
*/
public function find(int $id, bool $withLog = true): ?Job
{
$columns = $withLog ? [...self::COLUMNS, 'returned_text'] : self::COLUMNS;
$row = $this->query("SELECT i.`" . implode("`, i.`", $columns) . "`,
s.`name` AS source_name, s.`description` AS source_description, s.`host1` AS source_host1,
s.`port1` AS source_port1, s.`enc1` AS source_enc1, s.`auth_type` AS source_auth_type,
s.`created_by` AS source_created_by, s.`scope` AS source_scope, s.`oauth_flow` AS source_oauth_flow,
s.`active` AS source_active, s.`oauth_token_expires` AS source_oauth_token_expires,
s.`oauth_last_refresh_error` AS source_oauth_last_refresh_error,
t.`token_expires` AS user_token_expires, t.`last_refresh_error` AS user_token_error,
CASE WHEN t.`username` IS NULL THEN 0 ELSE 1 END AS user_token_exists
FROM `imapsync` i
LEFT JOIN `imapsync_source` s ON i.`source_id` = s.`id`
LEFT JOIN `imapsync_source_oauth_token` t
ON t.`source_id` = i.`source_id` AND t.`owner` = i.`user2` AND t.`username` = i.`user1`
WHERE i.`id` = :id", ['id' => $id])->fetch(PDO::FETCH_ASSOC);
return $row ? Job::fromRow($row) : null;
}
/** @return int[] job ids of a local mailbox in queue order */
public function idsForMailbox(string $user2): array
{
return array_map('intval', $this->query("SELECT `id` FROM `imapsync` WHERE `user2` = :user2 " . self::QUEUE_ORDER,
['user2' => $user2])->fetchAll(PDO::FETCH_COLUMN));
}
/** @return int[] all job ids (incl. orphaned ones) in queue order */
public function allIds(): array
{
return array_map('intval', $this->query("SELECT `id` FROM `imapsync` " . self::QUEUE_ORDER)->fetchAll(PDO::FETCH_COLUMN));
}
/** True when another job (not $exceptId) already syncs (source, user1) into user2 */
public function tupleExists(int $sourceId, string $user2, string $user1, ?int $exceptId = null): bool
{
return (bool)$this->query("SELECT 1 FROM `imapsync`
WHERE `user2` = :user2 AND `user1` = :user1 AND `source_id` = :source_id AND `id` != :id",
['user2' => $user2, 'user1' => $user1, 'source_id' => $sourceId, 'id' => $exceptId ?? 0])->fetchColumn();
}
public function insert(Job $job): int
{
return $job->id = $this->insertRow('imapsync', $this->values($job) + ['user2' => $job->user2]);
}
public function update(Job $job): void
{
$this->updateRow('imapsync', (int)$job->id,
$this->values($job) + ['last_run' => $job->lastRun, 'success' => $job->success, 'prio' => $job->prio]);
}
public function delete(int $id): void
{
$this->query("DELETE FROM `imapsync` WHERE `id` = :id", ['id' => $id]);
}
/**
* Moves idle jobs to the front of the queue: each gets a prio above the current global max
* (lowest current prio first) and last_run/success are cleared. The rows are locked first, so the
* runner cannot claim a job in between (its claim UPDATE waits for this transaction).
*
* @param int[] $ids
* @return array{running: int[], moved: int} ids that are running (left alone) and the number of moved jobs
*/
public function moveToFront(array $ids): array
{
return $this->transaction(function () use ($ids): array {
$rows = $this->query("SELECT `id`, `is_running` FROM `imapsync` WHERE `id` IN (" . self::placeholders($ids) . ") FOR UPDATE",
$ids)->fetchAll(PDO::FETCH_KEY_PAIR);
$running = array_keys(array_filter($rows, fn($isRunning): bool => (int)$isRunning === 1));
$idle = array_values(array_diff(array_keys($rows), $running));
$moved = 0;
if (!empty($idle)) {
$this->pdo->exec("SET @m := (SELECT COALESCE(MAX(`prio`), 0) FROM `imapsync`)");
$this->pdo->exec("SET @r := 0");
$moved = $this->query("UPDATE `imapsync` SET `prio` = @m + (@r := @r + 1), `last_run` = NULL, `success` = NULL
WHERE `id` IN (" . self::placeholders($idle) . ") AND `is_running` = 0 ORDER BY `prio` ASC, `id` ASC", $idle)->rowCount();
}
return ['running' => $running, 'moved' => $moved];
});
}
/** Values of the columns a user can edit (shared by insert and update) */
private function values(Job $job): array
{
$values = ['mins_interval' => $job->minsInterval, 'maxbytespersecond' => $job->maxBytesPerSecond,
'custom_params' => $job->customParams, 'source_id' => $job->sourceId];
// Columns with a property of the same name
foreach (['user1', 'password1', 'subfolder2', 'exclude', 'maxage', 'timeout1', 'timeout2', 'active', 'delete1', 'delete2',
'delete2duplicates', 'automap', 'skipcrossduplicates', 'subscribeall', 'dry'] as $column) {
$values[$column] = $job->$column;
}
return $values;
}
private static function placeholders(array $values): string
{
return implode(',', array_fill(0, count($values), '?'));
}
}
@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Repository;
use Mailcow\Core\Repository;
use Mailcow\Imapsync\Model\Settings;
use PDO;
/**
* SQL for the global sync job settings (table `imapsync_settings`, name => value).
*/
class SettingsRepository extends Repository
{
/** Stored settings; keys that are not stored yet keep the defaults of the model */
public function load(): Settings
{
$rows = $this->query("SELECT `name`, `value` FROM `imapsync_settings`")->fetchAll(PDO::FETCH_KEY_PAIR);
$settings = new Settings();
$settings->maxParallel = (int)($rows['max_parallel'] ?? $settings->maxParallel);
$settings->maxBytesPerSecond = (int)($rows['max_bytes_per_second'] ?? $settings->maxBytesPerSecond);
return $settings;
}
public function save(Settings $settings): void
{
$sql = "INSERT INTO `imapsync_settings` (`name`, `value`) VALUES (:name, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
$this->query($sql, ['name' => 'max_parallel', 'value' => (string)$settings->maxParallel]);
$this->query($sql, ['name' => 'max_bytes_per_second', 'value' => (string)$settings->maxBytesPerSecond]);
}
}
@@ -1,215 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Repository;
use Mailcow\Core\Context;
use Mailcow\Core\Repository;
use Mailcow\Imapsync\Model\Source;
use PDO;
/**
* SQL for `imapsync_source` and its target tables `imapsync_source_domain` / `imapsync_source_user`.
* No permission decisions here except the visibility filter, which is pure SQL (see visibility()).
*/
class SourceRepository extends Repository
{
/** Full source incl. secrets and targets, without visibility filter */
public function find(int $id): ?Source
{
return $this->fetch('1=1', ['id' => $id]);
}
/** Like find(), but only when the source is visible to the caller */
public function findVisible(int $id, Context $context): ?Source
{
[$visible, $params] = $this->visibility($context);
return $this->fetch($visible, $params + ['id' => $id]);
}
/** @return int[] ids of all sources visible to the caller, ordered by name */
public function visibleIds(Context $context): array
{
[$visible, $params] = $this->visibility($context);
return array_map('intval', $this->query("SELECT s.`id` FROM `imapsync_source` s WHERE $visible ORDER BY s.`name`, s.`id`",
$params)->fetchAll(PDO::FETCH_COLUMN));
}
public function nameExists(string $name, string $createdBy, ?int $exceptId = null): bool
{
return (bool)$this->query("SELECT 1 FROM `imapsync_source` WHERE `name` = :name AND `created_by` = :created_by AND `id` != :id",
['name' => $name, 'created_by' => $createdBy, 'id' => $exceptId ?? 0])->fetchColumn();
}
/** True while a sync job references the source */
public function isInUse(int $id): bool
{
return (bool)$this->query("SELECT 1 FROM `imapsync` WHERE `source_id` = :id LIMIT 1", ['id' => $id])->fetchColumn();
}
public function isSuperAdmin(string $username): bool
{
return (bool)$this->query("SELECT 1 FROM `admin` WHERE `username` = :username AND `superadmin` = '1'",
['username' => $username])->fetchColumn();
}
/** Inserts the source with its targets and returns the new id */
public function insert(Source $source): int
{
return $this->transaction(function () use ($source): int {
$source->id = $this->insertRow('imapsync_source',
$this->values($source) + ['created_by' => $source->createdBy, 'scope' => $source->scope]);
$this->writeTargets($source);
return $source->id;
});
}
/**
* Updates the source. $writeTargets also rewrites scope + target lists, $resetTokens drops the shared
* token and all per-user tokens of the source. All in one transaction.
*/
public function update(Source $source, bool $writeTargets, bool $resetTokens): void
{
$this->transaction(function () use ($source, $writeTargets, $resetTokens): void {
$id = ['id' => $source->id];
$this->updateRow('imapsync_source', (int)$source->id, $this->values($source));
if ($writeTargets) {
$this->query("DELETE FROM `imapsync_source_domain` WHERE `source_id` = :id", $id);
$this->query("DELETE FROM `imapsync_source_user` WHERE `source_id` = :id", $id);
$this->writeTargets($source);
}
if ($resetTokens) {
$this->query("UPDATE `imapsync_source` SET `oauth_access_token` = NULL, `oauth_token_expires` = NULL,
`oauth_last_refresh_error` = NULL, `oauth_last_refresh_attempt` = NULL WHERE `id` = :id", $id);
$this->query("DELETE FROM `imapsync_source_oauth_token` WHERE `source_id` = :id", $id);
}
});
}
/** Deletes the source; targets and per-user tokens go via FK ON DELETE CASCADE */
public function delete(int $id): void
{
$this->query("DELETE FROM `imapsync_source` WHERE `id` = :id", ['id' => $id]);
}
/** Deletes the source and its child rows explicitly, for callers running with FOREIGN_KEY_CHECKS = 0 (mailbox rename) */
public function deleteWithChildren(int $id): void
{
foreach (['imapsync_source_oauth_token', 'imapsync_source_user', 'imapsync_source_domain'] as $table) {
$this->query("DELETE FROM `$table` WHERE `source_id` = :id", ['id' => $id]);
}
$this->delete($id);
}
/** @return Source[] sources created by an account (id and name only) */
public function listCreatedBy(string $username): array
{
return array_map([Source::class, 'fromRow'], $this->query("SELECT `id`, `name` FROM `imapsync_source` WHERE `created_by` = :username",
['username' => $username])->fetchAll(PDO::FETCH_ASSOC));
}
/** Hands a source over to the admins (created_by '') under the given name */
public function handOver(int $id, string $name): void
{
$this->query("UPDATE `imapsync_source` SET `created_by` = '', `name` = :name WHERE `id` = :id", ['name' => $name, 'id' => $id]);
}
/** Follows an account rename: creator and user targets */
public function renameAccount(string $old, string $new): void
{
$this->query("UPDATE `imapsync_source` SET `created_by` = :new WHERE `created_by` = :old", ['new' => $new, 'old' => $old]);
$this->query("UPDATE `imapsync_source_user` SET `username` = :new WHERE `username` = :old", ['new' => $new, 'old' => $old]);
}
/** Removes a deleted account from all user target lists */
public function deleteUserTarget(string $username): void
{
$this->query("DELETE FROM `imapsync_source_user` WHERE `username` = :username", ['username' => $username]);
}
private function fetch(string $where, array $params): ?Source
{
$row = $this->query("SELECT s.* FROM `imapsync_source` s WHERE s.`id` = :id AND $where", $params)->fetch(PDO::FETCH_ASSOC);
if (!$row) {
return null;
}
$source = Source::fromRow($row);
$id = ['id' => $source->id];
$source->domains = $this->query("SELECT `domain` FROM `imapsync_source_domain` WHERE `source_id` = :id ORDER BY `domain`",
$id)->fetchAll(PDO::FETCH_COLUMN);
$source->users = $this->query("SELECT `username` FROM `imapsync_source_user` WHERE `source_id` = :id ORDER BY `username`",
$id)->fetchAll(PDO::FETCH_COLUMN);
return $source;
}
/**
* WHERE fragment over `imapsync_source s` that limits rows to what the caller may see, and its parameters.
* admin everything
* domainadmin own + 'all' + sources targeting his domains or their mailboxes + sources created by them
* user own + 'all' + sources targeting him or his domain
*
* @return array{0: string, 1: array}
*/
private function visibility(Context $context): array
{
$username = $context->username;
if ($context->isAdmin()) {
return ['1=1', []];
}
// Native prepares: a placeholder may appear only once, so the username is bound under one name per use
$managed = "SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = ";
if ($context->isDomainAdmin()) {
return ["(s.`created_by` = :vis_self OR s.`scope` = 'all'
OR (s.`scope` = 'domain' AND EXISTS (SELECT 1 FROM `imapsync_source_domain` t
WHERE t.`source_id` = s.`id` AND t.`domain` IN ($managed :vis_d1)))
OR (s.`scope` = 'user' AND EXISTS (SELECT 1 FROM `imapsync_source_user` t JOIN `mailbox` mb ON mb.`username` = t.`username`
WHERE t.`source_id` = s.`id` AND mb.`domain` IN ($managed :vis_d2)))
OR s.`created_by` IN (SELECT `username` FROM `mailbox` WHERE `domain` IN ($managed :vis_d3)))",
['vis_self' => $username, 'vis_d1' => $username, 'vis_d2' => $username, 'vis_d3' => $username]];
}
if ($context->isUser()) {
return ["(s.`created_by` = :vis_self OR s.`scope` = 'all'
OR (s.`scope` = 'user' AND EXISTS (SELECT 1 FROM `imapsync_source_user` t
WHERE t.`source_id` = s.`id` AND t.`username` = :vis_u1))
OR (s.`scope` = 'domain' AND EXISTS (SELECT 1 FROM `imapsync_source_domain` t JOIN `mailbox` mb ON mb.`domain` = t.`domain`
WHERE t.`source_id` = s.`id` AND mb.`username` = :vis_u2)))",
['vis_self' => $username, 'vis_u1' => $username, 'vis_u2' => $username]];
}
return ['0=1', []];
}
private function writeTargets(Source $source): void
{
foreach ($source->domains as $domain) {
$this->query("INSERT INTO `imapsync_source_domain` (`source_id`, `domain`) VALUES (:id, :domain)",
['id' => $source->id, 'domain' => $domain]);
}
foreach ($source->users as $username) {
$this->query("INSERT INTO `imapsync_source_user` (`source_id`, `username`) VALUES (:id, :username)",
['id' => $source->id, 'username' => $username]);
}
$this->query("UPDATE `imapsync_source` SET `scope` = :scope WHERE `id` = :id", ['scope' => $source->scope, 'id' => $source->id]);
}
private function values(Source $source): array
{
return [
'name' => $source->name,
'description' => $source->description,
'host1' => $source->host,
'port1' => $source->port,
'enc1' => $source->enc,
'auth_type' => $source->authType,
'oauth_flow' => $source->oauthFlow,
'oauth_token_endpoint' => $source->tokenEndpoint,
'oauth_authorize_endpoint' => $source->authorizeEndpoint,
'oauth_userinfo_endpoint' => $source->userinfoEndpoint,
'oauth_client_id' => $source->clientId,
'oauth_client_secret' => $source->clientSecret,
'oauth_scope' => $source->oauthScope,
'oauth_extra_params' => $source->extraParams,
'active' => $source->active,
];
}
}
@@ -1,123 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Repository;
use Mailcow\Core\Repository;
use Mailcow\Imapsync\Model\OAuthToken;
use Mailcow\Imapsync\Model\Source;
use PDO;
/**
* SQL for OAuth tokens: the shared token of a client_credentials source (columns of `imapsync_source`)
* and the per-user tokens of authorization_code sources (`imapsync_source_oauth_token`).
*/
class TokenRepository extends Repository
{
private const USER_TOKEN = "`source_id` = :sid AND `owner` = :owner AND `username` = :user";
public function exists(int $sourceId, string $owner, string $username): bool
{
return (bool)$this->query("SELECT 1 FROM `imapsync_source_oauth_token` WHERE " . self::USER_TOKEN,
['sid' => $sourceId, 'owner' => $owner, 'user' => $username])->fetchColumn();
}
/**
* Stores the token of a finished browser sign-in. An existing refresh token is kept when the
* provider sent none (Microsoft only sends one with offline_access).
*/
public function saveUserToken(OAuthToken $token): void
{
$this->query("INSERT INTO `imapsync_source_oauth_token`
(`source_id`, `owner`, `username`, `access_token`, `refresh_token`, `token_expires`, `last_refresh_error`, `oauth_last_refresh_attempt`)
VALUES (:sid, :owner, :user, :acc, :ref, :exp, NULL, NOW())
ON DUPLICATE KEY UPDATE
`access_token` = VALUES(`access_token`),
`refresh_token` = IF(VALUES(`refresh_token`) = '', `refresh_token`, VALUES(`refresh_token`)),
`token_expires` = VALUES(`token_expires`),
`last_refresh_error` = NULL,
`oauth_last_refresh_attempt` = NOW()",
$this->key($token) + ['acc' => $token->accessToken, 'ref' => (string)$token->refreshToken, 'exp' => $token->tokenExpires]);
}
/** Stores a refreshed per-user token and clears its last error */
public function storeUserRefresh(OAuthToken $token): void
{
$this->query("UPDATE `imapsync_source_oauth_token` SET `access_token` = :acc, `refresh_token` = :ref,
`token_expires` = :exp, `last_refresh_error` = NULL, `oauth_last_refresh_attempt` = NOW() WHERE " . self::USER_TOKEN,
$this->key($token) + ['acc' => $token->accessToken, 'ref' => $token->refreshToken, 'exp' => $token->tokenExpires]);
}
public function storeUserError(OAuthToken $token, string $error): void
{
$this->query("UPDATE `imapsync_source_oauth_token` SET `last_refresh_error` = :err, `oauth_last_refresh_attempt` = NOW()
WHERE " . self::USER_TOKEN, $this->key($token) + ['err' => $error]);
}
public function storeSourceToken(int $sourceId, string $accessToken, int $expires): void
{
$this->query("UPDATE `imapsync_source` SET `oauth_access_token` = :tok, `oauth_token_expires` = :exp,
`oauth_last_refresh_error` = NULL, `oauth_last_refresh_attempt` = NOW() WHERE `id` = :id",
['tok' => $accessToken, 'exp' => $expires, 'id' => $sourceId]);
}
public function storeSourceError(int $sourceId, string $error): void
{
$this->query("UPDATE `imapsync_source` SET `oauth_last_refresh_error` = :err, `oauth_last_refresh_attempt` = NOW()
WHERE `id` = :id", ['err' => $error, 'id' => $sourceId]);
}
/** @return Source[] active client_credentials sources (incl. secrets), each with refreshAttemptAge */
public function refreshableSources(): array
{
return array_map([Source::class, 'fromRow'], $this->query("SELECT *,
TIMESTAMPDIFF(SECOND, `oauth_last_refresh_attempt`, NOW()) AS `refresh_attempt_age`
FROM `imapsync_source`
WHERE `active` = 1 AND `auth_type` = 'XOAUTH2' AND `oauth_flow` = 'client_credentials'
ORDER BY `id`")->fetchAll(PDO::FETCH_ASSOC));
}
/** @return OAuthToken[] per-user tokens with a refresh token on active authorization_code sources, with refreshAttemptAge */
public function refreshableUserTokens(): array
{
return array_map([OAuthToken::class, 'fromRow'], $this->query("SELECT t.*,
TIMESTAMPDIFF(SECOND, t.`oauth_last_refresh_attempt`, NOW()) AS `refresh_attempt_age`
FROM `imapsync_source_oauth_token` t
JOIN `imapsync_source` s ON s.`id` = t.`source_id`
WHERE s.`active` = 1 AND s.`auth_type` = 'XOAUTH2' AND s.`oauth_flow` = 'authorization_code'
AND t.`refresh_token` <> ''
ORDER BY t.`source_id`, t.`owner`, t.`username`")->fetchAll(PDO::FETCH_ASSOC));
}
/** Drops a per-user token once no job uses (source, owner, username) anymore */
public function deleteIfUnused(int $sourceId, string $owner, string $username): void
{
$this->query("DELETE FROM `imapsync_source_oauth_token` WHERE " . self::USER_TOKEN . "
AND NOT EXISTS (SELECT 1 FROM `imapsync` WHERE `source_id` = :sid2 AND `user2` = :owner2 AND `user1` = :user2)",
['sid' => $sourceId, 'owner' => $owner, 'user' => $username, 'sid2' => $sourceId, 'owner2' => $owner, 'user2' => $username]);
}
/** Drops per-user tokens that no job uses and that are older than $seconds. Returns the number of rows. */
public function deleteUnusedOlderThan(int $seconds): int
{
return $this->query("DELETE t FROM `imapsync_source_oauth_token` t
LEFT JOIN `imapsync` i ON i.`source_id` = t.`source_id` AND i.`user2` = t.`owner` AND i.`user1` = t.`username`
WHERE i.`id` IS NULL AND t.`created` < NOW() - INTERVAL :seconds SECOND", ['seconds' => $seconds])->rowCount();
}
public function deleteByOwner(string $owner): void
{
$this->query("DELETE FROM `imapsync_source_oauth_token` WHERE `owner` = :owner", ['owner' => $owner]);
}
public function renameOwner(string $old, string $new): void
{
$this->query("UPDATE `imapsync_source_oauth_token` SET `owner` = :new WHERE `owner` = :old", ['new' => $new, 'old' => $old]);
}
private function key(OAuthToken $token): array
{
return ['sid' => $token->sourceId, 'owner' => $token->owner, 'user' => $token->username];
}
}
@@ -1,286 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Service;
use Mailcow\Core\AccessDenied;
use Mailcow\Core\Context;
use Mailcow\Core\Input;
use Mailcow\Core\UserError;
use Mailcow\Imapsync\Model\CustomParams;
use Mailcow\Imapsync\Model\Job;
use Mailcow\Imapsync\Model\Settings;
use Mailcow\Imapsync\Model\Source;
use Mailcow\Imapsync\Repository\JobRepository;
use Mailcow\Imapsync\Repository\SettingsRepository;
use Mailcow\Imapsync\Repository\TokenRepository;
/**
* Use cases and rules for sync jobs and the global sync settings.
*
* A job belongs to its local mailbox (user2): whoever may access that mailbox may see and manage it
* (admins also orphaned jobs). Writing needs the `syncjobs` ACL. Jobs on an authorization_code source
* need the per-user token of (source, owner = user2, username = user1) first (C1).
*/
class JobService
{
private const FLAGS = ['active', 'delete2duplicates', 'subscribeall', 'dry', 'delete1', 'delete2', 'automap', 'skipcrossduplicates'];
public function __construct(private JobRepository $jobs, private SourceService $sources, private TokenRepository $tokens,
private SettingsRepository $settings, private Context $context, private array $allowlist)
{
}
/** @throws AccessDenied without the syncjobs ACL */
public function requireAcl(): void
{
if (!$this->context->hasAcl('syncjobs')) {
throw new AccessDenied();
}
}
/** The allowed custom params (option => rule), for the job form */
public function customParamOptions(): array
{
return $this->allowlist;
}
/**
* The job when the caller may access its mailbox (incl. password1: never pass the object to the UI, use toArray()).
* Admins may also manage orphaned jobs whose local mailbox no longer exists.
*/
public function get(int $id, bool $withLog = true): ?Job
{
$job = $this->jobs->find($id, $withLog);
return ($job !== null && ($this->context->isAdmin() || $this->context->canAccessMailbox($job->user2))) ? $job : null;
}
/** @return int[] job ids of a mailbox (default: the caller's own) in queue order; [] without access */
public function idsForMailbox(mixed $mailbox): array
{
if ($mailbox !== null && filter_var($mailbox, FILTER_VALIDATE_EMAIL)) {
if (!$this->context->canAccessMailbox((string)$mailbox)) {
return [];
}
} else {
$mailbox = $this->context->username;
}
return ($mailbox === null) ? [] : $this->jobs->idsForMailbox((string)$mailbox);
}
/** Only admins list all jobs at once (incl. orphaned ones) */
public function canListAll(): bool
{
return $this->context->isAdmin();
}
/** @return int[] all job ids in queue order; [] for non-admins */
public function allIds(): array
{
return $this->canListAll() ? $this->jobs->allIds() : [];
}
/** @throws UserError */
public function create(array $input): Job
{
$this->requireAcl();
$job = new Job();
$job->user2 = $this->ownerMailbox($input);
// Add keeps its legacy parsing of these attributes (e.g. maxage '1.5' means none, on edit 1); then the edit rules apply
$input['source_id'] = (int)($input['source_id'] ?? 0);
$input['maxage'] = Input::intInRange($input['maxage'] ?? null, 1, 32000) ?? 0;
$input['maxbytespersecond'] = Input::intInRange($input['maxbytespersecond'] ?? null, 1, 125000000) ?? 0;
$this->save($job, null, $input + ['custom_params' => '']);
return $job;
}
/** @throws UserError */
public function update(int $id, array $input): Job
{
$this->requireAcl();
$current = $this->get($id, false) ?? throw new AccessDenied();
$job = clone $current;
$this->save($job, $current, $input);
return $job;
}
/** @throws UserError access_denied when the job does not exist or the caller may not access it */
public function delete(int $id): void
{
$this->requireAcl();
$job = $this->get($id, false) ?? throw new AccessDenied();
// prio is a sparse sort key (ties allowed), so no gap needs closing
$this->jobs->delete($id);
$this->tokens->deleteIfUnused($job->sourceId, $job->user2, $job->user1);
}
/**
* Admin only (C4): moves jobs to the front of the queue. Running jobs are left alone; the runner never resets prio.
*
* @param int[] $ids
* @return array{running: int[], moved: int}
*/
public function runNext(array $ids): array
{
$ids = array_values(array_unique($ids));
if (!$this->context->isAdmin() || empty($ids)) {
throw new AccessDenied();
}
return $this->jobs->moveToFront($ids);
}
/** @throws AccessDenied for non-admins */
public function getSettings(): Settings
{
if (!$this->context->isAdmin()) {
throw new AccessDenied();
}
return $this->settings->load();
}
/**
* Saves the global settings (admin only). The UI sends the bandwidth limit in KB/s; stored as bytes/s
* like the per-job limit.
*
* @throws UserError
*/
public function saveSettings(array $input): Settings
{
if (!$this->context->isAdmin()) {
throw new AccessDenied();
}
$maxParallel = Input::intInRange($input['max_parallel'] ?? null, 1, 32);
$kb = $input['max_kb_per_second'] ?? 0;
if ($maxParallel === null) {
throw new UserError('imapsync_max_parallel_invalid');
}
// 0 (unlimited) .. 10 GiB/s
$kb = Input::intInRange(($kb === '') ? 0 : $kb, 0, 10485760) ?? throw new UserError('imapsync_max_kb_per_second_invalid');
$settings = new Settings();
$settings->maxParallel = $maxParallel;
$settings->maxBytesPerSecond = $kb * 1024;
$this->settings->save($settings);
return $settings;
}
/**
* Applies an add ($current = null) or edit request to $job and stores it. Attributes missing in $input keep
* the value of $job, an empty password keeps the stored one. On edit the source is only checked when the job
* moves to another source, so a domain admin or user can still edit or deactivate a job bound to a source
* he cannot see.
*
* @throws UserError
*/
private function save(Job $job, ?Job $current, array $input): void
{
foreach (self::FLAGS as $flag) {
if (isset($input[$flag])) {
$job->$flag = (int)$input[$flag];
}
}
foreach (['maxage', 'timeout1', 'timeout2'] as $number) {
if (isset($input[$number]) && $input[$number] != '') {
$job->$number = (int)$input[$number];
}
}
if (isset($input['maxbytespersecond']) && $input['maxbytespersecond'] != '') {
$job->maxBytesPerSecond = (string)(int)$input['maxbytespersecond'];
}
// last_run/success are reset when present; prio is a global-queue concern that only admins may change
$job->lastRun = isset($input['last_run']) ? null : $job->lastRun;
$job->success = isset($input['success']) ? null : $job->success;
if (isset($input['prio']) && is_numeric($input['prio']) && $this->context->isAdmin()) {
$job->prio = (int)$input['prio'];
}
// An empty user1 keeps the stored one on edit; a new job needs one
$user1 = (isset($input['user1']) && Input::text($input['user1']) !== '') ? Input::text($input['user1']) : $job->user1;
if ($user1 === null || mb_strlen($user1) > 255 || ($current === null && $user1 === '')) {
throw new UserError('imapsync_user1_invalid');
}
$password = (isset($input['password1']) && is_scalar($input['password1']) && (string)$input['password1'] !== '')
? (string)$input['password1'] : $job->password1;
$sourceId = (!empty($input['source_id']) && is_scalar($input['source_id'])) ? (int)$input['source_id'] : $job->sourceId;
$sourceChanged = ($current === null || $sourceId !== $current->sourceId);
$tupleChanged = $sourceChanged || $user1 !== $current->user1;
$job->user1 = $user1;
$job->sourceId = $sourceId;
$source = $sourceChanged ? $this->sources->get($sourceId) : $current->source;
if ($source === null || ($sourceChanged && $source->active !== 1)) {
throw new UserError('imapsync_source_unavailable');
}
if ($source->usesClientCredentials() && $tupleChanged && !$sourceChanged && !$this->mayUse($source)) {
// The app token reaches every mailbox of the tenant, so only callers that may use the source
// may pick another remote mailbox (user1). Other fields stay editable.
throw new AccessDenied();
}
$job->password1 = $this->checkAuth($source, $job, $password, $tupleChanged);
if (array_key_exists('custom_params', $input)) {
$job->customParams = CustomParams::toJson($input['custom_params'], $this->allowlist);
}
$job->maxage = Input::intInRange($job->maxage, 1, 32000) ?? 0;
$job->timeout1 = Input::intInRange($job->timeout1, 1, 32000) ?? 600;
$job->timeout2 = Input::intInRange($job->timeout2, 1, 32000) ?? 600;
$job->maxBytesPerSecond = (string)(Input::intInRange($job->maxBytesPerSecond, 1, 125000000) ?? 0);
$exclude = isset($input['exclude']) ? Input::text($input['exclude']) : $job->exclude;
$subfolder2 = isset($input['subfolder2']) ? Input::text($input['subfolder2']) : $job->subfolder2;
$minsInterval = Input::intInRange(!empty($input['mins_interval']) ? $input['mins_interval'] : $job->minsInterval, 1, 43800);
// exclude must be a valid regex, the interval 1..43800 minutes
if ($exclude === null || $subfolder2 === null || $minsInterval === null || @preg_match("/" . $exclude . "/", '') === false) {
throw new AccessDenied();
}
$job->exclude = $exclude;
$job->subfolder2 = $subfolder2;
$job->minsInterval = $minsInterval;
if ($tupleChanged && $this->jobs->tupleExists($job->sourceId, $job->user2, $job->user1, $current?->id)) {
throw new UserError('object_exists', [htmlspecialchars($source->name . ' / ' . $job->user1)]);
}
if ($current === null) {
$this->jobs->insert($job);
return;
}
$this->jobs->update($job);
// The previous (source, owner, remote user) token is dropped once no job uses it (C1)
if ($tupleChanged) {
$this->tokens->deleteIfUnused($current->sourceId, $current->user2, $current->user1);
}
}
/** Whether the caller may use the source for new jobs (sees it or may edit it) */
private function mayUse(Source $source): bool
{
return $this->sources->get((int)$source->id) !== null || $this->sources->canEdit($source);
}
/**
* Local mailbox of a new job: `username` (needs access), else the caller's own mailbox (users only).
*
* @throws UserError access_denied, no_user_defined
*/
private function ownerMailbox(array $input): string
{
$username = $input['username'] ?? null;
if (is_string($username) && filter_var($username, FILTER_VALIDATE_EMAIL)) {
return $this->context->canAccessMailbox($username) ? $username : throw new AccessDenied();
}
return $this->context->isUser() ? (string)$this->context->username : throw new UserError('no_user_defined');
}
/**
* Returns the password to store. XOAUTH2 sources carry no password (the runner uses an access token);
* an authorization_code source needs the per-user token of (source, owner, remote user) when it is new.
*
* @throws UserError imapsync_source_oauth_connect_required, password_empty
*/
private function checkAuth(Source $source, Job $job, string $password, bool $tupleChanged): string
{
if (!$source->isXOAuth2()) {
return ($password !== '') ? $password : throw new UserError('password_empty');
}
if ($source->usesAuthorizationCode() && $tupleChanged && !$this->tokens->exists($job->sourceId, $job->user2, $job->user1)) {
throw new UserError('imapsync_source_oauth_connect_required');
}
return '';
}
}
@@ -1,332 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Service;
use Exception;
use Mailcow\Core\Input;
use Mailcow\Core\UserError;
use Mailcow\Imapsync\Http\OAuthClient;
use Mailcow\Imapsync\Http\OAuthException;
use Mailcow\Imapsync\Model\OAuthToken;
use Mailcow\Imapsync\Model\Source;
use Mailcow\Imapsync\Repository\SourceRepository;
use Mailcow\Imapsync\Repository\TokenRepository;
use PDOException;
/**
* OAuth2 for XOAUTH2 sources: their OAuth settings, token requests for every grant type, the browser sign-in
* (authorization_code) and token refresh. Needs no Context, so the refresh cron can use it; callers check
* permissions first.
*/
class OAuthService
{
/** Tokens expiring within this many seconds are refreshed (the runner starts jobs with >= 15 min left) */
private const REFRESH_AHEAD = 1200;
/** A token whose last refresh failed is retried after this many seconds */
private const RETRY_AFTER = 180;
/** Extra params that belong to the browser sign-in; all others go to the token request */
private const AUTHORIZE_PARAMS = ['prompt', 'access_type', 'login_hint', 'domain_hint', 'hd'];
/** Per-user tokens that no job uses are kept this long (time to save the job after signing in) */
private const UNUSED_TOKEN_GRACE = 86400;
private string $hostname;
public function __construct(private SourceRepository $sources, private TokenRepository $tokens, private OAuthClient $client,
string $hostname)
{
$this->hostname = strtolower(trim($hostname));
}
/**
* Redirect URI to register at the provider. Only MAILCOW_HOSTNAME is supported: the popup callback
* needs the session cookie and opener origin of the UI, so the UI must be used on that host.
*/
public function redirectUri(): string
{
return ($this->hostname === '') ? '' : 'https://' . $this->hostname . '/syncjob-oauth';
}
/**
* Sets the OAuth settings of a source from request attributes: cleared for password sources, read and
* validated for XOAUTH2 (incl. the endpoint policy C5). Missing attributes keep the value of $source,
* an empty client secret keeps the stored one. Permission checks are the caller's job.
*
* @throws UserError
*/
public function applySettings(Source $source, array $input): void
{
if (!$source->isXOAuth2()) {
$source->oauthFlow = Source::FLOW_CLIENT_CREDENTIALS;
$source->tokenEndpoint = $source->authorizeEndpoint = $source->userinfoEndpoint = null;
$source->clientId = $source->clientSecret = $source->oauthScope = $source->extraParams = null;
return;
}
$source->oauthFlow = (($input['oauth_flow'] ?? $source->oauthFlow) === Source::FLOW_AUTHORIZATION_CODE)
? Source::FLOW_AUTHORIZATION_CODE : Source::FLOW_CLIENT_CREDENTIALS;
$source->tokenEndpoint = $this->oauthText($input, 'oauth_token_endpoint', $source->tokenEndpoint);
$authorizeEndpoint = $this->oauthText($input, 'oauth_authorize_endpoint', $source->authorizeEndpoint);
$userinfoEndpoint = $this->oauthText($input, 'oauth_userinfo_endpoint', $source->userinfoEndpoint);
$source->clientId = $this->oauthText($input, 'oauth_client_id', $source->clientId);
$source->oauthScope = $this->oauthText($input, 'oauth_scope', $source->oauthScope);
$secret = $input['oauth_client_secret'] ?? '';
if (!is_scalar($secret)) {
throw new UserError('imapsync_source_oauth_invalid');
}
$source->clientSecret = ((string)$secret !== '') ? (string)$secret : (string)$source->clientSecret;
$source->extraParams = $this->extraParams(array_key_exists('oauth_extra_params', $input) ? $input['oauth_extra_params'] : $source->extraParams);
if ($source->tokenEndpoint === '' || $source->clientId === '' || $source->clientSecret === '' || $source->oauthScope === '') {
throw new UserError('imapsync_source_oauth_required');
}
if (mb_strlen($source->clientId) > 500 || mb_strlen($source->clientSecret) > 500 || mb_strlen($source->oauthScope) > 500) {
throw new UserError('imapsync_source_oauth_invalid');
}
// C5, checked again before every request. The authorize endpoint is only opened in the browser,
// but must not point users to internal hosts either.
$endpoints = [$source->tokenEndpoint];
if ($source->usesAuthorizationCode()) {
$endpoints[] = $authorizeEndpoint;
}
if ($userinfoEndpoint !== '') {
$endpoints[] = $userinfoEndpoint;
}
foreach ($endpoints as $endpoint) {
if (!$this->endpointAllowed($endpoint, $source->createdBy)) {
throw new UserError('imapsync_source_oauth_endpoint_invalid');
}
}
$source->authorizeEndpoint = ($authorizeEndpoint === '') ? null : $authorizeEndpoint;
$source->userinfoEndpoint = ($userinfoEndpoint === '') ? null : $userinfoEndpoint;
}
/** Provider consent URL for the browser sign-in, or null when the authorize endpoint is not allowed */
public function authorizeUrl(Source $source, string $state): ?string
{
$endpoint = (string)$source->authorizeEndpoint;
if (!$this->endpointAllowed($endpoint, $source->createdBy)) {
return null;
}
// select_account: never sign in silently with the account the browser already uses (often the admin's)
$params = array_merge(['prompt' => 'select_account'], $this->extraParamsFor($source, true), ['client_id' => $source->clientId,
'response_type' => 'code', 'redirect_uri' => $this->redirectUri(), 'response_mode' => 'query',
'scope' => $source->oauthScope, 'state' => $state]);
return $endpoint . (str_contains($endpoint, '?') ? '&' : '?') . http_build_query($params);
}
/**
* Finishes the browser sign-in: exchanges the code, finds the remote identity and stores the token
* for the local mailbox $owner (C1). Returns the remote identity (the job's user1).
*
* @throws UserError token_failed (args: detail for the server log) or no_identity
*/
public function connectUser(Source $source, string $owner, string $code): string
{
try {
$token = $this->requestToken($source, ['grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $this->redirectUri()]);
} catch (OAuthException $e) {
throw new UserError('token_failed', ['token exchange failed for source ' . $source->id . ': ' . $e->getMessage()]);
}
$identity = $this->identity($source, $token);
if ($identity === null) {
throw new UserError('no_identity');
}
try {
$this->tokens->saveUserToken(OAuthToken::fromRow(['source_id' => $source->id, 'owner' => $owner, 'username' => $identity,
'access_token' => $token['access_token'], 'refresh_token' => $token['refresh_token'] ?? '',
'token_expires' => time() + (int)$token['expires_in']]));
} catch (PDOException $e) {
throw new UserError('token_failed', ['storing token failed for source ' . $source->id . ': ' . $e->getMessage()]);
}
return $identity;
}
/** Fetches and stores a new shared token of a client_credentials source (or stores the error); returns the error or null */
public function refreshSourceToken(Source $source): ?string
{
if (!$source->usesClientCredentials()) {
return 'not refreshable';
}
try {
$token = $this->requestToken($source, ['grant_type' => 'client_credentials']);
} catch (OAuthException $e) {
$this->tokens->storeSourceError((int)$source->id, $e->getMessage());
return $e->getMessage();
}
$this->tokens->storeSourceToken((int)$source->id, $token['access_token'], time() + (int)$token['expires_in']);
return null;
}
/** Refreshes and stores a per-user token via its refresh token (or stores the error); returns the error or null */
public function refreshUserToken(Source $source, OAuthToken $token): ?string
{
if (!$source->usesAuthorizationCode() || (string)$token->refreshToken === '') {
return 'not refreshable';
}
try {
$new = $this->requestToken($source, ['grant_type' => 'refresh_token', 'refresh_token' => $token->refreshToken]);
} catch (OAuthException $e) {
$this->tokens->storeUserError($token, $e->getMessage());
return $e->getMessage();
}
$token->accessToken = $new['access_token'];
// Some providers omit a new refresh token on refresh: keep the existing one then
$token->refreshToken = empty($new['refresh_token']) ? $token->refreshToken : $new['refresh_token'];
$token->tokenExpires = time() + (int)$new['expires_in'];
$this->tokens->storeUserRefresh($token);
return null;
}
/**
* Cron run: refreshes due source tokens, drops unused per-user tokens, refreshes due per-user tokens.
*
* @return array<int, array{priority: string, message: string}> log entries for CRON_LOG
*/
public function refreshDue(): array
{
$log = [];
foreach ($this->tokens->refreshableSources() as $source) {
if ($this->isDue($source->tokenExpires, $source->lastRefreshError, $source->refreshAttemptAge)) {
try {
$error = $this->refreshSourceToken($source);
} catch (Exception $e) {
$error = get_class($e);
}
$label = "source '" . $source->name . "' (id=" . $source->id . ")";
$log[] = $this->logEntry($error, "Refreshed token for " . $label, "Refresh failed for " . $label);
}
}
$removed = $this->tokens->deleteUnusedOlderThan(self::UNUSED_TOKEN_GRACE);
if ($removed > 0) {
$log[] = ['priority' => 'info', 'message' => "Removed " . $removed . " unused user token(s)"];
}
foreach ($this->tokens->refreshableUserTokens() as $token) {
if ($this->isDue($token->tokenExpires, $token->lastRefreshError, $token->refreshAttemptAge)) {
$source = null;
try {
$source = $this->sources->find($token->sourceId);
$error = ($source === null) ? 'not refreshable' : $this->refreshUserToken($source, $token);
} catch (Exception $e) {
$error = get_class($e);
}
$label = "user token '" . $token->username . "' (mailbox " . $token->owner . ") on source '"
. ($source->name ?? '') . "' (id=" . $token->sourceId . ")";
$log[] = $this->logEntry($error, "Refreshed " . $label, "Refresh failed for " . $label);
}
}
return $log;
}
/** Due when the token expires soon (or is missing) and the last failed attempt is old enough */
private function isDue(?int $expires, ?string $lastError, ?int $attemptAge): bool
{
return ($expires === null || $expires < time() + self::REFRESH_AHEAD)
&& ($lastError === null || $attemptAge === null || $attemptAge > self::RETRY_AFTER);
}
private function logEntry(?string $error, string $success, string $failure): array
{
return ($error === null) ? ['priority' => 'info', 'message' => $success]
: ['priority' => 'warning', 'message' => $failure . ": " . $error];
}
/**
* Endpoint policy (C5). Endpoints of sources not created by an admin must resolve to public addresses;
* admin sources have no address restriction (API key sessions run as admin under the reserved name 'API').
*/
private function endpointAllowed(string $url, string $createdBy): bool
{
return $this->client->isAllowed($url, $this->isStrict($createdBy));
}
private function isStrict(string $createdBy): bool
{
return !($createdBy === 'API' || $this->sources->isSuperAdmin($createdBy));
}
/** One OAuth text attribute from the request, or the stored value when the attribute is missing */
private function oauthText(array $input, string $key, ?string $stored): string
{
$value = array_key_exists($key, $input) ? Input::text($input[$key]) : (string)$stored;
return $value ?? throw new UserError('imapsync_source_oauth_invalid');
}
/** Stored extra params of the browser sign-in ($authorize) or of the token request */
private function extraParamsFor(Source $source, bool $authorize): array
{
$extra = json_decode((string)$source->extraParams, true);
return array_filter(is_array($extra) ? $extra : [],
fn($key): bool => in_array($key, self::AUTHORIZE_PARAMS, true) === $authorize, ARRAY_FILTER_USE_KEY);
}
/**
* Extra parameters: a JSON object with scalar values, as string or as array (after 011-api.js
* auto-parsing). Returns the JSON to store, null for none.
*/
private function extraParams(mixed $extra): ?string
{
if ($extra === null || (is_string($extra) && trim($extra) === '')) {
$extra = [];
} elseif (is_string($extra)) {
$extra = json_decode($extra, true);
}
$bad = !is_array($extra);
foreach ((array)$extra as $key => $value) {
$bad = $bad || !is_string($key) || $key === '' || !(is_scalar($value) || $value === null);
}
$json = empty($extra) ? null : json_encode($extra, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($bad || strlen((string)$json) > 4000) {
throw new UserError('imapsync_source_oauth_extra_invalid');
}
return $json;
}
/**
* Token endpoint request for every grant type. Stored extra params never override the client and grant fields.
*
* @throws OAuthException
*/
private function requestToken(Source $source, array $grant): array
{
$post = array_merge($this->extraParamsFor($source, false),
['client_id' => $source->clientId, 'client_secret' => $source->clientSecret, 'scope' => $source->oauthScope], $grant);
$token = $this->client->request((string)$source->tokenEndpoint, $this->isStrict($source->createdBy), $post);
if (empty($token['access_token']) || !is_string($token['access_token'])) {
throw new OAuthException('malformed token response');
}
if (!isset($token['expires_in']) || !is_numeric($token['expires_in'])) {
$token['expires_in'] = 3600;
}
if (isset($token['refresh_token']) && !is_string($token['refresh_token'])) {
unset($token['refresh_token']);
}
return $token;
}
/** Remote identity: id_token claims (received directly from the token endpoint over TLS), else the userinfo endpoint */
private function identity(Source $source, array $token): ?string
{
$parts = explode('.', is_string($token['id_token'] ?? null) ? $token['id_token'] : '');
$email = (count($parts) === 3) ? $this->emailClaim(json_decode((string)base64_decode(strtr($parts[1], '-_', '+/')), true)) : null;
if ($email !== null || empty($source->userinfoEndpoint)) {
return $email;
}
try {
return $this->emailClaim($this->client->request($source->userinfoEndpoint, $this->isStrict($source->createdBy), null,
['Authorization: Bearer ' . $token['access_token']]));
} catch (OAuthException $e) {
return null;
}
}
private function emailClaim(mixed $claims): ?string
{
foreach (['email', 'preferred_username', 'upn', 'unique_name'] as $key) {
if (is_array($claims) && !empty($claims[$key]) && is_string($claims[$key]) && filter_var($claims[$key], FILTER_VALIDATE_EMAIL)) {
return strtolower($claims[$key]);
}
}
return null;
}
}
@@ -1,357 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Imapsync\Service;
use Mailcow\Core\AccessDenied;
use Mailcow\Core\Context;
use Mailcow\Core\Input;
use Mailcow\Core\UserError;
use Mailcow\Imapsync\Model\Source;
use Mailcow\Imapsync\Repository\SourceRepository;
use Mailcow\Imapsync\Repository\TokenRepository;
/**
* Use cases and rules for sync sources.
*
* Read: admin all; domain admin own + 'all' + sources targeting his domains/mailboxes + sources created by
* his mailboxes; user own + 'all' + sources targeting him or his domain (see SourceRepository).
* Edit: admin all; creator own; domain admin sources created by his mailboxes.
* XOAUTH2 sources are created and edited by admins and domain admins only (users may use them).
* Users and domain admins need the `syncjobs` ACL for everything.
*/
class SourceService
{
private const ENCRYPTIONS = ['TLS', 'SSL', 'PLAIN'];
private const AUTH_TYPES = ['PLAIN', 'LOGIN', 'CRAM-MD5', 'XOAUTH2'];
private const SCOPES = ['all', 'domain', 'user'];
public function __construct(private SourceRepository $repo, private TokenRepository $tokens, private OAuthService $oauth,
private Context $context)
{
}
/** @throws AccessDenied without the syncjobs ACL */
public function requireAcl(): void
{
if (!$this->context->hasAcl('syncjobs')) {
throw new AccessDenied();
}
}
/** Add/edit: a logged-in admin, domain admin or user with the syncjobs ACL */
public function requireEditor(): void
{
if (!($this->context->isAdmin() || $this->context->isDomainAdmin() || $this->context->isUser())) {
throw new AccessDenied();
}
$this->requireAcl();
}
/**
* The source when the caller may see it (incl. secrets: never pass the object to the UI, use toArray()).
* Users and domain admins without the syncjobs ACL see no sources.
*/
public function get(int $id): ?Source
{
return $this->canRead() ? $this->repo->findVisible($id, $this->context) : null;
}
/** @return int[] ids of the sources the caller may see, ordered by name */
public function visibleIds(): array
{
return $this->canRead() ? $this->repo->visibleIds($this->context) : [];
}
public function canEdit(Source $source): bool
{
if ($this->context->accessAll || $this->context->isAdmin() || $source->createdBy === $this->context->username) {
return true;
}
// A domain admin may manage sources created by mailboxes of his domains
return $this->context->isDomainAdmin() && $source->createdBy !== '' && $this->context->canAccessMailbox($source->createdBy);
}
/** API representation for the caller (can_edit, extra params only for editors, redirect URI) */
public function toArray(Source $source): array
{
return $source->toArray($this->canEdit($source), $this->oauth->redirectUri());
}
/**
* Creates a source from request attributes (C9 names; host1/port1/enc1 are accepted as aliases).
*
* @throws UserError
*/
public function create(array $input): Source
{
$this->requireEditor();
$source = new Source();
$source->createdBy = (string)$this->context->username;
$this->applyBasics($source, $input);
$this->applyOAuth($source, $input);
// Scope and targets are validated before anything is written
$this->applyScope($source, $input['scope'] ?? 'user', $input['domains'] ?? [], $input['users'] ?? []);
if ($this->repo->nameExists($source->name, $source->createdBy)) {
throw new UserError('object_exists', [htmlspecialchars($source->name)]);
}
$this->repo->insert($source);
$this->fetchToken($source);
return $source;
}
/**
* Updates a source; attributes missing in $input keep their value.
*
* @throws UserError
*/
public function update(int $id, array $input): Source
{
$this->requireEditor();
$current = $this->editable($id);
$source = clone $current;
$this->applyBasics($source, $input);
if ($source->name !== $current->name && $this->repo->nameExists($source->name, $source->createdBy, $id)) {
throw new UserError('object_exists', [htmlspecialchars($source->name)]);
}
$this->applyOAuth($source, $input);
// Targets only change when the request carries `scope`; client_credentials sources are always private
$writeTargets = isset($input['scope']) || $source->usesClientCredentials();
if ($writeTargets) {
$this->applyScope($source, $input['scope'] ?? $current->scope, $input['domains'] ?? [], $input['users'] ?? []);
}
$this->repo->update($source, $writeTargets, self::connection($current) !== self::connection($source));
$this->fetchToken($source);
return $source;
}
/**
* Deletes an unused source and returns it.
*
* @throws UserError access_denied, imapsync_source_in_use
*/
public function delete(int $id): Source
{
$this->requireAcl();
$source = $this->editable($id);
// FK ON DELETE RESTRICT prevents removal while sync jobs reference it
if ($this->repo->isInUse($id)) {
throw new UserError('imapsync_source_in_use', [$source->name]);
}
$this->repo->delete($id);
return $source;
}
/**
* Fetches a new shared token for a client_credentials source now.
*
* @throws UserError access_denied, imapsync_source_refresh_unsupported, imapsync_source_token_refresh_failed
*/
public function refreshToken(int $id): Source
{
$this->requireAcl();
$source = $this->editable($id);
if (!$source->usesClientCredentials()) {
throw new UserError('imapsync_source_refresh_unsupported');
}
$error = $this->oauth->refreshSourceToken($source);
if ($error !== null) {
throw new UserError('imapsync_source_token_refresh_failed', [$source->name . ' (' . $error . ')']);
}
return $source;
}
/** A visible, active authorization_code source the caller may sign in to (OAuth popup), else null */
public function findConnectable(int $id): ?Source
{
$source = $this->get($id);
return ($source !== null && $source->active === 1 && $source->usesAuthorizationCode()) ? $source : null;
}
/**
* Account rename (mailbox, admin, domain admin): sources, user targets and token owners follow.
* Leftover sources of an earlier account named $new are released first (see releaseSourcesOf()).
* Runs on the caller's PDO; a PDOException is not caught, so a caller transaction can roll back.
*/
public function onAccountRenamed(string $old, string $new): void
{
if ($old === '' || $old === $new) {
return;
}
$this->releaseSourcesOf($new);
$this->repo->renameAccount($old, $new);
$this->tokens->renameOwner($old, $new);
}
/** Account deleted (after its jobs were deleted): sources released, tokens and user targets removed */
public function onAccountDeleted(string $old): void
{
if ($old === '') {
return;
}
$this->releaseSourcesOf($old);
$this->tokens->deleteByOwner($old);
$this->repo->deleteUserTarget($old);
}
private function canRead(): bool
{
return $this->context->hasAcl('syncjobs') || $this->context->isAdmin();
}
/** @throws AccessDenied when the caller cannot see or edit the source */
private function editable(int $id): Source
{
$source = $this->get($id);
if ($source === null || !$this->canEdit($source)) {
throw new AccessDenied();
}
return $source;
}
/**
* Sources of an account are deleted when unused, otherwise handed over to the admins
* (created_by ''), so a re-created account inherits nothing.
*/
private function releaseSourcesOf(string $username): void
{
foreach ($this->repo->listCreatedBy($username) as $source) {
$id = (int)$source->id;
if (!$this->repo->isInUse($id)) {
// Explicit child deletes: the mailbox rename runs with FOREIGN_KEY_CHECKS = 0 (no cascade)
$this->repo->deleteWithChildren($id);
continue;
}
// Keep (created_by, name) unique when handing over
$name = $this->repo->nameExists($source->name, '') ? mb_substr($source->name, 0, 80) . ' (#' . $id . ')' : $source->name;
$this->repo->handOver($id, $name);
}
}
/**
* Name, description, connection and active from the request; missing attributes keep the value of $source.
* On edit, stored host/port/enc that do not change are not validated again, so migrated legacy sources
* stay editable (e.g. host 'imap_server.lan'). A host that only differs in case keeps the stored spelling.
*
* @throws UserError
*/
private function applyBasics(Source $source, array $input): void
{
$stored = ($source->id !== null);
$name = isset($input['name']) ? Input::text($input['name']) : $source->name;
$description = isset($input['description']) ? Input::text($input['description']) : $source->description;
$host = $input['host'] ?? $input['host1'] ?? null;
$host = ($host === null) ? $source->host : Input::text($host);
if ($host !== null) {
$host = (strtolower($host) === strtolower($source->host)) ? $source->host : strtolower($host);
}
$port = $input['port'] ?? $input['port1'] ?? $source->port;
$port = is_scalar($port) ? (int)$port : 0;
$enc = $input['enc'] ?? $input['enc1'] ?? $source->enc;
$authType = $input['auth_type'] ?? $source->authType;
if ($name === null || $name === '' || mb_strlen($name) > 100) {
throw new UserError('imapsync_source_name_invalid');
}
if ($description === null || mb_strlen($description) > 255) {
throw new UserError('imapsync_source_description_invalid');
}
if (!($stored && $host === $source->host) && !$this->isValidHost($host)) {
throw new UserError('imapsync_source_host_invalid');
}
if (!($stored && $port === $source->port) && ($port < 1 || $port > 65535)) {
throw new UserError('imapsync_source_port_invalid');
}
if (!($stored && $enc === $source->enc) && !in_array($enc, self::ENCRYPTIONS, true)) {
throw new UserError('imapsync_source_enc_invalid');
}
if (!in_array($authType, self::AUTH_TYPES, true)) {
throw new UserError('imapsync_source_auth_invalid');
}
$source->name = $name;
$source->description = $description;
$source->host = $host;
$source->port = $port;
$source->enc = $enc;
$source->authType = $authType;
$source->active = isset($input['active']) ? (int)$input['active'] : $source->active;
}
private function isValidHost(?string $host): bool
{
return $host !== null && $host !== '' && strlen($host) <= 253
&& (filter_var($host, FILTER_VALIDATE_IP) || filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME));
}
/** @throws UserError */
private function applyOAuth(Source $source, array $input): void
{
// XOAUTH2 sources are managed by admins and domain admins only
if ($source->isXOAuth2() && $this->context->isUser()) {
throw new AccessDenied();
}
$this->oauth->applySettings($source, $input);
}
/**
* Validates scope and targets for the caller and sets them; nothing is written here.
* Users' sources and client_credentials sources (one app-wide token that reaches every mailbox) are always private.
*
* @throws UserError access_denied, imapsync_source_scope_empty
*/
private function applyScope(Source $source, mixed $scope, mixed $domains, mixed $users): void
{
if ($source->usesClientCredentials() || $this->context->isUser()) {
[$source->scope, $source->domains, $source->users] = ['user', [], []];
return;
}
// Only admins may create globally visible sources
if (!is_string($scope) || !in_array($scope, self::SCOPES, true) || ($scope === 'all' && !$this->context->isAdmin())) {
throw new AccessDenied();
}
$domains = ($scope === 'domain') ? $this->cleanList($domains) : [];
$users = ($scope === 'user') ? $this->cleanList($users) : [];
if ($scope === 'domain' && empty($domains)) {
throw new UserError('imapsync_source_scope_empty');
}
foreach ($domains as $domain) {
if (!$this->context->canAccessDomain($domain)) {
throw new AccessDenied();
}
}
foreach ($users as $user) {
if (!$this->context->canAccessMailbox($user)) {
throw new AccessDenied();
}
}
[$source->scope, $source->domains, $source->users] = [$scope, $domains, $users];
}
/** @return string[] trimmed, lowercase, unique, non-empty scalar values */
private function cleanList(mixed $list): array
{
$clean = [];
foreach ((array)$list as $value) {
if (is_scalar($value) && trim((string)$value) !== '') {
$clean[] = strtolower(trim((string)$value));
}
}
return array_values(array_unique($clean));
}
/** A saved, active client_credentials source gets its token now instead of at the next cron run (error in lastRefreshError) */
private function fetchToken(Source $source): void
{
if ($source->usesClientCredentials() && $source->active === 1) {
$source->lastRefreshError = $this->oauth->refreshSourceToken($source);
}
}
/** Tokens never follow a source to another OAuth client or IMAP server, so these values reset them */
private static function connection(Source $source): array
{
return [(string)$source->authType, $source->host, $source->port, (string)$source->enc, $source->oauthFlow,
(string)$source->tokenEndpoint, (string)$source->clientId];
}
}
-97
View File
@@ -1,97 +0,0 @@
# mailcow PHP classes (`Mailcow\`)
New backend code of the mailcow UI/API lives here as plain PHP classes, one directory per module.
It replaces the procedural `data/web/inc/functions.*.inc.php` files step by step. There is no framework,
no DI container and no ORM: just small classes, constructor injection and PDO.
The directory sits below `inc/lib/`, which nginx never serves. Classes are loaded by Composer's PSR-4
autoloader (`"Mailcow\\": "mailcow/"` in `inc/lib/composer.json`), which every entry point already
includes (`prerequisites.inc.php`, crons, `mailcowauth.php`).
## Layout
```
Core/ shared by all modules
Context.php who is calling: role, username, ACL, access-all; fromSession(), system()
UserError.php expected error with a lang key (+ args), shown as a danger message
AccessDenied.php UserError 'access_denied'
ApiResponse.php collects messages, writes $_SESSION['return'] (the only class that does)
Input.php helpers for loosely typed request values
Repository.php base class of all repositories: PDO, query(), insertRow(), updateRow(), transaction()
<Module>/
<Module>Module.php composition root: create(PDO, Context, array $config) wires everything;
fromGlobals() is the legacy entry point for procedural callers
Model/ plain typed objects (fromRow(), toArray() for the API shape), no DB
Repository/ SQL only (prepared statements); takes and returns models
Service/ use cases, business rules, permissions (Context), transactions via repositories
Http/ outbound I/O (e.g. OAuth requests)
Controller/ request in, messages out: adapter for json_api.php and pages
```
Example: `Imapsync/` (sync jobs, sync sources, OAuth). Use it as the template for new modules.
## Layers and rules
- **Model:** typed public properties, `fromRow()` from a DB row, `toArray()` for the API format (keys and
order are the API contract). Secrets stay in the model but never in `toArray()`. No getters/setters.
- **Repository:** extends `Core\Repository`; the only place with SQL (prepared statements via `query()`).
No permission decisions (a visibility filter that is pure SQL may take the `Context`). A method that writes
several tables wraps them in `transaction()`, which joins a transaction the caller already runs.
- **Service:** one use case per public method, working on single objects. Checks permissions with the
`Context` and throws `UserError` / `AccessDenied` with a lang key. No `$_SESSION`, no `global`, no output.
When add and edit share their rules, use one private routine for both (e.g. `JobService::save()`,
`SourceService::applyBasics()`): missing attributes keep the current value, a new object starts with defaults.
- **Controller:** normalises request input (batch ids, attribute merging), loops over batch ids, calls
services, turns `UserError` into messages via `ApiResponse` and returns what `json_api.php` passes to
`process_*_return()`. The API format (routes, JSON, message keys and order) stays as it was. Each write
method is a few lines on top of two private helpers: `handle()` (log entry, error mapping, flush) and
`batch()` (one message per id).
- **View:** Twig templates, unchanged.
## Conventions
- `declare(strict_types=1);` in every file; typed properties, parameters and return types; constructor
property promotion; `public readonly` properties instead of getters for values that never change.
- Class files contain only declarations (no code at file level), one class per file.
- Constructor injection. Global state (`$pdo`, `$_SESSION`, config arrays, `getenv()`) is only read at the
entry points and passed to `<Module>Module::create()`. Procedural legacy callers (`json_api.php`, pages,
`functions.*.inc.php`) use `<Module>Module::fromGlobals(?Context $context = null)`, the one documented
place in the module that reads the globals, instead of repeating the config lines; entry points that build
their own state (crons, CLI) call `create()` directly, e.g. with `Context::system()`.
`ApiResponse::flush()` is the one class that writes `$_SESSION['return']`.
- No interfaces until there are two implementations; no closures as injected dependencies (local callbacks
such as `transaction(fn ...)` or the controller's `handle()` are fine); small methods.
- Docblocks only where they add information (rules, API contract, non-obvious behaviour), not to repeat
the signature.
- Classes are not `final`, so PHPUnit can mock them. I/O that tests must replace sits in protected methods
(see `Imapsync/Http/OAuthClient::send()`).
- Messages: services throw, controllers report. The `msg` is a lang key of the matching section
(`success` / `danger`), optionally with arguments: `new UserError('object_exists', [$name])`.
- Logs: the `log` part of a message is `[<legacy function>, <action>, <type>, <data>]` as before; request
keys holding secrets are masked by `ApiResponse` (configure them in the module).
- A database error (`PDOException`) stops the call with one `mysql_error` message, like the legacy code.
- Naming: `Mailcow\<Module>\{Model,Repository,Service,Http,Controller}\<Class>`.
## Converting a legacy `functions.X.inc.php`
1. Capture the current API behaviour (E2E runs, raw JSON responses, DB state).
2. Build the module as above: models, repositories, services, controller, `<Module>Module::create()`.
3. Switch the callers (`json_api.php`, pages, crons, other function files) to the module.
4. Delete the function file and its `require` lines.
5. Compare the API output with step 1; the format must stay the same.
Large files (e.g. `functions.mailbox.inc.php`) are converted one area at a time (aliases, domains, ...),
each as its own module. Until `functions.inc.php` is converted, `Context::canAccessMailbox()` /
`canAccessDomain()` call the legacy `hasMailboxObjectAccess()` / `hasDomainAccess()`.
## Development
- Unit tests: `tests/php/<Module>/...` (dev-only, never deployed), run with
`helper-scripts/dev_tests/run_php_unit_tests.sh`. Services are tested with mocked repositories;
repositories are covered by the E2E suites (MariaDB-specific SQL).
- Autoloader: after adding the PSR-4 mapping nothing needs regenerating for new classes. If you ever run
`composer dump-autoload` (inside the php-fpm container, `-w /web/inc/lib`), never use `-o`, `-a` or
`--apcu`, and commit only the changed `autoload_*.php` files.
- No dev packages in `inc/lib/composer.json`.
- opcache runs with `validate_timestamps=0`: restart php-fpm after changing PHP files
(`docker compose restart php-fpm-mailcow`).
-1
View File
@@ -25,7 +25,6 @@ return array(
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'), 'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'),
'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'), 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'),
'Mailcow\\' => array($baseDir . '/mailcow'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'), 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'),
'LdapRecord\\' => array($vendorDir . '/directorytree/ldaprecord/src'), 'LdapRecord\\' => array($vendorDir . '/directorytree/ldaprecord/src'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/conditionable'), 'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/conditionable'),
-5
View File
@@ -58,7 +58,6 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
array ( array (
'MatthiasMullie\\PathConverter\\' => 29, 'MatthiasMullie\\PathConverter\\' => 29,
'MatthiasMullie\\Minify\\' => 22, 'MatthiasMullie\\Minify\\' => 22,
'Mailcow\\' => 8,
), ),
'L' => 'L' =>
array ( array (
@@ -178,10 +177,6 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
array ( array (
0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src',
), ),
'Mailcow\\' =>
array (
0 => __DIR__ . '/../..' . '/mailcow',
),
'League\\OAuth2\\Client\\' => 'League\\OAuth2\\Client\\' =>
array ( array (
0 => __DIR__ . '/..' . '/league/oauth2-client/src', 0 => __DIR__ . '/..' . '/league/oauth2-client/src',
+127 -94
View File
@@ -287,98 +287,131 @@ $RSPAMD_MAPS = array(
); );
// Allowed imapsync options for a sync job's custom_params (key = option name, lowercase, without dashes): $IMAPSYNC_OPTIONS = array(
// true = option requires a non-empty value 'whitelist' => array(
// false = pure flag, must NOT have a value 'abort',
// string = PCRE (no delimiters) the whole non-empty value must match 'authmd51',
// Anything not listed here is rejected when a job is saved (the runner passes stored options as they are). 'authmd52',
// Override in vars.local.inc.php per option, e.g. $IMAPSYNC_OPTIONS['someoption'] = true; or unset($IMAPSYNC_OPTIONS['debug']); 'authmech1',
// The old format $IMAPSYNC_OPTIONS['whitelist'] (list of names) is no longer supported and ignored (logged). 'authmech2',
// NEVER add (RCE): 'delete2foldersonly', 'delete2foldersbutnot', 'regexflag', 'regexmess', 'pipemess', 'regextrans2', 'maxlinelengthcmd', 'oauthrefreshcmd1', 'oauthrefreshcmd2' 'authuser1',
// NEVER add options taking local file paths (e.g. 'logfile', 'logdir', 'pidfile', 'passfile1', 'sslargs1' with SSL_ca_file) 'authuser2',
$IMAPSYNC_OPTIONS_DEFAULT = array( 'debug',
'abort' => false, 'debugcontent',
'authmd51' => false, 'debugcrossduplicates',
'authuser1' => true, 'debugflags',
'debug' => false, 'debugfolders',
'debugcontent' => false, 'debugimap',
'debugcrossduplicates' => false, 'debugimap1',
'debugflags' => false, 'debugimap2',
'debugfolders' => false, 'debugmemory',
'debugimap' => false, 'debugssl',
'debugimap1' => false, 'delete1emptyfolders',
'debugmemory' => false, 'delete2folders',
'debugssl' => true, 'disarmreadreceipts',
'delete1emptyfolders' => false, 'domain1',
'delete2folders' => false, 'domain2',
'disarmreadreceipts' => false, 'domino1',
'domain1' => true, 'domino2',
'domino1' => false, 'dry',
'errorsmax' => true, 'errorsmax',
'exchange1' => false, 'exchange1',
'exitwhenover' => true, 'exchange2',
'expunge1' => false, 'exitwhenover',
'f1f2' => true, 'expunge1',
'filterbuggyflags' => false, 'f1f2',
'folder' => true, 'filterbuggyflags',
'folderfirst' => true, 'folder',
'folderlast' => true, 'folderfirst',
'folderrec' => true, 'folderlast',
'gmail1' => false, 'folderrec',
'idatefromheader' => false, 'gmail1',
'include' => true, 'gmail2',
'inet4' => false, 'idatefromheader',
'inet6' => false, 'include',
'justconnect' => false, 'inet4',
'justfolders' => false, 'inet6',
'justfoldersizes' => false, 'justconnect',
'justlogin' => false, 'justfolders',
'keepalive1' => false, 'justfoldersizes',
'log' => false, 'justlogin',
'maxbytesafter' => true, 'keepalive1',
'maxlinelength' => true, 'keepalive2',
'maxmessagespersecond' => true, 'log',
'maxsize' => true, 'logdir',
'maxsleep' => true, 'logfile',
'minage' => true, 'maxbytesafter',
'minsize' => true, 'maxlinelength',
'noabletosearch' => false, 'maxmessagespersecond',
'noabletosearch1' => false, 'maxsize',
'noexpunge1' => false, 'maxsleep',
'nofoldersizesatend' => false, 'minage',
'noid' => false, 'minsize',
'nolog' => false, 'noabletosearch',
'nomixfolders' => false, 'noabletosearch1',
'noresyncflags' => false, 'noabletosearch2',
'nossl1' => false, 'noexpunge1',
'nosyncacls' => false, 'noexpunge2',
'notls1' => false, 'nofoldersizesatend',
'nousecache' => false, 'noid',
'office1' => false, 'nolog',
'prefix1' => true, 'nomixfolders',
'prefix2' => true, 'noresyncflags',
'proxyauth1' => false, 'nossl1',
'resyncflags' => false, 'nossl2',
'resynclabels' => false, 'nosyncacls',
'search' => true, 'notls1',
'search1' => true, 'notls2',
'sep1' => true, 'nouidexpunge2',
'sep2' => true, 'nousecache',
'skipemptyfolders' => false, 'oauthaccesstoken1',
'sslargs1' => '^SSL_verify_mode=[01]$', 'oauthaccesstoken2',
'subfolder1' => true, 'oauthdirect1',
'subscribe' => false, 'oauthdirect2',
'subscribed' => false, 'office1',
'syncacls' => false, 'office2',
'syncduplicates' => false, 'pidfile',
'syncinternaldates' => false, 'pidfilelocking',
'synclabels' => false, 'prefix1',
'tests' => false, 'prefix2',
'testslive' => false, 'proxyauth1',
'testslive6' => false, 'proxyauth2',
'truncmess' => true, 'resyncflags',
'usecache' => false, 'resynclabels',
'useheader' => true, 'search',
'useuid' => false, 'search1',
'search2',
'sep1',
'sep2',
'showpasswords',
'skipemptyfolders',
'ssl2',
'sslargs1',
'sslargs2',
'subfolder1',
'subscribe',
'subscribed',
'syncacls',
'syncduplicates',
'syncinternaldates',
'synclabels',
'tests',
'testslive',
'testslive6',
'tls2',
'truncmess',
'usecache',
'useheader',
'useuid'
),
'blacklist' => array(
'skipmess',
'delete2foldersonly',
'delete2foldersbutnot',
'regexflag',
'regexmess',
'pipemess',
'regextrans2',
'maxlinelengthcmd'
)
); );
$IMAPSYNC_OPTIONS = $IMAPSYNC_OPTIONS_DEFAULT;
+2 -8
View File
@@ -73,8 +73,7 @@ $(document).ready(function() {
} }
// form cache // form cache
// fields marked with data-formcache-skip (secrets) are never written to localStorage $('[data-cached-form="true"]').formcache({key: $(this).data('id')});
$('[data-cached-form="true"]').formcache({key: $(this).data('id'), controls: ['select', 'textarea', 'input:not([data-formcache-skip])']});
// tooltips // tooltips
$(function () { $(function () {
@@ -114,8 +113,6 @@ $(document).ready(function() {
} }
localStorage.setItem(key, tab_id); localStorage.setItem(key, tab_id);
// remember which card of the tab was opened
localStorage.setItem(key + ':collapse', $(this).attr('data-bs-target').substring(1));
}); });
// open last tab // open last tab
$('[role="tablist"]').each(function (idx, elem) { $('[role="tablist"]').each(function (idx, elem) {
@@ -131,10 +128,7 @@ $(document).ready(function() {
dropdown.find('.dropdown-menu').removeClass('show'); dropdown.find('.dropdown-menu').removeClass('show');
dropdown.find('.dropdown-toggle').attr('aria-expanded', 'false'); dropdown.find('.dropdown-toggle').attr('aria-expanded', 'false');
var tab = $('[id^="' + lastTab + '"]'); var tab = $('[id^="' + lastTab + '"]');
var lastCollapse = localStorage.getItem(key + ':collapse'); $(tab).find('.card-body.collapse:first').collapse('show');
var $card = lastCollapse ? $(tab).find('.card-body.collapse').filter(function() { return this.id === lastCollapse; }) : $();
if (!$card.length) $card = $(tab).find('.card-body.collapse:first');
$card.collapse('show');
} }
}); });
})(); })();
-783
View File
@@ -1,783 +0,0 @@
// IMAP sync jobs and sync sources (admin/domainadmin mailbox page, user page, edit page).
// Loaded via $js_minifier only on pages that show sync jobs.
var mailcowImapsync = (function($) {
'use strict';
// Lang strings: lang.syncjobs (mailbox/user page) or lang_syncjobs (edit page), '' if missing
function L(key) {
var src = (typeof lang_syncjobs !== 'undefined' && lang_syncjobs) ? lang_syncjobs :
((typeof lang !== 'undefined' && lang && lang.syncjobs) ? lang.syncjobs : {});
return (src[key] !== undefined && src[key] !== null) ? src[key] : '';
}
function esc(s) {
return escapeHtml(s === null || s === undefined ? '' : s);
}
// same format as "last run" (YYYY-MM-DD HH:mm:ss), independent of the browser locale
function formatDate(ts) {
if (!ts) return '';
var d = new Date(ts * 1000);
var p = function(n) { return (n < 10 ? '0' : '') + n; };
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' +
p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
}
function isAdmin() {
return typeof mailcow_cc_role !== 'undefined' && mailcow_cc_role === 'admin';
}
// bootstrap-select: refresh an existing instance, or init with the mailcow defaults
function refreshSelect($sel) {
if (typeof $sel.selectpicker !== 'function') return;
$sel.each(function() {
var $s = $(this);
if ($s.data('selectpicker')) {
$s.selectpicker('refresh');
} else {
$s.selectpicker({
'styleBase': 'btn btn-xs-lg',
'noneSelectedText': (typeof lang_footer !== 'undefined') ? lang_footer.nothing_selected : ''
});
}
});
}
// Long source labels must not overflow the modal on small screens
function injectStyle() {
if ($('#imapsync-style').length) return;
$('head').append('<style id="imapsync-style">' +
'.imapsync-select-wrap .bootstrap-select{max-width:100%;}' +
'.imapsync-select-wrap .bootstrap-select .dropdown-menu{max-width:100%;}' +
'.imapsync-select-wrap .bootstrap-select .dropdown-menu .text{white-space:normal;word-break:break-word;}' +
'.imapsync-select-wrap .bootstrap-select .filter-option-inner-inner{overflow:hidden;text-overflow:ellipsis;}' +
// long hosts / legacy names in table cells must wrap, otherwise the responsive tables hide every other column
'.imapsync-wrap{min-width:7rem;overflow-wrap:anywhere;word-break:break-word;}' +
'</style>');
}
// ===== Display helpers (tables) =====
// Visibility label of a source row
function scopeDisplay(item) {
if (item.scope === 'all') return '<i class="bi bi-globe"></i> ' + esc(L('source_scope_all'));
if (item.scope === 'domain') return esc(L('source_scope_domain') + ': ' + (item.domains || []).join(', '));
var us = item.users || [];
if (us.length === 0) return esc(item.created_by || '');
return esc(L('source_scope_user') + ': ' + us.join(', '));
}
// Token badge of a client_credentials source: based on token existence/error, expiry in title
function sourceTokenBadge(item) {
if (item.oauth_last_refresh_error) {
return '<span class="badge bg-danger" title="' + esc(item.oauth_last_refresh_error) + '">' + esc(L('token_state_failed')) + '</span>';
}
if (item.has_access_token) {
var title = item.oauth_token_expires ? L('source_token_status') + ': ' + formatDate(item.oauth_token_expires) : '';
return '<span class="badge bg-success" title="' + esc(title) + '">' + esc(L('token_state_issued')) + '</span>';
}
return '<span class="badge bg-warning">' + esc(L('token_state_none')) + '</span>';
}
// Auth column of a source row: auth type, and for XOAUTH2 the flow (+ token badge for client credentials)
function authDisplay(item) {
var out = esc(item.auth_type);
if (item.auth_type !== 'XOAUTH2') return out;
var flow = item.oauth_flow || 'client_credentials';
out += ' <span class="text-muted">(' + esc(flow === 'authorization_code' ? L('source_flow_badge_user') : L('source_flow_badge_app')) + ')</span>';
// authorization_code tokens live per job (local mailbox + remote user), not on the source
if (flow === 'authorization_code') return out;
return out + '<div class="mt-1">' + sourceTokenBadge(item) + '</div>';
}
// Per-job token badge for authorization_code sources ('' for everything else)
function jobTokenBadge(item) {
if (item.source_auth_type !== 'XOAUTH2' || item.source_oauth_flow !== 'authorization_code') return '';
if (item.user_token_error) {
return '<span class="badge bg-danger" title="' + esc(item.user_token_error) + '">' + esc(L('token_state_failed')) + '</span>';
}
if (parseInt(item.user_token_exists) === 1) {
var title = item.user_token_expires ? L('source_token_status') + ': ' + formatDate(item.user_token_expires) : '';
return '<span class="badge bg-success" title="' + esc(title) + '">' + esc(L('token_state_issued')) + '</span>';
}
return '<span class="badge bg-warning">' + esc(L('token_state_pending')) + '</span>';
}
// Warning badge for jobs with not migrated legacy custom parameters
function legacyBadge(item) {
if (!item.custom_params_legacy) return '';
return '<span class="badge bg-warning text-dark" title="' + esc(L('custom_params_legacy_badge_title')) + '">' +
'<i class="bi bi-exclamation-triangle"></i> ' + esc(L('custom_params_legacy_badge')) + '</span>';
}
// Wrappable table cell content (long hosts, mailboxes, legacy source names)
function wrapCell(html) {
return '<div class="imapsync-wrap">' + html + '</div>';
}
// Server column: "remote user @ host:port", source name below, badges after
function serverDisplay(item) {
var out = esc(item.user1 || '') + ' @ ' + esc(item.source_host || '?') + ':' + esc(item.source_port || '?');
if (item.source_name) out += '<div class="small text-muted">' + esc(item.source_name) + '</div>';
var badges = [jobTokenBadge(item), legacyBadge(item)].filter(Boolean);
if (badges.length) out += '<div class="mt-1">' + badges.join(' ') + '</div>';
return wrapCell(out);
}
// Prepares the shared fields of a job row (run state, last run, result)
function prepareJobRow(item) {
item.server_w_port = serverDisplay(item);
if (parseInt(item.is_running) === 1) {
item.is_running = '<span class="badge fs-6 bg-success">' + esc(L('status_running')) + '</span>';
} else if (parseInt(item.active) !== 1) {
item.is_running = '<span class="badge fs-6 bg-secondary">' + esc(L('status_inactive')) + '</span>';
} else {
item.is_running = '<span class="badge fs-6 bg-warning">' + esc(L('status_waiting')) + '</span>';
}
if (!item.last_run) {
item.last_run = '<span class="text-muted">' + esc(L('never_run')) + '</span>';
}
if (item.success == null) {
item.success = '-';
item.exit_status = '';
} else {
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
}
if (L('syncjob_' + item.exit_status)) {
item.exit_status = L('syncjob_' + item.exit_status);
} else if (item.success != '-') {
item.exit_status = L('syncjob_check_log');
}
item.exit_status = item.success + ' ' + esc(item.exit_status);
return item;
}
// Job tables: ignore DataTables state saved before the rework (old ID sort, column visibility)
var JOB_TABLE_STATE_VERSION = 2;
function jobTableStateSave(settings, data) {
data.imapsync_state = JOB_TABLE_STATE_VERSION;
}
function jobTableStateLoad(settings, data) {
if (!data || data.imapsync_state !== JOB_TABLE_STATE_VERSION) return false;
// column visibility depends on the role, never restore it
if (Array.isArray(data.columns)) {
data.columns.forEach(function(c) { if (c) delete c.visible; });
}
}
// Sync source table (mailbox + user page). canManage: session may add/edit sources at all.
function drawSourceTable(canManage) {
if ($.fn.DataTable.isDataTable('#imapsync_source_table')) {
$('#imapsync_source_table').DataTable().columns.adjust().responsive.recalc();
return;
}
$('#imapsync_source_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[2, 'asc']],
ajax: {
type: "GET",
url: "/api/v1/get/syncjob_source/all",
dataSrc: function(json) {
if (!Array.isArray(json)) return [];
$.each(json, function(i, item) {
item.endpoint = wrapCell(esc((item.host || item.host1) + ':' + (item.port || item.port1) + ' (' + (item.enc || item.enc1) + ')'));
item.name_display = esc(item.name);
if (item.description) item.name_display += '<div class="small text-muted">' + esc(item.description) + '</div>';
item.name_display = wrapCell(item.name_display);
item.auth_display = authDisplay(item);
item.visibility_display = wrapCell(scopeDisplay(item));
if (canManage && item.can_edit) {
item.action = '<div class="btn-group">' +
'<a href="/edit/syncjob_source/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + esc(lang.edit) + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-syncjob_source" data-api-url="delete/syncjob_source" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + esc(lang.remove) + '</a>' +
'</div>';
} else {
item.action = '<a href="/edit/syncjob_source/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-secondary"><i class="bi bi-eye"></i> ' + esc(L('source_view')) + '</a>';
}
});
return json;
},
error: function() {
mailcow_alert_box(L('source_list_error'), 'danger');
}
},
columns: [
{
// placeholder, so the responsive control does not cover the ID
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: '',
responsivePriority: 1
},
{ title: L('id'), data: 'id', defaultContent: '', responsivePriority: 4 },
{ title: L('source_name'), data: 'name_display', defaultContent: '', responsivePriority: 2 },
{ title: L('source_scope'), data: 'visibility_display', defaultContent: '', responsivePriority: 6 },
{ title: L('source_server'), data: 'endpoint', defaultContent: '', responsivePriority: 5 },
{ title: L('source_auth_type'), data: 'auth_display', defaultContent: '', responsivePriority: 3 },
{
title: lang.active,
data: 'active',
defaultContent: '',
responsivePriority: 7,
render: function(d) { return d == 1 ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>'; }
},
{
title: lang.action,
data: 'action',
orderable: false,
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
responsivePriority: 3,
defaultContent: ''
}
]
});
}
// ===== Source dropdown in the job modal / edit page =====
function setNoSourceState($sel, empty) {
$sel.closest('form').find('.imapsync-no-source').toggleClass('d-none', !empty);
}
// Replace the options of a source dropdown (array of jQuery options, kept in order) and select keepId (if present and enabled).
// bootstrap-select 1.14 keeps a detached title option (incl. its selectedness) across empty()/refresh,
// which overrides the selected option and leaves stale list entries, so destroy and re-init instead.
function rebuildSourceSelect($sel, $options, keepId) {
if (typeof $sel.selectpicker === 'function' && $sel.data('selectpicker')) $sel.selectpicker('destroy');
$sel.find('option').remove();
$sel.append($options);
var $keep = (keepId !== '') ? $sel.find('option').filter(function() {
return $(this).val() === keepId && !$(this).prop('disabled');
}).first() : $();
$sel.find('option').prop('selected', false).removeAttr('selected');
if ($keep.length) {
$keep.attr('selected', 'selected').prop('selected', true);
} else {
// nothing to keep: select nothing, the required check makes the user pick a source
$sel.prop('selectedIndex', -1);
}
$sel.prop('disabled', false);
refreshSelect($sel);
if ($keep.length && $sel.data('selectpicker')) $sel.selectpicker('val', keepId);
return $keep.length > 0;
}
// (Re)build every source dropdown from the ACL-filtered API
function populateSourceSelects() {
var $selects = $('select.imapsync-source-select');
if (!$selects.length) return;
$selects.each(function() {
var $sel = $(this);
if ($sel.val()) $sel.data('keep-id', String($sel.val()));
$sel.data('saved-options', $sel.find('option:not(.bs-title-option)').clone());
// disable while loading instead of a transient placeholder option
$sel.prop('disabled', true);
refreshSelect($sel);
});
$.get('/api/v1/get/syncjob_source/all').done(function(sources) {
if (!Array.isArray(sources)) sources = [];
sources.sort(function(a, b) { return String(a.name).localeCompare(String(b.name)); });
$selects.each(function() {
var $sel = $(this);
var cur = String($sel.data('current-source-id') || '');
var keepId = String($sel.data('keep-id') || cur);
var opts = [];
var selectable = 0;
var listed = false;
$.each(sources, function(_, src) {
var id = String(src.id);
var $opt = sourceOption(src.id, src.name, src.host || src.host1, src.port || src.port1, src.auth_type, src.oauth_flow);
if (src.active != 1) {
$opt.attr('data-subtext', $opt.attr('data-subtext') + ' · ' + L('status_inactive'));
// the backend rejects inactive sources, only the assigned one stays selectable on the edit page
if (id !== cur) $opt.prop('disabled', true);
}
if (!$opt.prop('disabled')) selectable++;
if (id === cur) listed = true;
opts.push($opt);
});
// edit page: the assigned source stays selectable even if the viewer cannot see it
if (cur !== '' && !listed) {
var $cur = sourceOption(cur, $sel.data('current-source-name'), $sel.data('current-source-host'),
$sel.data('current-source-port'), $sel.data('current-source-auth-type'), $sel.data('current-source-oauth-flow'));
$cur.text($cur.text() + ' ' + L('source_not_visible'));
opts.unshift($cur);
selectable++;
}
rebuildSourceSelect($sel, opts, keepId);
setNoSourceState($sel, selectable === 0);
$sel.trigger('change');
});
}).fail(function() {
// keep what was there before (e.g. the assigned source on the edit page)
$selects.each(function() {
var $sel = $(this);
rebuildSourceSelect($sel, $sel.data('saved-options') || $(), String($sel.data('keep-id') || $sel.data('current-source-id') || ''));
$sel.trigger('change');
});
mailcow_alert_box(L('source_list_error'), 'danger');
});
}
function sourceOption(id, name, host, port, authType, flow) {
var sub = (host || '?') + ':' + (port || '?') + ' · ' + (authType || '');
if (authType === 'XOAUTH2') sub += ' (' + (flow === 'authorization_code' ? L('source_flow_badge_user') : L('source_flow_badge_app')) + ')';
return $('<option></option>').val(id).text(name || ('#' + id))
.attr('data-subtext', sub)
.attr('data-auth-type', authType || '')
.attr('data-oauth-flow', flow || '');
}
// Toggle password / user1 / connect rows by the selected source's auth type
function applySourceSelection($sel) {
var $opt = $sel.find('option:selected');
var $form = $sel.closest('form');
var isOauth = ($opt.attr('data-auth-type') === 'XOAUTH2');
var isAuthCode = isOauth && ($opt.attr('data-oauth-flow') === 'authorization_code');
var isEdit = ($form.data('id') === 'editsyncjob');
var $user1 = $form.find('input[name="user1"]');
var prevSource = $sel.data('prev-source');
var curSource = $sel.val() || '';
// a token belongs to (source, mailbox, remote user): switching the source drops the connect result
if (prevSource !== undefined && prevSource !== curSource && $user1.prop('readonly')) {
$user1.prop('readonly', false).val(isEdit ? $user1.data('initial') || '' : '');
setConnectStatus($form, '', '');
}
$sel.data('prev-source', curSource);
$form.find('.password1-row').toggle(!isOauth);
// edit: an empty password keeps the stored one
$form.find('input[name="password1"]').prop('required', !isOauth && !isEdit);
$form.find('.imapsync-oauth-connect-row').toggle(isAuthCode);
$form.find('.imapsync-user1-row').toggle(!isAuthCode);
$user1.prop('required', !isAuthCode && !isEdit);
}
// ===== OAuth connect (authorization_code) =====
var oauthPending = null;
function setConnectStatus($form, type, text, busy) {
var $status = $form.find('.imapsync-oauth-connect-status');
$status.attr('class', 'imapsync-oauth-connect-status ms-2' + (type ? ' text-' + type : '')).empty();
if (busy) $status.append('<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>');
$status.append(document.createTextNode(text || ''));
}
function clearPending() {
if (oauthPending && oauthPending.timer) clearInterval(oauthPending.timer);
oauthPending = null;
}
// Local mailbox the token is stored for (C1)
function connectMailbox($form) {
var $mb = $form.find('[name="username"]');
if ($mb.length) return $mb.val() || '';
if ($form.data('mailbox')) return String($form.data('mailbox'));
return (typeof mailcow_cc_username !== 'undefined') ? mailcow_cc_username : '';
}
function startConnect($form) {
var sourceId = $form.find('select.imapsync-source-select').val();
if (!sourceId) return;
var mailbox = connectMailbox($form);
if (!mailbox) {
setConnectStatus($form, 'danger', L('oauth_select_mailbox'));
return;
}
var url = '/syncjob-oauth?action=start&source_id=' + encodeURIComponent(sourceId) + '&mailbox=' + encodeURIComponent(mailbox);
var popup = window.open(url, 'syncjob_oauth', 'width=600,height=700');
if (!popup || popup.closed || typeof popup.closed === 'undefined') {
setConnectStatus($form, 'danger', L('oauth_popup_blocked'));
return;
}
clearPending();
var pending = { form: $form, sourceId: String(sourceId), mailbox: mailbox.toLowerCase() };
oauthPending = pending;
setConnectStatus($form, 'muted', L('oauth_waiting'), true);
pending.timer = setInterval(function() {
if (!popup.closed) return;
clearInterval(pending.timer);
pending.timer = null;
// stays pending: a result message that arrives after the close still wins
if (oauthPending === pending) setConnectStatus($form, 'muted', L('oauth_window_closed'));
}, 500);
}
function onOauthMessage(ev) {
if (ev.origin !== window.location.origin || !ev.data || ev.data.type !== 'syncjob_oauth') return;
var p = oauthPending;
if (!p) return;
// only the row that started the flow is updated
if (ev.data.ok && (String(ev.data.source_id) !== p.sourceId || String(ev.data.mailbox || '').toLowerCase() !== p.mailbox)) return;
clearPending();
if (ev.data.ok) {
p.form.find('input[name="user1"]').val(ev.data.user1).prop('readonly', true);
setConnectStatus(p.form, 'success', L('syncjob_oauth_connected') + ': ' + ev.data.user1);
} else {
var code = String(ev.data.error || '').replace(/[^a-z_]/g, '');
setConnectStatus(p.form, 'danger', L('oauth_error_' + code) || L('oauth_error_unknown'));
}
}
// ===== Custom params editor =====
// Allowlist { name: true (value) | false (flag) | 'regex' (value) }, loaded once on demand
var cpOptions = null;
var cpLoading = null;
function cpEnsureOptions(cb) {
if (cpOptions !== null) { cb(); return; }
if (!cpLoading) {
cpLoading = $.get('/api/v1/get/syncjob_options').done(function(opts) {
cpOptions = (opts && typeof opts === 'object' && !Array.isArray(opts)) ? opts : {};
}).fail(function() {
cpLoading = null;
mailcow_alert_box(L('custom_params_options_error'), 'danger');
});
}
cpLoading.done(function() { cb(); });
}
function cpApplyValueState($row) {
var name = $row.find('.imapsync-cp-opt').val() || '';
var isFlag = (name !== '') && !!cpOptions && cpOptions[name] === false;
var $val = $row.find('.imapsync-cp-val');
$val.prop('disabled', isFlag);
if (isFlag) $val.val('');
$val.prop('required', (name !== '') && !isFlag);
$val.attr('placeholder', isFlag ? L('custom_param_no_value') : L('custom_param_value'));
}
var cpRowSeq = 0;
function cpAddRow($editor, o, v) {
var n = ++cpRowSeq;
var $row = $(
'<div class="input-group mb-2 imapsync-cp-row">' +
'<select class="form-select imapsync-cp-opt" id="imapsync-cp-opt-' + n + '" style="flex:0 0 35%"></select>' +
'<input type="text" class="form-control imapsync-cp-val" id="imapsync-cp-val-' + n + '">' +
'<button type="button" class="btn btn-secondary imapsync-cp-remove"><i class="bi bi-trash" aria-hidden="true"></i></button>' +
'</div>'
);
// aria-label: label elements inside .input-group break its first/last child styling
$row.find('.imapsync-cp-opt').attr('aria-label', L('custom_param_option'));
$row.find('.imapsync-cp-val').attr('aria-label', L('custom_param_value'));
$row.find('.imapsync-cp-remove').attr({ 'title': L('custom_param_remove'), 'aria-label': L('custom_param_remove') });
var $sel = $row.find('.imapsync-cp-opt');
$sel.append($('<option></option>').attr('value', '').text('— ' + L('custom_param_option') + ' —'));
Object.keys(cpOptions || {}).sort().forEach(function(name) {
$sel.append($('<option></option>').attr('value', name).text('--' + name));
});
// keep a stored option that is no longer allowlisted visible (the backend rejects it on save)
if (o && !(cpOptions && (o in cpOptions))) {
$sel.append($('<option></option>').attr('value', o).text('--' + o));
}
$sel.val(o || '');
$row.find('.imapsync-cp-val').val(v || '');
$editor.find('.imapsync-cp-rows').append($row);
cpApplyValueState($row);
}
function cpBuild($editor) {
var $hidden = $editor.siblings('.imapsync-cp-value');
$editor.find('.imapsync-cp-rows').empty();
var pairs = [];
try { pairs = JSON.parse($hidden.val() || '[]'); } catch (e) { pairs = []; }
if (!Array.isArray(pairs)) pairs = [];
pairs.forEach(function(p) {
if (p && typeof p === 'object') cpAddRow($editor, p.o, p.v);
});
}
function cpSerialize($editor) {
var out = [];
$editor.find('.imapsync-cp-row').each(function() {
var o = $.trim($(this).find('.imapsync-cp-opt').val() || '').replace(/^-+/, '');
if (o === '') return;
out.push({ o: o, v: $(this).find('.imapsync-cp-val').val() || '' });
});
$editor.siblings('.imapsync-cp-value').val(JSON.stringify(out));
}
function cpInit($scope) {
var $editors = $scope.find('.imapsync-cp-editor');
if (!$editors.length) return;
cpEnsureOptions(function() {
$editors.each(function() { cpBuild($(this)); });
});
}
// ===== Source editor (add modal + edit page) =====
function sourceFormToggle($form) {
var isOauth = $form.find('select.imapsync-source-auth-type').val() === 'XOAUTH2';
var isAuthCode = $form.find('select.imapsync-source-oauth-flow').val() === 'authorization_code';
var isCc = isOauth && !isAuthCode;
$form.find('.imapsync-source-oauth-block').toggle(isOauth);
$form.find('.imapsync-source-authcode-block').toggle(isOauth && isAuthCode);
$form.find('.imapsync-source-cc-block').toggle(isCc);
// client credentials sources are forced private by the backend
$form.find('.imapsync-source-scope-block').toggle(!isCc);
$form.find('.imapsync-source-scope-cc-hint').toggle(isCc);
}
function sourceScopeToggle($form) {
var scope = $form.find('select.imapsync-source-scope').val();
$form.find('.imapsync-source-domains-row').toggle(scope === 'domain');
$form.find('.imapsync-source-users-row').toggle(scope === 'user');
if (scope === 'domain') fillScopeSelect($form.find('select.imapsync-source-domains'), 'domain/all', 'domain_name');
if (scope === 'user') fillScopeSelect($form.find('select.imapsync-source-users'), 'mailbox/all', 'username');
}
// Add the selectable domains/mailboxes to a scope multiselect (keeps selected options)
function fillScopeSelect($sel, url, field) {
if (!$sel.length || $sel.prop('disabled') || $sel.data('filled')) return;
$sel.data('filled', true);
$.get('/api/v1/get/' + url).done(function(rows) {
var have = {};
$sel.find('option').each(function() { have[$(this).val()] = true; });
$.each(Array.isArray(rows) ? rows : [], function(_, r) {
var v = r[field];
if (!v || have[v]) return;
$sel.append($('<option></option>').val(v).text(v));
});
// refresh duplicates preselected entries in the button label, re-init instead
if (typeof $sel.selectpicker === 'function' && $sel.data('selectpicker')) {
var keep = $sel.val();
$sel.selectpicker('destroy');
refreshSelect($sel);
$sel.selectpicker('val', keep);
} else {
refreshSelect($sel);
}
}).fail(function() {
$sel.data('filled', false);
mailcow_alert_box(lang_danger.access_denied, 'danger');
});
}
// Extra params must be a JSON object; checked before the form is posted
function extraParamsValid($form) {
var $f = $form.find('input[name="oauth_extra_params"]');
if (!$f.length || $f.prop('disabled') || !$f.is(':visible')) return true;
var v = $.trim($f.val() || '');
if (v === '') { $f.removeClass('inputMissingAttr'); return true; }
var ok = false;
try {
var o = JSON.parse(v);
ok = (o !== null && typeof o === 'object' && !Array.isArray(o));
} catch (e) { ok = false; }
$f.toggleClass('inputMissingAttr', !ok);
if (!ok) mailcow_alert_box(lang_danger.imapsync_source_oauth_extra_invalid, 'danger');
return ok;
}
function copyField(id) {
var el = document.getElementById(id);
if (!el) return;
var done = function() { mailcow_alert_box(L('copied'), 'success'); };
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(el.value).then(done);
} else {
el.select();
try { document.execCommand('copy'); done(); } catch (e) {}
}
}
// ===== Provider presets (add source modal) =====
// Server and OAuth settings of well-known providers; fields a preset leaves out get the defaults of applyPreset
var m365 = 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/';
var presets = {
m365_app: {host: 'outlook.office365.com', auth_type: 'XOAUTH2', oauth_token_endpoint: m365 + 'token',
oauth_scope: 'https://outlook.office365.com/.default'},
m365_user: {host: 'outlook.office365.com', auth_type: 'XOAUTH2', oauth_flow: 'authorization_code',
oauth_authorize_endpoint: m365 + 'authorize', oauth_token_endpoint: m365 + 'token',
oauth_scope: 'https://outlook.office.com/IMAP.AccessAsUser.All offline_access openid email profile'},
// Google issues a refresh token only for offline access, and again only after consent
google_user: {host: 'imap.gmail.com', auth_type: 'XOAUTH2', oauth_flow: 'authorization_code',
oauth_authorize_endpoint: 'https://accounts.google.com/o/oauth2/v2/auth', oauth_token_endpoint: 'https://oauth2.googleapis.com/token',
oauth_scope: 'https://mail.google.com/ openid email', oauth_extra_params: '{"access_type":"offline","prompt":"consent select_account"}'},
gmail: {host: 'imap.gmail.com'},
gmx: {host: 'imap.gmx.net'},
webde: {host: 'imap.web.de'},
tonline: {host: 'secureimap.t-online.de'},
icloud: {host: 'imap.mail.me.com'},
yahoo: {host: 'imap.mail.yahoo.com'},
ionos: {host: 'imap.ionos.de'},
strato: {host: 'imap.strato.de'}
};
// The name follows the preset until it is changed by hand; client ID and secret are kept
function applyPreset($link) {
var $form = $link.closest('form');
var preset = $.extend({port: 993, enc: 'SSL', auth_type: 'PLAIN', oauth_flow: 'client_credentials', oauth_authorize_endpoint: '',
oauth_token_endpoint: '', oauth_userinfo_endpoint: '', oauth_scope: '', oauth_extra_params: ''}, presets[$link.data('preset')]);
var $name = $form.find('input[name="name"]');
var names = $form.find('.imapsync-source-preset').map(function() { return $.trim($(this).text()); }).get();
if (!$name.val() || names.indexOf($name.val()) !== -1) $name.val($.trim($link.text()));
// bootstrap-select 1.14 duplicates the button label on refresh after .val(), use its own setter
$.each(preset, function(field, value) {
var $f = $form.find('[name="' + field + '"]');
if ($f.data('selectpicker')) {
$f.selectpicker('val', value);
} else {
$f.val(value);
}
});
var needsTenant = preset.oauth_token_endpoint.indexOf('{tenant}') !== -1;
$form.find('.imapsync-source-tenant-row').toggle(needsTenant);
applyTenant($form);
sourceFormToggle($form);
if (needsTenant) $form.find('.imapsync-source-tenant').trigger('focus');
}
// M365: the tenant (ID or domain) is part of the endpoint URLs
function applyTenant($form) {
var tenant = encodeURIComponent($.trim($form.find('.imapsync-source-tenant').val() || '')) || '{tenant}';
$form.find('input[name="oauth_authorize_endpoint"], input[name="oauth_token_endpoint"]').val(function(_, url) {
return url.replace(/^(https:\/\/login\.microsoftonline\.com\/)[^\/]*/, function(_, base) { return base + tenant; });
});
}
// A visible endpoint must not keep the {tenant} placeholder; checked before the form is posted
function tenantValid($form) {
var missing = $form.find('input[name="oauth_authorize_endpoint"]:visible, input[name="oauth_token_endpoint"]:visible')
.filter(function() { return this.value.indexOf('{tenant}') !== -1; }).length > 0;
$form.find('.imapsync-source-tenant').toggleClass('inputMissingAttr', missing);
if (missing) {
$form.find('.imapsync-source-tenant-row').show();
mailcow_alert_box(lang_danger.imapsync_source_tenant_missing, 'danger');
}
return !missing;
}
// ===== Wiring =====
function bind() {
injectStyle();
// custom params editor
$(document).on('change', '.imapsync-cp-opt', function() {
cpApplyValueState($(this).closest('.imapsync-cp-row'));
cpSerialize($(this).closest('.imapsync-cp-editor'));
});
$(document).on('input', '.imapsync-cp-val', function() {
cpSerialize($(this).closest('.imapsync-cp-editor'));
});
$(document).on('click', '.imapsync-cp-add', function() {
var $editor = $(this).closest('.imapsync-cp-editor');
cpEnsureOptions(function() { cpAddRow($editor); });
});
$(document).on('click', '.imapsync-cp-remove', function() {
var $editor = $(this).closest('.imapsync-cp-editor');
$(this).closest('.imapsync-cp-row').remove();
cpSerialize($editor);
});
// job form
$(document).on('change', 'select.imapsync-source-select', function() {
applySourceSelection($(this));
});
// a token is bound to the local mailbox: changing it drops the connect result
$(document).on('change', 'form[data-id="add_syncjob"] select[name="username"]', function() {
var $form = $(this).closest('form');
var $user1 = $form.find('input[name="user1"]');
if ($user1.prop('readonly')) {
$user1.prop('readonly', false).val('');
setConnectStatus($form, '', '');
}
});
$(document).on('click', '.imapsync-oauth-connect-btn', function() {
startConnect($(this).closest('form'));
});
window.addEventListener('message', onOauthMessage);
$(document).on('click', '.imapsync-open-source-modal', function(e) {
e.preventDefault();
var $current = $(this).closest('.modal');
var open = function() { $('#addImapsyncSourceModal').modal('show'); };
if ($current.length) {
$current.one('hidden.bs.modal', open).modal('hide');
} else {
open();
}
});
// authorization_code: user1 comes from the provider sign-in, require it before posting
$('[data-action="add_item"][data-id="add_syncjob"], [data-action="edit_selected"][data-id="editsyncjob"]').on('click', function(e) {
var $form = $(this).closest('form');
var $opt = $form.find('select.imapsync-source-select option:selected');
if ($opt.attr('data-auth-type') === 'XOAUTH2' && $opt.attr('data-oauth-flow') === 'authorization_code' &&
!$.trim($form.find('input[name="user1"]').val() || '')) {
e.preventDefault();
e.stopImmediatePropagation();
setConnectStatus($form, 'danger', lang_danger.imapsync_source_oauth_connect_required);
return false;
}
});
// source editor
$(document).on('change', 'select.imapsync-source-auth-type, select.imapsync-source-oauth-flow', function() {
sourceFormToggle($(this).closest('form'));
});
$(document).on('change', 'select.imapsync-source-scope', function() {
sourceScopeToggle($(this).closest('form'));
});
$('[data-action="add_item"][data-id="add_imapsync_source"], [data-action="edit_selected"][data-id="edit_imapsync_source"]').on('click', function(e) {
var $form = $(this).closest('form');
if (!extraParamsValid($form) || !tenantValid($form)) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
});
$(document).on('click', '.imapsync-source-preset', function(e) {
e.preventDefault();
applyPreset($(this));
});
$(document).on('input', '.imapsync-source-tenant', function() {
applyTenant($(this).closest('form'));
});
$(document).on('click', '.imapsync-copy-btn', function(e) {
e.preventDefault();
copyField($(this).data('copy-target'));
});
// modals
$(document).on('shown.bs.modal', '#addImapsyncSourceModal', function() {
var $form = $(this).find('form');
sourceFormToggle($form);
sourceScopeToggle($form);
});
$(document).on('shown.bs.modal', '#addSyncJobModalAdmin, #addSyncJobModal', function() {
populateSourceSelects();
cpInit($(this));
});
// edit page: forms are already rendered
$('form[data-id="edit_imapsync_source"]').each(function() {
sourceFormToggle($(this));
sourceScopeToggle($(this));
});
$('form[data-id="editsyncjob"]').each(function() {
var $form = $(this);
$form.find('input[name="user1"]').data('initial', $form.find('input[name="user1"]').val());
populateSourceSelects();
cpInit($form);
});
}
$(document).ready(bind);
return {
drawSourceTable: drawSourceTable,
jobTableStateSave: jobTableStateSave,
jobTableStateLoad: jobTableStateLoad,
prepareJobRow: prepareJobRow,
wrapCell: wrapCell,
L: L
};
})(jQuery);
+33 -26
View File
@@ -2169,11 +2169,7 @@ jQuery(function($){
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
// keep the backend order (C4: prio desc, never run first, least recently run) order: [[2, 'desc']],
order: [],
// drop state saved before the rework (it would restore the old ID sort / column visibility)
stateSaveParams: mailcowImapsync.jobTableStateSave,
stateLoadParams: mailcowImapsync.jobTableStateLoad,
initComplete: function(){ initComplete: function(){
hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table'); hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table');
}, },
@@ -2181,21 +2177,40 @@ jQuery(function($){
type: "GET", type: "GET",
url: "/api/v1/get/syncjobs/all/no_log", url: "/api/v1/get/syncjobs/all/no_log",
dataSrc: function(json){ dataSrc: function(json){
if (!Array.isArray(json)) return [];
$.each(json, function (i, item) { $.each(json, function (i, item) {
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + encodeURIComponent(item.id) + '">' + lang.open_logs + '</a>'
item.user2 = escapeHtml(item.user2);
if (!item.exclude > 0) { if (!item.exclude > 0) {
item.exclude = '-'; item.exclude = '-';
} else { } else {
item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>'; item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>';
} }
mailcowImapsync.prepareJobRow(item); item.server_w_port = escapeHtml(item.user1) + '@' + escapeHtml(item.host1) + ':' + escapeHtml(item.port1);
item.user2 = mailcowImapsync.wrapCell(escapeHtml(item.user2));
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<a href="/edit/syncjob/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
'<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-info"><i class="bi bi-card-list"></i> ' + lang.open_logs + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' + '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
'</div>'; '</div>';
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
if (item.is_running == 1) {
item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>';
} else {
item.is_running = '<span id="inactive-script" class="badge fs-6 bg-warning">' + lang.waiting + '</span>';
}
if (!item.last_run > 0) {
item.last_run = lang.waiting;
}
if (item.success == null) {
item.success = '-';
item.exit_status = '';
} else {
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
}
if (lang['syncjob_'+item.exit_status]) {
item.exit_status = lang['syncjob_'+item.exit_status];
} else if (item.success != '-') {
item.exit_status = lang.syncjob_check_log;
}
item.exit_status = item.success + ' ' + item.exit_status;
}); });
return json; return json;
@@ -2220,46 +2235,40 @@ jQuery(function($){
responsivePriority: 2 responsivePriority: 2
}, },
{ {
title: mailcowImapsync.L('id'), title: 'ID',
data: 'id', data: 'id',
responsivePriority: 3, responsivePriority: 3,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.syncjobs.prio, title: lang.owner,
data: 'prio',
visible: role === 'admin',
responsivePriority: 3,
defaultContent: ''
},
{
title: lang.syncjobs.user2_label,
data: 'user2', data: 'user2',
responsivePriority: 4, responsivePriority: 4,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.syncjobs.source_server, title: 'Server',
data: 'server_w_port', data: 'server_w_port',
responsivePriority: 6,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.last_run, title: lang.last_run,
data: 'last_run', data: 'last_run',
responsivePriority: 9,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.syncjobs.syncjob_last_run_result, title: lang.syncjob_last_run_result,
data: 'exit_status', data: 'exit_status',
responsivePriority: 8, defaultContent: ''
},
{
title: 'Log',
data: 'log',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',
responsivePriority: 10,
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
@@ -2268,7 +2277,6 @@ jQuery(function($){
{ {
title: lang.status, title: lang.status,
data: 'is_running', data: 'is_running',
responsivePriority: 7,
defaultContent: '' defaultContent: ''
}, },
{ {
@@ -2478,7 +2486,6 @@ jQuery(function($){
onVisible("[id^=alias_table]", () => draw_alias_table()); onVisible("[id^=alias_table]", () => draw_alias_table());
onVisible("[id^=aliasdomain_table]", () => draw_aliasdomain_table()); onVisible("[id^=aliasdomain_table]", () => draw_aliasdomain_table());
onVisible("[id^=sync_job_table]", () => draw_sync_job_table()); onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
onVisible("[id^=imapsync_source_table]", () => mailcowImapsync.drawSourceTable(role === 'admin' || acl_data.syncjobs === 1));
onVisible("[id^=filter_table]", () => draw_filter_table()); onVisible("[id^=filter_table]", () => draw_filter_table());
onVisible("[id^=bcc_table]", () => draw_bcc_table()); onVisible("[id^=bcc_table]", () => draw_bcc_table());
onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table()); onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table());
+44 -18
View File
@@ -272,27 +272,22 @@ jQuery(function($){
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
// keep the backend order (C4: prio desc, never run first, least recently run)
order: [],
// drop state saved before the rework (it would restore the old ID sort / column visibility)
stateSaveParams: mailcowImapsync.jobTableStateSave,
stateLoadParams: mailcowImapsync.jobTableStateLoad,
ajax: { ajax: {
type: "GET", type: "GET",
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log', url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
dataSrc: function(data){ dataSrc: function(data){
if (!Array.isArray(data)) return [];
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.user1 = escapeHtml(item.user1);
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
if (!item.exclude > 0) { if (!item.exclude > 0) {
item.exclude = '-'; item.exclude = '-';
} else { } else {
item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>'; item.exclude = '<code>' + escapeHtml(item.exclude) + '</code>';
} }
mailcowImapsync.prepareJobRow(item); item.server_w_port = escapeHtml(item.user1 + '@' + item.host1 + ':' + item.port1);
if (acl_data.syncjobs === 1) { if (acl_data.syncjobs === 1) {
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
'<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '" class="btn btn-xs btn-xs-half btn-info"><i class="bi bi-card-list"></i> ' + lang.open_logs + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' + '<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
'</div>'; '</div>';
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
@@ -301,6 +296,26 @@ jQuery(function($){
item.action = '<span>-</span>'; item.action = '<span>-</span>';
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />'; item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
} }
if (item.is_running == 1) {
item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>';
} else {
item.is_running = '<span id="inactive-script" class="badge fs-6 bg-warning">' + lang.waiting + '</span>';
}
if (!item.last_run > 0) {
item.last_run = lang.waiting;
}
if (item.success == null) {
item.success = '-';
item.exit_status = '';
} else {
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
}
if (lang['syncjob_'+item.exit_status]) {
item.exit_status = lang['syncjob_'+item.exit_status];
} else if (item.success != '-') {
item.exit_status = lang.syncjob_check_log;
}
item.exit_status = item.success + ' ' + item.exit_status;
}); });
return data; return data;
@@ -325,34 +340,41 @@ jQuery(function($){
responsivePriority: 2 responsivePriority: 2
}, },
{ {
title: mailcowImapsync.L('id'), title: 'ID',
data: 'id', data: 'id',
defaultContent: '', defaultContent: '',
responsivePriority: 3 responsivePriority: 3
}, },
{ {
title: lang.syncjobs.source_server, title: 'Server',
data: 'server_w_port', data: 'server_w_port',
defaultContent: ''
},
{
title: lang.username,
data: 'user1',
defaultContent: '', defaultContent: '',
responsivePriority: 4 responsivePriority: 3
}, },
{ {
title: lang.last_run, title: lang.last_run,
data: 'last_run', data: 'last_run',
defaultContent: '', defaultContent: ''
responsivePriority: 7
}, },
{ {
title: lang.syncjobs.syncjob_last_run_result, title: lang.syncjob_last_run_result,
data: 'exit_status', data: 'exit_status',
defaultContent: '', defaultContent: ''
responsivePriority: 6 },
{
title: 'Log',
data: 'log',
defaultContent: ''
}, },
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
responsivePriority: 8,
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>' return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'
} }
@@ -363,6 +385,11 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 5 responsivePriority: 5
}, },
{
title: lang.encryption,
data: 'enc1',
defaultContent: ''
},
{ {
title: lang.excludes, title: lang.excludes,
data: 'exclude', data: 'exclude',
@@ -672,7 +699,6 @@ jQuery(function($){
onVisible("[id^=bl_policy_mailbox_table]", () => draw_bl_policy_mailbox_table()); onVisible("[id^=bl_policy_mailbox_table]", () => draw_bl_policy_mailbox_table());
onVisible("[id^=wl_policy_mailbox_table]", () => draw_wl_policy_mailbox_table()); onVisible("[id^=wl_policy_mailbox_table]", () => draw_wl_policy_mailbox_table());
onVisible("[id^=sync_job_table]", () => draw_sync_job_table()); onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
onVisible("[id^=imapsync_source_table]", () => mailcowImapsync.drawSourceTable(acl_data.syncjobs === 1));
onVisible("[id^=app_passwd_table]", () => draw_app_passwd_table()); onVisible("[id^=app_passwd_table]", () => draw_app_passwd_table());
onVisible("[id^=recent-logins]", () => last_logins('get')); onVisible("[id^=recent-logins]", () => last_logins('get'));
}); });
+51 -53
View File
@@ -19,7 +19,7 @@ function api_log($_data) {
if ($value) { if ($value) {
if (is_array($value)) unset($value["csrf_token"]); if (is_array($value)) unset($value["csrf_token"]);
foreach ($value as $key => &$val) { foreach ($value as $key => &$val) {
if(preg_match("/pass|secret|oauth_extra_params/i", $key)) { if(preg_match("/pass/i", $key)) {
$val = '*'; $val = '*';
} }
} }
@@ -317,10 +317,7 @@ if (isset($_GET['query'])) {
process_add_return(admin('add', $attr)); process_add_return(admin('add', $attr));
break; break;
case "syncjob": case "syncjob":
process_add_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->addJob($attr)); process_add_return(mailbox('add', 'syncjob', $attr));
break;
case "syncjob_source":
process_add_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->addSource($attr));
break; break;
case "bcc": case "bcc":
process_add_return(bcc('add', $attr)); process_add_return(bcc('add', $attr));
@@ -1049,37 +1046,61 @@ if (isset($_GET['query'])) {
} }
process_get_return($data); process_get_return($data);
break; break;
case "syncjob_options":
process_get_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getOptions());
break;
case "imapsync_settings":
process_get_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getSettings());
break;
case "syncjob_source":
switch ($object) {
case "all":
process_get_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getSources());
break;
default:
process_get_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getSource($object));
break;
}
break;
case "syncjobs": case "syncjobs":
switch ($object) { switch ($object) {
case "all": case "all":
// Admins see every job (incl. orphaned ones), others the jobs of the mailboxes they manage $domains = mailbox('get', 'domains');
$mailboxes = array(); if (!empty($domains)) {
if ($_SESSION['mailcow_cc_role'] != 'admin') { foreach ($domains as $domain) {
foreach (mailbox('get', 'domains') ?: array() as $domain) { $mailboxes = mailbox('get', 'mailboxes', $domain);
$mailboxes = array_merge($mailboxes, mailbox('get', 'mailboxes', $domain) ?: array()); if (!empty($mailboxes)) {
foreach ($mailboxes as $mailbox) {
$syncjobs = mailbox('get', 'syncjobs', $mailbox);
if (!empty($syncjobs)) {
foreach ($syncjobs as $syncjob) {
if (isset($extra)) {
$details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
}
else {
$details = mailbox('get', 'syncjob_details', $syncjob);
}
if ($details) {
$data[] = $details;
}
else {
continue;
}
}
}
}
}
} }
process_get_return($data);
}
else {
echo '{}';
} }
process_get_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getAllJobs($extra, $mailboxes));
break; break;
default: default:
process_get_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getJobs($object, $extra)); $syncjobs = mailbox('get', 'syncjobs', $object);
if (!empty($syncjobs)) {
foreach ($syncjobs as $syncjob) {
if (isset($extra)) {
$details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
}
else {
$details = mailbox('get', 'syncjob_details', $syncjob);
}
if ($details) {
$data[] = $details;
}
else {
continue;
}
}
}
process_get_return($data);
break; break;
} }
break; break;
@@ -1721,10 +1742,7 @@ if (isset($_GET['query'])) {
process_delete_return(rsettings('delete', array('id' => $items))); process_delete_return(rsettings('delete', array('id' => $items)));
break; break;
case "syncjob": case "syncjob":
process_delete_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->deleteJobs($items)); process_delete_return(mailbox('delete', 'syncjob', array('id' => $items)));
break;
case "syncjob_source":
process_delete_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->deleteSources($items));
break; break;
case "filter": case "filter":
process_delete_return(mailbox('delete', 'filter', array('id' => $items))); process_delete_return(mailbox('delete', 'filter', array('id' => $items)));
@@ -1950,27 +1968,7 @@ if (isset($_GET['query'])) {
} }
break; break;
case "syncjob": case "syncjob":
switch ($object) { process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr)));
case "run_next":
process_edit_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->runNext($items));
break;
default:
process_edit_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->editJobs($items, $attr));
break;
}
break;
case "imapsync_settings":
process_edit_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->editSettings($attr));
break;
case "syncjob_source":
switch ($object) {
case "refresh_token":
process_edit_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->refreshSourceToken($items));
break;
default:
process_edit_return(\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->editSources($items, $attr));
break;
}
break; break;
case "filter": case "filter":
process_edit_return(mailbox('edit', 'filter', array_merge(array('id' => $items), $attr))); process_edit_return(mailbox('edit', 'filter', array_merge(array('id' => $items), $attr)));
+49 -31
View File
@@ -44,16 +44,24 @@
"app_name": "Име на приложението", "app_name": "Име на приложението",
"app_password": "Добавяне на парола за приложение", "app_password": "Добавяне на парола за приложение",
"app_passwd_protocols": "Разрешени протоколи за паролата на приложението", "app_passwd_protocols": "Разрешени протоколи за паролата на приложението",
"automap": "Опит за автоматично картографиране на папки (\"Изпратени\", \"Изпратени\" => \"Изпратени\" и т.н.)",
"backup_mx_options": "Опции за реле", "backup_mx_options": "Опции за реле",
"bcc_dest_format": "BCC дестинацията трябва да бъде един валиден имейл адрес.<br>Ако имате нужда да изпратите копие до множество адреси, създайте псевдоним и го използвайте тук.", "bcc_dest_format": "BCC дестинацията трябва да бъде един валиден имейл адрес.<br>Ако имате нужда да изпратите копие до множество адреси, създайте псевдоним и го използвайте тук.",
"comment_info": "Частен коментар не е видим за потребителя, докато публичен коментар се показва като подсказка при преминаване с мишката върху него в прегледа на потребителя", "comment_info": "Частен коментар не е видим за потребителя, докато публичен коментар се показва като подсказка при преминаване с мишката върху него в прегледа на потребителя",
"custom_params": "Персонализирани параметри",
"custom_params_hint": "Дясно: --param=xy, грешно: --param xy",
"delete1": "Изтриване от източника след завършване",
"delete2": "Изтриване на съобщенията в дестинацията, които не са в източника",
"delete2duplicates": "Изтриване на дубликати в дестинацията",
"description": "Описание", "description": "Описание",
"destination": "Дестинация", "destination": "Дестинация",
"disable_login": "Забраняване на вход (входящите съобщения все още се приемат)", "disable_login": "Забраняване на вход (входящите съобщения все още се приемат)",
"domain": "Домейн", "domain": "Домейн",
"domain_matches_hostname": "Домейнът %s съвпада с името на хоста", "domain_matches_hostname": "Домейнът %s съвпада с името на хоста",
"domain_quota_m": "Обща квота на домейна (MiB)", "domain_quota_m": "Обща квота на домейна (MiB)",
"dry": "Симулиране на синхронизация",
"enc_method": "Метод на криптиране", "enc_method": "Метод на криптиране",
"exclude": "Изключване на обекти (regex)",
"full_name": "Пълно име", "full_name": "Пълно име",
"gal": "Глобален адресен списък", "gal": "Глобален адресен списък",
"gal_info": "Глобалният адресен списък съдържа всички обекти на домейна и не може да бъде редактиран от нито един потребител. Липсва информация за заетост/свободно време в SOGo, ако е деактивирано! <b>Рестартирайте SOGo, за да приложите промените.</b>", "gal_info": "Глобалният адресен списък съдържа всички обекти на домейна и не може да бъде редактиран от нито един потребител. Липсва информация за заетост/свободно време в SOGo, ако е деактивирано! <b>Рестартирайте SOGo, за да приложите промените.</b>",
@@ -69,6 +77,7 @@
"mailbox_username": "Потребителско име (лява част на имейл адрес)", "mailbox_username": "Потребителско име (лява част на имейл адрес)",
"max_aliases": "Макс. възможни псевдоними", "max_aliases": "Макс. възможни псевдоними",
"max_mailboxes": "Макс. възможни пощенски кутии", "max_mailboxes": "Макс. възможни пощенски кутии",
"mins_interval": "Интервал за проверка (минути)",
"multiple_bookings": "Множествени резервации", "multiple_bookings": "Множествени резервации",
"nexthop": "Следващ хоп", "nexthop": "Следващ хоп",
"password": "Парола", "password": "Парола",
@@ -88,10 +97,16 @@
"select_domain": "Моля, изберете първо домейн", "select_domain": "Моля, изберете първо домейн",
"sieve_desc": "Кратко описание", "sieve_desc": "Кратко описание",
"sieve_type": "Тип на филтър", "sieve_type": "Тип на филтър",
"skipcrossduplicates": "Пропускане на дублирани съобщения между папки (първи дошъл, първи обслужен)",
"subscribeall": "Абониране за всички папки",
"syncjob": "Добавяне на синхронизираща задача",
"syncjob_hint": "Имайте предвид, че паролите трябва да бъдат запазени като обикновен текст!",
"tags": "Тагове", "tags": "Тагове",
"target_address": "Адреси за пренасочване", "target_address": "Адреси за пренасочване",
"target_address_info": "<small>Пълни имейл адреси (разделени с запетая).</small>", "target_address_info": "<small>Пълни имейл адреси (разделени с запетая).</small>",
"target_domain": "Целеви домейн", "target_domain": "Целеви домейн",
"timeout1": "Таймаут за връзка с отдалечен хост",
"timeout2": "Таймаут за връзка с локален хост",
"username": "Потребителско име", "username": "Потребителско име",
"validate": "Валидиране", "validate": "Валидиране",
"validation_success": "Успешно валидиране" "validation_success": "Успешно валидиране"
@@ -615,6 +630,7 @@
"app_name": "Име на приложението", "app_name": "Име на приложението",
"app_passwd": "Парола за приложение", "app_passwd": "Парола за приложение",
"app_passwd_protocols": "Разрешени протоколи за паролата на приложението", "app_passwd_protocols": "Разрешени протоколи за паролата на приложението",
"automap": "Опит за автоматично картографиране на папки (\"Изпратени\", \"Изпратени\" => \"Изпратени\" и т.н.)",
"backup_mx_options": "Опции за реле", "backup_mx_options": "Опции за реле",
"bcc_dest_format": "BCC дестинацията трябва да бъде един валиден имейл адрес.<br>Ако имате нужда да изпратите копие до множество адреси, създайте псевдоним и го използвайте тук.", "bcc_dest_format": "BCC дестинацията трябва да бъде един валиден имейл адрес.<br>Ако имате нужда да изпратите копие до множество адреси, създайте псевдоним и го използвайте тук.",
"client_id": "Идентификатор на клиента", "client_id": "Идентификатор на клиента",
@@ -622,6 +638,9 @@
"comment_info": "Частен коментар не е видим за потребителя, докато публичен коментар се показва като подсказка при преминаване с мишката върху него в прегледа на потребителя", "comment_info": "Частен коментар не е видим за потребителя, докато публичен коментар се показва като подсказка при преминаване с мишката върху него в прегледа на потребителя",
"created_on": "Създаден на", "created_on": "Създаден на",
"custom_attributes": "Персонализирани атрибути", "custom_attributes": "Персонализирани атрибути",
"delete1": "Изтриване от източника след завършване",
"delete2": "Изтриване на съобщенията в дестинацията, които не са в източника",
"delete2duplicates": "Изтриване на дубликати в дестинацията",
"delete_ays": "Моля, потвърдете процеса на изтриване.", "delete_ays": "Моля, потвърдете процеса на изтриване.",
"description": "Описание", "description": "Описание",
"disable_login": "Забраняване на вход (входящите съобщения все още се приемат)", "disable_login": "Забраняване на вход (входящите съобщения все още се приемат)",
@@ -645,6 +664,7 @@
"dont_check_sender_acl": "Деактивиране на проверката на изпращач за домейн %s (+ домейни на псевдоними)", "dont_check_sender_acl": "Деактивиране на проверката на изпращач за домейн %s (+ домейни на псевдоними)",
"edit_alias_domain": "Редактиране на домейн на псевдоним", "edit_alias_domain": "Редактиране на домейн на псевдоним",
"encryption": "Криптиране", "encryption": "Криптиране",
"exclude": "Изключване на обекти (regex)",
"extended_sender_acl": "Външни адреси на изпращач", "extended_sender_acl": "Външни адреси на изпращач",
"extended_sender_acl_info": "Трябва да бъде импортиран DKIM домейн ключ, ако е наличен.<br>\r\n Помнете да добавите този сървър към съответния SPF TXT запис.<br>\r\n Когато домейн или домейн на псевдоним бъде добавен към този сървър, който се припокрива с външен адрес, външният адрес ще бъде премахнат.<br>\r\n Използвайте @domain.tld, за да разрешите изпращане като *@domain.tld.", "extended_sender_acl_info": "Трябва да бъде импортиран DKIM домейн ключ, ако е наличен.<br>\r\n Помнете да добавите този сървър към съответния SPF TXT запис.<br>\r\n Когато домейн или домейн на псевдоним бъде добавен към този сървър, който се припокрива с външен адрес, външният адрес ще бъде премахнат.<br>\r\n Използвайте @domain.tld, за да разрешите изпращане като *@domain.tld.",
"footer_exclude": "Изключване от подвал", "footer_exclude": "Изключване от подвал",
@@ -671,7 +691,10 @@
"max_aliases": "Макс. псевдоними", "max_aliases": "Макс. псевдоними",
"max_mailboxes": "Макс. възможни пощенски кутии", "max_mailboxes": "Макс. възможни пощенски кутии",
"max_quota": "Макс. квота за пощенска кутия (MiB)", "max_quota": "Макс. квота за пощенска кутия (MiB)",
"maxage": "Максимална възраст на съобщенията в дни, които ще бъдат проверени от отдалечен<br><small>(0 = игнорирай възраст)</small>",
"maxbytespersecond": "Макс. байтове за секунда <br><small>(0 = неограничен)</small>",
"mbox_rl_info": "Това ограничение на скоростта се прилага върху SASL името за вход, то съвпада с всеки \"от\" адрес, използван от влезлия потребител. Ограничението на скоростта на пощенска кутия пренарежда ограничението на скоростта за домейн.", "mbox_rl_info": "Това ограничение на скоростта се прилага върху SASL името за вход, то съвпада с всеки \"от\" адрес, използван от влезлия потребител. Ограничението на скоростта на пощенска кутия пренарежда ограничението на скоростта за домейн.",
"mins_interval": "Интервал (мин)",
"multiple_bookings": "Множествени резервации", "multiple_bookings": "Множествени резервации",
"nexthop": "Следващ хоп", "nexthop": "Следващ хоп",
"none_inherit": "Без / Наследяване", "none_inherit": "Без / Наследяване",
@@ -712,6 +735,7 @@
"sender_acl_info": "Ако потребителят на пощенска кутия A е разрешен да изпраща като потребителя на пощенска кутия B, адресът на изпращач не се показва автоматично като избираем \"от\" поле в SOGo.<br>\r\n Потребителят на пощенска кутия B трябва да създаде делегация в SOGo, за да разреши на потребителя на пощенска кутия A да избира техния адрес като изпращач. За да делегирате пощенска кутия в SOGo, използвайте менюто (три точки) вдясно от името на пощенската кутия в горния ляв ъгъл, докато сте в режим на поща. Това поведение не се прилага за адреси на псевдоними.", "sender_acl_info": "Ако потребителят на пощенска кутия A е разрешен да изпраща като потребителя на пощенска кутия B, адресът на изпращач не се показва автоматично като избираем \"от\" поле в SOGo.<br>\r\n Потребителят на пощенска кутия B трябва да създаде делегация в SOGo, за да разреши на потребителя на пощенска кутия A да избира техния адрес като изпращач. За да делегирате пощенска кутия в SOGo, използвайте менюто (три точки) вдясно от името на пощенската кутия в горния ляв ъгъл, докато сте в режим на поща. Това поведение не се прилага за адреси на псевдоними.",
"sieve_desc": "Кратко описание", "sieve_desc": "Кратко описание",
"sieve_type": "Тип на филтър", "sieve_type": "Тип на филтър",
"skipcrossduplicates": "Пропускане на дублирани съобщения между папки (първи дошъл, първи обслужен)",
"sogo_access": "Директно препращане към SOGo", "sogo_access": "Директно препращане към SOGo",
"sogo_access_info": "След влизане, потребителят се пренасочва автоматично към SOGo.", "sogo_access_info": "След влизане, потребителят се пренасочва автоматично към SOGo.",
"sogo_visible": "Псевдонимът е видим в SOGo", "sogo_visible": "Псевдонимът е видим в SOGo",
@@ -720,8 +744,12 @@
"spam_filter": "Филтър за спам", "spam_filter": "Филтър за спам",
"spam_policy": "Добавяне или премахване на елементи към черния/бял списък", "spam_policy": "Добавяне или премахване на елементи към черния/бял списък",
"spam_score": "Задаване на персонализиран резултат за спам", "spam_score": "Задаване на персонализиран резултат за спам",
"subfolder2": "Синхронизиране в подпапка в дестинацията<br><small>(празно = не използвай подпапка)</small>",
"syncjob": "Редактиране на синхронизираща задача",
"target_address": "Адрес/и за пренасочване <small>(разделени с запетая)</small>", "target_address": "Адрес/и за пренасочване <small>(разделени с запетая)</small>",
"target_domain": "Целеви домейн", "target_domain": "Целеви домейн",
"timeout1": "Таймаут за връзка с отдалечен хост",
"timeout2": "Таймаут за връзка с локален хост",
"title": "Редактиране на обект", "title": "Редактиране на обект",
"unchanged_if_empty": "Ако не е променено, оставете празно", "unchanged_if_empty": "Ако не е променено, оставете празно",
"username": "Потребителско име", "username": "Потребителско име",
@@ -936,6 +964,16 @@
"spam_aliases": "Времеви псевдоними", "spam_aliases": "Времеви псевдоними",
"stats": "Статистика", "stats": "Статистика",
"status": "Статус", "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": "Размер на таблицата",
"table_size_show_n": "Показване на %s елемента", "table_size_show_n": "Показване на %s елемента",
"target_address": "Адрес за пренасочване", "target_address": "Адрес за пренасочване",
@@ -1166,37 +1204,6 @@
"waiting_usb_register": "<i>Изчакване на USB устройство...</i><br><br>Моля, въведете паролата си по-горе и потвърдете регистрацията си, като докоснете бутона на вашето USB устройство.", "waiting_usb_register": "<i>Изчакване на USB устройство...</i><br><br>Моля, въведете паролата си по-горе и потвърдете регистрацията си, като докоснете бутона на вашето USB устройство.",
"yubi_otp": "Удостоверяване с Yubico OTP" "yubi_otp": "Удостоверяване с Yubico OTP"
}, },
"syncjobs": {
"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": "Грешно потребителско име или парола",
"add": "Добавяне на синхронизираща задача",
"add_hint": "Имайте предвид, че паролите трябва да бъдат запазени като обикновен текст!",
"edit": "Редактиране на синхронизираща задача",
"create": "Създаване на нова синхронизираща задача",
"dry": "Симулиране на синхронизация",
"subfolder2": "Синхронизиране в подпапка в дестинацията<br><small>(празно = не използвай подпапка)</small>",
"mins_interval": "Интервал за проверка (минути)",
"maxage": "Максимална възраст на съобщенията в дни, които ще бъдат проверени от отдалечен<br><small>(0 = игнорирай възраст)</small>",
"exclude": "Изключване на обекти (regex)",
"automap": "Опит за автоматично картографиране на папки (\"Изпратени\", \"Изпратени\" => \"Изпратени\" и т.н.)",
"delete1": "Изтриване от източника след завършване",
"delete2": "Изтриване на съобщенията в дестинацията, които не са в източника",
"delete2duplicates": "Изтриване на дубликати в дестинацията",
"skipcrossduplicates": "Пропускане на дублирани съобщения между папки (първи дошъл, първи обслужен)",
"subscribeall": "Абониране за всички папки",
"timeout1": "Таймаут за връзка с отдалечен хост",
"timeout2": "Таймаут за връзка с локален хост",
"maxbytespersecond": "Макс. байтове за секунда <br><small>(0 = неограничен)</small>",
"custom_params": "Персонализирани параметри"
},
"user": { "user": {
"action": "Действие", "action": "Действие",
"active": "Активен", "active": "Активен",
@@ -1227,6 +1234,7 @@
"clear_recent_successful_connections": "Изчистване на видимите успешни връзки", "clear_recent_successful_connections": "Изчистване на видимите успешни връзки",
"client_configuration": "Показване на ръководства за настройка на пощенски клиенти и смартфони", "client_configuration": "Показване на ръководства за настройка на пощенски клиенти и смартфони",
"create_app_passwd": "Създаване на парола за приложение", "create_app_passwd": "Създаване на парола за приложение",
"create_syncjob": "Създаване на нова синхронизираща задача",
"created_on": "Създаден на", "created_on": "Създаден на",
"daily": "Ежедневно", "daily": "Ежедневно",
"day": "ден", "day": "ден",
@@ -1332,6 +1340,16 @@
"spamfilter_wl_desc": "Адресите в белия списък <b>никога</b> ще бъдат класифицирани като спам. Поддържат се уайлдкардове.<br>Съобщенията, които не са класифицирани като спам, ще бъдат доставени в папката за нежелана поща, ако резултатът за спам е над стойността за висок резултат за спам.", "spamfilter_wl_desc": "Адресите в белия списък <b>никога</b> ще бъдат класифицирани като спам. Поддържат се уайлдкардове.<br>Съобщенията, които не са класифицирани като спам, ще бъдат доставени в папката за нежелана поща, ако резултатът за спам е над стойността за висок резултат за спам.",
"spamfilter_yellow": "Жълто: това съобщение може да е спам, ще бъде маркирано като спам и преместено в папката за нежелана поща", "spamfilter_yellow": "Жълто: това съобщение може да е спам, ще бъде маркирано като спам и преместено в папката за нежелана поща",
"status": "Статус", "status": "Статус",
"sync_jobs": "Синхронизиращи задачи",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Проблем с удостоверяването",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Грешно потребителско име или парола",
"syncjob_EXIT_CONNECTION_FAILURE": "Проблем с връзката",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Не може да се свърже с отдалечен сървър",
"syncjob_EXIT_OVERQUOTA": "Целевата пощенска кутия е над квотата",
"syncjob_EXIT_TLS_FAILURE": "Проблем с криптираната връзка",
"syncjob_EX_OK": "Успех",
"syncjob_check_log": "Проверка на лога",
"syncjob_last_run_result": "Резултат от последното изпълнение",
"tag_handling": "Настройка за тагове", "tag_handling": "Настройка за тагове",
"tag_help_example": "Пример за тагован имейл адрес: me<b>+Facebook</b>@example.org", "tag_help_example": "Пример за тагован имейл адрес: me<b>+Facebook</b>@example.org",
"tag_help_explain": "В подпапка: ще бъде създадена нова подпапка под INBOX (\"INBOX/Facebook\").<br>\r\nВ тема: първото име на тага ще бъде добавено към темата на съобщението, например: \"[Facebook] Моите новини\".", "tag_help_explain": "В подпапка: ще бъде създадена нова подпапка под INBOX (\"INBOX/Facebook\").<br>\r\nВ тема: първото име на тага ще бъде добавено към темата на съобщението, например: \"[Facebook] Моите новини\".",
+26 -19
View File
@@ -40,11 +40,16 @@
"alias_address_info": "<small>Adreces de correu completes, o @exemple.com per a atrapar tots els missatges per a un domini (separats per coma). <b>Només dominis interns</b>.</small>", "alias_address_info": "<small>Adreces de correu completes, o @exemple.com per a atrapar tots els missatges per a un domini (separats per coma). <b>Només dominis interns</b>.</small>",
"alias_domain": "Domini àlies", "alias_domain": "Domini àlies",
"alias_domain_info": "<small>Només noms de domini vàlids (separats per coma).</small>", "alias_domain_info": "<small>Només noms de domini vàlids (separats per coma).</small>",
"automap": "Provar de mapejar automàticament (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Opcions backup MX:", "backup_mx_options": "Opcions backup MX:",
"delete1": "Esborrar de l'origen un cop s'han copiat",
"delete2": "Esborrar els missatges a destí que no son al origen",
"delete2duplicates": "Eliminar els duplicats al destí",
"description": "Descripció:", "description": "Descripció:",
"domain": "Domini", "domain": "Domini",
"domain_quota_m": "Quota total del domini (MiB):", "domain_quota_m": "Quota total del domini (MiB):",
"enc_method": "Mètode de xifrat", "enc_method": "Mètode de xifrat",
"exclude": "Excloure els objectes (regex)",
"full_name": "Nom complet:", "full_name": "Nom complet:",
"goto_null": "Descartar mail silenciosament", "goto_null": "Descartar mail silenciosament",
"hostname": "Hostname", "hostname": "Hostname",
@@ -53,6 +58,7 @@
"mailbox_username": "Nom d'usuari (part de l'esquerra de l'adreça @):", "mailbox_username": "Nom d'usuari (part de l'esquerra de l'adreça @):",
"max_aliases": "Màx. àlies possibles:", "max_aliases": "Màx. àlies possibles:",
"max_mailboxes": "Màx. bústies possibles:", "max_mailboxes": "Màx. bústies possibles:",
"mins_interval": "Intèrval (minuts)",
"multiple_bookings": "Reserves múltiples", "multiple_bookings": "Reserves múltiples",
"password": "Constrasenya:", "password": "Constrasenya:",
"password_repeat": "Confirmació de contrasenya (repetir):", "password_repeat": "Confirmació de contrasenya (repetir):",
@@ -66,6 +72,9 @@
"select_domain": "Si et plau, primer selecciona un domini", "select_domain": "Si et plau, primer selecciona un domini",
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Saltar missatges duplicats entre carpetes (només es desa el primer)",
"syncjob": "Afegir sync job",
"syncjob_hint": "Tingues en compte que les contrasenyes es desen sense xifrar!",
"target_address": "Adreces goto:", "target_address": "Adreces goto:",
"target_address_info": "<small>Adreces de corru completes (separades per coma).</small>", "target_address_info": "<small>Adreces de corru completes (separades per coma).</small>",
"target_domain": "Domini destí:", "target_domain": "Domini destí:",
@@ -77,9 +86,12 @@
"app_passwd_protocols": "Protocols autoritzats per la contrasenya de l'aplicació", "app_passwd_protocols": "Protocols autoritzats per la contrasenya de l'aplicació",
"bcc_dest_format": "La destinació c/o ha de ser una única adreça de correu vàlida.<br>Si necessiteu enviar una còpia a diverses adreces, creeu un àlies i utilitzeu-lo aquí.", "bcc_dest_format": "La destinació c/o ha de ser una única adreça de correu vàlida.<br>Si necessiteu enviar una còpia a diverses adreces, creeu un àlies i utilitzeu-lo aquí.",
"comment_info": "Els comentaris privats no són visibles per l'usuari, mentre que els comentaris públics apareixen com una descripció emergent a la informació de l'usuari", "comment_info": "Els comentaris privats no són visibles per l'usuari, mentre que els comentaris públics apareixen com una descripció emergent a la informació de l'usuari",
"custom_params": "Paràmetres personalitzats",
"custom_params_hint": "Correcte: --param=xy, incorrecte: --param xy",
"destination": "Destí", "destination": "Destí",
"disable_login": "No permetre l'inici de sessió (els missatges entrants continuen sent acceptats)", "disable_login": "No permetre l'inici de sessió (els missatges entrants continuen sent acceptats)",
"domain_matches_hostname": "El domini %s coincideix amb el nom del servidor", "domain_matches_hostname": "El domini %s coincideix amb el nom del servidor",
"dry": "Simular la sincronització",
"gal": "Llista d'adreces global", "gal": "Llista d'adreces global",
"generate": "genereu", "generate": "genereu",
"inactive": "Inactiu", "inactive": "Inactiu",
@@ -237,7 +249,11 @@
"edit": { "edit": {
"active": "Actiu", "active": "Actiu",
"alias": "Editar àlies", "alias": "Editar àlies",
"automap": "Provar de mapejar automàticament (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Opcions backup MX:", "backup_mx_options": "Opcions backup MX:",
"delete1": "Esborrar de l'origen un cop s'han copiat",
"delete2": "Esborrar els missatges a destí que no son al origen",
"delete2duplicates": "Eliminar els duplicats al destí",
"description": "Descripció:", "description": "Descripció:",
"domain": "Editar domini", "domain": "Editar domini",
"domain_admin": "Editar administrador del domini", "domain_admin": "Editar administrador del domini",
@@ -246,6 +262,7 @@
"dont_check_sender_acl": "No verifiques remitente para el dominio %s", "dont_check_sender_acl": "No verifiques remitente para el dominio %s",
"edit_alias_domain": "Editar àlies de domini", "edit_alias_domain": "Editar àlies de domini",
"encryption": "Xifrat", "encryption": "Xifrat",
"exclude": "Excloure els objectes (regex)",
"force_pw_update": "Forçar l'actualització de la contrassenya al proper login", "force_pw_update": "Forçar l'actualització de la contrassenya al proper login",
"force_pw_update_info": "Aquest usuari només podrà accedir a la interfície de gestió.", "force_pw_update_info": "Aquest usuari només podrà accedir a la interfície de gestió.",
"full_name": "Nom complet", "full_name": "Nom complet",
@@ -255,6 +272,9 @@
"max_aliases": "Màx. àlies:", "max_aliases": "Màx. àlies:",
"max_mailboxes": "Màx. bústies possibles:", "max_mailboxes": "Màx. bústies possibles:",
"max_quota": "Màx. quota per bústia (MiB):", "max_quota": "Màx. quota per bústia (MiB):",
"maxage": "Salta missatges més vells del número de dies<br><small>(0 = no salta res)</small>",
"maxbytespersecond": "Màx. bytes per segon<br><small>(0 = il.limitat)</small>",
"mins_interval": "Intèrval (min)",
"multiple_bookings": "Reserves múltiples", "multiple_bookings": "Reserves múltiples",
"password": "Contrasenya:", "password": "Contrasenya:",
"password_repeat": "Confirmació de contrasenya (repetir):", "password_repeat": "Confirmació de contrasenya (repetir):",
@@ -269,9 +289,12 @@
"sender_acl": "Permetre enviar com a", "sender_acl": "Permetre enviar com a",
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Saltar missatges duplicats entre carpetes (només es desa el primer)",
"spam_alias": "Crear o canviar alies temporals limitats per temps", "spam_alias": "Crear o canviar alies temporals limitats per temps",
"spam_policy": "Afegeix o treu elementes a la white-/blacklist", "spam_policy": "Afegeix o treu elementes a la white-/blacklist",
"spam_score": "Especifica una puntuació de spam", "spam_score": "Especifica una puntuació de spam",
"subfolder2": "Sincronitza a la subcarpeta destí<br><small>(buit = no usar subcarpeta)</small>",
"syncjob": "Sync job",
"target_address": "Direcció/ns goto <small>(separades per coma)</small>:", "target_address": "Direcció/ns goto <small>(separades per coma)</small>:",
"target_domain": "Domini destí:", "target_domain": "Domini destí:",
"title": "Editar l'objecte", "title": "Editar l'objecte",
@@ -382,6 +405,7 @@
"sieve_info": "Podeu emmagatzemar diversos filtres per usuari, però només un filtre previ i un filtre posterior poden estar actius al mateix temps.<br>\r\nCada filtre es processarà en l'ordre descrit. Ni un script que falli, ni un \"keep\" emès farà que es deixin de processar els altres scripts.<br>\r\nFiltre previ → Filtre d'usuari → Filtre posterior → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after\" target=\"_blank\">Filtre global</a>", "sieve_info": "Podeu emmagatzemar diversos filtres per usuari, però només un filtre previ i un filtre posterior poden estar actius al mateix temps.<br>\r\nCada filtre es processarà en l'ordre descrit. Ni un script que falli, ni un \"keep\" emès farà que es deixin de processar els altres scripts.<br>\r\nFiltre previ → Filtre d'usuari → Filtre posterior → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after\" target=\"_blank\">Filtre global</a>",
"spam_aliases": "Temp. àlies", "spam_aliases": "Temp. àlies",
"status": "Estat", "status": "Estat",
"sync_jobs": "Sync jobs",
"target_address": "Direcció Goto", "target_address": "Direcció Goto",
"target_domain": "Domini destí", "target_domain": "Domini destí",
"tls_enforce_in": "Forçar TLS al rebre", "tls_enforce_in": "Forçar TLS al rebre",
@@ -476,25 +500,6 @@
"waiting_usb_register": "<i>Esperant el dispositiu USB...</i><br><br>Posa el teu password i confirma el registre del teu WebAuthn apretant el botó del teu dispositiiu USB WebAuthn.", "waiting_usb_register": "<i>Esperant el dispositiu USB...</i><br><br>Posa el teu password i confirma el registre del teu WebAuthn apretant el botó del teu dispositiiu USB WebAuthn.",
"yubi_otp": "Autenticació OTP de Yubico" "yubi_otp": "Autenticació OTP de Yubico"
}, },
"syncjobs": {
"sync_jobs": "Feines de sincronització",
"add": "Afegir sync job",
"add_hint": "Tingues en compte que les contrasenyes es desen sense xifrar!",
"edit": "Sync job",
"create": "Afegir treball de sincronitzaió",
"dry": "Simular la sincronització",
"subfolder2": "Sincronitza a la subcarpeta destí<br><small>(buit = no usar subcarpeta)</small>",
"mins_interval": "Intèrval (minuts)",
"maxage": "Salta missatges més vells del número de dies<br><small>(0 = no salta res)</small>",
"exclude": "Excloure els objectes (regex)",
"automap": "Provar de mapejar automàticament (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"delete1": "Esborrar de l'origen un cop s'han copiat",
"delete2": "Esborrar els missatges a destí que no son al origen",
"delete2duplicates": "Eliminar els duplicats al destí",
"skipcrossduplicates": "Saltar missatges duplicats entre carpetes (només es desa el primer)",
"maxbytespersecond": "Màx. bytes per segon<br><small>(0 = il.limitat)</small>",
"custom_params": "Paràmetres personalitzats"
},
"user": { "user": {
"action": "Acció", "action": "Acció",
"active": "Actiu", "active": "Actiu",
@@ -511,6 +516,7 @@
"aliases_send_as_all": "Al enviar no es verificarà l'adreça remitent per aquests dominis:", "aliases_send_as_all": "Al enviar no es verificarà l'adreça remitent per aquests dominis:",
"change_password": "Canviar la contrasenya", "change_password": "Canviar la contrasenya",
"client_configuration": "Guies de configuració per als clients de correu més habituals", "client_configuration": "Guies de configuració per als clients de correu més habituals",
"create_syncjob": "Afegir treball de sincronitzaió",
"day": "Dia", "day": "Dia",
"description": "Descripció", "description": "Descripció",
"direct_aliases": "Adreces àlies directes", "direct_aliases": "Adreces àlies directes",
@@ -561,6 +567,7 @@
"spamfilter_wl_desc": "Les adreces de la lista blanca <b>mai</b> es clasificaran como a spam. Es pot fer serivir *@exemple.org.", "spamfilter_wl_desc": "Les adreces de la lista blanca <b>mai</b> es clasificaran como a spam. Es pot fer serivir *@exemple.org.",
"spamfilter_yellow": "Groc: el missatge pot ser spam, s'etiquetarà com a spam i es mourà a la carpeta de correu brossa", "spamfilter_yellow": "Groc: el missatge pot ser spam, s'etiquetarà com a spam i es mourà a la carpeta de correu brossa",
"status": "Estat", "status": "Estat",
"sync_jobs": "Feines de sincronització",
"tag_handling": "Al rebre un missatge etiquetat", "tag_handling": "Al rebre un missatge etiquetat",
"tag_help_example": "Ejemplo de una dirección email etiquetada: mi<b>+Facebook</b>@ejemplo.org", "tag_help_example": "Ejemplo de una dirección email etiquetada: mi<b>+Facebook</b>@ejemplo.org",
"tag_help_explain": "Moure a subcarpeta: es crearà una subcarpeta anomenada com la etiqueta a INBOX (\"INBOX/Facebook\") i es mourà el correu allà.<br>\r\nMarcar al assumpte: s'afegirà el nom de la etiqueta al assumpte del missatge, per exemple: \"[Facebook] Les meves notícies\".", "tag_help_explain": "Moure a subcarpeta: es crearà una subcarpeta anomenada com la etiqueta a INBOX (\"INBOX/Facebook\") i es mourà el correu allà.<br>\r\nMarcar al assumpte: s'afegirà el nom de la etiqueta al assumpte del missatge, per exemple: \"[Facebook] Les meves notícies\".",
+49 -31
View File
@@ -44,9 +44,15 @@
"app_name": "Název aplikace", "app_name": "Název aplikace",
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací", "app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
"app_password": "Přidat heslo aplikace", "app_password": "Přidat heslo aplikace",
"automap": "Pokusit se automaticky mapovat složky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
"backup_mx_options": "Možnosti záložního MX", "backup_mx_options": "Možnosti záložního MX",
"bcc_dest_format": "Cíl kopie musí být jedna platná email adresa. Pokud potřebujete posílat kopie na více adres, vytvořte Alias a použijte jej zde.", "bcc_dest_format": "Cíl kopie musí být jedna platná email adresa. Pokud potřebujete posílat kopie na více adres, vytvořte Alias a použijte jej zde.",
"comment_info": "Soukromý komentář se nezobrazí uživateli; veřejný komentář se zobrazí jako nápověda při zastavení se kurzorem v přehledu uživatelů", "comment_info": "Soukromý komentář se nezobrazí uživateli; veřejný komentář se zobrazí jako nápověda při zastavení se kurzorem v přehledu uživatelů",
"custom_params": "Vlastní parametry",
"custom_params_hint": "Správně: --param=xy, špatně: --param xy",
"delete1": "Odstranit ze zdrojové schránky, po dokončení přenosu",
"delete2": "Odstranit zprávy v cílové schránce, pokud nejsou ve zdrojové",
"delete2duplicates": "Odstranit duplicity v cílové schránce",
"description": "Popis", "description": "Popis",
"destination": "Cíl", "destination": "Cíl",
"disable_login": "Zakázat přihlášení (ale stále přijímat poštu)", "disable_login": "Zakázat přihlášení (ale stále přijímat poštu)",
@@ -54,6 +60,7 @@
"domain_matches_hostname": "Doména %s se shoduje s názvem hostitele", "domain_matches_hostname": "Doména %s se shoduje s názvem hostitele",
"domain_quota_m": "Celková kvóta domény (MiB)", "domain_quota_m": "Celková kvóta domény (MiB)",
"enc_method": "Způsob šifrování", "enc_method": "Způsob šifrování",
"exclude": "Vyloučené objekty (regex)",
"full_name": "Celé jméno", "full_name": "Celé jméno",
"gal": "Globální seznam adres", "gal": "Globální seznam adres",
"gal_info": "Globální seznam adres obsahuje všechny objekty v doméně a uživatel jej nemůže měnit. Pokud je vypnut, budou v SOGo chybět informace o obsazenosti! <b>Při změně nutno restartovat SOGo.</b>", "gal_info": "Globální seznam adres obsahuje všechny objekty v doméně a uživatel jej nemůže měnit. Pokud je vypnut, budou v SOGo chybět informace o obsazenosti! <b>Při změně nutno restartovat SOGo.</b>",
@@ -69,6 +76,7 @@
"mailbox_username": "Uživatelské jméno (levá část email adresy)", "mailbox_username": "Uživatelské jméno (levá část email adresy)",
"max_aliases": "Max. počet aliasů", "max_aliases": "Max. počet aliasů",
"max_mailboxes": "Max. počet mailových schránek", "max_mailboxes": "Max. počet mailových schránek",
"mins_interval": "Interval kontroly (minuty)",
"multiple_bookings": "Vícenásobné rezervace", "multiple_bookings": "Vícenásobné rezervace",
"nexthop": "Další skok", "nexthop": "Další skok",
"password": "Heslo", "password": "Heslo",
@@ -88,13 +96,20 @@
"select_domain": "Nejdříve vyberte doménu", "select_domain": "Nejdříve vyberte doménu",
"sieve_desc": "Krátký popis", "sieve_desc": "Krátký popis",
"sieve_type": "Typ filtru", "sieve_type": "Typ filtru",
"skipcrossduplicates": "Přeskočit duplicitní zprávy (\"první přijde, první mele\")",
"subscribeall": "Synchronizovat všechny složky",
"syncjob": "Přidat synchronizační úlohu",
"syncjob_hint": "Upozornění: Heslo bude uloženo jako prostý text!",
"target_address": "Cílové adresy", "target_address": "Cílové adresy",
"target_address_info": "<small>Kompletní email adresa/y (oddělené čárkami).</small>", "target_address_info": "<small>Kompletní email adresa/y (oddělené čárkami).</small>",
"target_domain": "Cílová doména", "target_domain": "Cílová doména",
"timeout1": "Časový limit pro připojení ke vzdálenému serveru",
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
"username": "Uživatelské jméno", "username": "Uživatelské jméno",
"validate": "Ověřit", "validate": "Ověřit",
"validation_success": "Úspěšně ověřeno", "validation_success": "Úspěšně ověřeno",
"tags": "Štítky", "tags": "Štítky",
"dry": "Simulovat synchronizaci",
"internal": "Interní", "internal": "Interní",
"internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů.", "internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů.",
"sender_allowed": "Povolit jako alias pro odesílání" "sender_allowed": "Povolit jako alias pro odesílání"
@@ -630,12 +645,16 @@
"app_name": "Název aplikace", "app_name": "Název aplikace",
"app_passwd": "Heslo aplikace", "app_passwd": "Heslo aplikace",
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací", "app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
"automap": "Pokusit se automaticky mapovat složky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
"backup_mx_options": "Možnosti záložního MX", "backup_mx_options": "Možnosti záložního MX",
"bcc_dest_format": "Cíl kopie musí být jedna platná email adresa. Pokud potřebujete posílat kopie na více adres, vytvořte Alias a použijte jej zde.", "bcc_dest_format": "Cíl kopie musí být jedna platná email adresa. Pokud potřebujete posílat kopie na více adres, vytvořte Alias a použijte jej zde.",
"client_id": "ID klienta", "client_id": "ID klienta",
"client_secret": "Tajný klíč klienta", "client_secret": "Tajný klíč klienta",
"created_on": "Vytvoreno", "created_on": "Vytvoreno",
"comment_info": "Soukromý komentář se nezobrazí uživateli; veřejný komentář se zobrazí jako nápověda při zastavení se kurzorem v přehledu uživatelů", "comment_info": "Soukromý komentář se nezobrazí uživateli; veřejný komentář se zobrazí jako nápověda při zastavení se kurzorem v přehledu uživatelů",
"delete1": "Odstranit ze zdrojové schránky, po dokončení přenosu",
"delete2": "Odstranit zprávy v cílové schránce, pokud nejsou ve zdrojové",
"delete2duplicates": "Odstranit duplicity v cílové schránce",
"delete_ays": "Potvrďte odstranění.", "delete_ays": "Potvrďte odstranění.",
"description": "Popis", "description": "Popis",
"disable_login": "Zakázat přihlášení (ale stále přijímat poštu)", "disable_login": "Zakázat přihlášení (ale stále přijímat poštu)",
@@ -646,6 +665,7 @@
"dont_check_sender_acl": "Vypnout kontrolu odesílatele pro doménu %s (+ doménové aliasy)", "dont_check_sender_acl": "Vypnout kontrolu odesílatele pro doménu %s (+ doménové aliasy)",
"edit_alias_domain": "Upravit doménový alias", "edit_alias_domain": "Upravit doménový alias",
"encryption": "Šifrování", "encryption": "Šifrování",
"exclude": "Vyloučit objekty (regex)",
"extended_sender_acl": "Externí adresy odesílatele", "extended_sender_acl": "Externí adresy odesílatele",
"extended_sender_acl_info": "Je dobré importovat DKIM klíč domény, pokud existuje.<br>\r\n Nezapomeňte přidat tento server do příslušného záznamu SPF TXT.<br>\r\n Je-li na tomto serveru vytvořena doména nebo doménový alias, který se shoduje s externí adresou, je tato externí adresa smazána.<br>\r\n Použije formát @domain.tld, chcete-li odesílat jako *@domain.tld.", "extended_sender_acl_info": "Je dobré importovat DKIM klíč domény, pokud existuje.<br>\r\n Nezapomeňte přidat tento server do příslušného záznamu SPF TXT.<br>\r\n Je-li na tomto serveru vytvořena doména nebo doménový alias, který se shoduje s externí adresou, je tato externí adresa smazána.<br>\r\n Použije formát @domain.tld, chcete-li odesílat jako *@domain.tld.",
"force_pw_update": "Vynutit změnu hesla při příštím přihlášení", "force_pw_update": "Vynutit změnu hesla při příštím přihlášení",
@@ -666,7 +686,10 @@
"max_aliases": "Max. počet aliasů", "max_aliases": "Max. počet aliasů",
"max_mailboxes": "Max. počet mailových schránek", "max_mailboxes": "Max. počet mailových schránek",
"max_quota": "Max. kvóta mailové schránky (MiB)", "max_quota": "Max. kvóta mailové schránky (MiB)",
"maxage": "Maximální stáří stahovaných zpráv, ve dnech<br><small>(0 = ignorovat stáří)</small>",
"maxbytespersecond": "Max. bajtů za sekundu <br><small>(0 = neomezeno)</small>",
"mbox_rl_info": "Toto omezení provozu se vyhodnocuje podle přihlašovacího jména SASL, porovnává se s jakoukoliv adresou \"od\" použitou přihlášeným uživatelem. Omezení provozu mailové schránky má prioritu před omezením provozu domény.", "mbox_rl_info": "Toto omezení provozu se vyhodnocuje podle přihlašovacího jména SASL, porovnává se s jakoukoliv adresou \"od\" použitou přihlášeným uživatelem. Omezení provozu mailové schránky má prioritu před omezením provozu domény.",
"mins_interval": "Interval (min)",
"multiple_bookings": "Vícenásobné rezervace", "multiple_bookings": "Vícenásobné rezervace",
"nexthop": "Další skok", "nexthop": "Další skok",
"none_inherit": "Žádné / Podědit", "none_inherit": "Žádné / Podědit",
@@ -704,6 +727,7 @@
"sender_acl_info": "Má-li uživatel schránky A dovoleno odesílat jako uživatel schránky B, nezobrazuje se adresa odesílatele B v seznamu \"Od\" v SOGo automaticky.<br>\r\n Uživatel schránky A musí nejdříve v SOGo vytvořit pověření, jež umožní uživateli B vybrat svou adresu jako odesílatele. Tento mechanismus neplatí pro aliasy.", "sender_acl_info": "Má-li uživatel schránky A dovoleno odesílat jako uživatel schránky B, nezobrazuje se adresa odesílatele B v seznamu \"Od\" v SOGo automaticky.<br>\r\n Uživatel schránky A musí nejdříve v SOGo vytvořit pověření, jež umožní uživateli B vybrat svou adresu jako odesílatele. Tento mechanismus neplatí pro aliasy.",
"sieve_desc": "Krátký popis", "sieve_desc": "Krátký popis",
"sieve_type": "Typ filtru", "sieve_type": "Typ filtru",
"skipcrossduplicates": "Přeskočit duplicitní zprávy (\"první přijde, první mele\")",
"sogo_access": "Přímé předání na SOGo", "sogo_access": "Přímé předání na SOGo",
"sogo_access_info": "Po přihlášení je uživatel automaticky přesměrován do služby SOGo.", "sogo_access_info": "Po přihlášení je uživatel automaticky přesměrován do služby SOGo.",
"sogo_visible": "Alias dostupný v SOGo", "sogo_visible": "Alias dostupný v SOGo",
@@ -712,8 +736,12 @@
"spam_filter": "Spam filtr", "spam_filter": "Spam filtr",
"spam_policy": "Přidat nebo odebrat položky seznamu", "spam_policy": "Přidat nebo odebrat položky seznamu",
"spam_score": "Nastavte vlastní skóre spamu", "spam_score": "Nastavte vlastní skóre spamu",
"subfolder2": "Synchronizace do podsložky v cílovém umístění<br><small>(prázdné = nepoužívat podsložku)</small>",
"syncjob": "Upravit synchronizační úlohu",
"target_address": "Cílová adresa/y <small>(oddělte čárkou)</small>", "target_address": "Cílová adresa/y <small>(oddělte čárkou)</small>",
"target_domain": "Cílová doména", "target_domain": "Cílová doména",
"timeout1": "Časový limit pro připojení ke vzdálenému serveru",
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
"title": "Úprava objektu", "title": "Úprava objektu",
"unchanged_if_empty": "Pokud se nemění, ponechte prázdné", "unchanged_if_empty": "Pokud se nemění, ponechte prázdné",
"username": "Uživatelské jméno", "username": "Uživatelské jméno",
@@ -969,6 +997,16 @@
"spam_aliases": "Dočasný alias", "spam_aliases": "Dočasný alias",
"stats": "Statistika", "stats": "Statistika",
"status": "Stav", "status": "Stav",
"sync_jobs": "Synchronizační úlohy",
"syncjob_check_log": "Zkontrolujte záznam",
"syncjob_last_run_result": "Výsledek posledního spuštění",
"syncjob_EX_OK": "Úspěch",
"syncjob_EXIT_CONNECTION_FAILURE": "Problém se spojením",
"syncjob_EXIT_TLS_FAILURE": "Problém se šifrovaným spojením",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikací",
"syncjob_EXIT_OVERQUOTA": "Cílová schránka je plná",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nelze se připojit ke vzdálenému serveru",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživatelské jméno nebo heslo",
"table_size": "Velikost tabulky", "table_size": "Velikost tabulky",
"table_size_show_n": "Zobrazit %s položek", "table_size_show_n": "Zobrazit %s položek",
"target_address": "Cílová adresa", "target_address": "Cílová adresa",
@@ -1203,37 +1241,6 @@
"authenticators": "Autentifikátory", "authenticators": "Autentifikátory",
"u2f_deprecated_important": "Registrujte svůj klíč novou metodou WebAuthn ve správě správců." "u2f_deprecated_important": "Registrujte svůj klíč novou metodou WebAuthn ve správě správců."
}, },
"syncjobs": {
"sync_jobs": "Synchronizační úlohy",
"syncjob_check_log": "Zkontrolujte záznam",
"syncjob_last_run_result": "Výsledek posledního spuštění",
"syncjob_EX_OK": "Úspěch",
"syncjob_EXIT_CONNECTION_FAILURE": "Problém se spojením",
"syncjob_EXIT_TLS_FAILURE": "Problém se šifrovaným spojením",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikací",
"syncjob_EXIT_OVERQUOTA": "Cílová schránka je plná",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nelze se připojit ke vzdálenému serveru",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživatelské jméno nebo heslo",
"add": "Přidat synchronizační úlohu",
"add_hint": "Upozornění: Heslo bude uloženo jako prostý text!",
"edit": "Upravit synchronizační úlohu",
"create": "Vytvořit novou synchronizační úlohu",
"dry": "Simulovat synchronizaci",
"subfolder2": "Synchronizace do podsložky v cílovém umístění<br><small>(prázdné = nepoužívat podsložku)</small>",
"mins_interval": "Interval kontroly (minuty)",
"maxage": "Maximální stáří stahovaných zpráv, ve dnech<br><small>(0 = ignorovat stáří)</small>",
"exclude": "Vyloučené objekty (regex)",
"automap": "Pokusit se automaticky mapovat složky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
"delete1": "Odstranit ze zdrojové schránky, po dokončení přenosu",
"delete2": "Odstranit zprávy v cílové schránce, pokud nejsou ve zdrojové",
"delete2duplicates": "Odstranit duplicity v cílové schránce",
"skipcrossduplicates": "Přeskočit duplicitní zprávy (\"první přijde, první mele\")",
"subscribeall": "Synchronizovat všechny složky",
"timeout1": "Časový limit pro připojení ke vzdálenému serveru",
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
"maxbytespersecond": "Max. bajtů za sekundu <br><small>(0 = neomezeno)</small>",
"custom_params": "Vlastní parametry"
},
"user": { "user": {
"action": "Akce", "action": "Akce",
"active": "Aktivní", "active": "Aktivní",
@@ -1262,6 +1269,7 @@
"clear_recent_successful_connections": "Vymazat nedávné úspěšné přihlášení", "clear_recent_successful_connections": "Vymazat nedávné úspěšné přihlášení",
"client_configuration": "Zobrazit průvodce nastavením e-mailových klientů a smartphonů", "client_configuration": "Zobrazit průvodce nastavením e-mailových klientů a smartphonů",
"create_app_passwd": "Vytvořit heslo aplikace", "create_app_passwd": "Vytvořit heslo aplikace",
"create_syncjob": "Vytvořit novou synchronizační úlohu",
"created_on": "Vytvoreno", "created_on": "Vytvoreno",
"daily": "Každý den", "daily": "Každý den",
"day": "den", "day": "den",
@@ -1362,6 +1370,16 @@
"spamfilter_wl_desc": "Povolené emailové adresy <b>nebudou nikdy klasifikovány jako spam</b>. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.", "spamfilter_wl_desc": "Povolené emailové adresy <b>nebudou nikdy klasifikovány jako spam</b>. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.",
"spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty", "spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty",
"status": "Stav", "status": "Stav",
"sync_jobs": "Synchronizační úlohy",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikací",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživatelské jméno nebo heslo",
"syncjob_EXIT_CONNECTION_FAILURE": "Problém se spojením",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nelze se připojit ke vzdálenému serveru",
"syncjob_EXIT_OVERQUOTA": "Cílová schránka je plná",
"syncjob_EXIT_TLS_FAILURE": "Problém se šifrovaným spojením",
"syncjob_EX_OK": "Úspěch",
"syncjob_check_log": "Zkontrolujte záznam",
"syncjob_last_run_result": "Výsledek posledního spuštění",
"tag_handling": "Zacházení s označkovanou poštou", "tag_handling": "Zacházení s označkovanou poštou",
"tag_help_example": "Příklad e-mailové adresy se značkou: me<b>+Facebook</b>@example.org", "tag_help_example": "Příklad e-mailové adresy se značkou: me<b>+Facebook</b>@example.org",
"tag_help_explain": "V podsložce: v doručené poště bude vytvořena nová podsložka pojmenovaná po značce zprávy (\"INBOX / Facebook\").<br>\r\nV předmětu: název značky bude přidáván k předmětu mailu, například: \"[Facebook] Moje zprávy\".", "tag_help_explain": "V podsložce: v doručené poště bude vytvořena nová podsložka pojmenovaná po značce zprávy (\"INBOX / Facebook\").<br>\r\nV předmětu: název značky bude přidáván k předmětu mailu, například: \"[Facebook] Moje zprávy\".",
+30 -21
View File
@@ -42,8 +42,14 @@
"alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>", "alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",
"app_name": "App navn", "app_name": "App navn",
"app_password": "Tilføj app-adgangskode", "app_password": "Tilføj app-adgangskode",
"automap": "Prøv at automatisere mapper (\"Sendte emner\", \"Sendt\" => \"Sendt\" etc.)",
"backup_mx_options": "Relæindstillinger", "backup_mx_options": "Relæindstillinger",
"comment_info": "En privat kommentar er ikke synlig for brugeren, mens en offentlig kommentar vises som et værktøjstip, når den svæver i brugeroversigten", "comment_info": "En privat kommentar er ikke synlig for brugeren, mens en offentlig kommentar vises som et værktøjstip, når den svæver i brugeroversigten",
"custom_params": "Brugerdefinerede parametre",
"custom_params_hint": "Rigtigt: --param=xy, forkert: --param xy",
"delete1": "Slet fra kilden, når den er afsluttet",
"delete2": "Slet meddelelser på destinationen, der ikke er fra kilden",
"delete2duplicates": "Slet duplikater på destinationen",
"description": "Beskrivelse", "description": "Beskrivelse",
"destination": "Bestemmelsessted", "destination": "Bestemmelsessted",
"disable_login": "Tillad ikke login (indgående mail accepteres stadig)", "disable_login": "Tillad ikke login (indgående mail accepteres stadig)",
@@ -51,6 +57,7 @@
"domain_matches_hostname": "Domæne %s matcher værtsnavn", "domain_matches_hostname": "Domæne %s matcher værtsnavn",
"domain_quota_m": "Samlet domænekvote (Mb)", "domain_quota_m": "Samlet domænekvote (Mb)",
"enc_method": "Krypteringsmetode", "enc_method": "Krypteringsmetode",
"exclude": "Ekskluder objekter (regex)",
"full_name": "Fulde navn", "full_name": "Fulde navn",
"gal": "Global adresseliste", "gal": "Global adresseliste",
"gal_info": "GAL indeholder alle objekter i et domæne og kan ikke redigeres af nogen bruger. Information om ledig / optaget i SOGo mangler, hvis deaktiveret! <b> Genstart SOGo for at anvende ændringer. </b>", "gal_info": "GAL indeholder alle objekter i et domæne og kan ikke redigeres af nogen bruger. Information om ledig / optaget i SOGo mangler, hvis deaktiveret! <b> Genstart SOGo for at anvende ændringer. </b>",
@@ -66,6 +73,7 @@
"mailbox_username": "Brugernavn (venstre del af en e-mail-adressen)", "mailbox_username": "Brugernavn (venstre del af en e-mail-adressen)",
"max_aliases": "Maks. mulige aliasser", "max_aliases": "Maks. mulige aliasser",
"max_mailboxes": "Maks. mulige postkasser", "max_mailboxes": "Maks. mulige postkasser",
"mins_interval": "Afstemningsinterval (minuter)",
"multiple_bookings": "Flere bookinger", "multiple_bookings": "Flere bookinger",
"nexthop": "Næste hop", "nexthop": "Næste hop",
"password": "Kodeord", "password": "Kodeord",
@@ -85,9 +93,15 @@
"select_domain": "Vælg først et domæne", "select_domain": "Vælg først et domæne",
"sieve_desc": "Kort beskrivelse", "sieve_desc": "Kort beskrivelse",
"sieve_type": "Filtertype", "sieve_type": "Filtertype",
"skipcrossduplicates": "Spring duplikatbeskeder over mapper (først til mølle)",
"subscribeall": "Abonner på alle mapper",
"syncjob": "Tilføj synkroniseringsjob",
"syncjob_hint": "Vær opmærksom på, at adgangskoder skal gemmes som almindelig tekst!",
"target_address": "Gå til adresser", "target_address": "Gå til adresser",
"target_address_info": "<small>Fuld e-mail-adresser (komma-adskilt).</small>", "target_address_info": "<small>Fuld e-mail-adresser (komma-adskilt).</small>",
"target_domain": "Måldomæne", "target_domain": "Måldomæne",
"timeout1": "Timeout for forbindelse til ekstern vært",
"timeout2": "Timeout for forbindelse til lokal vært",
"username": "Brugernavn", "username": "Brugernavn",
"validate": "Bekræft", "validate": "Bekræft",
"validation_success": "Valideret med succes", "validation_success": "Valideret med succes",
@@ -458,11 +472,15 @@
"allowed_protocols": "Tilladte protokoller", "allowed_protocols": "Tilladte protokoller",
"app_name": "Appens navn", "app_name": "Appens navn",
"app_passwd": "App-adgangskode", "app_passwd": "App-adgangskode",
"automap": "Prøv at automatisere mapper (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Relæindstillinger", "backup_mx_options": "Relæindstillinger",
"bcc_dest_format": "BCC-destination skal være en enkelt gyldig e-mail-adresse.", "bcc_dest_format": "BCC-destination skal være en enkelt gyldig e-mail-adresse.",
"client_id": "Klient-id", "client_id": "Klient-id",
"client_secret": "Klienthemmelighed", "client_secret": "Klienthemmelighed",
"comment_info": "En privat kommentar er ikke synlig for brugeren, mens en offentlig kommentar vises som værktøjstip, når den svæver i en brugeroversigt", "comment_info": "En privat kommentar er ikke synlig for brugeren, mens en offentlig kommentar vises som værktøjstip, når den svæver i en brugeroversigt",
"delete1": "Slet fra kilden, når den er afsluttet",
"delete2": "Slet meddelelser på destinationen, der ikke er fra kilden",
"delete2duplicates": "Slet duplikater på destinationen",
"delete_ays": "Bekræft sletningsprocessen.", "delete_ays": "Bekræft sletningsprocessen.",
"description": "Beskrivelse", "description": "Beskrivelse",
"disable_login": "Tillad ikke login (indgående mail accepteres stadig)", "disable_login": "Tillad ikke login (indgående mail accepteres stadig)",
@@ -473,6 +491,7 @@
"dont_check_sender_acl": "Deaktiver afsenderkontrol for domæne %s (+ alias-domæner)", "dont_check_sender_acl": "Deaktiver afsenderkontrol for domæne %s (+ alias-domæner)",
"edit_alias_domain": "Rediger Alias-domæne", "edit_alias_domain": "Rediger Alias-domæne",
"encryption": "Kryptering", "encryption": "Kryptering",
"exclude": "Ekskluder objekter (regex)",
"extended_sender_acl": "Eksterne afsenderadresser", "extended_sender_acl": "Eksterne afsenderadresser",
"extended_sender_acl_info": "En DKIM-domænenøgle skal importeres, hvis den er tilgængelig.<br>\r\n Husk at tilføje denne server til den tilsvarende SPF TXT-post.<br>\r\n Når et domæne eller aliasdomæne føjes til denne server, der overlapper en ekstern adresse, fjernes den eksterne adresse.<br>\r\n Brug @domain.tld til at tillade at sende som *@domæne.tld.", "extended_sender_acl_info": "En DKIM-domænenøgle skal importeres, hvis den er tilgængelig.<br>\r\n Husk at tilføje denne server til den tilsvarende SPF TXT-post.<br>\r\n Når et domæne eller aliasdomæne føjes til denne server, der overlapper en ekstern adresse, fjernes den eksterne adresse.<br>\r\n Brug @domain.tld til at tillade at sende som *@domæne.tld.",
"force_pw_update": "Tving adgangskodeopdatering til næste login", "force_pw_update": "Tving adgangskodeopdatering til næste login",
@@ -490,7 +509,10 @@
"max_aliases": "Maks. aliaser", "max_aliases": "Maks. aliaser",
"max_mailboxes": "Maks. mulige postkasser", "max_mailboxes": "Maks. mulige postkasser",
"max_quota": "Maks. kvote pr. postkasse (MiB)", "max_quota": "Maks. kvote pr. postkasse (MiB)",
"maxage": "Maksimumalder for beskeder i dage, der vil blive pollet fra fjernbetjeningen<br><small>(0 = ignorere alder)</small>",
"maxbytespersecond": "Maks. bytes pr. sekund <br><small>(0 = ubegrænset)</small>",
"mbox_rl_info": "Denne satsgrænse anvendes på SASL-loginnavnet, den matcher ethvert \"from\" adresse, der bruges af den indloggede bruger. En grænse for sats for postkasse tilsidesætter en satsgrænse for hele domænet.", "mbox_rl_info": "Denne satsgrænse anvendes på SASL-loginnavnet, den matcher ethvert \"from\" adresse, der bruges af den indloggede bruger. En grænse for sats for postkasse tilsidesætter en satsgrænse for hele domænet.",
"mins_interval": "Interval (min)",
"multiple_bookings": "Flere bookinger", "multiple_bookings": "Flere bookinger",
"nexthop": "Næste hop", "nexthop": "Næste hop",
"password": "Adgangskode", "password": "Adgangskode",
@@ -524,13 +546,18 @@
"sender_acl_info": "Hvis postkassebruger A har tilladelse til at sende som postkassebruger B, vises afsenderadressen ikke automatisk som valgbar \"from\" felt i SOGo.<br>\r\n Postkassebruger B skal oprette en delegation i SOGo for at tillade postkassebruger A at vælge deres adresse som afsender. For at delegere en postkasse i SOGo skal du bruge menuen (tre prikker) til højre for dit postkassens navn øverst til venstre, mens du er i postvisningen. Denne adfærd gælder ikke for aliasadresser.", "sender_acl_info": "Hvis postkassebruger A har tilladelse til at sende som postkassebruger B, vises afsenderadressen ikke automatisk som valgbar \"from\" felt i SOGo.<br>\r\n Postkassebruger B skal oprette en delegation i SOGo for at tillade postkassebruger A at vælge deres adresse som afsender. For at delegere en postkasse i SOGo skal du bruge menuen (tre prikker) til højre for dit postkassens navn øverst til venstre, mens du er i postvisningen. Denne adfærd gælder ikke for aliasadresser.",
"sieve_desc": "Kort beskrivelse", "sieve_desc": "Kort beskrivelse",
"sieve_type": "Filtertype", "sieve_type": "Filtertype",
"skipcrossduplicates": "Spring duplikatbeskeder over mapper (først til mølle)",
"sogo_visible": "Alias er synligt i SOGo", "sogo_visible": "Alias er synligt i SOGo",
"sogo_visible_info": "Denne indstilling påvirker kun objekter, der kan vises i SOGo (delte eller ikke-delte aliasadresser, der peger på mindst en lokal postkasse). Hvis skjult, vises et alias ikke som valgbar afsender i SOGo.", "sogo_visible_info": "Denne indstilling påvirker kun objekter, der kan vises i SOGo (delte eller ikke-delte aliasadresser, der peger på mindst en lokal postkasse). Hvis skjult, vises et alias ikke som valgbar afsender i SOGo.",
"spam_alias": "Opret eller skift tidsbegrænsede aliasadresser", "spam_alias": "Opret eller skift tidsbegrænsede aliasadresser",
"spam_policy": "Tilføj eller fjern genstande til hvid- / sortliste", "spam_policy": "Tilføj eller fjern genstande til hvid- / sortliste",
"spam_score": "Indstil en brugerdefineret spam-score", "spam_score": "Indstil en brugerdefineret spam-score",
"subfolder2": "Synkroniser til undermappe på destinationen<br><small>(tom = brug ikke undermappe)</small>",
"syncjob": "Rediger synkroniseringsjob",
"target_address": "Gå til adresse (r) <small>(komma separeret)</small>", "target_address": "Gå til adresse (r) <small>(komma separeret)</small>",
"target_domain": "Måldomæne", "target_domain": "Måldomæne",
"timeout1": "Timeout for forbindelse til ekstern vært",
"timeout2": "Timeout for forbindelse til lokal vært",
"title": "Rediger objekt", "title": "Rediger objekt",
"unchanged_if_empty": "Lad være tomt, hvis uændret", "unchanged_if_empty": "Lad være tomt, hvis uændret",
"username": "Brugernavn", "username": "Brugernavn",
@@ -704,6 +731,7 @@
"spam_aliases": "Temp. alias", "spam_aliases": "Temp. alias",
"stats": "Statistikker", "stats": "Statistikker",
"status": "Status", "status": "Status",
"sync_jobs": "Synkroniser job",
"table_size": "Bordstørrelse", "table_size": "Bordstørrelse",
"table_size_show_n": "Vis %s genstande", "table_size_show_n": "Vis %s genstande",
"target_address": "Gå til adresse", "target_address": "Gå til adresse",
@@ -911,27 +939,6 @@
"fido2_success": "Enheden blev registreret", "fido2_success": "Enheden blev registreret",
"fido2_validation_failed": "Validering mislykkedes" "fido2_validation_failed": "Validering mislykkedes"
}, },
"syncjobs": {
"sync_jobs": "Sync jobs",
"add": "Tilføj synkroniseringsjob",
"add_hint": "Vær opmærksom på, at adgangskoder skal gemmes som almindelig tekst!",
"edit": "Rediger synkroniseringsjob",
"create": "Opret nyt sync job",
"subfolder2": "Synkroniser til undermappe på destinationen<br><small>(tom = brug ikke undermappe)</small>",
"mins_interval": "Afstemningsinterval (minuter)",
"maxage": "Maksimumalder for beskeder i dage, der vil blive pollet fra fjernbetjeningen<br><small>(0 = ignorere alder)</small>",
"exclude": "Ekskluder objekter (regex)",
"automap": "Prøv at automatisere mapper (\"Sendte emner\", \"Sendt\" => \"Sendt\" etc.)",
"delete1": "Slet fra kilden, når den er afsluttet",
"delete2": "Slet meddelelser på destinationen, der ikke er fra kilden",
"delete2duplicates": "Slet duplikater på destinationen",
"skipcrossduplicates": "Spring duplikatbeskeder over mapper (først til mølle)",
"subscribeall": "Abonner på alle mapper",
"timeout1": "Timeout for forbindelse til ekstern vært",
"timeout2": "Timeout for forbindelse til lokal vært",
"maxbytespersecond": "Maks. bytes pr. sekund <br><small>(0 = ubegrænset)</small>",
"custom_params": "Brugerdefinerede parametre"
},
"user": { "user": {
"action": "Handling", "action": "Handling",
"active": "Aktiv", "active": "Aktiv",
@@ -956,6 +963,7 @@
"change_password": "Skift kodeord", "change_password": "Skift kodeord",
"client_configuration": "Vis konfigurationsvejledning for email klinter og mobiltelefoner", "client_configuration": "Vis konfigurationsvejledning for email klinter og mobiltelefoner",
"create_app_passwd": "Opret app kodeord", "create_app_passwd": "Opret app kodeord",
"create_syncjob": "Opret nyt sync job",
"daily": "Dagligt", "daily": "Dagligt",
"day": "dag", "day": "dag",
"description": "Beskrivelse", "description": "Beskrivelse",
@@ -1040,6 +1048,7 @@
"spamfilter_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.", "spamfilter_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.",
"spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe", "spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe",
"status": "Status", "status": "Status",
"sync_jobs": "Sync jobs",
"tag_handling": "Set handling for taggede mails", "tag_handling": "Set handling for taggede mails",
"tag_help_example": "Eksempel på en tagget e-mail-adresse : me<b>+Tacaly</b>@example.org", "tag_help_example": "Eksempel på en tagget e-mail-adresse : me<b>+Tacaly</b>@example.org",
"tag_help_explain": "I undermappe: En ny undermappe opkaldt efter tagget oprettes under INBOX (\"INBOX/Tacaly\").<br>\r\nI emne: navnene på tags bliver præpenderet til mailsemnet, eksempel: \"[Tacaly] Mine Nyhedder\".", "tag_help_explain": "I undermappe: En ny undermappe opkaldt efter tagget oprettes under INBOX (\"INBOX/Tacaly\").<br>\r\nI emne: navnene på tags bliver præpenderet til mailsemnet, eksempel: \"[Tacaly] Mine Nyhedder\".",
+53 -163
View File
@@ -28,7 +28,7 @@
"spam_alias": "Temporäre E-Mail-Aliasse", "spam_alias": "Temporäre E-Mail-Aliasse",
"spam_policy": "Deny/Allowlist", "spam_policy": "Deny/Allowlist",
"spam_score": "Spam-Bewertung", "spam_score": "Spam-Bewertung",
"syncjobs": "Sync-Jobs", "syncjobs": "Sync Jobs",
"tls_policy": "Verschlüsselungsrichtlinie", "tls_policy": "Verschlüsselungsrichtlinie",
"unlimited_quota": "Unendliche Quota für Mailboxen" "unlimited_quota": "Unendliche Quota für Mailboxen"
}, },
@@ -45,16 +45,24 @@
"app_name": "App-Name", "app_name": "App-Name",
"app_password": "App-Passwort hinzufügen", "app_password": "App-Passwort hinzufügen",
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort", "app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Relay-Optionen", "backup_mx_options": "Relay-Optionen",
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.", "bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.", "comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
"custom_params": "Eigene Parameter",
"custom_params_hint": "Richtig: --param=xy, falsch: --param xy",
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server.",
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf dem Quell-Server vorhanden sind.",
"delete2duplicates": "Lösche Duplikate im Ziel",
"description": "Beschreibung", "description": "Beschreibung",
"destination": "Ziel", "destination": "Ziel",
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)", "disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
"domain": "Domain", "domain": "Domain",
"domain_matches_hostname": "Domain %s darf nicht dem Hostnamen entsprechen", "domain_matches_hostname": "Domain %s darf nicht dem Hostnamen entsprechen",
"domain_quota_m": "Domain-Speicherplatz gesamt (MiB)", "domain_quota_m": "Domain-Speicherplatz gesamt (MiB)",
"dry": "Synchronisation simulieren",
"enc_method": "Verschlüsselung", "enc_method": "Verschlüsselung",
"exclude": "Elemente ausschließen (Regex)",
"full_name": "Vor- und Nachname", "full_name": "Vor- und Nachname",
"gal": "Globales Adressbuch", "gal": "Globales Adressbuch",
"gal_info": "Das globale Adressbuch enthält alle Objekte einer Domain und kann durch keinen Benutzer geändert werden. Die Verfügbarkeitsinformation in SOGo ist nur bei eingeschaltetem globalen Adressbuch ersichtlich! <b>Zum Anwenden einer Änderung muss SOGo neugestartet werden.</b>", "gal_info": "Das globale Adressbuch enthält alle Objekte einer Domain und kann durch keinen Benutzer geändert werden. Die Verfügbarkeitsinformation in SOGo ist nur bei eingeschaltetem globalen Adressbuch ersichtlich! <b>Zum Anwenden einer Änderung muss SOGo neugestartet werden.</b>",
@@ -73,6 +81,7 @@
"mailbox_username": "Benutzername (linker Teil der E-Mail-Adresse)", "mailbox_username": "Benutzername (linker Teil der E-Mail-Adresse)",
"max_aliases": "Max. mögliche Aliasse", "max_aliases": "Max. mögliche Aliasse",
"max_mailboxes": "Max. mögliche Mailboxen", "max_mailboxes": "Max. mögliche Mailboxen",
"mins_interval": "Abrufintervall (Minuten)",
"multiple_bookings": "Mehrfaches Buchen möglich", "multiple_bookings": "Mehrfaches Buchen möglich",
"nexthop": "Next Hop", "nexthop": "Next Hop",
"password": "Passwort", "password": "Passwort",
@@ -92,9 +101,15 @@
"select_domain": "Bitte zuerst eine Domain auswählen", "select_domain": "Bitte zuerst eine Domain auswählen",
"sieve_desc": "Kurze Beschreibung", "sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
"subscribeall": "Alle synchronisierten Ordner abonnieren",
"syncjob": "Sync-Job hinzufügen",
"syncjob_hint": "Passwörter werden unverschlüsselt abgelegt!",
"target_address": "Ziel-Adresse(n)", "target_address": "Ziel-Adresse(n)",
"target_address_info": "<small>Vollständige E-Mail-Adresse(n). Getrennt durch Komma.</small>", "target_address_info": "<small>Vollständige E-Mail-Adresse(n). Getrennt durch Komma.</small>",
"target_domain": "Ziel-Domain", "target_domain": "Ziel-Domain",
"timeout1": "Timeout für Verbindung zum Remote-Host",
"timeout2": "Timeout für Verbindung zum lokalen Host",
"username": "Benutzername", "username": "Benutzername",
"validate": "Validieren", "validate": "Validieren",
"validation_success": "Erfolgreich validiert", "validation_success": "Erfolgreich validiert",
@@ -403,12 +418,7 @@
"logo_dark_label": "Invertiert für den Darkmode", "logo_dark_label": "Invertiert für den Darkmode",
"logo_normal_label": "Normal", "logo_normal_label": "Normal",
"user_link": "Nutzer-Link", "user_link": "Nutzer-Link",
"filter": "Filter", "filter": "Filter"
"syncjobs": "Sync-Jobs",
"syncjob_max_parallel": "Max. parallele Sync-Prozesse",
"syncjob_max_parallel_info": "Wie viele Sync-Jobs gleichzeitig laufen dürfen (1-32).",
"syncjob_max_kb_per_second": "Bandbreiten-Limit pro Prozess (KB/s)",
"syncjob_max_kb_per_second_info": "0 = unbegrenzt. Begrenzt jeden Sync-Prozess in KB/s; ein Job-Limit (Bytes/s) wird hierdurch zusätzlich begrenzt. Gesamt ≈ dieser Wert × parallele Prozesse."
}, },
"danger": { "danger": {
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten", "access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
@@ -557,31 +567,7 @@
"template_exists": "Vorlage %s existiert bereits", "template_exists": "Vorlage %s existiert bereits",
"template_id_invalid": "Vorlagen-ID %s ungültig", "template_id_invalid": "Vorlagen-ID %s ungültig",
"template_name_invalid": "Name der Vorlage ungültig", "template_name_invalid": "Name der Vorlage ungültig",
"required_data_missing": "Die benötigten Daten: %s fehlen", "required_data_missing": "Die benötigten Daten: %s fehlen"
"imapsync_source_in_use": "Sync-Quelle %s wird noch von einem Sync-Job verwendet und kann nicht gelöscht werden",
"imapsync_source_token_refresh_failed": "OAuth-Token-Erneuerung für %s fehlgeschlagen",
"imapsync_source_scope_empty": "Für die gewählte Sichtbarkeit mindestens eine Domain wählen",
"imapsync_source_unavailable": "Die gewählte Sync-Quelle ist nicht verfügbar",
"imapsync_source_oauth_connect_required": "Vor dem Speichern dieses Sync-Jobs beim Provider anmelden",
"imapsync_source_name_invalid": "Ungültiger Name der Sync-Quelle",
"imapsync_source_host_invalid": "Ungültiger Hostname oder ungültige IP-Adresse",
"imapsync_source_port_invalid": "Ungültiger Port",
"imapsync_source_enc_invalid": "Ungültige Verschlüsselungsmethode",
"imapsync_source_auth_invalid": "Ungültige Authentifizierungsmethode",
"imapsync_custom_param_invalid": "Eigene Parameter enthalten eine nicht erlaubte imapsync-Option: %s",
"imapsync_source_oauth_required": "OAuth2 erfordert Token-Endpunkt, Client-ID, Client-Secret und Scope",
"imapsync_source_oauth_endpoint_invalid": "Ungültiger OAuth2-Endpunkt: Erforderlich ist eine https://-URL mit gültigem Hostnamen (Quellen, die nicht von einem Administrator angelegt wurden, brauchen einen öffentlich erreichbaren Host)",
"imapsync_source_oauth_extra_invalid": "OAuth2-Extra-Parameter müssen ein JSON-Objekt mit einfachen Werten sein, z. B. {\"resource\": \"https://example.com\"}",
"imapsync_source_tenant_missing": "Bitte den Microsoft-365-Tenant angeben",
"imapsync_source_oauth_invalid": "Ungültige OAuth2-Konfiguration",
"imapsync_max_parallel_invalid": "Die Anzahl paralleler Prozesse muss zwischen 1 und 32 liegen",
"imapsync_source_description_invalid": "Die Beschreibung darf höchstens 255 Zeichen lang sein",
"imapsync_source_refresh_unsupported": "Die manuelle Token-Erneuerung ist nur für Quellen mit Client-Credentials verfügbar",
"imapsync_custom_params_format": "Eigene Parameter müssen als Liste von Option/Wert-Paaren übermittelt werden",
"imapsync_custom_param_value": "Ungültiger Wert für die imapsync-Option %s (Wert fehlt, Wert bei einem Schalter oder nicht erlaubter Wert)",
"imapsync_job_running": "Sync-Job %s läuft gerade und kann nicht als Nächstes eingeplant werden",
"imapsync_user1_invalid": "Ungültiger Benutzername an der Quelle",
"imapsync_max_kb_per_second_invalid": "Das Bandbreitenlimit muss zwischen 0 und 10485760 KB/s liegen"
}, },
"datatables": { "datatables": {
"collapse_all": "Alle Einklappen", "collapse_all": "Alle Einklappen",
@@ -670,6 +656,7 @@
"app_name": "App-Name", "app_name": "App-Name",
"app_passwd": "App-Passwörter", "app_passwd": "App-Passwörter",
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort", "app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Relay-Optionen", "backup_mx_options": "Relay-Optionen",
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.", "bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
"client_id": "Client-ID", "client_id": "Client-ID",
@@ -677,6 +664,9 @@
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.", "comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
"created_on": "Erstellt am", "created_on": "Erstellt am",
"custom_attributes": "benutzerdefinierte Attribute", "custom_attributes": "benutzerdefinierte Attribute",
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server",
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind",
"delete2duplicates": "Lösche Duplikate im Ziel",
"delete_ays": "Bitte den Löschvorgang bestätigen.", "delete_ays": "Bitte den Löschvorgang bestätigen.",
"description": "Beschreibung", "description": "Beschreibung",
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)", "disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
@@ -700,6 +690,7 @@
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen", "dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
"edit_alias_domain": "Alias-Domain bearbeiten", "edit_alias_domain": "Alias-Domain bearbeiten",
"encryption": "Verschlüsselung", "encryption": "Verschlüsselung",
"exclude": "Elemente ausschließen (Regex)",
"extended_sender_acl": "Externe Absenderadressen", "extended_sender_acl": "Externe Absenderadressen",
"extended_sender_acl_info": "Der DKIM-Domainkey der externen Absenderdomain sollte in diesen Server importiert werden, falls vorhanden.<br>\r\n Wird SPF verwendet, muss diesem Server der Versand gestattet werden.<br>\r\n Wird eine Domain oder Alias-Domain zu diesem Server hinzugefügt, die sich mit der externen Absenderadresse überschneidet, wird der externe Absender hier entfernt.<br>\r\n Ein Eintrag @domain.tld erlaubt den Versand als *@domain.tld", "extended_sender_acl_info": "Der DKIM-Domainkey der externen Absenderdomain sollte in diesen Server importiert werden, falls vorhanden.<br>\r\n Wird SPF verwendet, muss diesem Server der Versand gestattet werden.<br>\r\n Wird eine Domain oder Alias-Domain zu diesem Server hinzugefügt, die sich mit der externen Absenderadresse überschneidet, wird der externe Absender hier entfernt.<br>\r\n Ein Eintrag @domain.tld erlaubt den Versand als *@domain.tld",
"force_pw_update": "Erzwinge Passwortänderung bei nächstem Login", "force_pw_update": "Erzwinge Passwortänderung bei nächstem Login",
@@ -730,7 +721,10 @@
"max_aliases": "Max. Aliasse", "max_aliases": "Max. Aliasse",
"max_mailboxes": "Max. Mailboxanzahl", "max_mailboxes": "Max. Mailboxanzahl",
"max_quota": "Max. Größe per Mailbox (MiB)", "max_quota": "Max. Größe per Mailbox (MiB)",
"maxage": "Maximales Alter in Tagen einer Nachricht, die kopiert werden soll<br><small>(0 = alle Nachrichten kopieren)</small>",
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s (0 für unlimitiert)",
"mbox_rl_info": "Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.", "mbox_rl_info": "Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.",
"mins_interval": "Intervall (min)",
"mta_sts": "MTA-STS", "mta_sts": "MTA-STS",
"mta_sts_info": "<a href='https://de.wikipedia.org/wiki/STARTTLS#MTA-STS' target='_blank'>MTA-STS</a> ist ein Standard, der den E-Mail-Versand zwischen Mailservern zwingt, TLS mit gültigen Zertifikaten zu verwenden. <br>Er wird verwendet, wenn <a target='_blank' href='https://de.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> aufgrund fehlender oder nicht unterstützter DNSSEC nicht möglich ist.<br><b>Hinweis</b>: Wenn die empfangende Domain DANE mit DNSSEC unterstützt, wird DANE <b>immer</b> bevorzugt – MTA-STS fungiert nur als Fallback.", "mta_sts_info": "<a href='https://de.wikipedia.org/wiki/STARTTLS#MTA-STS' target='_blank'>MTA-STS</a> ist ein Standard, der den E-Mail-Versand zwischen Mailservern zwingt, TLS mit gültigen Zertifikaten zu verwenden. <br>Er wird verwendet, wenn <a target='_blank' href='https://de.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> aufgrund fehlender oder nicht unterstützter DNSSEC nicht möglich ist.<br><b>Hinweis</b>: Wenn die empfangende Domain DANE mit DNSSEC unterstützt, wird DANE <b>immer</b> bevorzugt – MTA-STS fungiert nur als Fallback.",
"mta_sts_version": "Version", "mta_sts_version": "Version",
@@ -782,6 +776,7 @@
"sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.", "sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.",
"sieve_desc": "Kurze Beschreibung", "sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
"sogo_access": "Direktes weiterleiten an SOGo", "sogo_access": "Direktes weiterleiten an SOGo",
"sogo_access_info": "Nach dem Einloggen wird der Benutzer automatisch an SOGo weitergeleitet.", "sogo_access_info": "Nach dem Einloggen wird der Benutzer automatisch an SOGo weitergeleitet.",
"sogo_visible": "Alias in SOGo sichtbar", "sogo_visible": "Alias in SOGo sichtbar",
@@ -790,8 +785,12 @@
"spam_filter": "Spamfilter", "spam_filter": "Spamfilter",
"spam_policy": "Hinzufügen und Entfernen von Einträgen in Allow- und Denylisten", "spam_policy": "Hinzufügen und Entfernen von Einträgen in Allow- und Denylisten",
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen", "spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
"syncjob": "Sync-Job bearbeiten",
"target_address": "Ziel-Adresse(n)<small>(kommasepariert)</small>", "target_address": "Ziel-Adresse(n)<small>(kommasepariert)</small>",
"target_domain": "Ziel-Domain", "target_domain": "Ziel-Domain",
"timeout1": "Timeout für Verbindung zum Remote-Host",
"timeout2": "Timeout für Verbindung zum lokalen Host",
"title": "Objekt bearbeiten", "title": "Objekt bearbeiten",
"unchanged_if_empty": "Unverändert, wenn leer", "unchanged_if_empty": "Unverändert, wenn leer",
"username": "Benutzername", "username": "Benutzername",
@@ -1005,6 +1004,7 @@
"spam_aliases": "Spam-Alias", "spam_aliases": "Spam-Alias",
"stats": "Statistik", "stats": "Statistik",
"status": "Status", "status": "Status",
"sync_jobs": "Synchronisationen",
"table_size": "Tabellengröße", "table_size": "Tabellengröße",
"table_size_show_n": "Zeige %s Einträge", "table_size_show_n": "Zeige %s Einträge",
"target_address": "Ziel-Adresse", "target_address": "Ziel-Adresse",
@@ -1032,8 +1032,17 @@
"open_logs": "Öffne Logs", "open_logs": "Öffne Logs",
"recipient": "Empfänger", "recipient": "Empfänger",
"sender": "Sender", "sender": "Sender",
"syncjob_EX_OK": "Erfolg",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
"syncjob_check_log": "Logs überprüfen",
"all_domains": "Alle Domains", "all_domains": "Alle Domains",
"catch_all": "Catch-All", "catch_all": "Catch-All",
"syncjob_last_run_result": "Letztes Ausführungsergebnis",
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
"iam": "Identitätsanbieter", "iam": "Identitätsanbieter",
"force_tfa": "2-Faktor-Authentifizierung" "force_tfa": "2-Faktor-Authentifizierung"
}, },
@@ -1209,13 +1218,7 @@
"verified_fido2_login": "FIDO2-Anmeldung verifiziert", "verified_fido2_login": "FIDO2-Anmeldung verifiziert",
"verified_totp_login": "TOTP-Anmeldung verifiziert", "verified_totp_login": "TOTP-Anmeldung verifiziert",
"verified_webauthn_login": "WebAuthn-Anmeldung verifiziert", "verified_webauthn_login": "WebAuthn-Anmeldung verifiziert",
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert", "verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
"imapsync_source_added": "Sync-Quelle %s wurde hinzugefügt",
"imapsync_source_modified": "Sync-Quelle %s wurde gespeichert",
"imapsync_source_deleted": "Sync-Quelle %s wurde gelöscht",
"imapsync_source_token_refreshed": "OAuth-Token für %s wurde erneuert",
"max_parallel_saved": "Sync-Job-Einstellungen gespeichert",
"imapsync_run_next_done": "%s Sync-Job(s) an den Anfang der Warteschlange gesetzt"
}, },
"tfa": { "tfa": {
"authenticators": "Authentikatoren", "authenticators": "Authentikatoren",
@@ -1250,130 +1253,6 @@
"setup_required": "Ihr Konto erfordert Zwei-Faktor-Authentifizierung. Bitte richten Sie eine 2FA-Methode ein, um fortzufahren.", "setup_required": "Ihr Konto erfordert Zwei-Faktor-Authentifizierung. Bitte richten Sie eine 2FA-Methode ein, um fortzufahren.",
"cancel_setup": "Abbrechen und abmelden" "cancel_setup": "Abbrechen und abmelden"
}, },
"syncjobs": {
"sources_panel": "Sync-Quellen",
"sources_hint": "Sync-Quellen definieren IMAP-Server (mit ihrer Authentifizierungsmethode), die von Sync-Jobs wiederverwendet werden können. Jede Quelle wird einmal angelegt und dann beim Anlegen eines Sync-Jobs aus dem Dropdown ausgewählt.",
"source_add": "Quelle hinzufügen",
"source": "Quelle",
"source_name": "Name",
"source_description": "Beschreibung",
"source_scope": "Sichtbarkeit",
"source_scope_all": "Alle Benutzer",
"source_scope_domain": "Bestimmte Domains",
"source_scope_user": "Bestimmte Benutzer",
"source_domains": "Domains",
"source_users": "Benutzer",
"source_select": "Quelle wählen",
"source_auth_type": "Authentifizierung",
"source_oauth_block": "OAuth2-Konfiguration",
"source_token_status": "Token gültig bis",
"source_token_error": "Letzter Refresh-Fehler",
"source_refresh_now": "Token jetzt erneuern",
"source_secret_unchanged_hint": "Leer lassen, um das gespeicherte Secret beizubehalten.",
"source_oauth_flow": "OAuth2-Flow",
"source_oauth_flow_app": "App (Client Credentials)",
"source_oauth_flow_user": "Benutzer-Login (Authorization Code)",
"source_flow_badge_app": "Client Credentials",
"source_flow_badge_user": "Benutzer-Login",
"token_state_issued": "Token ausgestellt",
"token_state_pending": "Anmeldung ausstehend",
"token_state_failed": "Token fehlgeschlagen",
"source_authorize_endpoint": "Autorisierungs-Endpunkt",
"source_userinfo_endpoint": "Userinfo-Endpunkt",
"source_redirect_uri": "Redirect-URI",
"source_redirect_uri_hint": "Diese URI in der Provider-App (z. B. Azure) eintragen. Die Anmeldung beim Provider funktioniert nur, wenn mailcow über diesen Hostnamen aufgerufen wird.",
"syncjob_oauth_connect": "Beim Provider anmelden",
"syncjob_oauth_connected": "Verbunden",
"sync_jobs": "Sync-Jobs",
"syncjob_check_log": "Logs überprüfen",
"syncjob_last_run_result": "Letztes Ausführungsergebnis",
"syncjob_EX_OK": "Erfolg",
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Keine Verbindung zum Quellserver möglich",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
"syncjob_OAUTH_TOKEN_NOT_READY": "OAuth-Token nicht bereit (erneut anmelden oder auf die Token-Erneuerung warten)",
"add": "Sync-Job hinzufügen",
"add_hint": "Passwörter werden unverschlüsselt abgelegt!",
"edit": "Sync-Job bearbeiten",
"create": "Neuen Sync-Job erstellen",
"dry": "Synchronisation simulieren",
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
"mins_interval": "Abrufintervall (Minuten)",
"maxage": "Maximales Alter in Tagen einer Nachricht, die kopiert werden soll<br><small>(0 = alle Nachrichten kopieren)</small>",
"exclude": "Elemente ausschließen (Regex)",
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"delete1": "Nachrichten nach der Übertragung auf dem Quellserver löschen",
"delete2": "Nachrichten im Ziel löschen, die auf dem Quellserver nicht vorhanden sind",
"delete2duplicates": "Lösche Duplikate im Ziel",
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
"subscribeall": "Alle synchronisierten Ordner abonnieren",
"timeout1": "Timeout für die Verbindung zum Quellserver",
"timeout2": "Timeout für die Verbindung zum lokalen Server",
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s<br><small>(0 = unbegrenzt, zusätzlich begrenzt durch das globale Limit pro Prozess in KB/s)</small>",
"custom_params": "Eigene Parameter",
"custom_params_hint": "Pro Zeile eine imapsync-Option. Es werden nur erlaubte Optionen akzeptiert; der Wert darf beliebige Zeichen enthalten (Leerzeichen, Regex, …) und wird als ein einzelnes Argument übergeben.",
"custom_param_option": "Option",
"custom_param_value": "Wert",
"custom_param_no_value": "kein Wert nötig",
"custom_param_add": "Parameter hinzufügen",
"prio": "Priorität",
"prio_hint": "Jobs mit höherer Priorität laufen zuerst. Bei gleicher Priorität kommen noch nie gelaufene Jobs zuerst, danach die am längsten nicht gelaufenen (0 = normal).",
"oauth_popup_title": "Beim Provider anmelden",
"oauth_popup_success": "Verbunden. Dieses Fenster kann geschlossen werden.",
"oauth_popup_failed": "Anmeldung fehlgeschlagen. Dieses Fenster kann geschlossen werden.",
"oauth_error_access_denied": "Sie dürfen keine Sync-Jobs verwalten",
"oauth_error_mailbox_denied": "Kein Zugriff auf die gewählte lokale Mailbox",
"oauth_error_source_unavailable": "Die Sync-Quelle ist nicht verfügbar oder nicht für die Anmeldung beim Provider eingerichtet",
"oauth_error_provider_denied": "Der Provider hat die Anmeldung abgelehnt",
"oauth_error_invalid_state": "Die Anmeldesitzung ist abgelaufen oder ungültig, bitte erneut versuchen",
"oauth_error_token_failed": "Der Provider hat kein Token ausgestellt (Client-ID, Secret und Redirect-URI prüfen)",
"oauth_error_no_identity": "Der Provider hat keine E-Mail-Adresse für das angemeldete Konto geliefert",
"user2_label": "Lokale Mailbox (Ziel)",
"user1_label": "Benutzername an der Quelle",
"password1_label": "Passwort an der Quelle",
"no_sources": "Keine Sync-Quelle verfügbar. Bitte zuerst eine Sync-Quelle anlegen.",
"source_list_error": "Sync-Quellen konnten nicht geladen werden",
"source_not_visible": "(nicht sichtbar)",
"oauth_waiting": "Warte auf den Provider…",
"oauth_popup_blocked": "Das Anmeldefenster wurde blockiert. Bitte Pop-ups für diese Seite erlauben und erneut versuchen.",
"oauth_select_mailbox": "Bitte zuerst die lokale Mailbox wählen",
"oauth_error_unknown": "Anmeldung fehlgeschlagen",
"oauth_hint": "Der Benutzername an der Quelle wird aus dem Konto übernommen, mit dem die Anmeldung beim Provider erfolgt.",
"token_state_none": "Token ausstehend",
"custom_params_legacy": "Alt-Parameter (nicht übernommen)",
"custom_params_legacy_hint": "Diese eigenen Parameter des ursprünglichen Sync-Jobs konnten nicht übernommen werden und werden ignoriert. Noch benötigte Optionen bitte oben im Parameter-Editor neu anlegen.",
"custom_params_legacy_badge": "Alt-Parameter",
"custom_param_remove": "Parameter entfernen",
"never_run": "Noch nie",
"status_inactive": "Inaktiv",
"source_server": "Quellserver",
"id": "ID",
"source_created_by": "Erstellt von",
"source_oauth_token_endpoint": "Token-Endpunkt",
"source_oauth_client_id": "Client-ID",
"source_oauth_client_secret": "Client-Secret",
"source_oauth_scope": "Scope",
"source_oauth_extra_params": "Zusätzliche Parameter (JSON)",
"source_oauth_extra_params_hint": "Optionales JSON-Objekt. prompt, access_type, login_hint, domain_hint und hd gehen an die Anmeldung beim Provider, alle anderen an die Token-Anfrage, z. B. {\"resource\": \"https://provider.example.com\"}",
"source_presets": "Vorlagen",
"source_presets_hint": "Füllt die Serverdaten des Anbieters aus. Viele Anbieter (z. B. Gmail, iCloud, Yahoo) verlangen für IMAP ein App-Passwort oder die Freischaltung von IMAP in den Postfach-Einstellungen.",
"source_tenant": "Microsoft-365-Tenant",
"source_tenant_hint": "Verzeichnis-ID (Mandant) oder eine Domain des Tenants, z. B. contoso.onmicrosoft.com. Wird in die Endpunkte eingesetzt.",
"source_scope_cc_hint": "Quellen mit Client-Credentials-Flow sind immer nur für ihren Ersteller sichtbar.",
"source_readonly_hint": "Diese Sync-Quelle ist freigegeben und kann nur angesehen werden.",
"copy": "Kopieren",
"copied": "In die Zwischenablage kopiert",
"source_view": "Anzeigen",
"custom_params_options_error": "Die Liste der erlaubten imapsync-Optionen konnte nicht geladen werden",
"source_missing": "Diesem Sync-Job ist keine Sync-Quelle zugeordnet. Bitte eine Quelle wählen.",
"oauth_window_closed": "Das Anmeldefenster wurde geschlossen, bevor ein Ergebnis ankam. Falls die Anmeldung abgeschlossen wurde, die Seite neu laden oder es erneut versuchen.",
"custom_params_legacy_badge_title": "Nicht übernommene alte Parameter werden ignoriert. Details auf der Bearbeiten-Seite.",
"status_waiting": "Wartend",
"status_running": "Wird ausgeführt"
},
"user": { "user": {
"action": "Aktion", "action": "Aktion",
"active": "Aktiv", "active": "Aktiv",
@@ -1404,6 +1283,7 @@
"clear_recent_successful_connections": "Alle erfolgreichen Verbindungen bereinigen", "clear_recent_successful_connections": "Alle erfolgreichen Verbindungen bereinigen",
"client_configuration": "Konfigurationsanleitungen für E-Mail-Programme und Smartphones anzeigen", "client_configuration": "Konfigurationsanleitungen für E-Mail-Programme und Smartphones anzeigen",
"create_app_passwd": "Erstelle App-Passwort", "create_app_passwd": "Erstelle App-Passwort",
"create_syncjob": "Neuen Sync-Job erstellen",
"created_on": "Erstellt am", "created_on": "Erstellt am",
"daily": "Täglich", "daily": "Täglich",
"day": "Tag", "day": "Tag",
@@ -1511,6 +1391,7 @@
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte Nicht-„Catch-All“-Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.", "spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte Nicht-„Catch-All“-Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
"spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben", "spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben",
"status": "Status", "status": "Status",
"sync_jobs": "Sync Jobs",
"tag_handling": "Umgang mit getaggten E-Mails steuern", "tag_handling": "Umgang mit getaggten E-Mails steuern",
"tag_help_example": "Beispiel für eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org", "tag_help_example": "Beispiel für eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org",
"tag_help_explain": "Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt (\"INBOX/Facebook\").<br>\r\nIn Betreff: Der Name des Tags wird dem Betreff angefügt, etwa \"[Facebook] Meine Neuigkeiten\".", "tag_help_explain": "Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt (\"INBOX/Facebook\").<br>\r\nIn Betreff: Der Name des Tags wird dem Betreff angefügt, etwa \"[Facebook] Meine Neuigkeiten\".",
@@ -1535,7 +1416,16 @@
"with_app_password": "mit App-Passwort", "with_app_password": "mit App-Passwort",
"year": "Jahr", "year": "Jahr",
"years": "Jahren", "years": "Jahren",
"syncjob_EX_OK": "Erfolg",
"open_logs": "Öffne Logs", "open_logs": "Öffne Logs",
"syncjob_check_log": "Logs überprüfen",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
"syncjob_last_run_result": "Letztes Ausführungsergebnis",
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
"pushover_sound": "Ton" "pushover_sound": "Ton"
}, },
"warning": { "warning": {
+52 -162
View File
@@ -45,16 +45,24 @@
"app_name": "App name", "app_name": "App name",
"app_password": "Add app password", "app_password": "Add app password",
"app_passwd_protocols": "Allowed protocols for app password", "app_passwd_protocols": "Allowed protocols for app password",
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Relay options", "backup_mx_options": "Relay options",
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.", "bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview", "comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
"custom_params": "Custom parameters",
"custom_params_hint": "Right: --param=xy, wrong: --param xy",
"delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Delete duplicates on destination",
"description": "Description", "description": "Description",
"destination": "Destination", "destination": "Destination",
"disable_login": "Disallow login (incoming mail is still accepted)", "disable_login": "Disallow login (incoming mail is still accepted)",
"domain": "Domain", "domain": "Domain",
"domain_matches_hostname": "Domain %s matches hostname", "domain_matches_hostname": "Domain %s matches hostname",
"domain_quota_m": "Total domain quota (MiB)", "domain_quota_m": "Total domain quota (MiB)",
"dry": "Simulate synchronization",
"enc_method": "Encryption method", "enc_method": "Encryption method",
"exclude": "Exclude objects (regex)",
"full_name": "Full name", "full_name": "Full name",
"gal": "Global Address List", "gal": "Global Address List",
"gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>", "gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
@@ -73,6 +81,7 @@
"mailbox_username": "Username (left part of an email address)", "mailbox_username": "Username (left part of an email address)",
"max_aliases": "Max. possible aliases", "max_aliases": "Max. possible aliases",
"max_mailboxes": "Max. possible mailboxes", "max_mailboxes": "Max. possible mailboxes",
"mins_interval": "Polling interval (minutes)",
"multiple_bookings": "Multiple bookings", "multiple_bookings": "Multiple bookings",
"nexthop": "Next hop", "nexthop": "Next hop",
"password": "Password", "password": "Password",
@@ -92,10 +101,16 @@
"select_domain": "Please select a domain first", "select_domain": "Please select a domain first",
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"subscribeall": "Subscribe all folders",
"syncjob": "Add sync job",
"syncjob_hint": "Be aware that passwords need to be saved plain-text!",
"tags": "Tags", "tags": "Tags",
"target_address": "Goto addresses", "target_address": "Goto addresses",
"target_address_info": "<small>Full email address/es (comma-separated).</small>", "target_address_info": "<small>Full email address/es (comma-separated).</small>",
"target_domain": "Target domain", "target_domain": "Target domain",
"timeout1": "Timeout for connection to remote host",
"timeout2": "Timeout for connection to local host",
"username": "Username", "username": "Username",
"validate": "Validate", "validate": "Validate",
"validation_success": "Validated successfully" "validation_success": "Validated successfully"
@@ -403,12 +418,7 @@
"user_quicklink": "Hide Quicklink to User Login Page", "user_quicklink": "Hide Quicklink to User Login Page",
"validate_license_now": "Validate GUID against license server", "validate_license_now": "Validate GUID against license server",
"verify": "Verify", "verify": "Verify",
"yes": "&#10003;", "yes": "&#10003;"
"syncjobs": "Sync jobs",
"syncjob_max_parallel": "Max. parallel sync processes",
"syncjob_max_parallel_info": "How many sync jobs may run at the same time (1-32).",
"syncjob_max_kb_per_second": "Bandwidth limit per process (KB/s)",
"syncjob_max_kb_per_second_info": "0 = unlimited. Caps every sync process in KB/s; a per-job limit (bytes/s) is capped by this. Total ≈ this × max parallel."
}, },
"danger": { "danger": {
"access_denied": "Access denied or invalid form data", "access_denied": "Access denied or invalid form data",
@@ -557,31 +567,7 @@
"validity_missing": "Please assign a period of validity", "validity_missing": "Please assign a period of validity",
"value_missing": "Please provide all values", "value_missing": "Please provide all values",
"version_invalid": "Version %s is invalid", "version_invalid": "Version %s is invalid",
"yotp_verification_failed": "Yubico OTP verification failed: %s", "yotp_verification_failed": "Yubico OTP verification failed: %s"
"imapsync_source_in_use": "Sync source %s is still used by a sync job and cannot be deleted",
"imapsync_source_token_refresh_failed": "OAuth token refresh for %s failed",
"imapsync_source_scope_empty": "Select at least one domain for the chosen visibility",
"imapsync_source_unavailable": "The selected sync source is unavailable",
"imapsync_source_oauth_connect_required": "Sign in with the provider before saving this sync job",
"imapsync_source_name_invalid": "Invalid sync source name",
"imapsync_source_host_invalid": "Invalid host name or IP address",
"imapsync_source_port_invalid": "Invalid port",
"imapsync_source_enc_invalid": "Invalid encryption method",
"imapsync_source_auth_invalid": "Invalid authentication method",
"imapsync_custom_param_invalid": "Custom parameters contain a disallowed imapsync option: %s",
"imapsync_source_oauth_required": "OAuth2 requires token endpoint, client ID, client secret and scope",
"imapsync_source_oauth_endpoint_invalid": "Invalid OAuth2 endpoint: an https:// URL with a valid host name is required (sources not created by an administrator need a publicly reachable host)",
"imapsync_source_oauth_extra_invalid": "OAuth2 extra parameters must be a JSON object with simple values, e.g. {\"resource\": \"https://example.com\"}",
"imapsync_source_tenant_missing": "Please enter the Microsoft 365 tenant",
"imapsync_source_oauth_invalid": "Invalid OAuth2 configuration",
"imapsync_max_parallel_invalid": "Parallel process count must be between 1 and 32",
"imapsync_source_description_invalid": "The description must not exceed 255 characters",
"imapsync_source_refresh_unsupported": "Manual token refresh is only available for client credentials sources",
"imapsync_custom_params_format": "Custom parameters must be sent as a list of option/value pairs",
"imapsync_custom_param_value": "Invalid value for imapsync option %s (missing value, value on a flag or disallowed value)",
"imapsync_job_running": "Sync job %s is currently running and cannot be scheduled next",
"imapsync_user1_invalid": "Invalid remote username",
"imapsync_max_kb_per_second_invalid": "Bandwidth limit must be between 0 and 10485760 KB/s"
}, },
"datatables": { "datatables": {
"collapse_all": "Collapse All", "collapse_all": "Collapse All",
@@ -670,6 +656,7 @@
"app_name": "App name", "app_name": "App name",
"app_passwd": "App password", "app_passwd": "App password",
"app_passwd_protocols": "Allowed protocols for app password", "app_passwd_protocols": "Allowed protocols for app password",
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Relay options", "backup_mx_options": "Relay options",
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.", "bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
"client_id": "Client ID", "client_id": "Client ID",
@@ -677,6 +664,9 @@
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview", "comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
"created_on": "Created on", "created_on": "Created on",
"custom_attributes": "Custom attributes", "custom_attributes": "Custom attributes",
"delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Delete duplicates on destination",
"delete_ays": "Please confirm the deletion process.", "delete_ays": "Please confirm the deletion process.",
"description": "Description", "description": "Description",
"disable_login": "Disallow login (incoming mail is still accepted)", "disable_login": "Disallow login (incoming mail is still accepted)",
@@ -700,6 +690,7 @@
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)", "dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
"edit_alias_domain": "Edit Alias domain", "edit_alias_domain": "Edit Alias domain",
"encryption": "Encryption", "encryption": "Encryption",
"exclude": "Exclude objects (regex)",
"extended_sender_acl": "External sender addresses", "extended_sender_acl": "External sender addresses",
"extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.", "extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.",
"force_pw_update": "Force password update at next login", "force_pw_update": "Force password update at next login",
@@ -730,7 +721,10 @@
"max_aliases": "Max. aliases", "max_aliases": "Max. aliases",
"max_mailboxes": "Max. possible mailboxes", "max_mailboxes": "Max. possible mailboxes",
"max_quota": "Max. quota per mailbox (MiB)", "max_quota": "Max. quota per mailbox (MiB)",
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.", "mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.",
"mins_interval": "Interval (min)",
"mta_sts": "MTA-STS", "mta_sts": "MTA-STS",
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> is a standard that enforces email delivery between mail servers to use TLS with valid certificates. <br>It is used when <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> is not possible due to missing or unsupported DNSSEC.<br><b>Note</b>: If the receiving domain supports DANE with DNSSEC, DANE is <b>always</b> preferred – MTA-STS only acts as a fallback.", "mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> is a standard that enforces email delivery between mail servers to use TLS with valid certificates. <br>It is used when <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> is not possible due to missing or unsupported DNSSEC.<br><b>Note</b>: If the receiving domain supports DANE with DNSSEC, DANE is <b>always</b> preferred – MTA-STS only acts as a fallback.",
"mta_sts_version": "Version", "mta_sts_version": "Version",
@@ -783,6 +777,7 @@
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.", "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"sogo_access": "Direct forwarding to SOGo", "sogo_access": "Direct forwarding to SOGo",
"sogo_access_info": "After logging in, the user is automatically redirected to SOGo.", "sogo_access_info": "After logging in, the user is automatically redirected to SOGo.",
"sogo_visible": "Alias is visible in SOGo", "sogo_visible": "Alias is visible in SOGo",
@@ -791,8 +786,12 @@
"spam_filter": "Spam filter", "spam_filter": "Spam filter",
"spam_policy": "Add or remove items to allow-/denylist", "spam_policy": "Add or remove items to allow-/denylist",
"spam_score": "Set a custom spam score", "spam_score": "Set a custom spam score",
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
"syncjob": "Edit sync job",
"target_address": "Goto address/es <small>(comma-separated)</small>", "target_address": "Goto address/es <small>(comma-separated)</small>",
"target_domain": "Target domain", "target_domain": "Target domain",
"timeout1": "Timeout for connection to remote host",
"timeout2": "Timeout for connection to local host",
"title": "Edit object", "title": "Edit object",
"unchanged_if_empty": "If unchanged leave blank", "unchanged_if_empty": "If unchanged leave blank",
"username": "Username", "username": "Username",
@@ -1014,6 +1013,16 @@
"spam_aliases": "Temp. alias", "spam_aliases": "Temp. alias",
"stats": "Statistics", "stats": "Statistics",
"status": "Status", "status": "Status",
"sync_jobs": "Sync jobs",
"syncjob_check_log": "Check log",
"syncjob_last_run_result": "Last run result",
"syncjob_EX_OK": "Success",
"syncjob_EXIT_CONNECTION_FAILURE": "Connection problem",
"syncjob_EXIT_TLS_FAILURE": "Problem with encrypted connection",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentication problem",
"syncjob_EXIT_OVERQUOTA": "Target mailbox is over quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Can't connect to remote server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Wrong username or password",
"table_size": "Table size", "table_size": "Table size",
"table_size_show_n": "Show %s items", "table_size_show_n": "Show %s items",
"target_address": "Goto address", "target_address": "Goto address",
@@ -1216,13 +1225,7 @@
"verified_fido2_login": "Verified FIDO2 login", "verified_fido2_login": "Verified FIDO2 login",
"verified_totp_login": "Verified TOTP login", "verified_totp_login": "Verified TOTP login",
"verified_webauthn_login": "Verified WebAuthn login", "verified_webauthn_login": "Verified WebAuthn login",
"verified_yotp_login": "Verified Yubico OTP login", "verified_yotp_login": "Verified Yubico OTP login"
"imapsync_source_added": "Sync source %s has been added",
"imapsync_source_modified": "Sync source %s has been saved",
"imapsync_source_deleted": "Sync source %s has been deleted",
"imapsync_source_token_refreshed": "OAuth token for %s has been refreshed",
"max_parallel_saved": "Sync job settings saved",
"imapsync_run_next_done": "%s sync job(s) moved to the front of the queue"
}, },
"tfa": { "tfa": {
"authenticators": "Authenticators", "authenticators": "Authenticators",
@@ -1257,130 +1260,6 @@
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.", "waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
"yubi_otp": "Yubico OTP authentication" "yubi_otp": "Yubico OTP authentication"
}, },
"syncjobs": {
"sources_panel": "Sync sources",
"sources_hint": "Sync sources define IMAP servers (and their authentication method) that sync jobs can reuse. Define each source once, then pick it from the dropdown when creating sync jobs.",
"source_add": "Add source",
"source": "Source",
"source_name": "Name",
"source_description": "Description",
"source_scope": "Visibility",
"source_scope_all": "All users",
"source_scope_domain": "Specific domains",
"source_scope_user": "Specific users",
"source_domains": "Domains",
"source_users": "Users",
"source_select": "Select source",
"source_auth_type": "Authentication",
"source_oauth_block": "OAuth2 configuration",
"source_token_status": "Token valid until",
"source_token_error": "Last refresh error",
"source_refresh_now": "Refresh token now",
"source_secret_unchanged_hint": "Leave blank to keep the stored secret.",
"source_oauth_flow": "OAuth2 flow",
"source_oauth_flow_app": "App (client credentials)",
"source_oauth_flow_user": "User login (authorization code)",
"source_flow_badge_app": "Client credentials",
"source_flow_badge_user": "User login",
"token_state_issued": "Token issued",
"token_state_pending": "Token pending",
"token_state_failed": "Token failed",
"source_authorize_endpoint": "Authorize endpoint",
"source_userinfo_endpoint": "Userinfo endpoint",
"source_redirect_uri": "Redirect URI",
"source_redirect_uri_hint": "Register this URI in the provider app (e.g. Azure). Provider sign-in only works when mailcow is opened via this host name.",
"syncjob_oauth_connect": "Sign in with the provider",
"syncjob_oauth_connected": "Connected",
"sync_jobs": "Sync jobs",
"syncjob_check_log": "Check log",
"syncjob_last_run_result": "Last run result",
"syncjob_EX_OK": "Success",
"syncjob_EXIT_CONNECTION_FAILURE": "Connection problem",
"syncjob_EXIT_TLS_FAILURE": "Problem with encrypted connection",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentication problem",
"syncjob_EXIT_OVERQUOTA": "Target mailbox is over quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Can't connect to remote server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Wrong username or password",
"syncjob_OAUTH_TOKEN_NOT_READY": "OAuth token not ready (sign in again or wait for the token refresh)",
"add": "Add sync job",
"add_hint": "Be aware that passwords need to be saved plain-text!",
"edit": "Edit sync job",
"create": "Create new sync job",
"dry": "Simulate synchronization",
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
"mins_interval": "Polling interval (minutes)",
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
"exclude": "Exclude objects (regex)",
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Delete duplicates on destination",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"subscribeall": "Subscribe all folders",
"timeout1": "Timeout for connection to remote host",
"timeout2": "Timeout for connection to local host",
"maxbytespersecond": "Max. bandwidth in bytes/s<br><small>(0 = unlimited, additionally capped by the global limit per process in KB/s)</small>",
"custom_params": "Custom parameters",
"custom_params_hint": "Add one imapsync option per row. Only allowlisted options are accepted; the value may contain any character (spaces, regex, …) and is passed as a single argument.",
"custom_param_option": "Option",
"custom_param_value": "Value",
"custom_param_no_value": "no value needed",
"custom_param_add": "Add parameter",
"prio": "Priority",
"prio_hint": "Jobs with a higher priority run first. Within the same priority, jobs that never ran come first, then the least recently run ones (0 = normal).",
"oauth_popup_title": "Sign in with the provider",
"oauth_popup_success": "Connected. You can close this window.",
"oauth_popup_failed": "Sign-in failed. You can close this window.",
"oauth_error_access_denied": "You are not allowed to manage sync jobs",
"oauth_error_mailbox_denied": "No access to the selected local mailbox",
"oauth_error_source_unavailable": "The sync source is unavailable or not configured for provider sign-in",
"oauth_error_provider_denied": "The provider refused the sign-in",
"oauth_error_invalid_state": "The sign-in session expired or is invalid, please try again",
"oauth_error_token_failed": "The provider did not issue a token (check client ID, secret and redirect URI)",
"oauth_error_no_identity": "The provider did not return an e-mail address for the signed-in account",
"user2_label": "Local mailbox (destination)",
"user1_label": "Remote username (source)",
"password1_label": "Remote password (source)",
"no_sources": "No sync source is available. Please add a sync source first.",
"source_list_error": "Sync sources could not be loaded",
"source_not_visible": "(not visible)",
"oauth_waiting": "Waiting for the provider…",
"oauth_popup_blocked": "The sign-in window was blocked. Please allow pop-ups for this site and try again.",
"oauth_select_mailbox": "Please select the local mailbox first",
"oauth_error_unknown": "Sign-in failed",
"oauth_hint": "The remote username is taken from the account used to sign in with the provider.",
"token_state_none": "Token pending",
"custom_params_legacy": "Legacy parameters (not migrated)",
"custom_params_legacy_hint": "These custom parameters of the original sync job could not be converted and are ignored. Add the options you still need with the parameter editor above.",
"custom_params_legacy_badge": "Legacy parameters",
"custom_param_remove": "Remove parameter",
"never_run": "Never",
"status_inactive": "Inactive",
"source_server": "Source server",
"id": "ID",
"source_created_by": "Created by",
"source_oauth_token_endpoint": "Token endpoint",
"source_oauth_client_id": "Client ID",
"source_oauth_client_secret": "Client secret",
"source_oauth_scope": "Scope",
"source_oauth_extra_params": "Extra parameters (JSON)",
"source_oauth_extra_params_hint": "Optional JSON object. prompt, access_type, login_hint, domain_hint and hd are sent with the provider sign-in, all others with the token request, e.g. {\"resource\": \"https://provider.example.com\"}",
"source_presets": "Presets",
"source_presets_hint": "Fills in the provider's server settings. Many providers (e.g. Gmail, iCloud, Yahoo) require an app password or IMAP access to be enabled in the mailbox settings.",
"source_tenant": "Microsoft 365 tenant",
"source_tenant_hint": "Directory (tenant) ID or a domain of the tenant, e.g. contoso.onmicrosoft.com. It is inserted into the endpoints.",
"source_scope_cc_hint": "Sources using the client credentials flow are always private to their creator.",
"source_readonly_hint": "This sync source is shared with you and can only be viewed.",
"copy": "Copy",
"copied": "Copied to clipboard",
"source_view": "Show",
"custom_params_options_error": "The list of allowed imapsync options could not be loaded",
"source_missing": "No sync source is assigned to this job. Please select a source.",
"oauth_window_closed": "The sign-in window was closed before a result arrived. If you completed the sign-in, reload the page or try again.",
"custom_params_legacy_badge_title": "Legacy custom parameters that could not be converted are ignored. Open the edit page for details.",
"status_waiting": "Waiting",
"status_running": "Running"
},
"user": { "user": {
"action": "Action", "action": "Action",
"active": "Active", "active": "Active",
@@ -1411,6 +1290,7 @@
"clear_recent_successful_connections": "Clear seen successful connections", "clear_recent_successful_connections": "Clear seen successful connections",
"client_configuration": "Show configuration guides for email clients and smartphones", "client_configuration": "Show configuration guides for email clients and smartphones",
"create_app_passwd": "Create app password", "create_app_passwd": "Create app password",
"create_syncjob": "Create new sync job",
"created_on": "Created on", "created_on": "Created on",
"daily": "Daily", "daily": "Daily",
"day": "day", "day": "day",
@@ -1520,6 +1400,16 @@
"spamfilter_wl_desc": "Allowlisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.", "spamfilter_wl_desc": "Allowlisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
"spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder", "spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder",
"status": "Status", "status": "Status",
"sync_jobs": "Sync jobs",
"syncjob_check_log": "Check log",
"syncjob_last_run_result": "Last run result",
"syncjob_EX_OK": "Success",
"syncjob_EXIT_CONNECTION_FAILURE": "Connection problem",
"syncjob_EXIT_TLS_FAILURE": "Problem with encrypted connection",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentication problem",
"syncjob_EXIT_OVERQUOTA": "Target mailbox is over quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Can't connect to remote server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Wrong username or password",
"tag_handling": "Set handling for tagged mail", "tag_handling": "Set handling for tagged mail",
"tag_help_example": "Example for a tagged email address: me<b>+Facebook</b>@example.org", "tag_help_example": "Example for a tagged email address: me<b>+Facebook</b>@example.org",
"tag_help_explain": "In subfolder: a new subfolder named after the tag will be created below INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: the tags name will be prepended to the mails subject, example: \"[Facebook] My News\".", "tag_help_explain": "In subfolder: a new subfolder named after the tag will be created below INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: the tags name will be prepended to the mails subject, example: \"[Facebook] My News\".",
+40 -31
View File
@@ -40,12 +40,19 @@
"alias_address_info": "<small>Dirección(es) de correo completa(s) ó @dominio.com, para atrapar todos los mensajes para un dominio (separado por coma). <b>Dominios que existan en mailcow solamente</b>.</small>", "alias_address_info": "<small>Dirección(es) de correo completa(s) ó @dominio.com, para atrapar todos los mensajes para un dominio (separado por coma). <b>Dominios que existan en mailcow solamente</b>.</small>",
"alias_domain": "Dominio alias", "alias_domain": "Dominio alias",
"alias_domain_info": "<small>Nombres de dominio válidos solamente (separado por coma).</small>", "alias_domain_info": "<small>Nombres de dominio válidos solamente (separado por coma).</small>",
"automap": "Intentar enlazar carpetas (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Opciones del respaldo MX:", "backup_mx_options": "Opciones del respaldo MX:",
"custom_params": "Parámetros personalizados",
"custom_params_hint": "Correcto: --param=xy, Incorrecto: --param xy",
"delete1": "Eliminar de la fuente cuando se complete",
"delete2": "Eliminar mensajes en el destino que no están en la fuente",
"delete2duplicates": "Eliminar duplicados en el destino",
"description": "Descripción", "description": "Descripción",
"destination": "Destino", "destination": "Destino",
"domain": "Dominio", "domain": "Dominio",
"domain_quota_m": "Cuota total del dominio (MiB)", "domain_quota_m": "Cuota total del dominio (MiB)",
"enc_method": "Método de cifrado", "enc_method": "Método de cifrado",
"exclude": "Excluir objectos (regex)",
"full_name": "Nombre completo", "full_name": "Nombre completo",
"gal": "Lista global de direcciones (GAL)", "gal": "Lista global de direcciones (GAL)",
"gal_info": "El GAL contiene todos los objetos de un dominio y no puede ser editado por ningún usuario. Falta información de disponibilidad en SOGo, si está desactivada. <b>Reinicia SOGo para aplicar los cambios.</b>", "gal_info": "El GAL contiene todos los objetos de un dominio y no puede ser editado por ningún usuario. Falta información de disponibilidad en SOGo, si está desactivada. <b>Reinicia SOGo para aplicar los cambios.</b>",
@@ -60,6 +67,7 @@
"mailbox_username": "Nombre de usuario (parte izquierda de una dirección de correo)", "mailbox_username": "Nombre de usuario (parte izquierda de una dirección de correo)",
"max_aliases": "Máx. alias posibles", "max_aliases": "Máx. alias posibles",
"max_mailboxes": "Máx. buzones posibles", "max_mailboxes": "Máx. buzones posibles",
"mins_interval": "Intervalo de sondeo (minutos)",
"multiple_bookings": "Múltiples reservas", "multiple_bookings": "Múltiples reservas",
"nexthop": "Siguiente destino", "nexthop": "Siguiente destino",
"password": "Contraseña", "password": "Contraseña",
@@ -74,9 +82,15 @@
"select_domain": "Por favor elige un dominio primero", "select_domain": "Por favor elige un dominio primero",
"sieve_desc": "Descripción", "sieve_desc": "Descripción",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"skipcrossduplicates": "Omitir mensajes duplicados en carpetas (orden de llegada)",
"subscribeall": "Suscribirse a todas las carpetas",
"syncjob": "Añadir trabajo de sincronización",
"syncjob_hint": "Tenga en cuenta que las contraseñas deben guardarse en texto plano sin cifrar",
"target_address": "Direcciones destino:", "target_address": "Direcciones destino:",
"target_address_info": "<small>Dirección(es) de correo completa(s) (separado por coma).</small>", "target_address_info": "<small>Dirección(es) de correo completa(s) (separado por coma).</small>",
"target_domain": "Dominio destino:", "target_domain": "Dominio destino:",
"timeout1": "Tiempo de espera para la conexión al host remoto",
"timeout2": "Tiempo de espera para la conexión al host local",
"username": "Usuario", "username": "Usuario",
"validate": "Validar", "validate": "Validar",
"validation_success": "Validado exitosamente", "validation_success": "Validado exitosamente",
@@ -86,6 +100,7 @@
"public_comment": "Comentarios públicos", "public_comment": "Comentarios públicos",
"disable_login": "Desactivar login (el correo entrante seguirá activo)", "disable_login": "Desactivar login (el correo entrante seguirá activo)",
"comment_info": "Los comentarios privados no son visibles al usuario, mientras que los comentarios públicos aparecerán sobre la información general del usuario", "comment_info": "Los comentarios privados no son visibles al usuario, mientras que los comentarios públicos aparecerán sobre la información general del usuario",
"dry": "Simular la sincronización",
"private_comment": "Comentario privado", "private_comment": "Comentario privado",
"app_passwd_protocols": "Protocolos autorizados para la contraseña de la aplicación", "app_passwd_protocols": "Protocolos autorizados para la contraseña de la aplicación",
"relay_transport_info": "<div class=\"label label-info\">Información</div> Puede definir mapas de transporte para un destino personalizado para este dominio. En caso de no definirlo, se realizará una búsqueda MX.", "relay_transport_info": "<div class=\"label label-info\">Información</div> Puede definir mapas de transporte para un destino personalizado para este dominio. En caso de no definirlo, se realizará una búsqueda MX.",
@@ -580,10 +595,14 @@
"edit": { "edit": {
"active": "Activo", "active": "Activo",
"alias": "Editar alias", "alias": "Editar alias",
"automap": "Intentar enlazar carpetas (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Opciones del respaldo MX:", "backup_mx_options": "Opciones del respaldo MX:",
"bcc_dest_format": "El destino BCC debe ser una única dirección de correo electrónico válida", "bcc_dest_format": "El destino BCC debe ser una única dirección de correo electrónico válida",
"client_id": "ID de Cliente", "client_id": "ID de Cliente",
"client_secret": "Secreto de cliente", "client_secret": "Secreto de cliente",
"delete1": "Eliminar de la fuente cuando se complete",
"delete2": "Eliminar mensajes en el destino que no están en la fuente",
"delete2duplicates": "Eliminar duplicados en el destino",
"description": "Descripción:", "description": "Descripción:",
"domain": "Editar dominio", "domain": "Editar dominio",
"domain_admin": "Editar administrador del dominio", "domain_admin": "Editar administrador del dominio",
@@ -592,6 +611,7 @@
"dont_check_sender_acl": "No verificar remitente para el dominio %s", "dont_check_sender_acl": "No verificar remitente para el dominio %s",
"edit_alias_domain": "Editar alias de dominio", "edit_alias_domain": "Editar alias de dominio",
"encryption": "Cifrado", "encryption": "Cifrado",
"exclude": "Excluir objectos (regex)",
"force_pw_update": "Forzar cambio de contraseña en el próximo inicio de sesión", "force_pw_update": "Forzar cambio de contraseña en el próximo inicio de sesión",
"force_pw_update_info": "Este usuario solo podrá iniciar sesión en la interfaz de usuario.", "force_pw_update_info": "Este usuario solo podrá iniciar sesión en la interfaz de usuario.",
"full_name": "Nombre completo", "full_name": "Nombre completo",
@@ -607,6 +627,9 @@
"max_aliases": "Máx. alias:", "max_aliases": "Máx. alias:",
"max_mailboxes": "Máx. buzones posibles:", "max_mailboxes": "Máx. buzones posibles:",
"max_quota": "Máx. cuota por buzón (MiB):", "max_quota": "Máx. cuota por buzón (MiB):",
"maxage": "Antigüedad máxima de los mensajes en días que se migrarán<br><small>(0 = ignorar edad)</small>",
"maxbytespersecond": "Max. bytes por segundo <br><small>(0 = ilimitado)</small>",
"mins_interval": "Intervalo (min)",
"multiple_bookings": "Múltiples reservas", "multiple_bookings": "Múltiples reservas",
"nexthop": "Siguiente destino", "nexthop": "Siguiente destino",
"password": "Contraseña:", "password": "Contraseña:",
@@ -625,8 +648,13 @@
"sender_acl": "Permitir envío como:", "sender_acl": "Permitir envío como:",
"sieve_desc": "Descripción", "sieve_desc": "Descripción",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"skipcrossduplicates": "Omitir mensajes duplicados en carpetas (orden de llegada)",
"subfolder2": "Sincronizar en subcarpeta en destino<br><small>(vacío = no usar subcarpeta)</small>",
"syncjob": "Editar trabajo de sincronización",
"target_address": "Dirección/es destino <small>(separadas por coma)</small>:", "target_address": "Dirección/es destino <small>(separadas por coma)</small>:",
"target_domain": "Dominio destino:", "target_domain": "Dominio destino:",
"timeout1": "Tiempo de espera para la conexión al host remoto",
"timeout2": "Tiempo de espera para la conexión al host local",
"title": "Editar objeto", "title": "Editar objeto",
"unchanged_if_empty": "Si no hay cambios dejalo en blanco", "unchanged_if_empty": "Si no hay cambios dejalo en blanco",
"username": "Nombre de usuario", "username": "Nombre de usuario",
@@ -838,6 +866,7 @@
"set_prefilter": "Marcar como prefiltro", "set_prefilter": "Marcar como prefiltro",
"sieve_info": "Puedes almacenar múltiples filtros por usuario, pero solo un prefiltro y un postfiltro pueden estar activos al mismo tiempo.<br>\r\nCada filtro será procesado en el orden descrito. Ni un script fallido ni un \"keep;\" emitido detendrá el procesamiento de otros scripts.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Prefiltro sieve global</a> → Prefiltro → Scripts del usuario → Posfiltro → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Posfiltro sieve global</a>", "sieve_info": "Puedes almacenar múltiples filtros por usuario, pero solo un prefiltro y un postfiltro pueden estar activos al mismo tiempo.<br>\r\nCada filtro será procesado en el orden descrito. Ni un script fallido ni un \"keep;\" emitido detendrá el procesamiento de otros scripts.<br>\r\n<a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_before\" target=\"_blank\">Prefiltro sieve global</a> → Prefiltro → Scripts del usuario → Posfiltro → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/global_sieve_after\" target=\"_blank\">Posfiltro sieve global</a>",
"status": "Status", "status": "Status",
"sync_jobs": "Trabajos de sincronización",
"target_address": "Dirección destino", "target_address": "Dirección destino",
"target_domain": "Dominio destino", "target_domain": "Dominio destino",
"tls_enforce_in": "Forzar TLS entrance", "tls_enforce_in": "Forzar TLS entrance",
@@ -857,6 +886,7 @@
"sieve_preset_4": "Colocar en bandeja de entrada, omitir procesamiento posterior en filtros de sieve", "sieve_preset_4": "Colocar en bandeja de entrada, omitir procesamiento posterior en filtros de sieve",
"goto_spam": "Aprender como <b>correo no deseado</b>", "goto_spam": "Aprender como <b>correo no deseado</b>",
"sieve_preset_header": "Vea los preajustes de ejemplo más abajo. Para más detalles, consulte <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedia</a>.", "sieve_preset_header": "Vea los preajustes de ejemplo más abajo. Para más detalles, consulte <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedia</a>.",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nombre de usuario o contraseña incorrectos",
"tls_policy_maps_enforced_tls": "Estas políticas tendrán precedencia también para aquellos usuarios de buzones en los que sea obligatoria una conexión TLS. Si no se indica ninguna política a continuación, dichos usuarios aplicarán los valores predeterminados que se especifiquen en <code>smtp_tls_mandatory_protocols</code> y <code>smtp_tls_mandatory_ciphers</code>.", "tls_policy_maps_enforced_tls": "Estas políticas tendrán precedencia también para aquellos usuarios de buzones en los que sea obligatoria una conexión TLS. Si no se indica ninguna política a continuación, dichos usuarios aplicarán los valores predeterminados que se especifiquen en <code>smtp_tls_mandatory_protocols</code> y <code>smtp_tls_mandatory_ciphers</code>.",
"alias_domain_alias_hint": "Los alias <b>no</b> se aplican a dominios de alias automáticamente. Una dirección alias <code>mi-alias@dominio</code> <b>no cubre</b> la dirección <code>mi-alias@dominio-alias</code> (donde \"dominio-alias\" es un hipotético alias para el dominio \"dominio\").<br>Utilice un filtro sieve para redirigir el correo a un buzón externo (ver la pestaña \"Filtros\" o utilice SOGo -> Desvío). Utilice \"expandir alias a dominio de alias\" para agregar automáticamente los alias que falten.", "alias_domain_alias_hint": "Los alias <b>no</b> se aplican a dominios de alias automáticamente. Una dirección alias <code>mi-alias@dominio</code> <b>no cubre</b> la dirección <code>mi-alias@dominio-alias</code> (donde \"dominio-alias\" es un hipotético alias para el dominio \"dominio\").<br>Utilice un filtro sieve para redirigir el correo a un buzón externo (ver la pestaña \"Filtros\" o utilice SOGo -> Desvío). Utilice \"expandir alias a dominio de alias\" para agregar automáticamente los alias que falten.",
"domain_templates": "Plantillas de dominio", "domain_templates": "Plantillas de dominio",
@@ -900,6 +930,14 @@
"sogo_visible": "Alias visible en SOGo", "sogo_visible": "Alias visible en SOGo",
"sogo_visible_n": "Ocultar alias en SOGo", "sogo_visible_n": "Ocultar alias en SOGo",
"stats": "Estadísticas", "stats": "Estadísticas",
"syncjob_check_log": "Comprobar registros",
"syncjob_last_run_result": "Resultado de la última ejecución",
"syncjob_EX_OK": "Éxito",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema de conexión",
"syncjob_EXIT_TLS_FAILURE": "Problema con la conexión cifrada",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema de autenticación",
"syncjob_EXIT_OVERQUOTA": "El buzón de destino ha superado la cuota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "No es posible conectar con el servidor remoto",
"table_size": "Tamaño de la tabla", "table_size": "Tamaño de la tabla",
"table_size_show_n": "Mostrar %s elementos", "table_size_show_n": "Mostrar %s elementos",
"templates": "Plantillas", "templates": "Plantillas",
@@ -1031,37 +1069,6 @@
"waiting_usb_register": "<i>Esperando al dispositivo USB....</i><br><br>Ingrese su contraseña arriba y confirme su registro WebAuthn tocando el botón en su dispositivo USB WebAuthn.", "waiting_usb_register": "<i>Esperando al dispositivo USB....</i><br><br>Ingrese su contraseña arriba y confirme su registro WebAuthn tocando el botón en su dispositivo USB WebAuthn.",
"yubi_otp": "Yubico OTP" "yubi_otp": "Yubico OTP"
}, },
"syncjobs": {
"sync_jobs": "Trabajos de sincronización",
"syncjob_check_log": "Comprobar registros",
"syncjob_last_run_result": "Resultado de la última ejecución",
"syncjob_EX_OK": "Éxito",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema de conexión",
"syncjob_EXIT_TLS_FAILURE": "Problema con la conexión cifrada",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema de autenticación",
"syncjob_EXIT_OVERQUOTA": "El buzón de destino ha superado la cuota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "No es posible conectar con el servidor remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nombre de usuario o contraseña incorrectos",
"add": "Añadir trabajo de sincronización",
"add_hint": "Tenga en cuenta que las contraseñas deben guardarse en texto plano sin cifrar",
"edit": "Editar trabajo de sincronización",
"create": "Crear nuevo trabajo de sincronización",
"dry": "Simular la sincronización",
"subfolder2": "Sincronizar en subcarpeta en destino<br><small>(vacío = no usar subcarpeta)</small>",
"mins_interval": "Intervalo de sondeo (minutos)",
"maxage": "Antigüedad máxima de los mensajes en días que se migrarán<br><small>(0 = ignorar edad)</small>",
"exclude": "Excluir objectos (regex)",
"automap": "Intentar enlazar carpetas (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"delete1": "Eliminar de la fuente cuando se complete",
"delete2": "Eliminar mensajes en el destino que no están en la fuente",
"delete2duplicates": "Eliminar duplicados en el destino",
"skipcrossduplicates": "Omitir mensajes duplicados en carpetas (orden de llegada)",
"subscribeall": "Suscribirse a todas las carpetas",
"timeout1": "Tiempo de espera para la conexión al host remoto",
"timeout2": "Tiempo de espera para la conexión al host local",
"maxbytespersecond": "Max. bytes por segundo <br><small>(0 = ilimitado)</small>",
"custom_params": "Parámetros personalizados"
},
"user": { "user": {
"action": "Acción", "action": "Acción",
"active": "Activo", "active": "Activo",
@@ -1076,6 +1083,7 @@
"aliases_also_send_as": "También permitido enviar como", "aliases_also_send_as": "También permitido enviar como",
"aliases_send_as_all": "No verificar permisos del remitente para los siguientes dominios (y sus aliases)", "aliases_send_as_all": "No verificar permisos del remitente para los siguientes dominios (y sus aliases)",
"change_password": "Cambiar contraseña", "change_password": "Cambiar contraseña",
"create_syncjob": "Crear nuevo trabajo de sincronización",
"created_on": "Creado", "created_on": "Creado",
"daily": "Cada día", "daily": "Cada día",
"day": "Día", "day": "Día",
@@ -1130,6 +1138,7 @@
"spamfilter_wl_desc": "Direcciones en la lista blanca <b>nunca</b> clasificarán como spam. Se puede usar comodines. Filtro solo aplica a los alias directos (alias con un solo buzón de destino) excluyendo los alias catch-all.", "spamfilter_wl_desc": "Direcciones en la lista blanca <b>nunca</b> clasificarán como spam. Se puede usar comodines. Filtro solo aplica a los alias directos (alias con un solo buzón de destino) excluyendo los alias catch-all.",
"spamfilter_yellow": "Amarillo: éste mensaje puede ser spam, será etiquetado como spam y trasladado a tu carpeta de correo no deseado", "spamfilter_yellow": "Amarillo: éste mensaje puede ser spam, será etiquetado como spam y trasladado a tu carpeta de correo no deseado",
"status": "Status", "status": "Status",
"sync_jobs": "Trabajos de sincronización",
"tag_handling": "Establecer manejo para el correo etiquetado", "tag_handling": "Establecer manejo para el correo etiquetado",
"tag_help_example": "Ejemplo de una dirección email etiquetada: mi<b>+Facebook</b>@ejemplo.org", "tag_help_example": "Ejemplo de una dirección email etiquetada: mi<b>+Facebook</b>@ejemplo.org",
"tag_help_explain": "En subcarpeta: una nueva subcarpeta llamada como la etiqueta será creada debajo de INBOX (\"INBOX/Facebook\").<br>\r\nEn asunto: los nombres de las etiquetas serán añadidos al asunto de los correos, ejemplo: \"[Facebook] Mis Noticias\".", "tag_help_explain": "En subcarpeta: una nueva subcarpeta llamada como la etiqueta será creada debajo de INBOX (\"INBOX/Facebook\").<br>\r\nEn asunto: los nombres de las etiquetas serán añadidos al asunto de los correos, ejemplo: \"[Facebook] Mis Noticias\".",
+30 -21
View File
@@ -32,14 +32,21 @@
"alias_address_info": "<small>Koko sähköpostiosoite tai @ esimerkki.com, jotta kaikki verkkotunnuksen viestit saadaan (pilkuilla erotetut). <b>mailcow vain verkkotunnukset</b>.</small>", "alias_address_info": "<small>Koko sähköpostiosoite tai @ esimerkki.com, jotta kaikki verkkotunnuksen viestit saadaan (pilkuilla erotetut). <b>mailcow vain verkkotunnukset</b>.</small>",
"alias_domain": "Alias-verkkotunnus", "alias_domain": "Alias-verkkotunnus",
"alias_domain_info": "<small>Vain kelvolliset verkkotunnukset (pilkuin erotetut).</small>", "alias_domain_info": "<small>Vain kelvolliset verkkotunnukset (pilkuin erotetut).</small>",
"automap": "Yritä yhdistää kansioita automaattisesti(\"Lähetetyt kohteet\", \"Lähetti\" => \"Lähetti\" etc.)",
"backup_mx_options": "Varmuuskopioi MX-asetukset", "backup_mx_options": "Varmuuskopioi MX-asetukset",
"comment_info": "Yksityinen kommentti ei ole näkyvissä käyttäjälle, kun taas julkinen kommentti näytetään työkaluvihjeenä siirtäessäsi sitä käyttäjän katsaukseen", "comment_info": "Yksityinen kommentti ei ole näkyvissä käyttäjälle, kun taas julkinen kommentti näytetään työkaluvihjeenä siirtäessäsi sitä käyttäjän katsaukseen",
"custom_params": "Muokatut parametrit",
"custom_params_hint": "Oikea: --param=xy, väärä: --param xy",
"delete1": "Poista lähteestä, kun se on valmis",
"delete2": "Poista määränpään viestit, joita ei ole lähteessä",
"delete2duplicates": "Poista päällekkäisyydet määräpaikasta",
"description": "Kuvaus", "description": "Kuvaus",
"destination": "Määränpää", "destination": "Määränpää",
"domain": "Verkkotunnus", "domain": "Verkkotunnus",
"domain_matches_hostname": "Verkkotunnus %s vastaa isäntänimeä", "domain_matches_hostname": "Verkkotunnus %s vastaa isäntänimeä",
"domain_quota_m": "Verkkotunnuksen kokonaiskiintiö (MiB)", "domain_quota_m": "Verkkotunnuksen kokonaiskiintiö (MiB)",
"enc_method": "Salausmenetelmä", "enc_method": "Salausmenetelmä",
"exclude": "Sulje pois objects (regex)",
"full_name": "Koko nimi", "full_name": "Koko nimi",
"gal": "Yleinen osoite luettelo", "gal": "Yleinen osoite luettelo",
"gal_info": "GAL ( kalenteri ) sisältää kaikki verkkotunnus alueen objektit, eikä yksikään käyttäjä voi muokata sitä. Vapaat ja varatut ajat-tiedot SOGosta puuttuu, jos se on poistettu käytöstä! <b>Uudelleen käynnistä SOGo ja ota muutokset käyttöön.</b>", "gal_info": "GAL ( kalenteri ) sisältää kaikki verkkotunnus alueen objektit, eikä yksikään käyttäjä voi muokata sitä. Vapaat ja varatut ajat-tiedot SOGosta puuttuu, jos se on poistettu käytöstä! <b>Uudelleen käynnistä SOGo ja ota muutokset käyttöön.</b>",
@@ -54,6 +61,7 @@
"mailbox_username": "Käyttäjätunnus (sähköpostiosoitteen osa)", "mailbox_username": "Käyttäjätunnus (sähköpostiosoitteen osa)",
"max_aliases": "Max. mahdolliset aliakset", "max_aliases": "Max. mahdolliset aliakset",
"max_mailboxes": "Max. mahdolliset sähkö postilaatikot", "max_mailboxes": "Max. mahdolliset sähkö postilaatikot",
"mins_interval": "Äänestysväli (minuutit)",
"multiple_bookings": "Useita varauksia", "multiple_bookings": "Useita varauksia",
"nexthop": "Seuraava pysäkki", "nexthop": "Seuraava pysäkki",
"password": "Salasana", "password": "Salasana",
@@ -71,9 +79,15 @@
"select_domain": "Valitse ensin verkkotunnus", "select_domain": "Valitse ensin verkkotunnus",
"sieve_desc": "Lyhyt kuvaus", "sieve_desc": "Lyhyt kuvaus",
"sieve_type": "Suodattimen tyyppi", "sieve_type": "Suodattimen tyyppi",
"skipcrossduplicates": "Ohita päällekkäiset viestit kansioiden välillä (ensimmäinen tulee, ensimmäinen palvella)",
"subscribeall": "Tilaa kaikki kansiot",
"syncjob": "Lisää synkronointityö",
"syncjob_hint": "Huomaa, että salasanat on tallennettava pelkkänä tekstinä!",
"target_address": "Siirry osoitteisiin", "target_address": "Siirry osoitteisiin",
"target_address_info": "<small>Koko sähköpostiosoite (pilkuilla erotettu).</small>", "target_address_info": "<small>Koko sähköpostiosoite (pilkuilla erotettu).</small>",
"target_domain": "Kohdetunnus", "target_domain": "Kohdetunnus",
"timeout1": "Yhteyden aikakatkaisu etäisäntään",
"timeout2": "Aikakatkaisu yhteyden muodostamiseen paikalliseen isäntään",
"username": "Käyttäjätunnus", "username": "Käyttäjätunnus",
"validate": "Vahvista", "validate": "Vahvista",
"validation_success": "Vahvistettu onnistuneesti" "validation_success": "Vahvistettu onnistuneesti"
@@ -391,11 +405,15 @@
"edit": { "edit": {
"active": "Aktiivinen", "active": "Aktiivinen",
"alias": "Muokkaa aliasta", "alias": "Muokkaa aliasta",
"automap": "Yritä yhdistää kansioita automaattisesti(\"Lähetetyt kohteet\", \"Lähetti\" => \"Lähetti\" etc.)",
"backup_mx_options": "Varmuus kopioinnin MX-asetukset", "backup_mx_options": "Varmuus kopioinnin MX-asetukset",
"bcc_dest_format": "BCC-piilo-kopio kohteen on oltava yksittäinen kelvollinen sähköposti osoite.", "bcc_dest_format": "BCC-piilo-kopio kohteen on oltava yksittäinen kelvollinen sähköposti osoite.",
"client_id": "Asiakastunnus ID", "client_id": "Asiakastunnus ID",
"client_secret": "Asiakas salaisuus", "client_secret": "Asiakas salaisuus",
"comment_info": "Yksityinen kommentti ei ole näkyvissä käyttäjälle, kun taas julkinen kommentti näytetään työkaluvihjeenä siirtäessäsi sitä käyttäjän katsaukseen", "comment_info": "Yksityinen kommentti ei ole näkyvissä käyttäjälle, kun taas julkinen kommentti näytetään työkaluvihjeenä siirtäessäsi sitä käyttäjän katsaukseen",
"delete1": "Poista lähteestä, kun se on valmis",
"delete2": "Poista määränpään viestit, joita ei ole lähteessä",
"delete2duplicates": "Poista päällekkäisyydet määräpaikasta",
"description": "Kuvaus", "description": "Kuvaus",
"domain": "Muokkaa verkkotunnus aluetta", "domain": "Muokkaa verkkotunnus aluetta",
"domain_admin": "Muokkaa verkkotunnus alueen järjestelmänvalvojaa", "domain_admin": "Muokkaa verkkotunnus alueen järjestelmänvalvojaa",
@@ -404,6 +422,7 @@
"dont_check_sender_acl": "Poista lähettäjän verkkotunnus alueen tarkistus käytöstä %s (+ alias-verkkotunnukset)", "dont_check_sender_acl": "Poista lähettäjän verkkotunnus alueen tarkistus käytöstä %s (+ alias-verkkotunnukset)",
"edit_alias_domain": "Alias-verkkotunnus alueen muokkaaminen", "edit_alias_domain": "Alias-verkkotunnus alueen muokkaaminen",
"encryption": "Salaus", "encryption": "Salaus",
"exclude": "Objektien jättäminen pois (regex)",
"extended_sender_acl": "Ulkoisten lähettäjien osoitteet", "extended_sender_acl": "Ulkoisten lähettäjien osoitteet",
"extended_sender_acl_info": "DKIM-verkkotunnuksen avain tulisi tuoda, jos se on käytettävissä.<br>\r\nMuista lisätä tämä palvelin vastaavaan SPF TXT-tietueeseen.<br>\r\nAina kun verkkotunnus tai aliaksen verkkotunnus lisätään tähän palvelimeen, joka on päällekkäinen ulkoisen osoitteen kanssa, ulkoinen osoite poistetaan.<br>\r\nKäytä @ domain.tld antaaksesi lähettää muodossa *@domain.tld.", "extended_sender_acl_info": "DKIM-verkkotunnuksen avain tulisi tuoda, jos se on käytettävissä.<br>\r\nMuista lisätä tämä palvelin vastaavaan SPF TXT-tietueeseen.<br>\r\nAina kun verkkotunnus tai aliaksen verkkotunnus lisätään tähän palvelimeen, joka on päällekkäinen ulkoisen osoitteen kanssa, ulkoinen osoite poistetaan.<br>\r\nKäytä @ domain.tld antaaksesi lähettää muodossa *@domain.tld.",
"force_pw_update": "Pakota salasanan vaihto seuraavan sisään kirjautumisen jälkeen", "force_pw_update": "Pakota salasanan vaihto seuraavan sisään kirjautumisen jälkeen",
@@ -421,7 +440,10 @@
"max_aliases": "Maks. Aliaksia", "max_aliases": "Maks. Aliaksia",
"max_mailboxes": "Maksimi. mahdolliset posti laatikot", "max_mailboxes": "Maksimi. mahdolliset posti laatikot",
"max_quota": "Maks. Kiintiö sähköposti laatikkoa kohden (MiB)", "max_quota": "Maks. Kiintiö sähköposti laatikkoa kohden (MiB)",
"maxage": "Viestien enimmäisikä päivinä, jotka lähetetään kyselyllä etänä<br><small>(0 = ignore age)</small>",
"maxbytespersecond": "Maksimi. tavua sekunnissa <br><small>(0 = rajoittamaton)</small>",
"mbox_rl_info": "Tätä nopeutta käytetään SASL-Kirjautumisnimessä, se vastaa mitä tahansa kirjautuneen käyttäjän käyttämän osoitteen \"from\"-osoitetta.Sähköpostin nopeus rajoitus ohittaa toimialuelaajuisen kappale rajoituksen.", "mbox_rl_info": "Tätä nopeutta käytetään SASL-Kirjautumisnimessä, se vastaa mitä tahansa kirjautuneen käyttäjän käyttämän osoitteen \"from\"-osoitetta.Sähköpostin nopeus rajoitus ohittaa toimialuelaajuisen kappale rajoituksen.",
"mins_interval": "Aikaväli (min)",
"multiple_bookings": "Useita varauksia", "multiple_bookings": "Useita varauksia",
"nexthop": "Seuraava pysäkki", "nexthop": "Seuraava pysäkki",
"password": "Salasana", "password": "Salasana",
@@ -444,13 +466,18 @@
"sender_acl_info": "Jos postilaatikon käyttäjän A sallitaan lähettävän postilaatikon käyttäjäksi B, lähettäjän osoitetta ei näytetä automaattisesti valittavana \"alkaen\" -kentässä SOGossa.<br>\r\nSähkö postilaatikon käyttäjän A on luotava valtuutus SOGoon, jotta sähkö postilaatikon käyttäjä b voi valita osoitteen lähettäjäksi. Tämä käyttäytyminen ei koske alias-osoitteita", "sender_acl_info": "Jos postilaatikon käyttäjän A sallitaan lähettävän postilaatikon käyttäjäksi B, lähettäjän osoitetta ei näytetä automaattisesti valittavana \"alkaen\" -kentässä SOGossa.<br>\r\nSähkö postilaatikon käyttäjän A on luotava valtuutus SOGoon, jotta sähkö postilaatikon käyttäjä b voi valita osoitteen lähettäjäksi. Tämä käyttäytyminen ei koske alias-osoitteita",
"sieve_desc": "Lyhyt kuvaus", "sieve_desc": "Lyhyt kuvaus",
"sieve_type": "Suodattimen tyyppi", "sieve_type": "Suodattimen tyyppi",
"skipcrossduplicates": "Ohita päällekkäiset viestit kansioiden välillä (ensimmäinen tulee, ensimmäinen palvella)",
"sogo_visible": "Alias näkyy SOGo-kohteessa", "sogo_visible": "Alias näkyy SOGo-kohteessa",
"sogo_visible_info": "Tämä asetus vaikuttaa vain objekteihin, jotka voidaan näyttää SOGo (jaetut tai ei-jaetut alias-osoitteet, jotka viittaavat vähintään yhteen paikalliseen posti laatikkoon).", "sogo_visible_info": "Tämä asetus vaikuttaa vain objekteihin, jotka voidaan näyttää SOGo (jaetut tai ei-jaetut alias-osoitteet, jotka viittaavat vähintään yhteen paikalliseen posti laatikkoon).",
"spam_alias": "Luo tai muuta aika rajoitettuja aliaksen osoitteita", "spam_alias": "Luo tai muuta aika rajoitettuja aliaksen osoitteita",
"spam_policy": "Lisää tai Poista kohteita sallituista-/mustalta listalta", "spam_policy": "Lisää tai Poista kohteita sallituista-/mustalta listalta",
"spam_score": "Mukautetun roska posti arvon asettaminen", "spam_score": "Mukautetun roska posti arvon asettaminen",
"subfolder2": "Synkronoi kohteen alikansioon<br><small>(tyhjä = älä käytä alikansioon)</small>",
"syncjob": "Muokkaa synkronointi työtä",
"target_address": "Siirry osoitteeseen/es <small>(pilkulla-erotettu)</small>", "target_address": "Siirry osoitteeseen/es <small>(pilkulla-erotettu)</small>",
"target_domain": "Kohde verkkotunnus alue", "target_domain": "Kohde verkkotunnus alue",
"timeout1": "Yhteyden aikakatkaisu etäisäntään",
"timeout2": "Aikakatkaisu yhteyden muodostamiseen paikalliseen isäntään",
"title": "Muokkaa objektia", "title": "Muokkaa objektia",
"unchanged_if_empty": "Jos muuttumaton jätä tyhjäksi", "unchanged_if_empty": "Jos muuttumaton jätä tyhjäksi",
"username": "Käyttäjätunnus", "username": "Käyttäjätunnus",
@@ -596,6 +623,7 @@
"sogo_visible_y": "Näytä alias SOGo-kohteessa", "sogo_visible_y": "Näytä alias SOGo-kohteessa",
"spam_aliases": "Temp. alias", "spam_aliases": "Temp. alias",
"status": "Tila", "status": "Tila",
"sync_jobs": "Synkronoi työt",
"target_address": "Siiretty osoitteseen", "target_address": "Siiretty osoitteseen",
"target_domain": "Kohde verkkotunnus alue", "target_domain": "Kohde verkkotunnus alue",
"tls_enforce_in": "Pakota TLS saapuva", "tls_enforce_in": "Pakota TLS saapuva",
@@ -758,27 +786,6 @@
"waiting_usb_register": "<i>Odotetaan USB-laitetta...</i><br><br>Anna salasanasi yltä ja vahvista WebAuthn-rekisteröinti napauttamalla painiketta WebAuthn USB-laitteessa.", "waiting_usb_register": "<i>Odotetaan USB-laitetta...</i><br><br>Anna salasanasi yltä ja vahvista WebAuthn-rekisteröinti napauttamalla painiketta WebAuthn USB-laitteessa.",
"yubi_otp": "Yubico OTP-todennus" "yubi_otp": "Yubico OTP-todennus"
}, },
"syncjobs": {
"sync_jobs": "Synkronoi työt",
"add": "Lisää synkronointityö",
"add_hint": "Huomaa, että salasanat on tallennettava pelkkänä tekstinä!",
"edit": "Muokkaa synkronointi työtä",
"create": "Luo uusi synkronointi työ",
"subfolder2": "Synkronoi kohteen alikansioon<br><small>(tyhjä = älä käytä alikansioon)</small>",
"mins_interval": "Äänestysväli (minuutit)",
"maxage": "Viestien enimmäisikä päivinä, jotka lähetetään kyselyllä etänä<br><small>(0 = ignore age)</small>",
"exclude": "Sulje pois objects (regex)",
"automap": "Yritä yhdistää kansioita automaattisesti(\"Lähetetyt kohteet\", \"Lähetti\" => \"Lähetti\" etc.)",
"delete1": "Poista lähteestä, kun se on valmis",
"delete2": "Poista määränpään viestit, joita ei ole lähteessä",
"delete2duplicates": "Poista päällekkäisyydet määräpaikasta",
"skipcrossduplicates": "Ohita päällekkäiset viestit kansioiden välillä (ensimmäinen tulee, ensimmäinen palvella)",
"subscribeall": "Tilaa kaikki kansiot",
"timeout1": "Yhteyden aikakatkaisu etäisäntään",
"timeout2": "Aikakatkaisu yhteyden muodostamiseen paikalliseen isäntään",
"maxbytespersecond": "Maksimi. tavua sekunnissa <br><small>(0 = rajoittamaton)</small>",
"custom_params": "Muokatut parametrit"
},
"user": { "user": {
"action": "Toiminnot", "action": "Toiminnot",
"active": "Aktiivinen", "active": "Aktiivinen",
@@ -795,6 +802,7 @@
"aliases_send_as_all": "Älä tarkista lähettäjän käyttö oikeuksia seuraaville verkkotunnus alueille ja sen alias-verkko tunnuksille", "aliases_send_as_all": "Älä tarkista lähettäjän käyttö oikeuksia seuraaville verkkotunnus alueille ja sen alias-verkko tunnuksille",
"change_password": "Vaihda salasana", "change_password": "Vaihda salasana",
"client_configuration": "Näytä sähköposti ohjelmien ja älypuhelimien konfigurointi oppaat", "client_configuration": "Näytä sähköposti ohjelmien ja älypuhelimien konfigurointi oppaat",
"create_syncjob": "Luo uusi synkronointi työ",
"daily": "Päivittäin", "daily": "Päivittäin",
"day": "Päivä", "day": "Päivä",
"description": "Kuvaus", "description": "Kuvaus",
@@ -856,6 +864,7 @@
"spamfilter_wl_desc": "Sallittujen sähköposti osoitteita<b> ei koskaan</b> luokitella roska postiksi. Yleismerkkejä, voidaan käyttää. Suodatinta käytetään vain suora aliaksia (aliaksia, joilla on yksi kohde posti laatikko), lukuun ottamatta ( catch-all )saalistaa-kaikki aliaksia ja itse posti laatikkoa..", "spamfilter_wl_desc": "Sallittujen sähköposti osoitteita<b> ei koskaan</b> luokitella roska postiksi. Yleismerkkejä, voidaan käyttää. Suodatinta käytetään vain suora aliaksia (aliaksia, joilla on yksi kohde posti laatikko), lukuun ottamatta ( catch-all )saalistaa-kaikki aliaksia ja itse posti laatikkoa..",
"spamfilter_yellow": "Keltainen: Tämä viesti voi olla roska postia, merkitään roska postiksi ja siirretään roska posti kansioon", "spamfilter_yellow": "Keltainen: Tämä viesti voi olla roska postia, merkitään roska postiksi ja siirretään roska posti kansioon",
"status": "Tila", "status": "Tila",
"sync_jobs": "Synkronoi työt",
"tag_handling": "Merkittyjen Sähkö posti viestien käsittelyn määrittäminen", "tag_handling": "Merkittyjen Sähkö posti viestien käsittelyn määrittäminen",
"tag_help_example": "Esimerkki merkitylle Sähkö posti osoitteeksi: me<b>+Facebook</b>@example.org", "tag_help_example": "Esimerkki merkitylle Sähkö posti osoitteeksi: me<b>+Facebook</b>@example.org",
"tag_help_explain": "Alikansio: uusi tunnisteen jälkeen nimetty alikansio luodaan Saapuneet-kansion alapuolelle (\"Saapuneet/Facebook\").<br>\r\nAihe: tunnisteiden nimi lisätään sähkö postit-aiheeseen, esimerkiksi: \"[Facebook] My News\".", "tag_help_explain": "Alikansio: uusi tunnisteen jälkeen nimetty alikansio luodaan Saapuneet-kansion alapuolelle (\"Saapuneet/Facebook\").<br>\r\nAihe: tunnisteiden nimi lisätään sähkö postit-aiheeseen, esimerkiksi: \"[Facebook] My News\".",
+49 -31
View File
@@ -43,8 +43,14 @@
"alias_domain_info": "<small>Seulement des noms de domaines valides (séparés par des virgules).</small>", "alias_domain_info": "<small>Seulement des noms de domaines valides (séparés par des virgules).</small>",
"app_name": "Nom de l'application", "app_name": "Nom de l'application",
"app_password": "Mot de passe de l'application", "app_password": "Mot de passe de l'application",
"automap": "Tenter de cibler automatiquement les dossiers (« Sent items », « Sent » => « Sent » etc.)",
"backup_mx_options": "Options de MX secondaire", "backup_mx_options": "Options de MX secondaire",
"comment_info": "Un commentaire privé n'est pas visible pour l'utilisateur, tandis qu'un commentaire public est affiché sous forme d'info-bulle lorsqu'on le survole dans un aperçu des utilisateurs", "comment_info": "Un commentaire privé n'est pas visible pour l'utilisateur, tandis qu'un commentaire public est affiché sous forme d'info-bulle lorsqu'on le survole dans un aperçu des utilisateurs",
"custom_params": "Paramètres personnalisés",
"custom_params_hint": "Correct : --param=xy, incorrect : --param xy",
"delete1": "Supprimer sur la source lorsque la synchronisation est terminée",
"delete2": "Supprimer les messages sur la destination qui ne sont pas présents sur la source",
"delete2duplicates": "Supprimer les doublons sur la destination",
"description": "Description", "description": "Description",
"destination": "Destination", "destination": "Destination",
"disable_login": "Désactiver l'authentification (les courriels entrants resteront acceptés)", "disable_login": "Désactiver l'authentification (les courriels entrants resteront acceptés)",
@@ -52,6 +58,7 @@
"domain_matches_hostname": "Le domaine %s correspond à la machine (hostname)", "domain_matches_hostname": "Le domaine %s correspond à la machine (hostname)",
"domain_quota_m": "Quota total du domaine (Mio)", "domain_quota_m": "Quota total du domaine (Mio)",
"enc_method": "Méthode de chiffrement", "enc_method": "Méthode de chiffrement",
"exclude": "Exclure des objets (expression régulière - regex)",
"full_name": "Nom complet", "full_name": "Nom complet",
"gal": "Carnet d'Adresses Global (GAL)", "gal": "Carnet d'Adresses Global (GAL)",
"gal_info": "La liste d'adresse globale (GAL) contient tous les objets d'un domaine et ne peut être modifié par aucun utilisateur. Si elles sont désactivées,les informations libres/occupées dans SOGo sont cachées, ! <b>Redémarrez SOGo pour appliquer les changements.</b>", "gal_info": "La liste d'adresse globale (GAL) contient tous les objets d'un domaine et ne peut être modifié par aucun utilisateur. Si elles sont désactivées,les informations libres/occupées dans SOGo sont cachées, ! <b>Redémarrez SOGo pour appliquer les changements.</b>",
@@ -67,6 +74,7 @@
"mailbox_username": "Identifiant (partie gauche d'une adresse de courriel)", "mailbox_username": "Identifiant (partie gauche d'une adresse de courriel)",
"max_aliases": "Nombre maximal d'alias", "max_aliases": "Nombre maximal d'alias",
"max_mailboxes": "Nombre maximal de boîtes de réception", "max_mailboxes": "Nombre maximal de boîtes de réception",
"mins_interval": "Période de relève (minutes)",
"multiple_bookings": "Inscriptions multiples", "multiple_bookings": "Inscriptions multiples",
"nexthop": "Suivant", "nexthop": "Suivant",
"password": "Mot de passe", "password": "Mot de passe",
@@ -86,15 +94,22 @@
"select_domain": "Sélectionner d'abord un domaine", "select_domain": "Sélectionner d'abord un domaine",
"sieve_desc": "Description courte", "sieve_desc": "Description courte",
"sieve_type": "Type de filtre", "sieve_type": "Type de filtre",
"skipcrossduplicates": "Ignorer les messages en double dans les dossiers (premier arrivé, premier servi)",
"subscribeall": "S'abonner à tous les dossiers",
"syncjob": "Ajouter une tâche de synchronisation",
"syncjob_hint": "Sachez que les mots de passe seront enregistrés en clair !",
"target_address": "Aller à l'adresse", "target_address": "Aller à l'adresse",
"target_address_info": "<small>Adresse(s) de courriel complète(s) (séparées par des virgules).</small>", "target_address_info": "<small>Adresse(s) de courriel complète(s) (séparées par des virgules).</small>",
"target_domain": "Domaine cible", "target_domain": "Domaine cible",
"timeout1": "Délai d'expiration pour la connexion à l'hôte distant",
"timeout2": "Délai d'expiration pour la connexion à l'hôte local",
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
"validate": "Valider", "validate": "Valider",
"validation_success": "Validation réussie", "validation_success": "Validation réussie",
"bcc_dest_format": "La destination Cci doit être une seule adresse de courriel valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.", "bcc_dest_format": "La destination Cci doit être une seule adresse de courriel valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
"tags": "Etiquettes", "tags": "Etiquettes",
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application", "app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application",
"dry": "Simuler la synchronisation",
"internal": "Interne", "internal": "Interne",
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.", "internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.",
"sender_allowed": "Autoriser l'envoi sous cet alias" "sender_allowed": "Autoriser l'envoi sous cet alias"
@@ -604,11 +619,15 @@
"allowed_protocols": "Protocoles autorisés pour l'accès direct de l'utilisateur (n'affecte pas les protocoles de mot de passe de l'application)", "allowed_protocols": "Protocoles autorisés pour l'accès direct de l'utilisateur (n'affecte pas les protocoles de mot de passe de l'application)",
"app_name": "Nom de l'application", "app_name": "Nom de l'application",
"app_passwd": "Mot de passe de l'application", "app_passwd": "Mot de passe de l'application",
"automap": "Tenter de cibler automatiquement les dossiers (« Sent items », « Sent » => « Sent » etc.)",
"backup_mx_options": "Options Backup MX", "backup_mx_options": "Options Backup MX",
"bcc_dest_format": "La destination BCC doit être une seule adresse de courriel valide.", "bcc_dest_format": "La destination BCC doit être une seule adresse de courriel valide.",
"client_id": "ID client", "client_id": "ID client",
"client_secret": "Secret client", "client_secret": "Secret client",
"comment_info": "Un commentaire privé n’est pas visible pour l’utilisateur, tandis qu’un commentaire public est affiché comme infobulle lorsque vous le placez dans un aperçu des utilisateurs", "comment_info": "Un commentaire privé n’est pas visible pour l’utilisateur, tandis qu’un commentaire public est affiché comme infobulle lorsque vous le placez dans un aperçu des utilisateurs",
"delete1": "Supprimer de la source une fois terminé",
"delete2": "Supprimer les messages sur la destination qui ne sont pas sur la source",
"delete2duplicates": "Supprimer les doublons sur la destination",
"delete_ays": "Veuillez confirmer le processus de suppression.", "delete_ays": "Veuillez confirmer le processus de suppression.",
"description": "Description", "description": "Description",
"disable_login": "Refuser l’ouverture de session (le courrier entrant est toujours accepté)", "disable_login": "Refuser l’ouverture de session (le courrier entrant est toujours accepté)",
@@ -619,6 +638,7 @@
"dont_check_sender_acl": "Désactiver la vérification de l’expéditeur pour le domaine %s (+ alias de domaines)", "dont_check_sender_acl": "Désactiver la vérification de l’expéditeur pour le domaine %s (+ alias de domaines)",
"edit_alias_domain": "Édition des alias de domaine", "edit_alias_domain": "Édition des alias de domaine",
"encryption": "Chiffrement", "encryption": "Chiffrement",
"exclude": "Exclure des objets (regex)",
"extended_sender_acl": "Adresses de l’expéditeur externe", "extended_sender_acl": "Adresses de l’expéditeur externe",
"extended_sender_acl_info": "Une clé de domaine DKIM doit être importée, si disponible.<br>\r\n N’oubliez pas d’ajouter ce serveur à l’enregistrement TXT SPF correspondant.<br>\r\n Chaque fois qu’un domaine ou un alias de domaine est ajouté à ce serveur, et qui chevauche une adresse externe, l’adresse externe est supprimée.<br>\r\n Utiliser @domain.tld pour permettre l'envoi comme *@domain.tld.", "extended_sender_acl_info": "Une clé de domaine DKIM doit être importée, si disponible.<br>\r\n N’oubliez pas d’ajouter ce serveur à l’enregistrement TXT SPF correspondant.<br>\r\n Chaque fois qu’un domaine ou un alias de domaine est ajouté à ce serveur, et qui chevauche une adresse externe, l’adresse externe est supprimée.<br>\r\n Utiliser @domain.tld pour permettre l'envoi comme *@domain.tld.",
"force_pw_update": "Forcer la mise à jour du mot de passe à la prochaine ouverture de session", "force_pw_update": "Forcer la mise à jour du mot de passe à la prochaine ouverture de session",
@@ -637,7 +657,10 @@
"max_aliases": "Nombre max. d'alias", "max_aliases": "Nombre max. d'alias",
"max_mailboxes": "Nombre max. de boîte de réception possibles", "max_mailboxes": "Nombre max. de boîte de réception possibles",
"max_quota": "Quota max. par boîte de réception (Mio)", "max_quota": "Quota max. par boîte de réception (Mio)",
"maxage": "Âge maximal en jours des messages qui seront consultés à distance<br><small>(0 = ignorer la durée)</small>",
"maxbytespersecond": "Octets max. par seconde <br><small>(0 = pas de limite)</small>",
"mbox_rl_info": "Cette limite d'envoi est appliquée au nom de connexion SASL, elle correspond à toute adresse « from » utilisée par l’utilisateur connecté. Une limite d'envoi pour les boîtes de réception remplace une limite d'envoi pour l’ensemble du domaine.", "mbox_rl_info": "Cette limite d'envoi est appliquée au nom de connexion SASL, elle correspond à toute adresse « from » utilisée par l’utilisateur connecté. Une limite d'envoi pour les boîtes de réception remplace une limite d'envoi pour l’ensemble du domaine.",
"mins_interval": "Intervalle (min)",
"multiple_bookings": "Réservations multiples", "multiple_bookings": "Réservations multiples",
"nexthop": "Saut suivant", "nexthop": "Saut suivant",
"password": "Mot de passe", "password": "Mot de passe",
@@ -672,14 +695,19 @@
"sender_acl_info": "Si l’utilisateur de la boîte de réception A est autorisé à envoyer en tant qu’utilisateur de la boîte de réception B, l’adresse de l’expéditeur n’est pas automatiquement affichée comme sélectionnable du champ « de » dans SOGo.<br>\n L’utilisateur B de la boîte de réception doit créer une délégation dans SOGo pour permettre à l’utilisateur A de la boîte de réception de sélectionner son adresse comme expéditeur. Pour déléguer une boîte de réception dans SOGo, utilisez le menu (trois points) à droite du nom de votre boîte dans le coin supérieur gauche dans la vue de courrier. Ce comportement ne s’applique pas aux adresses alias.", "sender_acl_info": "Si l’utilisateur de la boîte de réception A est autorisé à envoyer en tant qu’utilisateur de la boîte de réception B, l’adresse de l’expéditeur n’est pas automatiquement affichée comme sélectionnable du champ « de » dans SOGo.<br>\n L’utilisateur B de la boîte de réception doit créer une délégation dans SOGo pour permettre à l’utilisateur A de la boîte de réception de sélectionner son adresse comme expéditeur. Pour déléguer une boîte de réception dans SOGo, utilisez le menu (trois points) à droite du nom de votre boîte dans le coin supérieur gauche dans la vue de courrier. Ce comportement ne s’applique pas aux adresses alias.",
"sieve_desc": "Description courte", "sieve_desc": "Description courte",
"sieve_type": "Type de filtre", "sieve_type": "Type de filtre",
"skipcrossduplicates": "Ignorer les messages en double dans les dossiers (premier arrivé, premier servi)",
"sogo_visible": "Alias visible dans SOGo", "sogo_visible": "Alias visible dans SOGo",
"sogo_visible_info": "Cette option affecte uniquement les objets qui peuvent être affichés dans SOGo (adresses alias partagées ou non partagées pointant vers au moins une boîte de réception locale). Si caché, un alias n’apparaîtra pas comme expéditeur sélectionnable dans SOGo.", "sogo_visible_info": "Cette option affecte uniquement les objets qui peuvent être affichés dans SOGo (adresses alias partagées ou non partagées pointant vers au moins une boîte de réception locale). Si caché, un alias n’apparaîtra pas comme expéditeur sélectionnable dans SOGo.",
"spam_alias": "Créer ou modifier des adresses alias limitées dans le temps", "spam_alias": "Créer ou modifier des adresses alias limitées dans le temps",
"spam_filter": "Filtre spam", "spam_filter": "Filtre spam",
"spam_policy": "Ajouter ou supprimer des éléments à la liste blanche/noire", "spam_policy": "Ajouter ou supprimer des éléments à la liste blanche/noire",
"spam_score": "Définir un score spam personnalisé", "spam_score": "Définir un score spam personnalisé",
"subfolder2": "Synchronisation dans le sous-dossier sur la destination<br><small>(vide = ne pas utiliser de sous-dossier)</small>",
"syncjob": "Modifier la tâche de synchronisation",
"target_address": "Adresse(s) Goto <small>(séparé(s) par des virgules)</small>", "target_address": "Adresse(s) Goto <small>(séparé(s) par des virgules)</small>",
"target_domain": "Domaine cible", "target_domain": "Domaine cible",
"timeout1": "Délai de connexion à l’hôte distant",
"timeout2": "Délai de connexion à l’hôte local",
"title": "Éditer l'objet", "title": "Éditer l'objet",
"unchanged_if_empty": "Si non modifié, laisser en blanc", "unchanged_if_empty": "Si non modifié, laisser en blanc",
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
@@ -918,6 +946,7 @@
"spam_aliases": "Alias temporaire", "spam_aliases": "Alias temporaire",
"stats": "Statistiques", "stats": "Statistiques",
"status": "Statut", "status": "Statut",
"sync_jobs": "Tâches de synchronisation",
"table_size": "Taille de la table", "table_size": "Taille de la table",
"table_size_show_n": "Montrer %s articles", "table_size_show_n": "Montrer %s articles",
"target_address": "Goto adresse", "target_address": "Goto adresse",
@@ -947,11 +976,20 @@
"mailbox_templates": "Modèle de boîte de réception", "mailbox_templates": "Modèle de boîte de réception",
"relay_unknown": "Relayer les boîtes de réception inconnues", "relay_unknown": "Relayer les boîtes de réception inconnues",
"all_domains": "Tous les domaines", "all_domains": "Tous les domaines",
"syncjob_EXIT_OVERQUOTA": "Quota dépassé de la boîte de réception cible",
"domain_templates": "Modèles de domaine", "domain_templates": "Modèles de domaine",
"syncjob_last_run_result": "Résultat de la dernière exécution",
"sender": "Expéditeur", "sender": "Expéditeur",
"add_template": "Ajouter un modèle", "add_template": "Ajouter un modèle",
"syncjob_EX_OK": "Succès",
"syncjob_EXIT_CONNECTION_FAILURE": "Problème de connexion",
"syncjob_EXIT_TLS_FAILURE": "Problème avec le chiffrement de la connexion",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problème d'authentification",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossible de se connecter au serveur distant",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Utilisateur ou mot de passe erroné",
"templates": "Modèles", "templates": "Modèles",
"template": "Modèle", "template": "Modèle",
"syncjob_check_log": "Vérifier le journal",
"recipient": "Destinataire", "recipient": "Destinataire",
"open_logs": "Afficher les journaux", "open_logs": "Afficher les journaux",
"iam": "Fournisseur d'identité", "iam": "Fournisseur d'identité",
@@ -1178,37 +1216,6 @@
"fido2_validation_failed": "La validation a échoué", "fido2_validation_failed": "La validation a échoué",
"set_fido2_touchid": "Enregistrer Touch ID sur Apple M1" "set_fido2_touchid": "Enregistrer Touch ID sur Apple M1"
}, },
"syncjobs": {
"sync_jobs": "Tâches de synchronisation",
"syncjob_check_log": "Vérifier le journal",
"syncjob_last_run_result": "Résultat du dernier lancement",
"syncjob_EX_OK": "Succès",
"syncjob_EXIT_CONNECTION_FAILURE": "Problème de connexion",
"syncjob_EXIT_TLS_FAILURE": "Problème de connexion chiffrée",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problème d'authentification",
"syncjob_EXIT_OVERQUOTA": "La boîte de réception cible a dépassé le quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossible de se connecter au serveur distant",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nom d'utilisateur ou mot de passe incorrect",
"add": "Ajouter une tâche de synchronisation",
"add_hint": "Sachez que les mots de passe seront enregistrés en clair !",
"edit": "Modifier la tâche de synchronisation",
"create": "Créer une tâche de synchronisation",
"dry": "Simuler la synchronisation",
"subfolder2": "Synchronisation dans le sous-dossier sur la destination<br><small>(vide = ne pas utiliser de sous-dossier)</small>",
"mins_interval": "Période de relève (minutes)",
"maxage": "Âge maximal en jours des messages qui seront consultés à distance<br><small>(0 = ignorer la durée)</small>",
"exclude": "Exclure des objets (expression régulière - regex)",
"automap": "Tenter de cibler automatiquement les dossiers (« Sent items », « Sent » => « Sent » etc.)",
"delete1": "Supprimer sur la source lorsque la synchronisation est terminée",
"delete2": "Supprimer les messages sur la destination qui ne sont pas présents sur la source",
"delete2duplicates": "Supprimer les doublons sur la destination",
"skipcrossduplicates": "Ignorer les messages en double dans les dossiers (premier arrivé, premier servi)",
"subscribeall": "S'abonner à tous les dossiers",
"timeout1": "Délai d'expiration pour la connexion à l'hôte distant",
"timeout2": "Délai d'expiration pour la connexion à l'hôte local",
"maxbytespersecond": "Octets max. par seconde <br><small>(0 = pas de limite)</small>",
"custom_params": "Paramètres personnalisés"
},
"user": { "user": {
"action": "Action", "action": "Action",
"active": "Actif", "active": "Actif",
@@ -1233,6 +1240,7 @@
"change_password": "Changer le mot de passe", "change_password": "Changer le mot de passe",
"client_configuration": "Afficher les guides de configuration pour les clients de messagerie et les smartphones", "client_configuration": "Afficher les guides de configuration pour les clients de messagerie et les smartphones",
"create_app_passwd": "Créer un mot de passe application", "create_app_passwd": "Créer un mot de passe application",
"create_syncjob": "Créer une tâche de synchronisation",
"daily": "Quotidien", "daily": "Quotidien",
"day": "jour", "day": "jour",
"description": "Description", "description": "Description",
@@ -1317,6 +1325,7 @@
"spamfilter_wl_desc": "La liste blanche est programmée pour <b> ne jamais</b> classer comme spam les adresses de courriel qu'elle contient. Des caractères génériques peuvent être utilisés. Un filtre n’est appliqué qu’aux alias directs (alias avec une seule boîte de réception cible), à l’exclusion des alias attrape-tout et d’une boîte de réception.", "spamfilter_wl_desc": "La liste blanche est programmée pour <b> ne jamais</b> classer comme spam les adresses de courriel qu'elle contient. Des caractères génériques peuvent être utilisés. Un filtre n’est appliqué qu’aux alias directs (alias avec une seule boîte de réception cible), à l’exclusion des alias attrape-tout et d’une boîte de réception.",
"spamfilter_yellow": "Jaune : ce message est peut-être un spam, il sera étiqueté comme spam et déplacé vers votre dossier Pourriel", "spamfilter_yellow": "Jaune : ce message est peut-être un spam, il sera étiqueté comme spam et déplacé vers votre dossier Pourriel",
"status": "Statut", "status": "Statut",
"sync_jobs": "Tâches de synchronisation",
"tag_handling": "Régler la manipulation du courrier étiqueté", "tag_handling": "Régler la manipulation du courrier étiqueté",
"tag_help_example": "Exemple pour une adresse de courriel étiquetée : me<b>+Facebook</b>@example.org", "tag_help_example": "Exemple pour une adresse de courriel étiquetée : me<b>+Facebook</b>@example.org",
"tag_help_explain": "Dans un sous-dossier : un nouveau sous-dossier nommé selon l'étiquette sera créé sous INBOX (« INBOX/Facebook »).<br>\nDans le sujet : le nom des balises sera ajouté au début du sujet de l'e-mail, exemple : « [Facebook] My News ».", "tag_help_explain": "Dans un sous-dossier : un nouveau sous-dossier nommé selon l'étiquette sera créé sous INBOX (« INBOX/Facebook »).<br>\nDans le sujet : le nom des balises sera ajouté au début du sujet de l'e-mail, exemple : « [Facebook] My News ».",
@@ -1345,11 +1354,20 @@
"direct_protocol_access": "Cet utilisateur de la boîte aux lettres dispose d'un <b>accès externe direct</b> aux protocoles et applications suivants. Votre administrateur contrôle ce paramètre. Il est possible de créer des mots de passe d'application pour accorder l'accès à des protocoles et des applications individuels.<br>Le bouton « Connexion au Webmail » permet une connexion unique à SOGo et est toujours disponible.", "direct_protocol_access": "Cet utilisateur de la boîte aux lettres dispose d'un <b>accès externe direct</b> aux protocoles et applications suivants. Votre administrateur contrôle ce paramètre. Il est possible de créer des mots de passe d'application pour accorder l'accès à des protocoles et des applications individuels.<br>Le bouton « Connexion au Webmail » permet une connexion unique à SOGo et est toujours disponible.",
"open_webmail_sso": "Connexion au Webmail", "open_webmail_sso": "Connexion au Webmail",
"recent_successful_connections": "Voir les connexions réussies", "recent_successful_connections": "Voir les connexions réussies",
"syncjob_EXIT_TLS_FAILURE": "Problème de connexion chiffrée",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problème d'authentification",
"syncjob_EXIT_OVERQUOTA": "La boîte de réception cible a dépassé le quota",
"login_history": "Historique des connexions", "login_history": "Historique des connexions",
"change_password_hint_app_passwords": "Votre compte a %d mots de passe d'application qui ne seront pas modifiés. Pour les gérer, allez dans l'onglet Mots de passe de l'application.", "change_password_hint_app_passwords": "Votre compte a %d mots de passe d'application qui ne seront pas modifiés. Pour les gérer, allez dans l'onglet Mots de passe de l'application.",
"clear_recent_successful_connections": "Vider l'historique des connexions réussies", "clear_recent_successful_connections": "Vider l'historique des connexions réussies",
"created_on": "Créé le", "created_on": "Créé le",
"last_ui_login": "Dernière connexion sur l'interface", "last_ui_login": "Dernière connexion sur l'interface",
"syncjob_check_log": "Vérifier le journal",
"syncjob_last_run_result": "Résultat du dernier lancement",
"syncjob_EX_OK": "Succès",
"syncjob_EXIT_CONNECTION_FAILURE": "Problème de connexion",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossible de se connecter au serveur distant",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nom d'utilisateur ou mot de passe incorrect",
"value": "Valeur", "value": "Valeur",
"allowed_protocols": "Protocoles autorisés", "allowed_protocols": "Protocoles autorisés",
"mailbox": "Boîte de réception", "mailbox": "Boîte de réception",
+9 -9
View File
@@ -1,13 +1,4 @@
{ {
"syncjobs": {
"dry": "Προσομοίωση συγχρονισμού",
"exclude": "Εξαίρεση αντικειμένων (regex)",
"automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)",
"delete1": "Διαγραφή όταν ολοκληρωθεί",
"delete2": "Διαγραφή μηνυμάτων στον προορισμό που δεν βρίσκονται στην πηγή",
"delete2duplicates": "Διαγραφή διπλότυπων στον προορισμό",
"custom_params": "Προσαρμοσμένες παράμετροι"
},
"user": { "user": {
"verify": "Επαλήθευση", "verify": "Επαλήθευση",
"week": "εβδομάδα", "week": "εβδομάδα",
@@ -72,16 +63,24 @@
"app_name": "Όνομα εφαρμογής", "app_name": "Όνομα εφαρμογής",
"app_password": "Προσθήκη κωδικού εφαρμογής", "app_password": "Προσθήκη κωδικού εφαρμογής",
"app_passwd_protocols": "Επιτρεπόμενα πρωτόκολλα για κωδικούς εφαρμογών", "app_passwd_protocols": "Επιτρεπόμενα πρωτόκολλα για κωδικούς εφαρμογών",
"automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)",
"backup_mx_options": "Επιλογές αναμετάδοσης", "backup_mx_options": "Επιλογές αναμετάδοσης",
"bcc_dest_format": "Η BCC διεύθυνση πρέπει να είναι μία και έγκυρη διεύθυνση e-mail.<br>Αν θέλετε να στείλετε αντίγραφα σε πολλούς παραλήπτες, δημιουργήστε ένα ψευδόνυμο για όλους και χρησιμοποιήστε το εδώ.", "bcc_dest_format": "Η BCC διεύθυνση πρέπει να είναι μία και έγκυρη διεύθυνση e-mail.<br>Αν θέλετε να στείλετε αντίγραφα σε πολλούς παραλήπτες, δημιουργήστε ένα ψευδόνυμο για όλους και χρησιμοποιήστε το εδώ.",
"comment_info": "Τα προσωπικά σχόλια δεν είναι ορατά στον χρήστη. Τα δημόσια σχόλια εμφανίζονται ως tooltips.", "comment_info": "Τα προσωπικά σχόλια δεν είναι ορατά στον χρήστη. Τα δημόσια σχόλια εμφανίζονται ως tooltips.",
"custom_params": "Προσαρμοσμένες παράμετροι",
"custom_params_hint": "Σωστή σύνταξη: --param=xy, λάθος σύνταξη: --param xy",
"delete1": "Διαγραφή όταν ολοκληρωθεί",
"delete2": "Διαγραφή μηνυμάτων στον προορισμό που δεν βρίσκονται στην πηγή",
"delete2duplicates": "Διαγραφή διπλότυπων στον προορισμό",
"description": "Περιγραφή", "description": "Περιγραφή",
"destination": "Προορισμός", "destination": "Προορισμός",
"disable_login": "Απαγόρευση εισόδου (η εισερχόμενη αλληλογραφία εξακολουθεί να γίνεται δεκτή)", "disable_login": "Απαγόρευση εισόδου (η εισερχόμενη αλληλογραφία εξακολουθεί να γίνεται δεκτή)",
"domain": "Τομέας", "domain": "Τομέας",
"domain_matches_hostname": "Ο τομέας %s είναι ο ίδιος με το όνομα του διακομιστή", "domain_matches_hostname": "Ο τομέας %s είναι ο ίδιος με το όνομα του διακομιστή",
"domain_quota_m": "Συνολικό όριο τομέα (MiB)", "domain_quota_m": "Συνολικό όριο τομέα (MiB)",
"dry": "Προσομοίωση συγχρονισμού",
"enc_method": "Μέθοδος κρυπτογράφησης", "enc_method": "Μέθοδος κρυπτογράφησης",
"exclude": "Εξαίρεση αντικειμένων (regex)",
"full_name": "Πλήρες όνομα", "full_name": "Πλήρες όνομα",
"gal": "Κοινόχρηστη λίστα διευθύνσεων" "gal": "Κοινόχρηστη λίστα διευθύνσεων"
}, },
@@ -181,6 +180,7 @@
"app_name": "Όνομα εφαρμογής", "app_name": "Όνομα εφαρμογής",
"app_passwd": "Κωδικός πρόσβασης εφαρμογής", "app_passwd": "Κωδικός πρόσβασης εφαρμογής",
"app_passwd_protocols": "Επιτρέπομενα πρωτόκολλα για τον κωδικό εφαρμογής", "app_passwd_protocols": "Επιτρέπομενα πρωτόκολλα για τον κωδικό εφαρμογής",
"automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)",
"backup_mx_options": "Επιλογές αναμετάδοσης" "backup_mx_options": "Επιλογές αναμετάδοσης"
} }
} }
+49 -31
View File
@@ -318,10 +318,14 @@
"acl": "ACL (Engedélyek)", "acl": "ACL (Engedélyek)",
"admin": "Adminisztrátor szerkesztése", "admin": "Adminisztrátor szerkesztése",
"app_passwd_protocols": "Engedélyezett protokollok az alkalmazás jelszavához", "app_passwd_protocols": "Engedélyezett protokollok az alkalmazás jelszavához",
"automap": "Próbálja automatikusan feltérképezni a mappákat (\"Elküldött elemek\", \"Elküldött\" => \"Elküldött\" stb.)",
"bcc_dest_format": "A BCC-célpontnak egyetlen érvényes e-mail címnek kell lennie.<br>Ha több címre kell másolatot küldenie, hozzon létre egy aliast, és használja azt itt.", "bcc_dest_format": "A BCC-célpontnak egyetlen érvényes e-mail címnek kell lennie.<br>Ha több címre kell másolatot küldenie, hozzon létre egy aliast, és használja azt itt.",
"comment_info": "A privát megjegyzés nem látható a felhasználó számára, míg a nyilvános megjegyzés tooltip-ként jelenik meg, amikor a felhasználó áttekintésében a megjegyzésre mutat.", "comment_info": "A privát megjegyzés nem látható a felhasználó számára, míg a nyilvános megjegyzés tooltip-ként jelenik meg, amikor a felhasználó áttekintésében a megjegyzésre mutat.",
"created_on": "Létrehozva", "created_on": "Létrehozva",
"custom_attributes": "Egyéni attribútumok", "custom_attributes": "Egyéni attribútumok",
"delete1": "Törlés a forrásból, ha befejeződött",
"delete2": "Üzenetek törlése a célállomáson, amelyek nincsenek a forráson",
"delete2duplicates": "Duplikáltak törlése a célállomáson",
"delete_ays": "Erősítse meg a törlési folyamatot.", "delete_ays": "Erősítse meg a törlési folyamatot.",
"disable_login": "Bejelentkezés letiltása (a bejövő leveleket továbbra is elfogadja)", "disable_login": "Bejelentkezés letiltása (a bejövő leveleket továbbra is elfogadja)",
"domain": "Domain szerkesztése", "domain": "Domain szerkesztése",
@@ -341,6 +345,7 @@
"domain_footer_skip_replies": "Lábléc figyelmen kívül hagyása válasz e-maileknél", "domain_footer_skip_replies": "Lábléc figyelmen kívül hagyása válasz e-maileknél",
"dont_check_sender_acl": "Küldő ellenőrzés letiltása a(z) %s tartományhoz (+ alias tartományok)", "dont_check_sender_acl": "Küldő ellenőrzés letiltása a(z) %s tartományhoz (+ alias tartományok)",
"encryption": "Titkosítás", "encryption": "Titkosítás",
"exclude": "Objektumok kizárása (regex)",
"extended_sender_acl": "Külső feladói címek", "extended_sender_acl": "Külső feladói címek",
"extended_sender_acl_info": "Importálni kell egy DKIM tartomány kulcsot, ha elérhető.<br>\r\n Ne felejtse el hozzáadni ezt a szervert a megfelelő SPF TXT rekordhoz.<br>\r\n Amikor egy tartományt vagy alias tartományt ad hozzá ehhez a szerverhez, amely átfedésben van egy külső címmel, a külső cím eltávolításra kerül.<br>\r\n Használja a @domain.tld címet, hogy engedélyezze a küldést *@domain.tld néven.", "extended_sender_acl_info": "Importálni kell egy DKIM tartomány kulcsot, ha elérhető.<br>\r\n Ne felejtse el hozzáadni ezt a szervert a megfelelő SPF TXT rekordhoz.<br>\r\n Amikor egy tartományt vagy alias tartományt ad hozzá ehhez a szerverhez, amely átfedésben van egy külső címmel, a külső cím eltávolításra kerül.<br>\r\n Használja a @domain.tld címet, hogy engedélyezze a küldést *@domain.tld néven.",
"force_pw_update": "Jelszófrissítés kényszerítése a következő bejelentkezéskor", "force_pw_update": "Jelszófrissítés kényszerítése a következő bejelentkezéskor",
@@ -367,7 +372,10 @@
"max_aliases": "Max. aliasok", "max_aliases": "Max. aliasok",
"max_mailboxes": "Max. lehetséges postafiókok", "max_mailboxes": "Max. lehetséges postafiókok",
"max_quota": "Max. kvóta postafiókonként (MiB)", "max_quota": "Max. kvóta postafiókonként (MiB)",
"maxage": "Üzenetek maximális életkora napokban, amelyek lekérdezésre kerülnek a távolról<br><small>(0 = figyelmen kívül hagyás)</small>",
"maxbytespersecond": "Max. bájt/másodperc <br><small>(0 = korlátlan)</small>",
"mbox_rl_info": "Ez a sebességkorlátozás a SASL bejelentkezési névre vonatkozik, és illeszkedik minden, a bejelentkezett felhasználó által használt \"from\" címre. Egy postafiók sebességkorlátozása felülírja a tartományszintű sebességkorlátozást.", "mbox_rl_info": "Ez a sebességkorlátozás a SASL bejelentkezési névre vonatkozik, és illeszkedik minden, a bejelentkezett felhasználó által használt \"from\" címre. Egy postafiók sebességkorlátozása felülírja a tartományszintű sebességkorlátozást.",
"mins_interval": "Időköz (perc)",
"multiple_bookings": "Több foglalás", "multiple_bookings": "Több foglalás",
"none_inherit": "Nincs / Örökölt", "none_inherit": "Nincs / Örökölt",
"nexthop": "Következő ugrás", "nexthop": "Következő ugrás",
@@ -408,6 +416,7 @@
"sender_acl_info": "Ha az A postafiók felhasználója küldhet a B postafiók felhasználójaként, a feladói cím nem jelenik meg automatikusan választható \"from\" mezőként a SOGo-ban.<br>\r\n A B postafiók felhasználójának delegálást kell létrehoznia a SOGo-ban, hogy az A postafiók felhasználója kiválaszthassa a címét feladóként. A SOGo-ban egy postafiók delegálásához használja a menüt (három pont) a postafiók neve jobb oldalán a bal felső sarokban, a levélnézetben. Ez a viselkedés nem vonatkozik az alias címekre.", "sender_acl_info": "Ha az A postafiók felhasználója küldhet a B postafiók felhasználójaként, a feladói cím nem jelenik meg automatikusan választható \"from\" mezőként a SOGo-ban.<br>\r\n A B postafiók felhasználójának delegálást kell létrehoznia a SOGo-ban, hogy az A postafiók felhasználója kiválaszthassa a címét feladóként. A SOGo-ban egy postafiók delegálásához használja a menüt (három pont) a postafiók neve jobb oldalán a bal felső sarokban, a levélnézetben. Ez a viselkedés nem vonatkozik az alias címekre.",
"sieve_desc": "Rövid leírás", "sieve_desc": "Rövid leírás",
"sieve_type": "Szűrő típusa", "sieve_type": "Szűrő típusa",
"skipcrossduplicates": "Átugrani a duplikált üzeneteket a mappák között (aki előbb jön, előbb kapja)",
"sogo_access": "Közvetlen továbbítás a SOGo-ra", "sogo_access": "Közvetlen továbbítás a SOGo-ra",
"sogo_access_info": "A bejelentkezés után a felhasználó automatikusan átirányításra kerül a SOGo-ra.", "sogo_access_info": "A bejelentkezés után a felhasználó automatikusan átirányításra kerül a SOGo-ra.",
"sogo_visible": "Alias látható a SOGo-ban", "sogo_visible": "Alias látható a SOGo-ban",
@@ -416,8 +425,12 @@
"spam_filter": "Spam szűrő", "spam_filter": "Spam szűrő",
"spam_policy": "Elemek hozzáadása vagy eltávolítása a tiltó- vagy engedélyezési listán", "spam_policy": "Elemek hozzáadása vagy eltávolítása a tiltó- vagy engedélyezési listán",
"spam_score": "Egyéni spam pontszám beállítása", "spam_score": "Egyéni spam pontszám beállítása",
"subfolder2": "Szinkronizálás a célállomáson egy almappába<br><small>(üres = ne használjon almappát)</small>",
"syncjob": "Szinkronizálási feladat szerkesztése",
"target_address": "Célcím(ek) <small>(vesszővel elválasztva)</small>", "target_address": "Célcím(ek) <small>(vesszővel elválasztva)</small>",
"target_domain": "Cél domain", "target_domain": "Cél domain",
"timeout1": "Időtúllépés a távoli állomáshoz való csatlakozáskor",
"timeout2": "Időtúllépés a helyi állomáshoz való csatlakozáskor",
"title": "Objektum szerkesztése", "title": "Objektum szerkesztése",
"unchanged_if_empty": "Ha nem változik, hagyja üresen", "unchanged_if_empty": "Ha nem változik, hagyja üresen",
"username": "Felhasználónév", "username": "Felhasználónév",
@@ -557,6 +570,7 @@
"spam_aliases": "Ideiglenes alias", "spam_aliases": "Ideiglenes alias",
"stats": "Statisztika", "stats": "Statisztika",
"status": "Státusz", "status": "Státusz",
"sync_jobs": "Szinkronizáló feladatok",
"table_size": "Tábla mérete", "table_size": "Tábla mérete",
"table_size_show_n": "%s sor mutatása", "table_size_show_n": "%s sor mutatása",
"target_address": "Címhez ugrás", "target_address": "Címhez ugrás",
@@ -634,6 +648,15 @@
"sogo_visible": "Alias látható a SOGo-ban", "sogo_visible": "Alias látható a SOGo-ban",
"sogo_visible_n": "Alias elrejtése a SOGo-ban", "sogo_visible_n": "Alias elrejtése a SOGo-ban",
"sogo_visible_y": "Alias megjelenítése a SOGo-ban", "sogo_visible_y": "Alias megjelenítése a SOGo-ban",
"syncjob_check_log": "Napló ellenőrzése",
"syncjob_last_run_result": "Utolsó futás eredménye",
"syncjob_EX_OK": "Siker",
"syncjob_EXIT_CONNECTION_FAILURE": "Kapcsolati probléma",
"syncjob_EXIT_TLS_FAILURE": "Probléma a titkosított kapcsolattal",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Hitelesítési probléma",
"syncjob_EXIT_OVERQUOTA": "A cél postafiók túllépte a kvótát",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nem lehet csatlakozni a távoli szerverhez",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Hibás felhasználónév vagy jelszó",
"templates": "Sablonok", "templates": "Sablonok",
"template": "Sablon", "template": "Sablon",
"tls_map_policy": "Irányelv", "tls_map_policy": "Irányelv",
@@ -816,37 +839,6 @@
"verified_yotp_login": "Yubico OTP bejelentkezés ellenőrizve", "verified_yotp_login": "Yubico OTP bejelentkezés ellenőrizve",
"mailbox_renamed": "A postafiók átnevezve %s-ről %s-re" "mailbox_renamed": "A postafiók átnevezve %s-ről %s-re"
}, },
"syncjobs": {
"sync_jobs": "Szinkronizáló feladatok",
"syncjob_check_log": "Napló ellenőrzése",
"syncjob_last_run_result": "Utolsó futás eredménye",
"syncjob_EX_OK": "Siker",
"syncjob_EXIT_CONNECTION_FAILURE": "Kapcsolati probléma",
"syncjob_EXIT_TLS_FAILURE": "Probléma a titkosított kapcsolattal",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Hitelesítési probléma",
"syncjob_EXIT_OVERQUOTA": "A cél postafiók túllépte a kvótát",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nem lehet csatlakozni a távoli szerverhez",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Hibás felhasználónév vagy jelszó",
"add": "Szinkronizálási feladat hozzáadása",
"add_hint": "Ne feledje, hogy a jelszavakat egyszerű szöveges formában kell elmenteni!",
"edit": "Szinkronizálási feladat szerkesztése",
"create": "Új szinkronizációs művelet létrehozása",
"dry": "Szinkronizálás szimulálása",
"subfolder2": "Szinkronizálás a célállomáson egy almappába<br><small>(üres = ne használjon almappát)</small>",
"mins_interval": "A lekérdezési időköz (perc)",
"maxage": "Üzenetek maximális életkora napokban, amelyek lekérdezésre kerülnek a távolról<br><small>(0 = figyelmen kívül hagyás)</small>",
"exclude": "Objektumok kizárása (regex)",
"automap": "Próbálja automatikusan feltérképezni a mappákat (\"Elküldött elemek\", \"Elküldött\" => \"Elküldött\" stb.)",
"delete1": "Törlés a forrásból, ha befejeződött",
"delete2": "A forráson kívüli üzenetek törlése a célállomáson",
"delete2duplicates": "Duplikáltak törlése a célállomáson",
"skipcrossduplicates": "Duplikált üzenetek átugrása mappák között (érkezési sorrendben)",
"subscribeall": "Feliratkozás minden mappára",
"timeout1": "Időtúllépés a távoli állomáshoz való csatlakozáskor",
"timeout2": "Időtúllépés a helyi állomáshoz való csatlakozáskor",
"maxbytespersecond": "Max. bájt/másodperc <br><small>(0 = korlátlan)</small>",
"custom_params": "Egyéni paraméterek"
},
"user": { "user": {
"action": "Művelet", "action": "Művelet",
"active": "Aktív", "active": "Aktív",
@@ -866,6 +858,7 @@
"change_password": "Jelszó cseréje", "change_password": "Jelszó cseréje",
"client_configuration": "Beállítások megjelenítése email-kliensekhez", "client_configuration": "Beállítások megjelenítése email-kliensekhez",
"create_app_passwd": "Applikáció jelszó létrehozása", "create_app_passwd": "Applikáció jelszó létrehozása",
"create_syncjob": "Új szinkronizációs művelet létrehozása",
"daily": "Napi", "daily": "Napi",
"day": "nap", "day": "nap",
"description": "Leírás", "description": "Leírás",
@@ -927,6 +920,7 @@
"spamfilter_wl_desc": "Az engedélyezőlistán szereplő email címek soha nem kerülnek spam-be. Joker karakter használható. A szűrő csak közvetlen aliasokra vonatkozik (alias egyetlen cél-postafiókkal), magára a postafiókra, illetve mindent elkapó aliasokra nem.", "spamfilter_wl_desc": "Az engedélyezőlistán szereplő email címek soha nem kerülnek spam-be. Joker karakter használható. A szűrő csak közvetlen aliasokra vonatkozik (alias egyetlen cél-postafiókkal), magára a postafiókra, illetve mindent elkapó aliasokra nem.",
"spamfilter_yellow": "Sárga: ez az üzenet spam-gyanús, spam-nek lesz címezve és elérhető lesz a levélszemét mappában", "spamfilter_yellow": "Sárga: ez az üzenet spam-gyanús, spam-nek lesz címezve és elérhető lesz a levélszemét mappában",
"status": "Státusz", "status": "Státusz",
"sync_jobs": "Szinkronizáló feladatok",
"tag_handling": "Címkézett üzenetek kezelése", "tag_handling": "Címkézett üzenetek kezelése",
"tag_help_example": "Példa címkézett email címre: en<b>+Facebook</b>@pelda.hu", "tag_help_example": "Példa címkézett email címre: en<b>+Facebook</b>@pelda.hu",
"tag_help_explain": "Almappában: új almappa készül a címke nevét viselve (pl.: \"Bejövő/Facebook\").<br>\r\nTárgyban: a címke a tárgy elé lesz fűzve, pl.: \"[Facebook] Hírek\".", "tag_help_explain": "Almappában: új almappa készül a címke nevét viselve (pl.: \"Bejövő/Facebook\").<br>\r\nTárgyban: a címke a tárgy elé lesz fűzve, pl.: \"[Facebook] Hírek\".",
@@ -998,6 +992,15 @@
"sogo_profile_reset_help": "Ez tönkreteszi a felhasználó SOGo profilját, és <b>visszavonhatatlanul törli az összes névjegy- és naptáradatot</b>.", "sogo_profile_reset_help": "Ez tönkreteszi a felhasználó SOGo profilját, és <b>visszavonhatatlanul törli az összes névjegy- és naptáradatot</b>.",
"sogo_profile_reset_now": "Profil visszaállítása most", "sogo_profile_reset_now": "Profil visszaállítása most",
"spamfilter_table_domain_policy": "n/a (tartományi szabályzat)", "spamfilter_table_domain_policy": "n/a (tartományi szabályzat)",
"syncjob_check_log": "Napló ellenőrzése",
"syncjob_last_run_result": "Utolsó futás eredménye",
"syncjob_EX_OK": "Siker",
"syncjob_EXIT_CONNECTION_FAILURE": "Kapcsolati probléma",
"syncjob_EXIT_TLS_FAILURE": "Probléma a titkosított kapcsolattal",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Hitelesítési probléma",
"syncjob_EXIT_OVERQUOTA": "A cél postafiók túllépte a kvótát",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nem lehet csatlakozni a távoli szerverhez",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Hibás felhasználónév vagy jelszó",
"tfa_info": "A kétlépcsős hitelesítés segít megvédeni a fiókodat. Ha engedélyezed, alkalmazás jelszavakra lesz szükséged, hogy bejelentkezz olyan alkalmazásokba vagy szolgáltatásokba, amelyek nem támogatják a kétlépcsős hitelesítést (pl. levelező kliensek).", "tfa_info": "A kétlépcsős hitelesítés segít megvédeni a fiókodat. Ha engedélyezed, alkalmazás jelszavakra lesz szükséged, hogy bejelentkezz olyan alkalmazásokba vagy szolgáltatásokba, amelyek nem támogatják a kétlépcsős hitelesítést (pl. levelező kliensek).",
"tls_policy_warning": "<strong>Figyelem:</strong> Ha titkosított levélátvitelt kényszerítesz ki, elveszíthetsz e-maileket.<br>Azok az üzenetek, amelyek nem felelnek meg a szabályzatnak, kemény hibával visszapattannak a levelezőrendszerből.<br>Ez az opció a fő e-mail címedre (bejelentkezési név), az alias tartományokból származó összes címre, valamint az alias címekre vonatkozik, amelyeknek <b>csak ez az egy postafiók</b> a célja.", "tls_policy_warning": "<strong>Figyelem:</strong> Ha titkosított levélátvitelt kényszerítesz ki, elveszíthetsz e-maileket.<br>Azok az üzenetek, amelyek nem felelnek meg a szabályzatnak, kemény hibával visszapattannak a levelezőrendszerből.<br>Ez az opció a fő e-mail címedre (bejelentkezési név), az alias tartományokból származó összes címre, valamint az alias címekre vonatkozik, amelyeknek <b>csak ez az egy postafiók</b> a célja.",
"value": "Érték", "value": "Érték",
@@ -1067,16 +1070,20 @@
"mailbox_quota_def": "Alapértelmezett postafiók kvóta", "mailbox_quota_def": "Alapértelmezett postafiók kvóta",
"password_repeat": "Megerősítő jelszó (ismétlés)", "password_repeat": "Megerősítő jelszó (ismétlés)",
"post_domain_add": "A \"sogo-mailcow\" SOGo konténert újra kell indítani egy új tartomány hozzáadása után!<br><br>Kiegészítésképpen a tartományok DNS-konfigurációját is felül kell vizsgálni. A DNS-konfiguráció jóváhagyása után indítsa újra az \"acme-mailcow\"-t, hogy automatikusan generáljon tanúsítványokat az új tartományhoz (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Ez a lépés opcionális, és 24 óránként megismétlődik.", "post_domain_add": "A \"sogo-mailcow\" SOGo konténert újra kell indítani egy új tartomány hozzáadása után!<br><br>Kiegészítésképpen a tartományok DNS-konfigurációját is felül kell vizsgálni. A DNS-konfiguráció jóváhagyása után indítsa újra az \"acme-mailcow\"-t, hogy automatikusan generáljon tanúsítványokat az új tartományhoz (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Ez a lépés opcionális, és 24 óránként megismétlődik.",
"dry": "Szinkronizálás szimulálása",
"inactive": "Inaktív", "inactive": "Inaktív",
"kind": "Típus", "kind": "Típus",
"mailbox_quota_m": "Maximális kvóta postafiókonként (MiB)", "mailbox_quota_m": "Maximális kvóta postafiókonként (MiB)",
"mailbox_username": "Felhasználónév (az e-mail cím bal oldali része)", "mailbox_username": "Felhasználónév (az e-mail cím bal oldali része)",
"max_aliases": "Max. lehetséges álnevek", "max_aliases": "Max. lehetséges álnevek",
"max_mailboxes": "Max. lehetséges postafiókok", "max_mailboxes": "Max. lehetséges postafiókok",
"mins_interval": "A lekérdezési időköz (perc)",
"password": "Jelszó", "password": "Jelszó",
"port": "Port", "port": "Port",
"public_comment": "Nyilvános megjegyzés", "public_comment": "Nyilvános megjegyzés",
"target_domain": "Céltartomány", "target_domain": "Céltartomány",
"timeout1": "Időtúllépés a távoli állomáshoz való csatlakozáskor",
"timeout2": "Időtúllépés a helyi állomáshoz való csatlakozáskor",
"validate": "Érvényesítsd", "validate": "Érvényesítsd",
"description": "Leírás", "description": "Leírás",
"destination": "Célállomás", "destination": "Célállomás",
@@ -1084,18 +1091,25 @@
"domain_matches_hostname": "Domain %s megegyezik a hostnévvel", "domain_matches_hostname": "Domain %s megegyezik a hostnévvel",
"domain_quota_m": "Teljes tartományi kvóta (MiB)", "domain_quota_m": "Teljes tartományi kvóta (MiB)",
"enc_method": "Titkosítási módszer", "enc_method": "Titkosítási módszer",
"exclude": "Objektumok kizárása (regex)",
"full_name": "Teljes név", "full_name": "Teljes név",
"gal": "Globális címlista", "gal": "Globális címlista",
"goto_ham": "Tanítás <span class=\"text-success\"><b>valódi</b></span> levélként", "goto_ham": "Tanítás <span class=\"text-success\"><b>valódi</b></span> levélként",
"goto_null": "Leveleket csendben eldobni", "goto_null": "Leveleket csendben eldobni",
"goto_spam": "Tanítás <span class=\"text-danger\"><b>spam</b></span>ként", "goto_spam": "Tanítás <span class=\"text-danger\"><b>spam</b></span>ként",
"syncjob_hint": "Ne feledje, hogy a jelszavakat egyszerű szöveges formában kell elmenteni!",
"target_address": "Továbbítási címek", "target_address": "Továbbítási címek",
"target_address_info": "<small>Teljes e-mail cím(ek) (vesszővel elválasztva).</small>", "target_address_info": "<small>Teljes e-mail cím(ek) (vesszővel elválasztva).</small>",
"bcc_dest_format": "A BCC-célpontnak egyetlen érvényes e-mail címnek kell lennie.<br>Ha több címre kell másolatot küldenie, hozzon létre egy aliast, és használja azt.", "bcc_dest_format": "A BCC-célpontnak egyetlen érvényes e-mail címnek kell lennie.<br>Ha több címre kell másolatot küldenie, hozzon létre egy aliast, és használja azt.",
"comment_info": "A privát megjegyzés nem látható a felhasználó számára, míg a nyilvános megjegyzés tooltip-ként jelenik meg, amikor a felhasználó áttekintésében a megjegyzésre mutat.", "comment_info": "A privát megjegyzés nem látható a felhasználó számára, míg a nyilvános megjegyzés tooltip-ként jelenik meg, amikor a felhasználó áttekintésében a megjegyzésre mutat.",
"custom_params": "Egyéni paraméterek",
"gal_info": "A GAL tartalmazza a tartomány összes objektumát, és egyetlen felhasználó sem szerkesztheti. A SOGo-ban a Szabad/Elfoglalt információ hiányzik, ha ki van kapcsolva! <b>Indítsa újra a SOGo-t a változások alkalmazásához.</b>", "gal_info": "A GAL tartalmazza a tartomány összes objektumát, és egyetlen felhasználó sem szerkesztheti. A SOGo-ban a Szabad/Elfoglalt információ hiányzik, ha ki van kapcsolva! <b>Indítsa újra a SOGo-t a változások alkalmazásához.</b>",
"hostname": "Hoszt", "hostname": "Hoszt",
"backup_mx_options": "Továbbítási opciók", "backup_mx_options": "Továbbítási opciók",
"custom_params_hint": "Megfelelő: --param=xy, Rossz: --param xy",
"delete1": "Törlés a forrásból, ha befejeződött",
"delete2": "A forráson kívüli üzenetek törlése a célállomáson",
"delete2duplicates": "Duplikáltak törlése a célállomáson",
"domain": "Domain", "domain": "Domain",
"nexthop": "Következő hop", "nexthop": "Következő hop",
"tags": "Címkék", "tags": "Címkék",
@@ -1113,6 +1127,7 @@
"alias_domain_info": "<small>Csak érvényes tartománynevek (vesszővel elválasztva).</small>", "alias_domain_info": "<small>Csak érvényes tartománynevek (vesszővel elválasztva).</small>",
"app_name": "Alkalmazás neve", "app_name": "Alkalmazás neve",
"app_passwd_protocols": "Engedélyezett protokollok az alkalmazás jelszavához", "app_passwd_protocols": "Engedélyezett protokollok az alkalmazás jelszavához",
"automap": "Próbálja automatikusan feltérképezni a mappákat (\"Elküldött elemek\", \"Elküldött\" => \"Elküldött\" stb.)",
"multiple_bookings": "Több foglalás", "multiple_bookings": "Több foglalás",
"quota_mb": "Kvóta (MiB)", "quota_mb": "Kvóta (MiB)",
"relay_all": "Az összes címzett továbbítása", "relay_all": "Az összes címzett továbbítása",
@@ -1125,6 +1140,9 @@
"select_domain": "Kérjük, először válasszon egy domaint", "select_domain": "Kérjük, először válasszon egy domaint",
"sieve_desc": "Rövid leírás", "sieve_desc": "Rövid leírás",
"sieve_type": "Szűrő típusa", "sieve_type": "Szűrő típusa",
"skipcrossduplicates": "Duplikált üzenetek átugrása mappák között (érkezési sorrendben)",
"subscribeall": "Feliratkozás minden mappára",
"syncjob": "Szinkronizálási feladat hozzáadása",
"internal": "Belső", "internal": "Belső",
"internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető.", "internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető.",
"sender_allowed": "Küldés engedélyezése ezzel az aliasszal" "sender_allowed": "Küldés engedélyezése ezzel az aliasszal"
+48 -31
View File
@@ -43,8 +43,14 @@
"alias_domain_info": "<small>Solo nomi di dominio validi (separati da virgola).</small>", "alias_domain_info": "<small>Solo nomi di dominio validi (separati da virgola).</small>",
"app_name": "Nome app", "app_name": "Nome app",
"app_password": "Aggiungi la password dell'app", "app_password": "Aggiungi la password dell'app",
"automap": "Prova a mappare automaticamente le cartelle (\"Sent items\", \"Sent\" => \"Posta inviata\" ecc.)",
"backup_mx_options": "Opzioni di inoltro", "backup_mx_options": "Opzioni di inoltro",
"comment_info": "Un commento privato non è visibile all'utente, mentre un commento pubblico viene mostrato come suggerimento quando si passa con il mouse nella panoramica di un utente", "comment_info": "Un commento privato non è visibile all'utente, mentre un commento pubblico viene mostrato come suggerimento quando si passa con il mouse nella panoramica di un utente",
"custom_params": "Parametri personalizzati",
"custom_params_hint": "Corretto: --param=xy, errato: --param xy",
"delete1": "Elimina dalla sorgente al termine",
"delete2": "Elimina i messaggi nella casella di destinazione che non sono presenti nell'origine",
"delete2duplicates": "Elimina duplicati nella destinazione",
"description": "Descrizione", "description": "Descrizione",
"destination": "Destinazione", "destination": "Destinazione",
"disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)", "disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)",
@@ -52,6 +58,7 @@
"domain_matches_hostname": "Il dominio %s corrisponde al'hostname", "domain_matches_hostname": "Il dominio %s corrisponde al'hostname",
"domain_quota_m": "Spazio totale dominio (MiB)", "domain_quota_m": "Spazio totale dominio (MiB)",
"enc_method": "Metodo di crittografia", "enc_method": "Metodo di crittografia",
"exclude": "Escludi oggetti (regex)",
"full_name": "Nome completo", "full_name": "Nome completo",
"gal": "Global Address List", "gal": "Global Address List",
"gal_info": "La GAL contiene tutti gli indirizzi del dominio e non può essere modificata da nessun utente. Le infomazioni sulla disponibilità di ogni utente non sono presenti in SOGo, se sono state disabilitate! <b>Riavvia SOGo per applicare le modifiche.</b>", "gal_info": "La GAL contiene tutti gli indirizzi del dominio e non può essere modificata da nessun utente. Le infomazioni sulla disponibilità di ogni utente non sono presenti in SOGo, se sono state disabilitate! <b>Riavvia SOGo per applicare le modifiche.</b>",
@@ -67,6 +74,7 @@
"mailbox_username": "Username (parte a sinistra della @)", "mailbox_username": "Username (parte a sinistra della @)",
"max_aliases": "Numero massimo alias", "max_aliases": "Numero massimo alias",
"max_mailboxes": "Numero massimo caselle di posta", "max_mailboxes": "Numero massimo caselle di posta",
"mins_interval": "intervallo (minuti)",
"multiple_bookings": "Prenotazioni multiple", "multiple_bookings": "Prenotazioni multiple",
"nexthop": "Hop successivo", "nexthop": "Hop successivo",
"password": "Password", "password": "Password",
@@ -86,9 +94,15 @@
"select_domain": "Seleziona prima un dominio", "select_domain": "Seleziona prima un dominio",
"sieve_desc": "Descrizione breve", "sieve_desc": "Descrizione breve",
"sieve_type": "Tipologia di filtro", "sieve_type": "Tipologia di filtro",
"skipcrossduplicates": "Salta i messaggi duplicati tra le cartelle (precedenza al primo arrivato)",
"subscribeall": "Iscriviti a tutte le cartelle",
"syncjob": "Aggiungi sincronizzazione",
"syncjob_hint": "Ricordati che le password vanno salvate in testo semplice!",
"target_address": "Inoltra all'indirizzo", "target_address": "Inoltra all'indirizzo",
"target_address_info": "<small>Indirizzo e-mail completo/i (separati da virgole).</small>", "target_address_info": "<small>Indirizzo e-mail completo/i (separati da virgole).</small>",
"target_domain": "Dominio di destinazione", "target_domain": "Dominio di destinazione",
"timeout1": "Timeout per la connessione all'host remoto",
"timeout2": "Timeout per la connessione all'host locale",
"username": "Nome utente", "username": "Nome utente",
"validate": "Convalida", "validate": "Convalida",
"validation_success": "Convalidato con successo", "validation_success": "Convalidato con successo",
@@ -561,12 +575,16 @@
"allowed_protocols": "Protocolli consentiti", "allowed_protocols": "Protocolli consentiti",
"app_name": "App name", "app_name": "App name",
"app_passwd": "App password", "app_passwd": "App password",
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Backup MX options", "backup_mx_options": "Backup MX options",
"bcc_dest_format": "BCC destination must be a single valid email address.", "bcc_dest_format": "BCC destination must be a single valid email address.",
"client_id": "Client ID", "client_id": "Client ID",
"client_secret": "Client secret", "client_secret": "Client secret",
"created_on": "Creato il", "created_on": "Creato il",
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview", "comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
"delete1": "Elimina dalla sorgente al termine",
"delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Elimina duplicati nella destinazione",
"delete_ays": "Si prega di confermare il processo di eliminazione.", "delete_ays": "Si prega di confermare il processo di eliminazione.",
"description": "Descrizione", "description": "Descrizione",
"disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)", "disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)",
@@ -577,6 +595,7 @@
"dont_check_sender_acl": "Disattiva il controllo del mittente per il dominio %s + alias di dominio", "dont_check_sender_acl": "Disattiva il controllo del mittente per il dominio %s + alias di dominio",
"edit_alias_domain": "Modifica alias di dominio", "edit_alias_domain": "Modifica alias di dominio",
"encryption": "Crittografia", "encryption": "Crittografia",
"exclude": "Escludi oggetti (regex)",
"extended_sender_acl": "External sender addresses", "extended_sender_acl": "External sender addresses",
"extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.", "extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.",
"force_pw_update": "Forza l'aggiornamento della password al prossimo accesso", "force_pw_update": "Forza l'aggiornamento della password al prossimo accesso",
@@ -596,7 +615,10 @@
"max_aliases": "Numero massimo alias", "max_aliases": "Numero massimo alias",
"max_mailboxes": "Caselle di posta massime", "max_mailboxes": "Caselle di posta massime",
"max_quota": "Massimo spazio per casella (MiB)", "max_quota": "Massimo spazio per casella (MiB)",
"maxage": "Massima età dei messaggi che verranno scaricati dal server remoto<br /><small>(0 = ignora età)</small>",
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.", "mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.",
"mins_interval": "Intervallo (min)",
"multiple_bookings": "Prenotazioni multiple", "multiple_bookings": "Prenotazioni multiple",
"nexthop": "Prossimo hop", "nexthop": "Prossimo hop",
"password": "Password", "password": "Password",
@@ -633,14 +655,19 @@
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.", "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"sieve_desc": "Breve descrizione", "sieve_desc": "Breve descrizione",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"sogo_visible": "L'alias è visibile in SOGo", "sogo_visible": "L'alias è visibile in SOGo",
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.", "sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"spam_alias": "Create or change time limited alias addresses", "spam_alias": "Create or change time limited alias addresses",
"spam_filter": "Spam filter", "spam_filter": "Spam filter",
"spam_policy": "Aggiungi o rimuovi elementi dalla whitelist/blacklist", "spam_policy": "Aggiungi o rimuovi elementi dalla whitelist/blacklist",
"spam_score": "Imposta un punteggio spam personalizzato", "spam_score": "Imposta un punteggio spam personalizzato",
"subfolder2": "Sincronizza in una sottocartella<br /><small>(vuoto = non sincronizzare in sottocartella)</small>",
"syncjob": "Modifica sincronizzazione",
"target_address": "Vai all'indirizzo/i <small>(separato da virgola)</small>", "target_address": "Vai all'indirizzo/i <small>(separato da virgola)</small>",
"target_domain": "Target dominio", "target_domain": "Target dominio",
"timeout1": "Timeout per la connessione all'host remoto",
"timeout2": "Timeout per la connessione all'host remoto",
"title": "Modifica oggetto", "title": "Modifica oggetto",
"unchanged_if_empty": "Se immutato lasciare vuoto", "unchanged_if_empty": "Se immutato lasciare vuoto",
"username": "Nome utente", "username": "Nome utente",
@@ -858,6 +885,7 @@
"spam_aliases": "Alias temporanei", "spam_aliases": "Alias temporanei",
"stats": "Statistiche", "stats": "Statistiche",
"status": "Stato", "status": "Stato",
"sync_jobs": "Processi di sync",
"table_size": "Dimensioni della tabella", "table_size": "Dimensioni della tabella",
"table_size_show_n": "Mostra %s elementi", "table_size_show_n": "Mostra %s elementi",
"target_address": "Vai agli indirizzi", "target_address": "Vai agli indirizzi",
@@ -878,13 +906,22 @@
"waiting": "In attesa", "waiting": "In attesa",
"weekly": "Settimanale", "weekly": "Settimanale",
"yes": "&#10003;", "yes": "&#10003;",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati",
"goto_ham": "Apprendi come <b>ham</b>", "goto_ham": "Apprendi come <b>ham</b>",
"goto_spam": "Apprendi come <b>spam</b>", "goto_spam": "Apprendi come <b>spam</b>",
"open_logs": "Apri i log", "open_logs": "Apri i log",
"syncjob_check_log": "Controlla il log",
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
"syncjob_EXIT_TLS_FAILURE": "Problema con la connessione criptata",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema di autenticazione",
"syncjob_EXIT_OVERQUOTA": "La casella di posta del destinatario ha superato la quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema di connessione",
"catch_all": "Catch-All", "catch_all": "Catch-All",
"sender": "Mittente", "sender": "Mittente",
"all_domains": "Tutti i domini", "all_domains": "Tutti i domini",
"recipient": "Destinatario", "recipient": "Destinatario",
"syncjob_EX_OK": "Successo",
"add_template": "Aggiungi template", "add_template": "Aggiungi template",
"force_pw_update": "Forza il cambio della password al prossimo accesso", "force_pw_update": "Forza il cambio della password al prossimo accesso",
"relay_unknown": "Inoltra a caselle di posta sconosciute", "relay_unknown": "Inoltra a caselle di posta sconosciute",
@@ -1092,37 +1129,6 @@
"u2f_deprecated_important": "Registra la tua chiave nel pannello di amministrazione con il nuovo metodo WebAuthn.", "u2f_deprecated_important": "Registra la tua chiave nel pannello di amministrazione con il nuovo metodo WebAuthn.",
"authenticators": "Autenticatori" "authenticators": "Autenticatori"
}, },
"syncjobs": {
"sync_jobs": "Processi di sync",
"syncjob_check_log": "Controlla i log",
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
"syncjob_EX_OK": "Successo",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema di connessione",
"syncjob_EXIT_TLS_FAILURE": "Problema con la connessione criptata",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema di autenticazione",
"syncjob_EXIT_OVERQUOTA": "La casella di posta del destinatario ha superato la quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati",
"add": "Aggiungi sincronizzazione",
"add_hint": "Ricordati che le password vanno salvate in testo semplice!",
"edit": "Modifica sincronizzazione",
"create": "Crea un azione di sync",
"dry": "Simula sincronizzazione",
"subfolder2": "Sincronizza in una sottocartella<br /><small>(vuoto = non sincronizzare in sottocartella)</small>",
"mins_interval": "intervallo (minuti)",
"maxage": "Massima età dei messaggi che verranno scaricati dal server remoto<br /><small>(0 = ignora età)</small>",
"exclude": "Escludi oggetti (regex)",
"automap": "Prova a mappare automaticamente le cartelle (\"Sent items\", \"Sent\" => \"Posta inviata\" ecc.)",
"delete1": "Elimina dalla sorgente al termine",
"delete2": "Elimina i messaggi nella casella di destinazione che non sono presenti nell'origine",
"delete2duplicates": "Elimina duplicati nella destinazione",
"skipcrossduplicates": "Salta i messaggi duplicati tra le cartelle (precedenza al primo arrivato)",
"subscribeall": "Iscriviti a tutte le cartelle",
"timeout1": "Timeout per la connessione all'host remoto",
"timeout2": "Timeout per la connessione all'host locale",
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
"custom_params": "Parametri personalizzati"
},
"user": { "user": {
"action": "Azione", "action": "Azione",
"active": "Attiva", "active": "Attiva",
@@ -1148,6 +1154,7 @@
"clear_recent_successful_connections": "Clear seen successful connections", "clear_recent_successful_connections": "Clear seen successful connections",
"client_configuration": "Show configuration guides for email clients and smartphones", "client_configuration": "Show configuration guides for email clients and smartphones",
"create_app_passwd": "Create app password", "create_app_passwd": "Create app password",
"create_syncjob": "Crea un azione di sync",
"created_on": "Creato il", "created_on": "Creato il",
"daily": "Giornaliero", "daily": "Giornaliero",
"day": "giorno", "day": "giorno",
@@ -1246,6 +1253,7 @@
"spamfilter_wl_desc": "Email inserita nella whitelist per <b>non essere mai</b> riconosciuta come spam. Si possono usare le wildcards. Un filtro viene applicato solo agli alias diretti (alias con una singola casella postale di destinazione) esclusi gli alias catch-all e la casella postale stessa.", "spamfilter_wl_desc": "Email inserita nella whitelist per <b>non essere mai</b> riconosciuta come spam. Si possono usare le wildcards. Un filtro viene applicato solo agli alias diretti (alias con una singola casella postale di destinazione) esclusi gli alias catch-all e la casella postale stessa.",
"spamfilter_yellow": "Giallo: Questo messaggio può essere spam, verrà segnalato come spam e spostato nella cartella spazzatura", "spamfilter_yellow": "Giallo: Questo messaggio può essere spam, verrà segnalato come spam e spostato nella cartella spazzatura",
"status": "Stato", "status": "Stato",
"sync_jobs": "Processi di sync",
"tag_handling": "Imposta le gestione della mail evidenziate", "tag_handling": "Imposta le gestione della mail evidenziate",
"tag_help_example": "Esempio di mail con tag: ich<b>+Facebook</b>@example.org", "tag_help_example": "Esempio di mail con tag: ich<b>+Facebook</b>@example.org",
"tag_help_explain": "In sottocartelle: Una nuova cartella con il nome del tag verrà creata sotto INBOX (\"INBOX/Facebook\").<br />\r\nNell'oggetto: Il nome del tag verrà aggiunto all'inizio dell'oggetto della mail, esempio: \"[Facebook] Le mie notizie\".", "tag_help_explain": "In sottocartelle: Una nuova cartella con il nome del tag verrà creata sotto INBOX (\"INBOX/Facebook\").<br />\r\nNell'oggetto: Il nome del tag verrà aggiunto all'inizio dell'oggetto della mail, esempio: \"[Facebook] Le mie notizie\".",
@@ -1268,9 +1276,18 @@
"year": "anno", "year": "anno",
"years": "anni", "years": "anni",
"change_password_hint_app_passwords": "Il tuo account ha %d password delle app che non verranno modificate. Per gestirle, vai alla scheda App passwords.", "change_password_hint_app_passwords": "Il tuo account ha %d password delle app che non verranno modificate. Per gestirle, vai alla scheda App passwords.",
"syncjob_check_log": "Controlla i log",
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
"open_logs": "Apri i log", "open_logs": "Apri i log",
"apple_connection_profile_with_app_password": "Una nuova password dell'app è stata generata ed aggiunta al profilo così che non sia più necessario inserire alcuna password quando si imposta il dispositivo. Si prega di non condividere il file in quanto concede l'accesso completo alla tua casella di posta elettronica.", "apple_connection_profile_with_app_password": "Una nuova password dell'app è stata generata ed aggiunta al profilo così che non sia più necessario inserire alcuna password quando si imposta il dispositivo. Si prega di non condividere il file in quanto concede l'accesso completo alla tua casella di posta elettronica.",
"allowed_protocols": "Protocolli consentiti", "allowed_protocols": "Protocolli consentiti",
"syncjob_EX_OK": "Successo",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema di connessione",
"syncjob_EXIT_TLS_FAILURE": "Problema con la connessione criptata",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema di autenticazione",
"syncjob_EXIT_OVERQUOTA": "La casella di posta del destinatario ha superato la quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati",
"with_app_password": "con password dell'app", "with_app_password": "con password dell'app",
"direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile.", "direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile.",
"pushover_sound": "Suono", "pushover_sound": "Suono",
+49 -31
View File
@@ -44,16 +44,24 @@
"app_name": "アプリ名", "app_name": "アプリ名",
"app_password": "アプリパスワードを追加", "app_password": "アプリパスワードを追加",
"app_passwd_protocols": "アプリパスワードの許可プロトコル", "app_passwd_protocols": "アプリパスワードの許可プロトコル",
"automap": "フォルダーを自動マッピングする試み(例:「送信済みアイテム」、「送信済み」=>「送信済み」)",
"backup_mx_options": "リレーオプション", "backup_mx_options": "リレーオプション",
"bcc_dest_format": "BCC送信先は単一の有効なメールアドレスである必要があります。<br>複数のアドレスにコピーを送信する必要がある場合は、エイリアスを作成し、ここで使用してください。", "bcc_dest_format": "BCC送信先は単一の有効なメールアドレスである必要があります。<br>複数のアドレスにコピーを送信する必要がある場合は、エイリアスを作成し、ここで使用してください。",
"comment_info": "プライベートコメントはユーザーには表示されませんが、パブリックコメントはユーザーの概要でホバーするとツールチップとして表示されます。", "comment_info": "プライベートコメントはユーザーには表示されませんが、パブリックコメントはユーザーの概要でホバーするとツールチップとして表示されます。",
"custom_params": "カスタムパラメータ",
"custom_params_hint": "正: --param=xy, 誤: --param xy",
"delete1": "完了時にソースから削除",
"delete2": "ソースに存在しないメッセージを送信先で削除",
"delete2duplicates": "送信先で重複を削除",
"description": "説明", "description": "説明",
"destination": "送信先", "destination": "送信先",
"disable_login": "ログインを禁止(受信メールは許可)", "disable_login": "ログインを禁止(受信メールは許可)",
"domain": "ドメイン", "domain": "ドメイン",
"domain_matches_hostname": "ドメイン %s はホスト名と一致します", "domain_matches_hostname": "ドメイン %s はホスト名と一致します",
"domain_quota_m": "ドメインの総クォータ(MiB)", "domain_quota_m": "ドメインの総クォータ(MiB)",
"dry": "同期をシミュレーション",
"enc_method": "暗号化方式", "enc_method": "暗号化方式",
"exclude": "オブジェクトを除外(正規表現)",
"full_name": "フルネーム", "full_name": "フルネーム",
"gal": "グローバルアドレスリスト", "gal": "グローバルアドレスリスト",
"gal_info": "GALにはドメインのすべてのオブジェクトが含まれ、どのユーザーも編集できません。無効にすると、SOGoの空き/取り込み情報が表示されなくなります!<b>変更を適用するにはSOGoを再起動してください。</b>", "gal_info": "GALにはドメインのすべてのオブジェクトが含まれ、どのユーザーも編集できません。無効にすると、SOGoの空き/取り込み情報が表示されなくなります!<b>変更を適用するにはSOGoを再起動してください。</b>",
@@ -69,6 +77,7 @@
"mailbox_username": "ユーザー名(メールアドレスの左部分)", "mailbox_username": "ユーザー名(メールアドレスの左部分)",
"max_aliases": "作成可能なエイリアスの最大数", "max_aliases": "作成可能なエイリアスの最大数",
"max_mailboxes": "作成可能なメールボックスの最大数", "max_mailboxes": "作成可能なメールボックスの最大数",
"mins_interval": "ポーリング間隔(分)",
"multiple_bookings": "複数の予約", "multiple_bookings": "複数の予約",
"nexthop": "次のホップ", "nexthop": "次のホップ",
"password": "パスワード", "password": "パスワード",
@@ -88,10 +97,16 @@
"select_domain": "最初にドメインを選択してください", "select_domain": "最初にドメインを選択してください",
"sieve_desc": "簡単な説明", "sieve_desc": "簡単な説明",
"sieve_type": "フィルタータイプ", "sieve_type": "フィルタータイプ",
"skipcrossduplicates": "フォルダー間で重複するメッセージをスキップ(先着順)",
"subscribeall": "すべてのフォルダーを購読",
"syncjob": "同期ジョブを追加",
"syncjob_hint": "パスワードはプレーンテキストで保存される必要があることに注意してください!",
"tags": "タグ", "tags": "タグ",
"target_address": "宛先アドレス", "target_address": "宛先アドレス",
"target_address_info": "<small>完全なメールアドレス(カンマ区切り)。</small>", "target_address_info": "<small>完全なメールアドレス(カンマ区切り)。</small>",
"target_domain": "ターゲットドメイン", "target_domain": "ターゲットドメイン",
"timeout1": "リモートホストへの接続タイムアウト",
"timeout2": "ローカルホストへの接続タイムアウト",
"username": "ユーザー名", "username": "ユーザー名",
"validate": "検証", "validate": "検証",
"validation_success": "正常に検証されました" "validation_success": "正常に検証されました"
@@ -570,6 +585,7 @@
"app_name": "アプリ名", "app_name": "アプリ名",
"app_passwd": "アプリパスワード", "app_passwd": "アプリパスワード",
"app_passwd_protocols": "アプリパスワードで許可されるプロトコル", "app_passwd_protocols": "アプリパスワードで許可されるプロトコル",
"automap": "フォルダーを自動マッピングする(例:\"Sent items\", \"Sent\" => \"Sent\" など)",
"backup_mx_options": "リレーオプション", "backup_mx_options": "リレーオプション",
"bcc_dest_format": "BCC送信先は1つの有効なメールアドレスである必要があります。<br>複数のアドレスにコピーを送信する必要がある場合は、エイリアスを作成してここで使用してください。", "bcc_dest_format": "BCC送信先は1つの有効なメールアドレスである必要があります。<br>複数のアドレスにコピーを送信する必要がある場合は、エイリアスを作成してここで使用してください。",
"client_id": "クライアントID", "client_id": "クライアントID",
@@ -577,6 +593,9 @@
"comment_info": "プライベートコメントはユーザーには表示されませんが、パブリックコメントはユーザー概要でホバーするとツールチップとして表示されます。", "comment_info": "プライベートコメントはユーザーには表示されませんが、パブリックコメントはユーザー概要でホバーするとツールチップとして表示されます。",
"created_on": "作成日時", "created_on": "作成日時",
"custom_attributes": "カスタム属性", "custom_attributes": "カスタム属性",
"delete1": "完了時にソースから削除",
"delete2": "ソースに存在しないメッセージを送信先で削除",
"delete2duplicates": "送信先で重複を削除",
"delete_ays": "削除プロセスを確認してください。", "delete_ays": "削除プロセスを確認してください。",
"description": "説明", "description": "説明",
"disable_login": "ログインを禁止(受信メールは許可)", "disable_login": "ログインを禁止(受信メールは許可)",
@@ -600,6 +619,7 @@
"dont_check_sender_acl": "ドメイン %s(およびエイリアスドメイン)の送信者チェックを無効化", "dont_check_sender_acl": "ドメイン %s(およびエイリアスドメイン)の送信者チェックを無効化",
"edit_alias_domain": "エイリアスドメインを編集", "edit_alias_domain": "エイリアスドメインを編集",
"encryption": "暗号化", "encryption": "暗号化",
"exclude": "オブジェクトを除外(正規表現)",
"extended_sender_acl": "外部送信者アドレス", "extended_sender_acl": "外部送信者アドレス",
"extended_sender_acl_info": "可能であればDKIMドメインキーをインポートしてください。<br>\r\n このサーバーを対応するSPF TXTレコードに追加することを忘れないでください。<br>\r\n このサーバーに外部アドレスと重複するドメインまたはエイリアスドメインを追加するたびに、外部アドレスは削除されます。<br>\r\n *@domain.tld として送信を許可するには @domain.tld を使用してください。", "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": "次回ログイン時にパスワードの更新を強制",
@@ -626,7 +646,10 @@
"max_aliases": "最大エイリアス数", "max_aliases": "最大エイリアス数",
"max_mailboxes": "作成可能な最大メールボックス数", "max_mailboxes": "作成可能な最大メールボックス数",
"max_quota": "メールボックスごとの最大クォータ(MiB)", "max_quota": "メールボックスごとの最大クォータ(MiB)",
"maxage": "リモートから取得するメッセージの最大日数<br><small>(0 = 日数を無視)</small>",
"maxbytespersecond": "毎秒の最大バイト数<br><small>(0 = 無制限)</small>",
"mbox_rl_info": "このレート制限はSASLログイン名に適用され、ログインしたユーザーによって使用されるすべての「送信者」アドレスに一致します。メールボックスレート制限は、ドメイン全体のレート制限を上書きします。", "mbox_rl_info": "このレート制限はSASLログイン名に適用され、ログインしたユーザーによって使用されるすべての「送信者」アドレスに一致します。メールボックスレート制限は、ドメイン全体のレート制限を上書きします。",
"mins_interval": "間隔(分)",
"multiple_bookings": "複数予約", "multiple_bookings": "複数予約",
"none_inherit": "なし / 継承", "none_inherit": "なし / 継承",
"nexthop": "次のホップ", "nexthop": "次のホップ",
@@ -667,6 +690,7 @@
"sender_acl_info": "メールボックスユーザーAがメールボックスユーザーBとして送信することを許可されている場合、SOGoで送信者アドレスが自動的に選択可能な「送信者」フィールドとして表示されることはありません。<br>\r\n メールボックスユーザーBは、SOGoでメールボックスを委任する必要があります。この委任は、メールビューの左上にあるメールボックス名の右にあるメニュー(三点ボタン)を使用して行います。この動作はエイリアスアドレスには適用されません。", "sender_acl_info": "メールボックスユーザーAがメールボックスユーザーBとして送信することを許可されている場合、SOGoで送信者アドレスが自動的に選択可能な「送信者」フィールドとして表示されることはありません。<br>\r\n メールボックスユーザーBは、SOGoでメールボックスを委任する必要があります。この委任は、メールビューの左上にあるメールボックス名の右にあるメニュー(三点ボタン)を使用して行います。この動作はエイリアスアドレスには適用されません。",
"sieve_desc": "簡単な説明", "sieve_desc": "簡単な説明",
"sieve_type": "フィルタータイプ", "sieve_type": "フィルタータイプ",
"skipcrossduplicates": "フォルダー間で重複するメッセージをスキップ(先着順)",
"sogo_access": "SOGoへの直接ログインアクセスを許可", "sogo_access": "SOGoへの直接ログインアクセスを許可",
"sogo_access_info": "メールUI内からのシングルサインオンは引き続き動作します。この設定は他のすべてのサービスへのアクセスには影響しません。また、ユーザーの既存のSOGoプロファイルを削除または変更するものでもありません。", "sogo_access_info": "メールUI内からのシングルサインオンは引き続き動作します。この設定は他のすべてのサービスへのアクセスには影響しません。また、ユーザーの既存のSOGoプロファイルを削除または変更するものでもありません。",
"sogo_visible": "SOGoにエイリアスが表示される", "sogo_visible": "SOGoにエイリアスが表示される",
@@ -675,8 +699,12 @@
"spam_filter": "スパムフィルター", "spam_filter": "スパムフィルター",
"spam_policy": "ホワイトリスト/ブラックリストにアイテムを追加または削除", "spam_policy": "ホワイトリスト/ブラックリストにアイテムを追加または削除",
"spam_score": "カスタムスパムスコアを設定", "spam_score": "カスタムスパムスコアを設定",
"subfolder2": "送信先でサブフォルダーに同期<br><small>(空欄 = サブフォルダーを使用しない)</small>",
"syncjob": "同期ジョブを編集",
"target_address": "転送先アドレス<small>(カンマ区切り)</small>", "target_address": "転送先アドレス<small>(カンマ区切り)</small>",
"target_domain": "ターゲットドメイン", "target_domain": "ターゲットドメイン",
"timeout1": "リモートホストへの接続タイムアウト",
"timeout2": "ローカルホストへの接続タイムアウト",
"title": "オブジェクトを編集", "title": "オブジェクトを編集",
"unchanged_if_empty": "変更がない場合は空欄にしてください", "unchanged_if_empty": "変更がない場合は空欄にしてください",
"username": "ユーザー名", "username": "ユーザー名",
@@ -887,6 +915,16 @@
"spam_aliases": "一時的なエイリアス", "spam_aliases": "一時的なエイリアス",
"stats": "統計", "stats": "統計",
"status": "ステータス", "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": "テーブルサイズ",
"table_size_show_n": "%s 件を表示", "table_size_show_n": "%s 件を表示",
"target_address": "転送先アドレス", "target_address": "転送先アドレス",
@@ -1116,37 +1154,6 @@
"waiting_usb_register": "<i>USBデバイスを待っています...</i><br><br>上記にパスワードを入力し、USBデバイスのボタンを押して登録を確認してください。", "waiting_usb_register": "<i>USBデバイスを待っています...</i><br><br>上記にパスワードを入力し、USBデバイスのボタンを押して登録を確認してください。",
"yubi_otp": "Yubico OTP認証" "yubi_otp": "Yubico OTP認証"
}, },
"syncjobs": {
"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": "ユーザー名またはパスワードが間違っています",
"add": "同期ジョブを追加",
"add_hint": "パスワードはプレーンテキストで保存される必要があることに注意してください!",
"edit": "同期ジョブを編集",
"create": "新しい同期ジョブを作成",
"dry": "同期をシミュレーション",
"subfolder2": "送信先でサブフォルダーに同期<br><small>(空欄 = サブフォルダーを使用しない)</small>",
"mins_interval": "ポーリング間隔(分)",
"maxage": "リモートから取得するメッセージの最大日数<br><small>(0 = 日数を無視)</small>",
"exclude": "オブジェクトを除外(正規表現)",
"automap": "フォルダーを自動マッピングする試み(例:「送信済みアイテム」、「送信済み」=>「送信済み」)",
"delete1": "完了時にソースから削除",
"delete2": "ソースに存在しないメッセージを送信先で削除",
"delete2duplicates": "送信先で重複を削除",
"skipcrossduplicates": "フォルダー間で重複するメッセージをスキップ(先着順)",
"subscribeall": "すべてのフォルダーを購読",
"timeout1": "リモートホストへの接続タイムアウト",
"timeout2": "ローカルホストへの接続タイムアウト",
"maxbytespersecond": "毎秒の最大バイト数<br><small>(0 = 無制限)</small>",
"custom_params": "カスタムパラメータ"
},
"user": { "user": {
"action": "アクション", "action": "アクション",
"active": "有効", "active": "有効",
@@ -1176,6 +1183,7 @@
"clear_recent_successful_connections": "最近の成功した接続をクリア", "clear_recent_successful_connections": "最近の成功した接続をクリア",
"client_configuration": "メールクライアントとスマートフォンの設定ガイドを表示", "client_configuration": "メールクライアントとスマートフォンの設定ガイドを表示",
"create_app_passwd": "アプリパスワードを作成", "create_app_passwd": "アプリパスワードを作成",
"create_syncjob": "新しい同期ジョブを作成",
"created_on": "作成日", "created_on": "作成日",
"daily": "毎日", "daily": "毎日",
"day": "日", "day": "日",
@@ -1281,6 +1289,16 @@
"spamfilter_wl_desc": "ホワイトリストに登録されたメールアドレスは<b>絶対に</b>スパムとして分類されません。ワイルドカードを使用できます。フィルターは直接エイリアス(単一ターゲットメールボックスを持つエイリアス)にのみ適用され、キャッチオールエイリアスやメールボックス自体には適用されません。", "spamfilter_wl_desc": "ホワイトリストに登録されたメールアドレスは<b>絶対に</b>スパムとして分類されません。ワイルドカードを使用できます。フィルターは直接エイリアス(単一ターゲットメールボックスを持つエイリアス)にのみ適用され、キャッチオールエイリアスやメールボックス自体には適用されません。",
"spamfilter_yellow": "黄: このメッセージはスパムの可能性があり、スパムとしてタグ付けされ、迷惑メールフォルダに移動されます", "spamfilter_yellow": "黄: このメッセージはスパムの可能性があり、スパムとしてタグ付けされ、迷惑メールフォルダに移動されます",
"status": "ステータス", "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_handling": "タグ付きメールの処理を設定",
"tag_help_example": "タグ付きメールアドレスの例: me<b>+Facebook</b>@example.org", "tag_help_example": "タグ付きメールアドレスの例: me<b>+Facebook</b>@example.org",
"tag_help_explain": "サブフォルダー: タグ名に基づいて新しいサブフォルダーがINBOXの下に作成されます(例: \"INBOX/Facebook\")。<br>\r\n件名: タグ名がメールの件名に追加されます(例: \"[Facebook] My News\")。", "tag_help_explain": "サブフォルダー: タグ名に基づいて新しいサブフォルダーがINBOXの下に作成されます(例: \"INBOX/Facebook\")。<br>\r\n件名: タグ名がメールの件名に追加されます(例: \"[Facebook] My News\")。",
+30 -21
View File
@@ -43,8 +43,14 @@
"alias_domain_info": "<small>유효한 도메인 명만 입력 (쉼표로 구분).</small>", "alias_domain_info": "<small>유효한 도메인 명만 입력 (쉼표로 구분).</small>",
"app_name": "앱 이름", "app_name": "앱 이름",
"app_password": "앱 비밀번호 추가", "app_password": "앱 비밀번호 추가",
"automap": "자동으로 폴더 연결 시도하기 (\"Sent items\", \"Sent\" => \"Sent\" 등등)",
"backup_mx_options": "예비 MX 설정", "backup_mx_options": "예비 MX 설정",
"comment_info": "비공개 메모는 사용자에게 보이지 않지만, 공개 메모는 사용자 개요에서 툴팁 형태로 볼 수 있습니다.", "comment_info": "비공개 메모는 사용자에게 보이지 않지만, 공개 메모는 사용자 개요에서 툴팁 형태로 볼 수 있습니다.",
"custom_params": "추가 변수",
"custom_params_hint": "올바른 설정: --param=xy, 올바르지 않은 설정: --param xy",
"delete1": "완료하면 원래 있던 곳에서 삭제",
"delete2": "원래 있는 곳에 있지 않는 메일을 도착지에서 삭제",
"delete2duplicates": "도착지에 있는 중복 메일 삭제",
"description": "설명", "description": "설명",
"destination": "목적지", "destination": "목적지",
"disable_login": "로그인 비활성화 (메일은 계속 받습니다.)", "disable_login": "로그인 비활성화 (메일은 계속 받습니다.)",
@@ -52,6 +58,7 @@
"domain_matches_hostname": "Domain %s matches hostname", "domain_matches_hostname": "Domain %s matches hostname",
"domain_quota_m": "도메인에 할당할 디스크 크기 (MiB)", "domain_quota_m": "도메인에 할당할 디스크 크기 (MiB)",
"enc_method": "암호화 방식", "enc_method": "암호화 방식",
"exclude": "제외할 항목 (정규식)",
"full_name": "이름", "full_name": "이름",
"gal": "글로벌 주소 리스트", "gal": "글로벌 주소 리스트",
"gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>", "gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
@@ -67,6 +74,7 @@
"mailbox_username": "사용자 이름 (이메일의 @ 왼쪽 부분)", "mailbox_username": "사용자 이름 (이메일의 @ 왼쪽 부분)",
"max_aliases": "최대 별칭 주소", "max_aliases": "최대 별칭 주소",
"max_mailboxes": "최대 메일함 수", "max_mailboxes": "최대 메일함 수",
"mins_interval": "폴링 간격 (단위: 분)",
"multiple_bookings": "Multiple bookings", "multiple_bookings": "Multiple bookings",
"nexthop": "Next hop", "nexthop": "Next hop",
"password": "비밀번호", "password": "비밀번호",
@@ -86,9 +94,15 @@
"select_domain": "도메인을 먼저 선택해주세요.", "select_domain": "도메인을 먼저 선택해주세요.",
"sieve_desc": "짧은 설명", "sieve_desc": "짧은 설명",
"sieve_type": "필터 종류", "sieve_type": "필터 종류",
"skipcrossduplicates": "폴더들의 중복 메세지 건너뛰기 (first come, first serve)",
"subscribeall": "모든 폴더 구독",
"syncjob": "동기화 작업 추가",
"syncjob_hint": "비밀번호가 일반 텍스트로 저장됨을 주의해주세요.",
"target_address": "타깃 주소", "target_address": "타깃 주소",
"target_address_info": "<small>완전한 이메일 주소 (쉼표로 구분).</small>", "target_address_info": "<small>완전한 이메일 주소 (쉼표로 구분).</small>",
"target_domain": "타깃 도메인", "target_domain": "타깃 도메인",
"timeout1": "원격 호스트 연결 시간 초과",
"timeout2": "로컬 호스트 연결 시간 초과",
"username": "사용자명", "username": "사용자명",
"validate": "확인하기", "validate": "확인하기",
"validation_success": "성공적으로 확인됨", "validation_success": "성공적으로 확인됨",
@@ -530,11 +544,15 @@
"alias": "Edit alias", "alias": "Edit alias",
"app_name": "App name", "app_name": "App name",
"app_passwd": "App password", "app_passwd": "App password",
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Backup MX options", "backup_mx_options": "Backup MX options",
"bcc_dest_format": "BCC destination must be a single valid email address.", "bcc_dest_format": "BCC destination must be a single valid email address.",
"client_id": "Client ID", "client_id": "Client ID",
"client_secret": "Client secret", "client_secret": "Client secret",
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview", "comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview",
"delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Delete duplicates on destination",
"delete_ays": "Please confirm the deletion process.", "delete_ays": "Please confirm the deletion process.",
"description": "Description", "description": "Description",
"disable_login": "Disallow login (incoming mail is still accepted)", "disable_login": "Disallow login (incoming mail is still accepted)",
@@ -545,6 +563,7 @@
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)", "dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
"edit_alias_domain": "Edit Alias domain", "edit_alias_domain": "Edit Alias domain",
"encryption": "Encryption", "encryption": "Encryption",
"exclude": "Exclude objects (regex)",
"extended_sender_acl": "External sender addresses", "extended_sender_acl": "External sender addresses",
"extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.", "extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.",
"force_pw_update": "Force password update at next login", "force_pw_update": "Force password update at next login",
@@ -563,7 +582,10 @@
"max_aliases": "Max. aliases", "max_aliases": "Max. aliases",
"max_mailboxes": "Max. possible mailboxes", "max_mailboxes": "Max. possible mailboxes",
"max_quota": "Max. quota per mailbox (MiB)", "max_quota": "Max. quota per mailbox (MiB)",
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.", "mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.",
"mins_interval": "Interval (min)",
"multiple_bookings": "Multiple bookings", "multiple_bookings": "Multiple bookings",
"nexthop": "Next hop", "nexthop": "Next hop",
"password": "Password", "password": "Password",
@@ -597,13 +619,18 @@
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.", "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"sogo_visible": "Alias is visible in SOGo", "sogo_visible": "Alias is visible in SOGo",
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.", "sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"spam_alias": "Create or change time limited alias addresses", "spam_alias": "Create or change time limited alias addresses",
"spam_policy": "Add or remove items to white-/blacklist", "spam_policy": "Add or remove items to white-/blacklist",
"spam_score": "Set a custom spam score", "spam_score": "Set a custom spam score",
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
"syncjob": "Edit sync job",
"target_address": "Goto address/es <small>(comma-separated)</small>", "target_address": "Goto address/es <small>(comma-separated)</small>",
"target_domain": "Target domain", "target_domain": "Target domain",
"timeout1": "Timeout for connection to remote host",
"timeout2": "Timeout for connection to local host",
"title": "Edit object", "title": "Edit object",
"unchanged_if_empty": "If unchanged leave blank", "unchanged_if_empty": "If unchanged leave blank",
"username": "Username", "username": "Username",
@@ -771,6 +798,7 @@
"spam_aliases": "Temp. alias", "spam_aliases": "Temp. alias",
"stats": "Statistics", "stats": "Statistics",
"status": "Status", "status": "Status",
"sync_jobs": "Sync jobs",
"table_size": "Table size", "table_size": "Table size",
"table_size_show_n": "Show %s items", "table_size_show_n": "Show %s items",
"target_address": "Goto address", "target_address": "Goto address",
@@ -950,27 +978,6 @@
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your WebAuthn registration by tapping the button on your WebAuthn USB device.", "waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your WebAuthn registration by tapping the button on your WebAuthn USB device.",
"yubi_otp": "Yubico OTP authentication" "yubi_otp": "Yubico OTP authentication"
}, },
"syncjobs": {
"sync_jobs": "동기화 작업",
"add": "동기화 작업 추가",
"add_hint": "비밀번호가 일반 텍스트로 저장됨을 주의해주세요.",
"edit": "Edit sync job",
"create": "새 동기화 작업 생성",
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
"mins_interval": "폴링 간격 (단위: 분)",
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
"exclude": "제외할 항목 (정규식)",
"automap": "자동으로 폴더 연결 시도하기 (\"Sent items\", \"Sent\" => \"Sent\" 등등)",
"delete1": "완료하면 원래 있던 곳에서 삭제",
"delete2": "원래 있는 곳에 있지 않는 메일을 도착지에서 삭제",
"delete2duplicates": "도착지에 있는 중복 메일 삭제",
"skipcrossduplicates": "폴더들의 중복 메세지 건너뛰기 (first come, first serve)",
"subscribeall": "모든 폴더 구독",
"timeout1": "원격 호스트 연결 시간 초과",
"timeout2": "로컬 호스트 연결 시간 초과",
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
"custom_params": "추가 변수"
},
"user": { "user": {
"action": "조치", "action": "조치",
"active": "활성화됨", "active": "활성화됨",
@@ -995,6 +1002,7 @@
"change_password": "비밀번호 변경", "change_password": "비밀번호 변경",
"client_configuration": "이메일 클라이언트와 스마트폰을 위한 계정 설정 가이드 보기", "client_configuration": "이메일 클라이언트와 스마트폰을 위한 계정 설정 가이드 보기",
"create_app_passwd": "앱 비밀번호 생성하기", "create_app_passwd": "앱 비밀번호 생성하기",
"create_syncjob": "새 동기화 작업 생성",
"daily": "매일", "daily": "매일",
"day": "일", "day": "일",
"description": "Description", "description": "Description",
@@ -1074,6 +1082,7 @@
"spamfilter_wl_desc": "화이트리스트에 등록된 메일로부터 온 메세지들은 <b>절대로</b> 스팸으로 분류되지 않습니다. 와일드카드를 사용할 수 있습니다. 필터는 1대1로 대응하는 이메일 별칭에만 적용되며, catch-all 형식의 별칭과 메일함 자체는 제외합니다.", "spamfilter_wl_desc": "화이트리스트에 등록된 메일로부터 온 메세지들은 <b>절대로</b> 스팸으로 분류되지 않습니다. 와일드카드를 사용할 수 있습니다. 필터는 1대1로 대응하는 이메일 별칭에만 적용되며, catch-all 형식의 별칭과 메일함 자체는 제외합니다.",
"spamfilter_yellow": "노랑: 이 메세지는 스팸일 수 있으며, 메일이 스팸함으로 이동합니다.", "spamfilter_yellow": "노랑: 이 메세지는 스팸일 수 있으며, 메일이 스팸함으로 이동합니다.",
"status": "상태", "status": "상태",
"sync_jobs": "동기화 작업",
"tag_handling": "태그가 있는 메일 처리 방식 설정", "tag_handling": "태그가 있는 메일 처리 방식 설정",
"tag_help_example": "태그가 있는 이메일 주소의 예시: me<b>+Facebook</b>@example.org", "tag_help_example": "태그가 있는 이메일 주소의 예시: me<b>+Facebook</b>@example.org",
"tag_help_explain": "하위 폴더로 이동: INBOX 아래에 태그와 같은 이름을 가진 새 하위 폴더를 생성합니다. (\"INBOX/Facebook\").<br>\r\n제목에 추가: 태그가 메일 제목 앞에 추가됩니다. 예시: \"[Facebook] 새 소식\".", "tag_help_explain": "하위 폴더로 이동: INBOX 아래에 태그와 같은 이름을 가진 새 하위 폴더를 생성합니다. (\"INBOX/Facebook\").<br>\r\n제목에 추가: 태그가 메일 제목 앞에 추가됩니다. 예시: \"[Facebook] 새 소식\".",
+8 -9
View File
@@ -38,11 +38,15 @@
"app_name": "Programėlės pavadinimas", "app_name": "Programėlės pavadinimas",
"app_password": "Pridėti programėlės slaptažodį", "app_password": "Pridėti programėlės slaptažodį",
"app_passwd_protocols": "Prieinamieji programėlės slaptažodžio protokolai", "app_passwd_protocols": "Prieinamieji programėlės slaptažodžio protokolai",
"custom_params": "Pasirinktiniai parametrai",
"delete1": "Ištrinti iš šaltinio, kai baigta",
"activate_filter_warn": "Visi kiti filtrai bus deaktivuoti, kai „aktyvus“ yra pražymėtas.", "activate_filter_warn": "Visi kiti filtrai bus deaktivuoti, kai „aktyvus“ yra pražymėtas.",
"custom_params_hint": "Teisingai: --param=xy, neteisingai: --param xy",
"description": "Aprašymas", "description": "Aprašymas",
"destination": "Gavėjas", "destination": "Gavėjas",
"domain": "Domenas", "domain": "Domenas",
"domain_quota_m": "Viso domeno kvota (MiB)", "domain_quota_m": "Viso domeno kvota (MiB)",
"dry": "Simuliuoti sinchronizaciją",
"enc_method": "Šifravimo metodas", "enc_method": "Šifravimo metodas",
"full_name": "Prilnas vardas", "full_name": "Prilnas vardas",
"generate": "generuoti", "generate": "generuoti",
@@ -60,9 +64,13 @@
"alias_address": "Pakaitiniai/is adresai/as", "alias_address": "Pakaitiniai/is adresai/as",
"alias_address_info": "<small>Pilnas el. pašto adresas (-ai) arba @example.com, kad gautumėte visus pranešimus iš domeno (atskirtus kableliais). <b>Tik \"mailcow\" domenai</b>.</small>", "alias_address_info": "<small>Pilnas el. pašto adresas (-ai) arba @example.com, kad gautumėte visus pranešimus iš domeno (atskirtus kableliais). <b>Tik \"mailcow\" domenai</b>.</small>",
"alias_domain": "Pakaitinis domenas", "alias_domain": "Pakaitinis domenas",
"automap": "Stenktis automatiškai susieti aplankus (\"Išsiųsti\", \"Išsiųsti\" => \"išsiųsti\" ir tt.)",
"backup_mx_options": "Perdavimo nustatymai", "backup_mx_options": "Perdavimo nustatymai",
"bcc_dest_format": "BCC gavimo adresas turi būti vienas galiojantis el. pašto adresas.<br>Jei turite siųsti kopiją į kelis adresus, sukurkite slapyvardį ir naudokite jį čia.", "bcc_dest_format": "BCC gavimo adresas turi būti vienas galiojantis el. pašto adresas.<br>Jei turite siųsti kopiją į kelis adresus, sukurkite slapyvardį ir naudokite jį čia.",
"delete2": "Ištrinti žinutes gavimo vietoje, kurios nėra šaltinyje.",
"delete2duplicates": "Ištrinti dublikatus gavimo vietoje",
"domain_matches_hostname": "Domenas %s atitinka serverio vardą", "domain_matches_hostname": "Domenas %s atitinka serverio vardą",
"exclude": "Išskirti objektus (regex)",
"gal": "Visuotinis adresų sąrašas", "gal": "Visuotinis adresų sąrašas",
"gal_info": "GAL (Global Address List) apima visus domeno objektus ir jų negali redaguoti joks vartotojas. SOGo trūksta laisvosios/užimtosios informacijos, jei tai išjungta! <b>Perkraukite SOGo, kad pritaikytumėte pakeitimus.</b>" "gal_info": "GAL (Global Address List) apima visus domeno objektus ir jų negali redaguoti joks vartotojas. SOGo trūksta laisvosios/užimtosios informacijos, jei tai išjungta! <b>Perkraukite SOGo, kad pritaikytumėte pakeitimus.</b>"
}, },
@@ -342,14 +350,5 @@
"dovecot_restart_success": "„Doveccot“ perkrautas", "dovecot_restart_success": "„Doveccot“ perkrautas",
"domain_added": "Pridėtas domenas %s", "domain_added": "Pridėtas domenas %s",
"domain_admin_modified": "Pakeitimai domeno adminustratoriui %s išsaugoti" "domain_admin_modified": "Pakeitimai domeno adminustratoriui %s išsaugoti"
},
"syncjobs": {
"dry": "Simuliuoti sinchronizaciją",
"exclude": "Išskirti objektus (regex)",
"automap": "Stenktis automatiškai susieti aplankus (\"Išsiųsti\", \"Išsiųsti\" => \"išsiųsti\" ir tt.)",
"delete1": "Ištrinti iš šaltinio, kai baigta",
"delete2": "Ištrinti žinutes gavimo vietoje, kurios nėra šaltinyje.",
"delete2duplicates": "Ištrinti dublikatus gavimo vietoje",
"custom_params": "Pasirinktiniai parametrai"
} }
} }
+27 -20
View File
@@ -37,11 +37,16 @@
"alias_address_info": "<small>Pilna epasta adrese/s vai @example.com, lai notvertu visus domēna ziņojumus (atdalītas ar komatu). <b>Tikai mailcow domēni</b>.</small>", "alias_address_info": "<small>Pilna epasta adrese/s vai @example.com, lai notvertu visus domēna ziņojumus (atdalītas ar komatu). <b>Tikai mailcow domēni</b>.</small>",
"alias_domain": "Aizstājdomēni", "alias_domain": "Aizstājdomēni",
"alias_domain_info": "<small>Tikai derīgi domēna vārdi (komatu atdalīti).</small>", "alias_domain_info": "<small>Tikai derīgi domēna vārdi (komatu atdalīti).</small>",
"automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" etc.)",
"backup_mx_options": "Releja iespējas", "backup_mx_options": "Releja iespējas",
"delete1": "Izdzēst no avota pēc pabeigšanas",
"delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā",
"delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
"description": "Apraksts", "description": "Apraksts",
"domain": "Domēns", "domain": "Domēns",
"domain_quota_m": "Kopējā domēna kvota (MiB)", "domain_quota_m": "Kopējā domēna kvota (MiB)",
"enc_method": "Šifrēšanas metode", "enc_method": "Šifrēšanas metode",
"exclude": "Izslēgt objektus (regex)",
"full_name": "Pilns vārds", "full_name": "Pilns vārds",
"goto_null": "Klusām atmest pastu", "goto_null": "Klusām atmest pastu",
"hostname": "Saimniekdators", "hostname": "Saimniekdators",
@@ -50,6 +55,7 @@
"mailbox_username": "Lietotājvārds (kriesā daļa no epasta adreses)", "mailbox_username": "Lietotājvārds (kriesā daļa no epasta adreses)",
"max_aliases": "Lielākais pieļaujamo aizstājvārdu skaits", "max_aliases": "Lielākais pieļaujamo aizstājvārdu skaits",
"max_mailboxes": "Maks. iespējamās pastkastes", "max_mailboxes": "Maks. iespējamās pastkastes",
"mins_interval": "Aptauajs intervāls (minūtes)",
"multiple_bookings": "Vairākas rezervācijas", "multiple_bookings": "Vairākas rezervācijas",
"password": "Parole", "password": "Parole",
"password_repeat": "Apstiprinājuma parole (atkārtot)", "password_repeat": "Apstiprinājuma parole (atkārtot)",
@@ -63,6 +69,9 @@
"select_domain": "Lūgums vispirms atlasīt domēnu", "select_domain": "Lūgums vispirms atlasīt domēnu",
"sieve_desc": "Īss apraksts", "sieve_desc": "Īss apraksts",
"sieve_type": "Filtra tips", "sieve_type": "Filtra tips",
"skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)",
"syncjob": "Pievienot sinhronizācijas darbu",
"syncjob_hint": "Ņemiet vērā, ka parole ir jāuzglabā vienkāršā tekstā!",
"target_address": "Mērķa adreses", "target_address": "Mērķa adreses",
"target_address_info": "<small>Pilna epasta adrese/s (atdalītas ar komatu).</small>", "target_address_info": "<small>Pilna epasta adrese/s (atdalītas ar komatu).</small>",
"target_domain": "Mērķa domēns", "target_domain": "Mērķa domēns",
@@ -260,7 +269,11 @@
"edit": { "edit": {
"active": "Aktīvs", "active": "Aktīvs",
"alias": "Labot aizstājvārdu", "alias": "Labot aizstājvārdu",
"automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" utt.)",
"backup_mx_options": "Retranslācijas iespējams", "backup_mx_options": "Retranslācijas iespējams",
"delete1": "Izdzēst no avota pēc pabeigšanas",
"delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā",
"delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
"description": "Apraksts", "description": "Apraksts",
"domain": "Labot domēnu", "domain": "Labot domēnu",
"domain_admin": "Labot domēna pārvaldītāju", "domain_admin": "Labot domēna pārvaldītāju",
@@ -269,6 +282,7 @@
"dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)", "dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)",
"edit_alias_domain": "Labot aizstājdomēnu", "edit_alias_domain": "Labot aizstājdomēnu",
"encryption": "Šifrēšana", "encryption": "Šifrēšana",
"exclude": "Neiekļaut objektus (regex)",
"force_pw_update": "Piespiedu paroles atjaunošana nākamajā pieteikšanās reizē", "force_pw_update": "Piespiedu paroles atjaunošana nākamajā pieteikšanās reizē",
"force_pw_update_info": "Šis lietotājs varēs pieteikties tikai %s. Lietotņu paroles joprojām būs izmantojamas.", "force_pw_update_info": "Šis lietotājs varēs pieteikties tikai %s. Lietotņu paroles joprojām būs izmantojamas.",
"full_name": "Pilns vārds", "full_name": "Pilns vārds",
@@ -279,6 +293,9 @@
"max_aliases": "Lielākais aizstājvārdu skaits", "max_aliases": "Lielākais aizstājvārdu skaits",
"max_mailboxes": "Maks. iespējamās pastkastes", "max_mailboxes": "Maks. iespējamās pastkastes",
"max_quota": "Maks. kvota uz pastkasti (MiB)", "max_quota": "Maks. kvota uz pastkasti (MiB)",
"maxage": "Lielākais pieļaujamais ziņojumu, kuri tiks vaicāti attālajā serverī, vecums dienās<br><small>(0 = neņemt vērā vecumu)</small>",
"maxbytespersecond": "Maks. baiti sekundē (0 ir vienāds ar neierobežotu skaitu)",
"mins_interval": "Intervāls (min)",
"multiple_bookings": "Vairāki rezervējumi", "multiple_bookings": "Vairāki rezervējumi",
"password": "Parole", "password": "Parole",
"password_repeat": "Apstiprinājuma parole (atkārtot)", "password_repeat": "Apstiprinājuma parole (atkārtot)",
@@ -293,9 +310,12 @@
"sender_acl": "Atļaut sūtīt kā", "sender_acl": "Atļaut sūtīt kā",
"sieve_desc": "Īss apraksts", "sieve_desc": "Īss apraksts",
"sieve_type": "Filtra tips", "sieve_type": "Filtra tips",
"skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)",
"spam_alias": "Izveidot vai mainīt laika ierobežotas aizstājadreses", "spam_alias": "Izveidot vai mainīt laika ierobežotas aizstājadreses",
"spam_policy": "Pievienot vai noņemt vienumus atļautajā/liegumu sarakstā", "spam_policy": "Pievienot vai noņemt vienumus atļautajā/liegumu sarakstā",
"spam_score": "Iestatīt pielāgotu mēstules vērtējumu", "spam_score": "Iestatīt pielāgotu mēstules vērtējumu",
"subfolder2": "Sinhronizēt galamērķa apakšmapē<br><small>(tukšs = neizmantot apakšmapi)</small>",
"syncjob": "Labot sinhronizācijas darbu",
"target_address": "Mērķa adrese/s <small>(atdalītas ar komatu)</small>", "target_address": "Mērķa adrese/s <small>(atdalītas ar komatu)</small>",
"target_domain": "Mērķa domēns", "target_domain": "Mērķa domēns",
"title": "Labot priekšmetu", "title": "Labot priekšmetu",
@@ -437,6 +457,7 @@
"sieve_info": "Katram lietotājam var saglabāt vairākus filtrus, bet tikai viens pirmsfiltrs un viens pēcfiltrs var darboties vienlaicīgi.<br>\nKatrs filtrs tiks apstrādāts norādītajā kārtībā. Ne kļūdains skripts, ne izdots \"keep;\" nepārtrauks turpmāko skriptu apstrādi. Vispārējo sieve skriptu izmaiņas izsauks Dovecot pārsāknēšanu.<br><br>Vispārējais sieve pirmsfiltrs &#8226; Pirmsfiltrs &#8226; Lietotāja skripti &#8226; Pēcfiltrs &#8226; Vispārējais sieve pēcfiltrs", "sieve_info": "Katram lietotājam var saglabāt vairākus filtrus, bet tikai viens pirmsfiltrs un viens pēcfiltrs var darboties vienlaicīgi.<br>\nKatrs filtrs tiks apstrādāts norādītajā kārtībā. Ne kļūdains skripts, ne izdots \"keep;\" nepārtrauks turpmāko skriptu apstrādi. Vispārējo sieve skriptu izmaiņas izsauks Dovecot pārsāknēšanu.<br><br>Vispārējais sieve pirmsfiltrs &#8226; Pirmsfiltrs &#8226; Lietotāja skripti &#8226; Pēcfiltrs &#8226; Vispārējais sieve pēcfiltrs",
"spam_aliases": "Pagaidu aizstājvārds", "spam_aliases": "Pagaidu aizstājvārds",
"status": "Status", "status": "Status",
"sync_jobs": "Sinhronizācijas darbi",
"target_address": "Doties uz adresi", "target_address": "Doties uz adresi",
"target_domain": "Mērķa domēns", "target_domain": "Mērķa domēns",
"tls_enforce_in": "Piespiest TLS ienākošajiem", "tls_enforce_in": "Piespiest TLS ienākošajiem",
@@ -460,7 +481,8 @@
"alias_domain_alias_hint": "Aizstājvārdi <b>netiek</b> automātiski piemēroti domēnu aizstājvārdiem. Aizstājadrese <code>my-alias@domain</code> <b>nenosedz</b> adresi <code>my-alias@alias-domain</code> (kur \"alias-domain\" ir iedomāts \"domain\" aizstājdomēns).<br>Lūgums izmantot sieta atlasi, lai pārvirzītu pastu uz ārēju pastkasti (skatīt cilti \"Atlasīšana\" vai izmantot SOGo -> Pārsūtītājs). \"Izvērst aizstājvārdu pār aizstājdomēniem\" ir izmantojams, lai automātiski pievienotu trūkstošos aiztājvārdus.", "alias_domain_alias_hint": "Aizstājvārdi <b>netiek</b> automātiski piemēroti domēnu aizstājvārdiem. Aizstājadrese <code>my-alias@domain</code> <b>nenosedz</b> adresi <code>my-alias@alias-domain</code> (kur \"alias-domain\" ir iedomāts \"domain\" aizstājdomēns).<br>Lūgums izmantot sieta atlasi, lai pārvirzītu pastu uz ārēju pastkasti (skatīt cilti \"Atlasīšana\" vai izmantot SOGo -> Pārsūtītājs). \"Izvērst aizstājvārdu pār aizstājdomēniem\" ir izmantojams, lai automātiski pievienotu trūkstošos aiztājvārdus.",
"alias_domain_backupmx": "Aizstājdomēns ir neaktīvs retranslācijas domēnam", "alias_domain_backupmx": "Aizstājdomēns ir neaktīvs retranslācijas domēnam",
"disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)",
"sieve_preset_1": "Atmest e-pasta vēstules ar iespējami bīstamiem datņu veidiem" "sieve_preset_1": "Atmest e-pasta vēstules ar iespējami bīstamiem datņu veidiem",
"syncjob_last_run_result": "Pēdējās izpildes iznākums"
}, },
"quarantine": { "quarantine": {
"action": "Darbības", "action": "Darbības",
@@ -570,24 +592,6 @@
"authenticators": "Autentificētāji", "authenticators": "Autentificētāji",
"u2f_deprecated_important": "Lūgums reģistrēt savu atslēgu pārvaldības lapā ar jauno WebAuthn veidu." "u2f_deprecated_important": "Lūgums reģistrēt savu atslēgu pārvaldības lapā ar jauno WebAuthn veidu."
}, },
"syncjobs": {
"sync_jobs": "Sinhronizācijas uzdevumi",
"syncjob_last_run_result": "Pēdējās izpildes iznākums",
"add": "Pievienot sinhronizācijas darbu",
"add_hint": "Ņemiet vērā, ka parole ir jāuzglabā vienkāršā tekstā!",
"edit": "Labot sinhronizācijas darbu",
"create": "Izveidot jaunu sinhronizācijas darbu",
"subfolder2": "Sinhronizēt galamērķa apakšmapē<br><small>(tukšs = neizmantot apakšmapi)</small>",
"mins_interval": "Aptauajs intervāls (minūtes)",
"maxage": "Lielākais pieļaujamais ziņojumu, kuri tiks vaicāti attālajā serverī, vecums dienās<br><small>(0 = neņemt vērā vecumu)</small>",
"exclude": "Izslēgt objektus (regex)",
"automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" etc.)",
"delete1": "Izdzēst no avota pēc pabeigšanas",
"delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā",
"delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
"skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)",
"maxbytespersecond": "Maks. baiti sekundē (0 ir vienāds ar neierobežotu skaitu)"
},
"user": { "user": {
"action": "Rīcība", "action": "Rīcība",
"active": "Aktīvs", "active": "Aktīvs",
@@ -604,6 +608,7 @@
"aliases_send_as_all": "Nepārbaudīt sūtītāja piekļuvi šiem domēniem un to aizstājdomēniem", "aliases_send_as_all": "Nepārbaudīt sūtītāja piekļuvi šiem domēniem un to aizstājdomēniem",
"change_password": "Nomainīt paroli", "change_password": "Nomainīt paroli",
"client_configuration": "Parādīt konfigurācijas norādes e-pasta klientiem un tālruņiem", "client_configuration": "Parādīt konfigurācijas norādes e-pasta klientiem un tālruņiem",
"create_syncjob": "Izveidot jaunu sinhronizācijas darbu",
"day": "Dienas", "day": "Dienas",
"description": "Apraksts", "description": "Apraksts",
"direct_aliases": "Tiešas aizstājadreses", "direct_aliases": "Tiešas aizstājadreses",
@@ -654,6 +659,7 @@
"spamfilter_wl_desc": "No atļautā saraksta e-pasta adresēm saņemtās vēstules <b>nekad</b> netiks atzīmētas kā mēstules. Var tikt izmantotas aizstājzīmes. Atlase tiek piemērota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", "spamfilter_wl_desc": "No atļautā saraksta e-pasta adresēm saņemtās vēstules <b>nekad</b> netiks atzīmētas kā mēstules. Var tikt izmantotas aizstājzīmes. Atlase tiek piemērota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.",
"spamfilter_yellow": "Dzeltens: šī vēstule visticamāk ir spams un tiks pārvietota uz Junk mapi", "spamfilter_yellow": "Dzeltens: šī vēstule visticamāk ir spams un tiks pārvietota uz Junk mapi",
"status": "Status", "status": "Status",
"sync_jobs": "Sinhronizācijas uzdevumi",
"tag_handling": "Iestatīt apstrādi par atzīmētajiem e-pastiem", "tag_handling": "Iestatīt apstrādi par atzīmētajiem e-pastiem",
"tag_help_example": "Piemērs ietagotai e-pasta adresei: me<b>+Facebook</b>@example.org", "tag_help_example": "Piemērs ietagotai e-pasta adresei: me<b>+Facebook</b>@example.org",
"tag_help_explain": "Iekš apakšmapes: Jaunas apakšmapes, kas izveidotas pēc taga, tiks izveidotas zem INBOX (\"INBOX/Facebook\").<br>\r\nSubjektā: Tagu nosaukums tiks pievienots e-pasta ziņojumam, piemēram: \"[Facebook] Manas ziņas\".", "tag_help_explain": "Iekš apakšmapes: Jaunas apakšmapes, kas izveidotas pēc taga, tiks izveidotas zem INBOX (\"INBOX/Facebook\").<br>\r\nSubjektā: Tagu nosaukums tiks pievienots e-pasta ziņojumam, piemēram: \"[Facebook] Manas ziņas\".",
@@ -687,7 +693,8 @@
"create_app_passwd": "Izveidot lietotnes paroli", "create_app_passwd": "Izveidot lietotnes paroli",
"empty": "Nav iznākuma", "empty": "Nav iznākuma",
"quarantine_notification_info": "Tiklīdz paziņojums ir nosūtīts, vienumi tiks atzīmēti kā \"paziņoti\", un par šo vienumu vairs netiks sūtīti paziņojumi.", "quarantine_notification_info": "Tiklīdz paziņojums ir nosūtīts, vienumi tiks atzīmēti kā \"paziņoti\", un par šo vienumu vairs netiks sūtīti paziņojumi.",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>" "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>",
"syncjob_last_run_result": "Pēdējās izpildes iznākums"
}, },
"datatables": { "datatables": {
"paginate": { "paginate": {
+23 -18
View File
@@ -196,19 +196,28 @@
"app_passwd_protocols": "Tillatte protokoller for app-passord", "app_passwd_protocols": "Tillatte protokoller for app-passord",
"hostname": "Vert", "hostname": "Vert",
"goto_ham": "Lær som <span class=\"text-success\"><b>ham</b></span>", "goto_ham": "Lær som <span class=\"text-success\"><b>ham</b></span>",
"custom_params": "Tilpassede parametre",
"target_address": "Gå-til-adresse", "target_address": "Gå-til-adresse",
"delete2": "Slett meldinger på måldestinasjonen som ikke finnes på kilden",
"syncjob": "Legg til synkroniseringsjobb",
"domain": "Domene", "domain": "Domene",
"timeout1": "Tidsavbrudd for tilkobling til ekstern vert",
"goto_null": "Slett epost uten varsling", "goto_null": "Slett epost uten varsling",
"custom_params_hint": "Riktig: --param=xy, feil: --param xy",
"multiple_bookings": "Flere reservasjoner", "multiple_bookings": "Flere reservasjoner",
"alias_domain": "Aliasdomene", "alias_domain": "Aliasdomene",
"exclude": "Ekskludér objekter (regex)",
"validate": "Validér", "validate": "Validér",
"dry": "Simulér synkronisering",
"relay_all_info": "↪ Hvis du velger å <b>ikke</b> videresende for alle mottakere, så må du legge til en (\"blind\") postkasse for hver eneste mottaker som det skal videresendes for.", "relay_all_info": "↪ Hvis du velger å <b>ikke</b> videresende for alle mottakere, så må du legge til en (\"blind\") postkasse for hver eneste mottaker som det skal videresendes for.",
"description": "Beskrivelse", "description": "Beskrivelse",
"destination": "Måldestinasjon", "destination": "Måldestinasjon",
"alias_domain_info": "<small>Kun gyldige domenenavn (kommaseparerte).</small>", "alias_domain_info": "<small>Kun gyldige domenenavn (kommaseparerte).</small>",
"mailbox_username": "Brukernavn (venstre del av en epostadresse)", "mailbox_username": "Brukernavn (venstre del av en epostadresse)",
"username": "Brukernavn", "username": "Brukernavn",
"mins_interval": "Sjekkintervall (minutter)",
"disable_login": "Ikke tillat innlogging (innkommende epost blir fremdeles mottatt)", "disable_login": "Ikke tillat innlogging (innkommende epost blir fremdeles mottatt)",
"delete2duplicates": "Slett duplikater på måldestinasjonen",
"post_domain_add": "SOGo-kontaineren, \"sogo-mailcow\", må startes på nytt etter at man har lagt til et nytt domene!<br><br>I tillegg må domenets DNS-oppsett ses gjennom. Når DNS-oppsettet er godkjent, start \"acme-mailcow\" på nytt for å automatisk opprette sertifikater for det nye domenet (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Dette steget er valgfritt og vil bli forsøkt utført hver 24. time.", "post_domain_add": "SOGo-kontaineren, \"sogo-mailcow\", må startes på nytt etter at man har lagt til et nytt domene!<br><br>I tillegg må domenets DNS-oppsett ses gjennom. Når DNS-oppsettet er godkjent, start \"acme-mailcow\" på nytt for å automatisk opprette sertifikater for det nye domenet (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Dette steget er valgfritt og vil bli forsøkt utført hver 24. time.",
"select": "Vennligst velg...", "select": "Vennligst velg...",
"full_name": "Fullt navn", "full_name": "Fullt navn",
@@ -231,6 +240,7 @@
"inactive": "Inaktiv", "inactive": "Inaktiv",
"domain_quota_m": "Total domenekvote (MiB)", "domain_quota_m": "Total domenekvote (MiB)",
"password": "Passord", "password": "Passord",
"delete1": "Slett fra kilde når fullført",
"tags": "Emneknagger", "tags": "Emneknagger",
"port": "Port", "port": "Port",
"backup_mx_options": "Alternativer for videresending", "backup_mx_options": "Alternativer for videresending",
@@ -239,15 +249,20 @@
"goto_spam": "Lær som <span class=\"text-danger\"><b>spam</b></span>", "goto_spam": "Lær som <span class=\"text-danger\"><b>spam</b></span>",
"target_domain": "Måldomene", "target_domain": "Måldomene",
"public_comment": "Offentlig kommentar", "public_comment": "Offentlig kommentar",
"timeout2": "Tidsavbrudd for tilkobling til lokal vert",
"mailbox_quota_def": "Standard kvote for postkasser", "mailbox_quota_def": "Standard kvote for postkasser",
"skipcrossduplicates": "Hopp over duplikatmeldinger på tvers av mapper (førstemann til mølla)",
"sieve_desc": "Kort beskrivelse", "sieve_desc": "Kort beskrivelse",
"mailbox_quota_m": "Maks. kvote pr. postkasse (MiB)", "mailbox_quota_m": "Maks. kvote pr. postkasse (MiB)",
"automap": "Forsøk automatisk mappe-tilordning (\"Sendte elementer\", \"Sendt\" => \"Sendt\" etc.)",
"validation_success": "Validering fullført", "validation_success": "Validering fullført",
"relay_all": "Videresend for alle mottakere", "relay_all": "Videresend for alle mottakere",
"subscribeall": "Abonnér på alle mapper",
"domain_matches_hostname": "Domene %s er det samme som vertsnavn", "domain_matches_hostname": "Domene %s er det samme som vertsnavn",
"generate": "opprett", "generate": "opprett",
"app_password": "Legg til app-passord", "app_password": "Legg til app-passord",
"max_mailboxes": "Maks. antall mulige postkasser", "max_mailboxes": "Maks. antall mulige postkasser",
"syncjob_hint": "Vær oppmerksom på at passord må lagres i klartekst!",
"relayhost_wrapped_tls_info": "Vennligst <b>ikke</b> bruk TLS-aktiverte porter (stort sett bare brukt på port 465).<br>\nBruk en annen ikke-TLS-aktivert port og send STARTTLS. En TLS-policy for å tvinge TLS kan opprettes i \"TLS-policy-mappinger\".", "relayhost_wrapped_tls_info": "Vennligst <b>ikke</b> bruk TLS-aktiverte porter (stort sett bare brukt på port 465).<br>\nBruk en annen ikke-TLS-aktivert port og send STARTTLS. En TLS-policy for å tvinge TLS kan opprettes i \"TLS-policy-mappinger\".",
"relay_unknown_only": "Videresend kun for ikke-eksisterende postkasser. Eksisterende postkasser vil bli levert lokalt.", "relay_unknown_only": "Videresend kun for ikke-eksisterende postkasser. Eksisterende postkasser vil bli levert lokalt.",
"sieve_type": "Filtertype", "sieve_type": "Filtertype",
@@ -345,6 +360,7 @@
"bcc_dest_format": "BCC-destinasjon må være en enkelt, gyldig epostadresse.<br>Hvis du trenger å sende en kopi til flere adresser, opprett et alias og bruk det her.", "bcc_dest_format": "BCC-destinasjon må være en enkelt, gyldig epostadresse.<br>Hvis du trenger å sende en kopi til flere adresser, opprett et alias og bruk det her.",
"pushover_info": "Innstillinger for pushvarslinger vil gjelde alle rene (ikke-spam) eposter levert til <b>%s</b>, inkludert aliaser (delte, ikke-delte, taggede).", "pushover_info": "Innstillinger for pushvarslinger vil gjelde alle rene (ikke-spam) eposter levert til <b>%s</b>, inkludert aliaser (delte, ikke-delte, taggede).",
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportmappinger for et spesifikt mål for dette domenet. Hvis dette ikke er definert, blir det gjort et MX-oppslag.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportmappinger for et spesifikt mål for dette domenet. Hvis dette ikke er definert, blir det gjort et MX-oppslag.",
"delete2duplicates": "Slett duplikater på målvert",
"description": "Beskrivelse", "description": "Beskrivelse",
"disable_login": "Ikke tillat innlogging (innkommende epost blir likevel mottatt)", "disable_login": "Ikke tillat innlogging (innkommende epost blir likevel mottatt)",
"domain_admin": "Endre domeneadministrator", "domain_admin": "Endre domeneadministrator",
@@ -371,6 +387,7 @@
"allow_from_smtp_info": "La stå tom for å tillate alle avsendere.<br>IPv4/IPv6-adresser og -nettverk.", "allow_from_smtp_info": "La stå tom for å tillate alle avsendere.<br>IPv4/IPv6-adresser og -nettverk.",
"domain": "Endre domene", "domain": "Endre domene",
"encryption": "Kryptering", "encryption": "Kryptering",
"exclude": "Ekskluder objekter (regex)",
"footer_exclude": "Ekskluder fra bunntekst", "footer_exclude": "Ekskluder fra bunntekst",
"gal_info": "GAL inneholder alle objeker i et domene og kan ikke redigeres av noen brukere. Ledig/opptatt-informasjon i SOGo mangler dersom den deaktiveres! <b>Start SOGo på nytt for å aktivere endringene.</b>", "gal_info": "GAL inneholder alle objeker i et domene og kan ikke redigeres av noen brukere. Ledig/opptatt-informasjon i SOGo mangler dersom den deaktiveres! <b>Start SOGo på nytt for å aktivere endringene.</b>",
"grant_types": "Grant-typer", "grant_types": "Grant-typer",
@@ -382,6 +399,8 @@
"mailbox": "Endre mailboks", "mailbox": "Endre mailboks",
"mailbox_quota_def": "Standardkvote for mailboks", "mailbox_quota_def": "Standardkvote for mailboks",
"max_quota": "Maks kvote pr. mailboks (MiB)", "max_quota": "Maks kvote pr. mailboks (MiB)",
"maxage": "Maksimal alder for meldinger, i dager, som vil hentes fra ekstern<br><small>(0 = ignorer alder)</small>",
"maxbytespersecond": "Maks bytes pr. sekund <br><small>(0 = ubegrenset)</small>",
"pushover_sender_array": "Bare vurder de følgende avsenderadressene <small>(komma-separert)</small>", "pushover_sender_array": "Bare vurder de følgende avsenderadressene <small>(komma-separert)</small>",
"pushover_sender_regex": "Vurder følgende avsender-regex", "pushover_sender_regex": "Vurder følgende avsender-regex",
"pushover_text": "Varslingstekst", "pushover_text": "Varslingstekst",
@@ -397,6 +416,7 @@
"full_name": "Fullt navn", "full_name": "Fullt navn",
"gal": "Global adresseliste", "gal": "Global adresseliste",
"max_aliases": "Maks. antall aliaser", "max_aliases": "Maks. antall aliaser",
"mins_interval": "Intervall (min)",
"multiple_bookings": "Flere bookinger", "multiple_bookings": "Flere bookinger",
"none_inherit": "Ingen / arve", "none_inherit": "Ingen / arve",
"acl": "ACL (rettighet)", "acl": "ACL (rettighet)",
@@ -408,11 +428,14 @@
"app_name": "Appnavn", "app_name": "Appnavn",
"app_passwd": "App-passord", "app_passwd": "App-passord",
"app_passwd_protocols": "Tillatte protokoller for app-passord", "app_passwd_protocols": "Tillatte protokoller for app-passord",
"automap": "Prøv å automatisk mappe opp mapper (\"Sent items\", \"Sent\" => \"Sendt\" etc.)",
"backup_mx_options": "Videresendingsalternativer", "backup_mx_options": "Videresendingsalternativer",
"client_secret": "Klient-hemmelighet", "client_secret": "Klient-hemmelighet",
"comment_info": "En privat kommentar er ikke synlig for brukeren, mens en offentlig kommentar vises som et tooltip når man holder muspekeren over det", "comment_info": "En privat kommentar er ikke synlig for brukeren, mens en offentlig kommentar vises som et tooltip når man holder muspekeren over det",
"created_on": "Opprettet den", "created_on": "Opprettet den",
"custom_attributes": "Valgfrie attributter", "custom_attributes": "Valgfrie attributter",
"delete1": "Slett fra kilde når fullført",
"delete2": "Slett meldinger på målvert som ikke finnes på kildeverten",
"delete_ays": "Vennligst bekreft slettingen.", "delete_ays": "Vennligst bekreft slettingen.",
"domain_footer_html": "HTML-bunntekst", "domain_footer_html": "HTML-bunntekst",
"domain_footer_plain": "PLAIN-bunntekst", "domain_footer_plain": "PLAIN-bunntekst",
@@ -441,23 +464,5 @@
"scope": "Omfang", "scope": "Omfang",
"sender_acl": "Tillat å sende som", "sender_acl": "Tillat å sende som",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Avsender-sjekk er deaktivert</span>" "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Avsender-sjekk er deaktivert</span>"
},
"syncjobs": {
"add": "Legg til synkroniseringsjobb",
"add_hint": "Vær oppmerksom på at passord må lagres i klartekst!",
"dry": "Simulér synkronisering",
"mins_interval": "Sjekkintervall (minutter)",
"maxage": "Maksimal alder for meldinger, i dager, som vil hentes fra ekstern<br><small>(0 = ignorer alder)</small>",
"exclude": "Ekskludér objekter (regex)",
"automap": "Forsøk automatisk mappe-tilordning (\"Sendte elementer\", \"Sendt\" => \"Sendt\" etc.)",
"delete1": "Slett fra kilde når fullført",
"delete2": "Slett meldinger på måldestinasjonen som ikke finnes på kilden",
"delete2duplicates": "Slett duplikater på måldestinasjonen",
"skipcrossduplicates": "Hopp over duplikatmeldinger på tvers av mapper (førstemann til mølla)",
"subscribeall": "Abonnér på alle mapper",
"timeout1": "Tidsavbrudd for tilkobling til ekstern vert",
"timeout2": "Tidsavbrudd for tilkobling til lokal vert",
"maxbytespersecond": "Maks bytes pr. sekund <br><small>(0 = ubegrenset)</small>",
"custom_params": "Tilpassede parametre"
} }
} }
+40 -31
View File
@@ -42,8 +42,14 @@
"alias_domain_info": "<small>Uitsluitend geldige domeinnamen (kommagescheiden).</small>", "alias_domain_info": "<small>Uitsluitend geldige domeinnamen (kommagescheiden).</small>",
"app_name": "Naam van app", "app_name": "Naam van app",
"app_password": "Maak appwachtwoord aan", "app_password": "Maak appwachtwoord aan",
"automap": "Probeer mappen automatisch te koppelen (\"Verzonden items\", \"Verzonden\" => \"Verzonden\" etc.)",
"backup_mx_options": "Relay-opties", "backup_mx_options": "Relay-opties",
"comment_info": "Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publieke opmerking wel getoond wordt in het overzicht van de gebruiker.", "comment_info": "Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publieke opmerking wel getoond wordt in het overzicht van de gebruiker.",
"custom_params": "Aangepaste parameters",
"custom_params_hint": "Juist: --param=xy, onjuist: --param xy",
"delete1": "Verwijder van bron wanneer voltooid",
"delete2": "Verwijder berichten die zich niet in de bron bevinden",
"delete2duplicates": "Verwijder duplicaten op de bestemming",
"description": "Beschrijving", "description": "Beschrijving",
"destination": "Bestemming", "destination": "Bestemming",
"disable_login": "Weiger aanmelden (inkomende mail blijft binnenkomen)", "disable_login": "Weiger aanmelden (inkomende mail blijft binnenkomen)",
@@ -51,6 +57,7 @@
"domain_matches_hostname": "Domein %s komt overeen met hostname", "domain_matches_hostname": "Domein %s komt overeen met hostname",
"domain_quota_m": "Totale domeinquota (MiB)", "domain_quota_m": "Totale domeinquota (MiB)",
"enc_method": "Versleutelingsmethode", "enc_method": "Versleutelingsmethode",
"exclude": "Sluit objecten uit (regex)",
"full_name": "Volledige naam", "full_name": "Volledige naam",
"gal": "Globale adreslijst", "gal": "Globale adreslijst",
"gal_info": "De globale adreslijst bevat alle objecten van een domein. Deze kunnen door geen enkele gebruiker worden bewerkt. <b>Herstart SOGo om wijzigingen door te voeren.</b>", "gal_info": "De globale adreslijst bevat alle objecten van een domein. Deze kunnen door geen enkele gebruiker worden bewerkt. <b>Herstart SOGo om wijzigingen door te voeren.</b>",
@@ -66,6 +73,7 @@
"mailbox_username": "Gebruikersnaam (linkergedeelte van een mailadres)", "mailbox_username": "Gebruikersnaam (linkergedeelte van een mailadres)",
"max_aliases": "Maximaal aantal aliassen", "max_aliases": "Maximaal aantal aliassen",
"max_mailboxes": "Maximaal aantal mailboxen", "max_mailboxes": "Maximaal aantal mailboxen",
"mins_interval": "Interval (min)",
"multiple_bookings": "Meerdere boekingen", "multiple_bookings": "Meerdere boekingen",
"nexthop": "Nexthop", "nexthop": "Nexthop",
"password": "Wachtwoord", "password": "Wachtwoord",
@@ -85,14 +93,21 @@
"select_domain": "Selecteer eerst een domein", "select_domain": "Selecteer eerst een domein",
"sieve_desc": "Korte beschrijving", "sieve_desc": "Korte beschrijving",
"sieve_type": "Filtertype", "sieve_type": "Filtertype",
"skipcrossduplicates": "Sla duplicaten verspreid over mappen over (wie het eerst komt, het eerst maalt)",
"subscribeall": "Abonneer op alle mappen",
"syncjob": "Voeg sync job toe",
"syncjob_hint": "Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!",
"target_address": "Doeladressen", "target_address": "Doeladressen",
"target_address_info": "<small>Volledig(e) mailadres(sen) (kommagescheiden).</small>", "target_address_info": "<small>Volledig(e) mailadres(sen) (kommagescheiden).</small>",
"target_domain": "Doeldomein", "target_domain": "Doeldomein",
"timeout1": "Time-out voor verbinding met externe hosts",
"timeout2": "Time-out voor verbinding met lokale hosts",
"username": "Gebruikersnaam", "username": "Gebruikersnaam",
"validate": "Verifieer", "validate": "Verifieer",
"validation_success": "Succesvol geverifieerd", "validation_success": "Succesvol geverifieerd",
"tags": "Tags", "tags": "Tags",
"bcc_dest_format": "BCC-bestemming moet één geldig e-mailadres zijn.<br>Als u een kopie naar meerdere adressen wilt sturen, maak dan een alias aan en gebruik die hier.", "bcc_dest_format": "BCC-bestemming moet één geldig e-mailadres zijn.<br>Als u een kopie naar meerdere adressen wilt sturen, maak dan een alias aan en gebruik die hier.",
"dry": "Synchronisatie simuleren",
"internal": "Intern", "internal": "Intern",
"internal_info": "Interne aliassen zijn alleen toegankelijk vanuit het eigen (alias)domein.", "internal_info": "Interne aliassen zijn alleen toegankelijk vanuit het eigen (alias)domein.",
"sender_allowed": "Toestaan om te verzenden als deze alias" "sender_allowed": "Toestaan om te verzenden als deze alias"
@@ -598,11 +613,15 @@
"allowed_protocols": "Toegestane protocollen voor directe gebruikerstoegang (heeft geen invloed op protocollen voor app-wachtwoorden).", "allowed_protocols": "Toegestane protocollen voor directe gebruikerstoegang (heeft geen invloed op protocollen voor app-wachtwoorden).",
"app_name": "Naam van app", "app_name": "Naam van app",
"app_passwd": "Appwachtwoord", "app_passwd": "Appwachtwoord",
"automap": "Probeer mappen automatisch te koppelen (\"Verzonden items\", \"Verzonden\" => \"Verzonden\" etc.)",
"backup_mx_options": "Relay-opties", "backup_mx_options": "Relay-opties",
"bcc_dest_format": "Een BCC-bestemming dient één geldig mailadres te zijn.", "bcc_dest_format": "Een BCC-bestemming dient één geldig mailadres te zijn.",
"client_id": "Client-ID", "client_id": "Client-ID",
"client_secret": "Client-secret", "client_secret": "Client-secret",
"comment_info": "Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publieke opmerking wel getoond wordt in het overzicht van de gebruiker.", "comment_info": "Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publieke opmerking wel getoond wordt in het overzicht van de gebruiker.",
"delete1": "Verwijder van bron wanneer voltooid",
"delete2": "Verwijder berichten die zich niet in de bron bevinden",
"delete2duplicates": "Verwijder duplicaten op de bestemming",
"delete_ays": "Bevestig de verwijdering.", "delete_ays": "Bevestig de verwijdering.",
"description": "Beschrijving", "description": "Beschrijving",
"disable_login": "Weiger aanmelden (inkomende mail blijft binnenkomen)", "disable_login": "Weiger aanmelden (inkomende mail blijft binnenkomen)",
@@ -613,6 +632,7 @@
"dont_check_sender_acl": "Schakel verzendcontrole uit voor domein %s (inclusief aliasdomeinen)", "dont_check_sender_acl": "Schakel verzendcontrole uit voor domein %s (inclusief aliasdomeinen)",
"edit_alias_domain": "Wijzig aliasdomein", "edit_alias_domain": "Wijzig aliasdomein",
"encryption": "Versleuteling", "encryption": "Versleuteling",
"exclude": "Sluit objecten uit (regex)",
"extended_sender_acl": "Externe verzendadressen", "extended_sender_acl": "Externe verzendadressen",
"extended_sender_acl_info": "Wanneer mogelijk dient er een ARC/DKIM-key geïmporteerd te worden. Vergeet niet om deze server toe te voegen aan het SPF-record <br>Zodra er een domein of aliasdomein wordt toegevoegd aan deze server, overeenkomend met een extern verzendadres, wordt het externe adres verwijderd.<br>Gebruik @domain.tld om verzenden vanuit *@domain.tld toe te staan.", "extended_sender_acl_info": "Wanneer mogelijk dient er een ARC/DKIM-key geïmporteerd te worden. Vergeet niet om deze server toe te voegen aan het SPF-record <br>Zodra er een domein of aliasdomein wordt toegevoegd aan deze server, overeenkomend met een extern verzendadres, wordt het externe adres verwijderd.<br>Gebruik @domain.tld om verzenden vanuit *@domain.tld toe te staan.",
"force_pw_update": "Vereis nieuw wachtwoord bij eerstvolgende login", "force_pw_update": "Vereis nieuw wachtwoord bij eerstvolgende login",
@@ -631,7 +651,10 @@
"max_aliases": "Maximaal aantal aliassen", "max_aliases": "Maximaal aantal aliassen",
"max_mailboxes": "Maximaal aantal mailboxen", "max_mailboxes": "Maximaal aantal mailboxen",
"max_quota": "Mailboxquota (MiB)", "max_quota": "Mailboxquota (MiB)",
"maxage": "Maximale leeftijd van berichten (in dagen) die extern worden opgehaald<br><small>(0 = negeer leeftijd)</small>",
"maxbytespersecond": "Maximale bytes per seconde <br><small>(0 = onbeperkt)</small>",
"mbox_rl_info": "De ratelimit wordt toegepast op de huidige mailboxgebruiker en geldt voor elk verzendadres die door deze wordt gebruikt. Een mailbox-ratelimit staat boven een domein-ratelimit.", "mbox_rl_info": "De ratelimit wordt toegepast op de huidige mailboxgebruiker en geldt voor elk verzendadres die door deze wordt gebruikt. Een mailbox-ratelimit staat boven een domein-ratelimit.",
"mins_interval": "Interval (min)",
"multiple_bookings": "Meerdere boekingen", "multiple_bookings": "Meerdere boekingen",
"nexthop": "Nexthop", "nexthop": "Nexthop",
"password": "Wachtwoord", "password": "Wachtwoord",
@@ -667,14 +690,19 @@
"sender_acl_info": "Wanneer mailboxgebruiker A toegestaan is te verzenden namens mailboxgebruiker B, zal het verzendadres niet automatisch worden weergegeven in het \"van\"-veld in SOGo. Mailboxgebruiker A dient hiervoor een aparte vermelding te maken in SOGo. Om een mailbox te delegeren in SOGo kan het menu (drie punten) aan de rechterkant van de naam van het mailbox linksboven worden gebruikt in de mailweergave. Dit is niet van toepassing op aliasadressen.", "sender_acl_info": "Wanneer mailboxgebruiker A toegestaan is te verzenden namens mailboxgebruiker B, zal het verzendadres niet automatisch worden weergegeven in het \"van\"-veld in SOGo. Mailboxgebruiker A dient hiervoor een aparte vermelding te maken in SOGo. Om een mailbox te delegeren in SOGo kan het menu (drie punten) aan de rechterkant van de naam van het mailbox linksboven worden gebruikt in de mailweergave. Dit is niet van toepassing op aliasadressen.",
"sieve_desc": "Korte beschrijving", "sieve_desc": "Korte beschrijving",
"sieve_type": "Filtertype", "sieve_type": "Filtertype",
"skipcrossduplicates": "Sla duplicaten verspreid over mappen over (wie het eerst komt, het eerst maalt)",
"sogo_visible": "Alias tonen in SOGo", "sogo_visible": "Alias tonen in SOGo",
"sogo_visible_info": "Wanneer verborgen zal een alias niet worden weergegeven als een selecteerbaar verzendadres. Deze optie beïnvloedt uitsluitend objecten die kunnen worden weergegeven in SOGo (gedeelde of niet-gedeelde aliasadressen die naar minstens één mailbox verwijzen).", "sogo_visible_info": "Wanneer verborgen zal een alias niet worden weergegeven als een selecteerbaar verzendadres. Deze optie beïnvloedt uitsluitend objecten die kunnen worden weergegeven in SOGo (gedeelde of niet-gedeelde aliasadressen die naar minstens één mailbox verwijzen).",
"spam_alias": "Maak een nieuw tijdelijk alias aan, of pas deze aan", "spam_alias": "Maak een nieuw tijdelijk alias aan, of pas deze aan",
"spam_filter": "Spamfilter", "spam_filter": "Spamfilter",
"spam_policy": "Voeg items toe of verwijder items van de deny- en allowlists", "spam_policy": "Voeg items toe of verwijder items van de deny- en allowlists",
"spam_score": "Stel een aangepaste spamscore in", "spam_score": "Stel een aangepaste spamscore in",
"subfolder2": "Synchroniseer in submap op bestemming<br><small>(leeg = gebruik geen submappen)</small>",
"syncjob": "Wijzig sync job",
"target_address": "Doeladres(sen) <small>(kommagescheiden)</small>", "target_address": "Doeladres(sen) <small>(kommagescheiden)</small>",
"target_domain": "Doeldomein", "target_domain": "Doeldomein",
"timeout1": "Time-out voor verbinding met externe hosts",
"timeout2": "Time-out voor verbinding met lokale hosts",
"title": "Wijzig object", "title": "Wijzig object",
"unchanged_if_empty": "Laat leeg wanneer ongewijzigd", "unchanged_if_empty": "Laat leeg wanneer ongewijzigd",
"username": "Gebruikersnaam", "username": "Gebruikersnaam",
@@ -910,6 +938,7 @@
"spam_aliases": "Tijdelijk alias", "spam_aliases": "Tijdelijk alias",
"stats": "Statistieken", "stats": "Statistieken",
"status": "Status", "status": "Status",
"sync_jobs": "Sync jobs",
"table_size": "Tabelgrootte", "table_size": "Tabelgrootte",
"table_size_show_n": "Toon %s items", "table_size_show_n": "Toon %s items",
"target_address": "Doeladres", "target_address": "Doeladres",
@@ -947,6 +976,15 @@
"recipient": "Ontvanger", "recipient": "Ontvanger",
"relay_unknown": "Onbekende mailboxen relayen", "relay_unknown": "Onbekende mailboxen relayen",
"sender": "Afzender", "sender": "Afzender",
"syncjob_check_log": "Controleer log",
"syncjob_last_run_result": "Laatste uitvoeringsresultaat",
"syncjob_EX_OK": "Succes",
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindingsprobleem",
"syncjob_EXIT_TLS_FAILURE": "Probleem met versleutelde verbinding",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authenticatieprobleem",
"syncjob_EXIT_OVERQUOTA": "Doel mailbox is over quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kan geen verbinding maken met de externe server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Verkeerde gebruikersnaam of wachtwoord",
"templates": "Sjablonen", "templates": "Sjablonen",
"template": "Sjabloon", "template": "Sjabloon",
"yes": "&#10003;" "yes": "&#10003;"
@@ -1149,37 +1187,6 @@
"fido2_validation_failed": "Validatie mislukt", "fido2_validation_failed": "Validatie mislukt",
"set_fido2_touchid": "Registreer Touch ID op Apple M1" "set_fido2_touchid": "Registreer Touch ID op Apple M1"
}, },
"syncjobs": {
"sync_jobs": "Sync jobs",
"syncjob_check_log": "Controleer log",
"syncjob_last_run_result": "Laatste uitvoeringsresultaat",
"syncjob_EX_OK": "Succes",
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindingsprobleem",
"syncjob_EXIT_TLS_FAILURE": "Probleem met versleutelde verbinding",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authenticatieprobleem",
"syncjob_EXIT_OVERQUOTA": "Doel mailbox is over quota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kan geen verbinding maken met de externe server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Verkeerde gebruikersnaam of wachtwoord",
"add": "Voeg sync job toe",
"add_hint": "Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!",
"edit": "Wijzig sync job",
"create": "Voeg sync job toe",
"dry": "Synchronisatie simuleren",
"subfolder2": "Synchroniseer in submap op bestemming<br><small>(leeg = gebruik geen submappen)</small>",
"mins_interval": "Interval (min)",
"maxage": "Maximale leeftijd van berichten (in dagen) die extern worden opgehaald<br><small>(0 = negeer leeftijd)</small>",
"exclude": "Sluit objecten uit (regex)",
"automap": "Probeer mappen automatisch te koppelen (\"Verzonden items\", \"Verzonden\" => \"Verzonden\" etc.)",
"delete1": "Verwijder van bron wanneer voltooid",
"delete2": "Verwijder berichten die zich niet in de bron bevinden",
"delete2duplicates": "Verwijder duplicaten op de bestemming",
"skipcrossduplicates": "Sla duplicaten verspreid over mappen over (wie het eerst komt, het eerst maalt)",
"subscribeall": "Abonneer op alle mappen",
"timeout1": "Time-out voor verbinding met externe hosts",
"timeout2": "Time-out voor verbinding met lokale hosts",
"maxbytespersecond": "Maximale bytes per seconde <br><small>(0 = onbeperkt)</small>",
"custom_params": "Aangepaste parameters"
},
"user": { "user": {
"action": "Handeling", "action": "Handeling",
"active": "Actief", "active": "Actief",
@@ -1204,6 +1211,7 @@
"change_password": "Wijzig wachtwoord", "change_password": "Wijzig wachtwoord",
"client_configuration": "Toon configuratiegidsen voor mailprogramma's", "client_configuration": "Toon configuratiegidsen voor mailprogramma's",
"create_app_passwd": "Maak appwachtwoord aan", "create_app_passwd": "Maak appwachtwoord aan",
"create_syncjob": "Voeg sync job toe",
"daily": "Dagelijks", "daily": "Dagelijks",
"day": "dag", "day": "dag",
"description": "Beschrijving", "description": "Beschrijving",
@@ -1289,6 +1297,7 @@
"spamfilter_wl_desc": "Zet mailadressen op de whitelist om ze <b>nooit</b> als spam te markeren.<br>Deze lijst wordt niet toegepast op een gedeeld aliasadres.<br>Wildcards (*) zijn toegestaan.", "spamfilter_wl_desc": "Zet mailadressen op de whitelist om ze <b>nooit</b> als spam te markeren.<br>Deze lijst wordt niet toegepast op een gedeeld aliasadres.<br>Wildcards (*) zijn toegestaan.",
"spamfilter_yellow": "Geel: dit bericht is mogelijk spam en zal in de spamfolder geplaatst worden.", "spamfilter_yellow": "Geel: dit bericht is mogelijk spam en zal in de spamfolder geplaatst worden.",
"status": "Status", "status": "Status",
"sync_jobs": "Sync jobs",
"tag_handling": "Mailtags", "tag_handling": "Mailtags",
"tag_help_example": "Voorbeeld van een maildres met tag: me<b>+Tesla</b>@example.org", "tag_help_example": "Voorbeeld van een maildres met tag: me<b>+Tesla</b>@example.org",
"tag_help_explain": "In submap: er wordt een nieuwe map aangemaakt, genoemd naar de tag (bijv.: \"INBOX/Tesla\").<br>In onderwerp: de tag wordt vóór het oorspronkelijke onderwerp geplaatst (bijv.: \"[Tesla] Uw serviceafspraak\").", "tag_help_explain": "In submap: er wordt een nieuwe map aangemaakt, genoemd naar de tag (bijv.: \"INBOX/Tesla\").<br>In onderwerp: de tag wordt vóór het oorspronkelijke onderwerp geplaatst (bijv.: \"[Tesla] Uw serviceafspraak\").",
+49 -31
View File
@@ -39,10 +39,13 @@
"alias_domain": "Alias domeny", "alias_domain": "Alias domeny",
"alias_domain_info": "<small>Tylko prawidłowe nazwy domen (oddzielone przecinkami).</small>", "alias_domain_info": "<small>Tylko prawidłowe nazwy domen (oddzielone przecinkami).</small>",
"backup_mx_options": "Opcje Backup MX", "backup_mx_options": "Opcje Backup MX",
"delete1": "Usuń ze źródła po zakończeniu",
"delete2duplicates": "Usuń duplikaty w miejscu docelowym",
"description": "Opis", "description": "Opis",
"domain": "Domena", "domain": "Domena",
"domain_quota_m": "Łączny limit domeny (MiB)", "domain_quota_m": "Łączny limit domeny (MiB)",
"enc_method": "Metoda szyfrowania", "enc_method": "Metoda szyfrowania",
"exclude": "Wyklucz obiekty (regex)",
"full_name": "Pełna nazwa", "full_name": "Pełna nazwa",
"hostname": "Nazwa hosta", "hostname": "Nazwa hosta",
"kind": "Rodzaj", "kind": "Rodzaj",
@@ -50,6 +53,7 @@
"mailbox_username": "Nazwa użytkownika (lewa strona adresu email)", "mailbox_username": "Nazwa użytkownika (lewa strona adresu email)",
"max_aliases": "Maks. liczba aliasów", "max_aliases": "Maks. liczba aliasów",
"max_mailboxes": "Maks. liczba skrzynek", "max_mailboxes": "Maks. liczba skrzynek",
"mins_interval": "Zakres pobierania (minuty)",
"multiple_bookings": "Wielokrotne rejestracje", "multiple_bookings": "Wielokrotne rejestracje",
"password": "Hasło", "password": "Hasło",
"password_repeat": "Potwierdź hasło(powtórz)", "password_repeat": "Potwierdź hasło(powtórz)",
@@ -61,6 +65,8 @@
"relay_domain": "Domena przekaźnikowa", "relay_domain": "Domena przekaźnikowa",
"select": "Proszę wybrać...", "select": "Proszę wybrać...",
"select_domain": "Proszę najpierw wybrać domenę", "select_domain": "Proszę najpierw wybrać domenę",
"syncjob": "Dodaj polecenie synchronizacji",
"syncjob_hint": "Pamiętaj, że hasła należy zapisywać w zwykłym tekście!",
"target_address": "Adresy Idź do", "target_address": "Adresy Idź do",
"target_address_info": "<small> Pełny/e adres/y email (oddzielone przecinkami).</small>", "target_address_info": "<small> Pełny/e adres/y email (oddzielone przecinkami).</small>",
"target_domain": "Domena docelowa", "target_domain": "Domena docelowa",
@@ -71,11 +77,16 @@
"app_name": "Nazwa aplikacji", "app_name": "Nazwa aplikacji",
"app_password": "Dodaj hasło do aplikacji", "app_password": "Dodaj hasło do aplikacji",
"app_passwd_protocols": "Dozwolone protokoły dla hasła aplikacji", "app_passwd_protocols": "Dozwolone protokoły dla hasła aplikacji",
"automap": "Spróbuj automatycznie mapować foldery (\"Wysłane elementy\", \"Wysłane\" => \"Wysłane\" itp.)",
"bcc_dest_format": "Miejscem docelowym BCC musi być jeden prawidłowy adres e-mail.<br>Jeśli chcesz wysłać kopię do wielu adresów, utwórz alias i użyj go tutaj.", "bcc_dest_format": "Miejscem docelowym BCC musi być jeden prawidłowy adres e-mail.<br>Jeśli chcesz wysłać kopię do wielu adresów, utwórz alias i użyj go tutaj.",
"comment_info": "Prywatny komentarz nie jest widoczny dla użytkownika, podczas gdy publiczny komentarz jest wyświetlany jako podpowiedź, które pojawia się, gdy użytkownik najedzie myszką nad elementem", "comment_info": "Prywatny komentarz nie jest widoczny dla użytkownika, podczas gdy publiczny komentarz jest wyświetlany jako podpowiedź, które pojawia się, gdy użytkownik najedzie myszką nad elementem",
"custom_params": "Niestandardowe parametry",
"custom_params_hint": "Właściwa: --param=xy, błędna: --param xy",
"delete2": "Usuń wiadomości w miejscu docelowym, które nie znajdują się w źródle",
"destination": "Miejsce docelowe", "destination": "Miejsce docelowe",
"disable_login": "Nie pozwalaj na logowanie(poczta przychodząca jest nadal akceptowana)", "disable_login": "Nie pozwalaj na logowanie(poczta przychodząca jest nadal akceptowana)",
"domain_matches_hostname": "Domena pasuje do nazwy hosta", "domain_matches_hostname": "Domena pasuje do nazwy hosta",
"dry": "Symulacja synchronizacji",
"gal": "Globalna lista adresów", "gal": "Globalna lista adresów",
"gal_info": "GAL zawiera wszystkie obiekty domeny i nie może być edytowany przez żadnego użytkownika. Wolne/zajęte logi w SOGo bedą nidostępne, jeśli są wyłączone! <b>Uruchom ponownie SOGo, aby zastosować zmiany.</b>", "gal_info": "GAL zawiera wszystkie obiekty domeny i nie może być edytowany przez żadnego użytkownika. Wolne/zajęte logi w SOGo bedą nidostępne, jeśli są wyłączone! <b>Uruchom ponownie SOGo, aby zastosować zmiany.</b>",
"generate": "generuj", "generate": "generuj",
@@ -94,7 +105,11 @@
"relayhost_wrapped_tls_info": "Proszę <b>nie</b> używać portów owiniętych TLS (głównie używanych na porcie 465).<br>\nUżyj dowolnego portu niezawiniętego i uruchom STARTTLS. Polityka TLS w celu egzekwowania TLS może być utworzona w \"mapie zasad TLS\".", "relayhost_wrapped_tls_info": "Proszę <b>nie</b> używać portów owiniętych TLS (głównie używanych na porcie 465).<br>\nUżyj dowolnego portu niezawiniętego i uruchom STARTTLS. Polityka TLS w celu egzekwowania TLS może być utworzona w \"mapie zasad TLS\".",
"sieve_desc": "Krótki opis", "sieve_desc": "Krótki opis",
"sieve_type": "Typ filtra", "sieve_type": "Typ filtra",
"skipcrossduplicates": "Pomiń duplikaty wiadomości w folderach (na zasadzie kolejności zgłoszeń)",
"subscribeall": "Subskrybuj wszystkie foldery",
"tags": "Tagi", "tags": "Tagi",
"timeout1": "Limit czasu połączenia z hostem zdalnym",
"timeout2": "Limit czasu połączenia dla lokalnego hosta",
"validate": "Zatwierdź", "validate": "Zatwierdź",
"validation_success": "Zatwierdzone z powodzeniem" "validation_success": "Zatwierdzone z powodzeniem"
}, },
@@ -547,6 +562,8 @@
"active": "Aktywny", "active": "Aktywny",
"alias": "Edytuj alias", "alias": "Edytuj alias",
"backup_mx_options": "Opcje backup MX", "backup_mx_options": "Opcje backup MX",
"delete1": "Usuń ze źródła po zakończeniu",
"delete2duplicates": "Usuń duplikaty w miejscu docelowym",
"description": "Opis", "description": "Opis",
"domain": "Edytuj domenę", "domain": "Edytuj domenę",
"domain_admin": "Edytuj administratora domeny", "domain_admin": "Edytuj administratora domeny",
@@ -555,6 +572,7 @@
"dont_check_sender_acl": "Wyłącz sprawdzanie nadawcy dla domeny %s + aliasów domeny", "dont_check_sender_acl": "Wyłącz sprawdzanie nadawcy dla domeny %s + aliasów domeny",
"edit_alias_domain": "Edytuj alias domeny", "edit_alias_domain": "Edytuj alias domeny",
"encryption": "Szyfrowanie", "encryption": "Szyfrowanie",
"exclude": "Wyklucz obiekty (regex)",
"full_name": "Pełna nazwa", "full_name": "Pełna nazwa",
"hostname": "Nazwa hosta", "hostname": "Nazwa hosta",
"inactive": "Nieaktywny", "inactive": "Nieaktywny",
@@ -563,6 +581,8 @@
"max_aliases": "Maks. liczba aliasów", "max_aliases": "Maks. liczba aliasów",
"max_mailboxes": "Maks. liczba skrzynek", "max_mailboxes": "Maks. liczba skrzynek",
"max_quota": "Maks. wielkość skrzynki (MiB)", "max_quota": "Maks. wielkość skrzynki (MiB)",
"maxage": "Maksymalny wiek wiadomości, liczony w dniach, które będą pobierane ze zdalnego<br><small>(0 = pomiń wiek>",
"mins_interval": "Zakres (min)",
"multiple_bookings": "Wielokrotne rejestracje", "multiple_bookings": "Wielokrotne rejestracje",
"password": "Hasło", "password": "Hasło",
"password_repeat": "Potwierdź hasło(powtórz)", "password_repeat": "Potwierdź hasło(powtórz)",
@@ -575,6 +595,8 @@
"resource": "Zasób", "resource": "Zasób",
"save": "Zapisz zmiany", "save": "Zapisz zmiany",
"sender_acl": "Zezwalaj na wysłanie jako", "sender_acl": "Zezwalaj na wysłanie jako",
"subfolder2": "Synchronizuj do podfolderu w skrzynce<br><small>(pusty = nie używaj subfolderu)</small>",
"syncjob": "Edytuj polecenie synchronizacji",
"target_address": "Adres/y Idź do <small>(rozdzielone przecinkiem)</small>", "target_address": "Adres/y Idź do <small>(rozdzielone przecinkiem)</small>",
"target_domain": "Domena docelowa", "target_domain": "Domena docelowa",
"title": "Edytuj obiekt", "title": "Edytuj obiekt",
@@ -590,12 +612,14 @@
"app_name": "Nazwa aplikacji", "app_name": "Nazwa aplikacji",
"app_passwd": "Hasło do aplikacji", "app_passwd": "Hasło do aplikacji",
"app_passwd_protocols": "Dozwolone protokoły dla hasła aplikacji", "app_passwd_protocols": "Dozwolone protokoły dla hasła aplikacji",
"automap": "Spróbuj automatycznie mapować foldery (np. „Sent items”, „Sent” ⇒ „Sent” itp.)",
"bcc_dest_format": "Miejscem docelowym BCC musi być jeden prawidłowy adres e-mail.<br>Jeśli chcesz wysłać kopię na wiele adresów, utwórz alias i użyj go tutaj.", "bcc_dest_format": "Miejscem docelowym BCC musi być jeden prawidłowy adres e-mail.<br>Jeśli chcesz wysłać kopię na wiele adresów, utwórz alias i użyj go tutaj.",
"client_id": "Id klienta", "client_id": "Id klienta",
"client_secret": "Tajny klucz klienta(sekret)", "client_secret": "Tajny klucz klienta(sekret)",
"comment_info": "Komentarz prywatny nie jest widoczny dla użytkownika, natomiast komentarz publiczny jest wyświetlany jako podpowiedź po najechaniu kursorem w widoku użytkownika", "comment_info": "Komentarz prywatny nie jest widoczny dla użytkownika, natomiast komentarz publiczny jest wyświetlany jako podpowiedź po najechaniu kursorem w widoku użytkownika",
"created_on": "Stworzony na", "created_on": "Stworzony na",
"custom_attributes": "Niestandardowe atrybuty", "custom_attributes": "Niestandardowe atrybuty",
"delete2": "Usuń wiadomości na koncie docelowym, które nie występują na koncie źródłowym",
"disable_login": "Zablokuj logowanie (przychodząca poczta nadal będzie przyjmowana)", "disable_login": "Zablokuj logowanie (przychodząca poczta nadal będzie przyjmowana)",
"domain_footer": "Stopka dla całej domeny", "domain_footer": "Stopka dla całej domeny",
"domain_footer_html": "Stopka HTML", "domain_footer_html": "Stopka HTML",
@@ -639,6 +663,7 @@
"sender_acl_info": "Jeżeli użytkownik skrzynki pocztowej A ma pozwolenie na wysyłkę jako użytkownik skrzynki B, adres nadawcy nie jest automatycznie wyświetlany jako opcja w polu „Od” w SOGo.<br>\nUżytkownik skrzynki B musi utworzyć delegację w SOGo, aby użytkownik A mógł wybrać ich adres jako nadawcę. Aby zdelegować skrzynkę w SOGo, użyj menu (trzy kropki) po prawej stronie nazwy skrzynki w lewym górnym rogu, będąc w widoku poczty. To zachowanie nie dotyczy adresów aliasów.", "sender_acl_info": "Jeżeli użytkownik skrzynki pocztowej A ma pozwolenie na wysyłkę jako użytkownik skrzynki B, adres nadawcy nie jest automatycznie wyświetlany jako opcja w polu „Od” w SOGo.<br>\nUżytkownik skrzynki B musi utworzyć delegację w SOGo, aby użytkownik A mógł wybrać ich adres jako nadawcę. Aby zdelegować skrzynkę w SOGo, użyj menu (trzy kropki) po prawej stronie nazwy skrzynki w lewym górnym rogu, będąc w widoku poczty. To zachowanie nie dotyczy adresów aliasów.",
"sieve_desc": "Krótki opis", "sieve_desc": "Krótki opis",
"sieve_type": "Typ filtra", "sieve_type": "Typ filtra",
"skipcrossduplicates": "Pomijaj duplikaty wiadomości w różnych folderach (pierwsza napotkana wiadomość zostaje zachowana).",
"sogo_access": "Bezpośrednie przekazywanie do SOGo", "sogo_access": "Bezpośrednie przekazywanie do SOGo",
"sogo_access_info": "Po zalogowaniu użytkownik jest automatycznie przekierowywany do SOGo.", "sogo_access_info": "Po zalogowaniu użytkownik jest automatycznie przekierowywany do SOGo.",
"sogo_visible": "Alias jest widoczny w SOGO", "sogo_visible": "Alias jest widoczny w SOGO",
@@ -647,10 +672,13 @@
"spam_filter": "Filtr spam", "spam_filter": "Filtr spam",
"spam_policy": "Dodaj lub usuń elementy z listy dozwolonych / zablokowanych", "spam_policy": "Dodaj lub usuń elementy z listy dozwolonych / zablokowanych",
"spam_score": "Ustaw własny poziom punktacji spamu", "spam_score": "Ustaw własny poziom punktacji spamu",
"timeout1": "Limit czasu połączenia z serwerem zdalnym",
"timeout2": "Limit czasu połączenia z serwerem lokalnym",
"validate_save": "Zatwierdź i zapisz", "validate_save": "Zatwierdź i zapisz",
"pushover_info": "Ustawienia powiadomień push będą miały zastosowanie do wszystkich czystych (niespamowych) wiadomości dostarczanych do <b>%s</b> w tym aliasów (współdzielonych, niewspółdzielonych, oznaczonych).", "pushover_info": "Ustawienia powiadomień push będą miały zastosowanie do wszystkich czystych (niespamowych) wiadomości dostarczanych do <b>%s</b> w tym aliasów (współdzielonych, niewspółdzielonych, oznaczonych).",
"mailbox_quota_def": "Domyślny limit skrzynki pocztowej", "mailbox_quota_def": "Domyślny limit skrzynki pocztowej",
"mailbox_relayhost_info": "Dotyczy wyłącznie skrzynki pocztowej i bezpośrednich aliasów, nadpisuje ustawienie serwera pośredniczącego (relayhost) dla domeny.", "mailbox_relayhost_info": "Dotyczy wyłącznie skrzynki pocztowej i bezpośrednich aliasów, nadpisuje ustawienie serwera pośredniczącego (relayhost) dla domeny.",
"maxbytespersecond": "Max. Ilość bajtów na sekundę <br><small>(0 = unlimited)</small>",
"mailbox_rename": "Zmień nazwę skrzynki pocztowej", "mailbox_rename": "Zmień nazwę skrzynki pocztowej",
"mailbox_rename_agree": "Stworzyłem kopię zapasową.", "mailbox_rename_agree": "Stworzyłem kopię zapasową.",
"mailbox_rename_warning": "WAŻNE! Utwórz kopię zapasową przed zmianą nazwy skrzynki pocztowej.", "mailbox_rename_warning": "WAŻNE! Utwórz kopię zapasową przed zmianą nazwy skrzynki pocztowej.",
@@ -787,6 +815,7 @@
"remove": "Usuń", "remove": "Usuń",
"resources": "Zasoby", "resources": "Zasoby",
"spam_aliases": "Alias tymczasowy", "spam_aliases": "Alias tymczasowy",
"sync_jobs": "Polecenie synchronizacji",
"target_address": "Adres Idź do", "target_address": "Adres Idź do",
"target_domain": "Domena docelowa", "target_domain": "Domena docelowa",
"tls_enforce_in": "Uruchom TLS przychodzące", "tls_enforce_in": "Uruchom TLS przychodzące",
@@ -819,6 +848,15 @@
"sogo_visible_y": "Pokaż alias w SOGo", "sogo_visible_y": "Pokaż alias w SOGo",
"stats": "Statystyki", "stats": "Statystyki",
"status": "Status", "status": "Status",
"syncjob_check_log": "Sprawdź log",
"syncjob_last_run_result": "Wynik ostatniego uruchomienia",
"syncjob_EX_OK": "Sukces",
"syncjob_EXIT_CONNECTION_FAILURE": "Problem z połączeniem",
"syncjob_EXIT_TLS_FAILURE": "Problem z szyfrowanym połączeniem",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problem uwierzytelniania",
"syncjob_EXIT_OVERQUOTA": "Docelowa skrzynka pocztowa przekroczyła limit pojemności",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nie można połączyć się ze zdalnym serwerem",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Niewłaściwa nazwa użytkownika lub hasło",
"table_size": "Rozmiar tabeli", "table_size": "Rozmiar tabeli",
"table_size_show_n": "Pokaż %s elementy", "table_size_show_n": "Pokaż %s elementy",
"templates": "Szablony", "templates": "Szablony",
@@ -998,37 +1036,6 @@
"u2f_deprecated": "Wygląda na to, że Twój klucz został zarejestrowany przy użyciu przestarzałej metody U2F. Dezaktywujemy dla Ciebie uwierzytelnianie dwuskładnikowe i usuniemy Twój klucz.", "u2f_deprecated": "Wygląda na to, że Twój klucz został zarejestrowany przy użyciu przestarzałej metody U2F. Dezaktywujemy dla Ciebie uwierzytelnianie dwuskładnikowe i usuniemy Twój klucz.",
"u2f_deprecated_important": "Zarejestruj swój klucz w panelu administracyjnym za pomocą nowej metody WebAuthn." "u2f_deprecated_important": "Zarejestruj swój klucz w panelu administracyjnym za pomocą nowej metody WebAuthn."
}, },
"syncjobs": {
"sync_jobs": "Zadania synchronizacji",
"syncjob_check_log": "Sprawdź log",
"syncjob_last_run_result": "Wynik ostatniego uruchomienia",
"syncjob_EX_OK": "Sukces",
"syncjob_EXIT_CONNECTION_FAILURE": "Problem z połączeniem",
"syncjob_EXIT_TLS_FAILURE": "Problem z szyfrowanym połączeniem",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problem uwierzytelniania",
"syncjob_EXIT_OVERQUOTA": "Docelowa skrzynka pocztowa przekroczyła limit pojemności",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nie można połączyć się ze zdalnym serwerem",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Niewłaściwa nazwa użytkownika lub hasło",
"add": "Dodaj polecenie synchronizacji",
"add_hint": "Pamiętaj, że hasła należy zapisywać w zwykłym tekście!",
"edit": "Edytuj polecenie synchronizacji",
"create": "Utwórz nowe polecenie synchronizacji",
"dry": "Symulacja synchronizacji",
"subfolder2": "Synchronizuj do podfolderu w skrzynce<br><small>(pusty = nie używaj subfolderu)</small>",
"mins_interval": "Zakres pobierania (minuty)",
"maxage": "Maksymalny wiek wiadomości, liczony w dniach, które będą pobierane ze zdalnego<br><small>(0 = pomiń wiek>",
"exclude": "Wyklucz obiekty (regex)",
"automap": "Spróbuj automatycznie mapować foldery (\"Wysłane elementy\", \"Wysłane\" => \"Wysłane\" itp.)",
"delete1": "Usuń ze źródła po zakończeniu",
"delete2": "Usuń wiadomości w miejscu docelowym, które nie znajdują się w źródle",
"delete2duplicates": "Usuń duplikaty w miejscu docelowym",
"skipcrossduplicates": "Pomiń duplikaty wiadomości w folderach (na zasadzie kolejności zgłoszeń)",
"subscribeall": "Subskrybuj wszystkie foldery",
"timeout1": "Limit czasu połączenia z hostem zdalnym",
"timeout2": "Limit czasu połączenia dla lokalnego hosta",
"maxbytespersecond": "Max. Ilość bajtów na sekundę <br><small>(0 = unlimited)</small>",
"custom_params": "Niestandardowe parametry"
},
"user": { "user": {
"action": "Działanie", "action": "Działanie",
"active": "Aktywny", "active": "Aktywny",
@@ -1045,6 +1052,7 @@
"aliases_send_as_all": "Nie sprawdzaj dostępu nadawcy dla następujących domen(y) i ich/jej aliasów", "aliases_send_as_all": "Nie sprawdzaj dostępu nadawcy dla następujących domen(y) i ich/jej aliasów",
"change_password": "Zmień hasło", "change_password": "Zmień hasło",
"client_configuration": "Instrukcja konfiguracji w programach pocztowych i na smartfonach", "client_configuration": "Instrukcja konfiguracji w programach pocztowych i na smartfonach",
"create_syncjob": "Utwórz nowe polecenie synchronizacji",
"daily": "Co dzień", "daily": "Co dzień",
"day": "Dzień", "day": "Dzień",
"description": "Opis", "description": "Opis",
@@ -1099,6 +1107,7 @@
"spamfilter_wl": "Biała lista", "spamfilter_wl": "Biała lista",
"spamfilter_wl_desc": "Adresy e-mail znajdujące się na liście dozwolonych (allowlist) są zaprogramowane tak, aby <b> nigdy nie </b> były klasyfikowane jako spam. Można używać symboli wieloznacznych (wildcardów).Filtr jest stosowany wyłącznie do bezpośrednich aliasów (aliasów wskazujących na jedną skrzynkę pocztową), z wyłączeniem aliasów typu „catch-all” oraz samej skrzynki pocztowej", "spamfilter_wl_desc": "Adresy e-mail znajdujące się na liście dozwolonych (allowlist) są zaprogramowane tak, aby <b> nigdy nie </b> były klasyfikowane jako spam. Można używać symboli wieloznacznych (wildcardów).Filtr jest stosowany wyłącznie do bezpośrednich aliasów (aliasów wskazujących na jedną skrzynkę pocztową), z wyłączeniem aliasów typu „catch-all” oraz samej skrzynki pocztowej",
"spamfilter_yellow": "Żółty: ta wiadomość może być spamem, zostanie oznaczona jako spam i przeniesiona do folderu spam", "spamfilter_yellow": "Żółty: ta wiadomość może być spamem, zostanie oznaczona jako spam i przeniesiona do folderu spam",
"sync_jobs": "Zadania synchronizacji",
"tag_handling": "Ustaw obsługę znaczników pocztowych", "tag_handling": "Ustaw obsługę znaczników pocztowych",
"tag_help_example": "Przykład adresu email z etykietą: ja<b>+Facebook</b>@example.org", "tag_help_example": "Przykład adresu email z etykietą: ja<b>+Facebook</b>@example.org",
"tag_help_explain": "W podfolderze: tworzy nowy podfolder z nazwą taką jak etykieta, który zostanie umieszczony pod Skrzynką odbiorczą (\"Skrzynka odbiorcza/Facebook\").<br>\r\nW temacie: nazwy etykiet zostaną dodane na początku tematów wiadomości, np.: \"[Facebook] Moje wiadomości\".", "tag_help_explain": "W podfolderze: tworzy nowy podfolder z nazwą taką jak etykieta, który zostanie umieszczony pod Skrzynką odbiorczą (\"Skrzynka odbiorcza/Facebook\").<br>\r\nW temacie: nazwy etykiet zostaną dodane na początku tematów wiadomości, np.: \"[Facebook] Moje wiadomości\".",
@@ -1179,6 +1188,15 @@
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sprawdzenie nadawcy jest wyłączone</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sprawdzenie nadawcy jest wyłączone</span>",
"spam_score_reset": "Przywróć domyślne ustawienia serwera", "spam_score_reset": "Przywróć domyślne ustawienia serwera",
"status": "Status", "status": "Status",
"syncjob_check_log": "Sprawdź log",
"syncjob_last_run_result": "Wynik ostatniego uruchomienia",
"syncjob_EX_OK": "Sukces",
"syncjob_EXIT_CONNECTION_FAILURE": "Problem z połączeniem",
"syncjob_EXIT_TLS_FAILURE": "Problem z szyfrowanym połączeniem",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problem uwierzytelniania",
"syncjob_EXIT_OVERQUOTA": "Docelowa skrzynka pocztowa przekroczyła limit pojemności",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nie można połączyć się ze zdalnym serwerem",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Niewłaściwa nazwa użytkownika lub hasło",
"text": "Tekst", "text": "Tekst",
"tfa_info": "Uwierzytelnianie dwuskładnikowe pomaga chronić Twoje konto. Jeśli je włączysz, będziesz potrzebować haseł aplikacji, aby logować się do programów lub usług, które nie obsługują uwierzytelniania dwuskładnikowego (np. klientów poczty).", "tfa_info": "Uwierzytelnianie dwuskładnikowe pomaga chronić Twoje konto. Jeśli je włączysz, będziesz potrzebować haseł aplikacji, aby logować się do programów lub usług, które nie obsługują uwierzytelniania dwuskładnikowego (np. klientów poczty).",
"title": "Tytuł", "title": "Tytuł",
+49 -31
View File
@@ -44,9 +44,15 @@
"app_name": "Nome do aplicativo", "app_name": "Nome do aplicativo",
"app_password": "Adicionar senha do aplicativo", "app_password": "Adicionar senha do aplicativo",
"app_passwd_protocols": "Protocolos permitidos para a senha do aplicativo", "app_passwd_protocols": "Protocolos permitidos para a senha do aplicativo",
"automap": "Tente mapear pastas automaticamente (“Itens enviados”, “Enviados” => “Enviados” etc.)",
"backup_mx_options": "Opções de relé", "backup_mx_options": "Opções de relé",
"bcc_dest_format": "O destino do BCC deve ser um único endereço de e-mail válido. <br>Se precisar enviar uma cópia para vários endereços, crie um alias e use-o aqui.", "bcc_dest_format": "O destino do BCC deve ser um único endereço de e-mail válido. <br>Se precisar enviar uma cópia para vários endereços, crie um alias e use-o aqui.",
"comment_info": "Um comentário privado não é visível para o usuário, enquanto um comentário público é mostrado como dica de ferramenta ao passar o mouse sobre ele na visão geral do usuário", "comment_info": "Um comentário privado não é visível para o usuário, enquanto um comentário público é mostrado como dica de ferramenta ao passar o mouse sobre ele na visão geral do usuário",
"custom_params": "Parâmetros personalizados",
"custom_params_hint": "Certo: --param=xy, errado: --param xy",
"delete1": "Excluir da fonte quando concluído",
"delete2": "Excluir mensagens no destino que não estão na origem",
"delete2duplicates": "Excluir duplicatas no destino",
"description": "Descrição", "description": "Descrição",
"destination": "Destino", "destination": "Destino",
"disable_login": "Não permitir login (e-mails recebidos ainda são aceitos)", "disable_login": "Não permitir login (e-mails recebidos ainda são aceitos)",
@@ -54,6 +60,7 @@
"domain_matches_hostname": "O domínio %s corresponde ao nome do host", "domain_matches_hostname": "O domínio %s corresponde ao nome do host",
"domain_quota_m": "Cota total de domínio (MiB)", "domain_quota_m": "Cota total de domínio (MiB)",
"enc_method": "Método de criptografia", "enc_method": "Método de criptografia",
"exclude": "Excluir objetos (regex)",
"full_name": "Nome completo", "full_name": "Nome completo",
"gal": "Lista de endereços global", "gal": "Lista de endereços global",
"gal_info": "A GAL contém todos os objetos de um domínio e não pode ser editada por nenhum usuário. Faltam informações de disponibilidade no SoGo, se desativadas! <b>Reinicie o SoGo para aplicar as alterações.</b>", "gal_info": "A GAL contém todos os objetos de um domínio e não pode ser editada por nenhum usuário. Faltam informações de disponibilidade no SoGo, se desativadas! <b>Reinicie o SoGo para aplicar as alterações.</b>",
@@ -69,6 +76,7 @@
"mailbox_username": "Nome de usuário (parte esquerda de um endereço de e-mail)", "mailbox_username": "Nome de usuário (parte esquerda de um endereço de e-mail)",
"max_aliases": "Máximo de aliases possíveis", "max_aliases": "Máximo de aliases possíveis",
"max_mailboxes": "Número máximo de mailboxes possíveis", "max_mailboxes": "Número máximo de mailboxes possíveis",
"mins_interval": "Intervalo de votação (minutos)",
"multiple_bookings": "Várias reservas", "multiple_bookings": "Várias reservas",
"nexthop": "Próximo salto", "nexthop": "Próximo salto",
"password": "Senha", "password": "Senha",
@@ -88,13 +96,20 @@
"select_domain": "Selecione primeiro um domínio", "select_domain": "Selecione primeiro um domínio",
"sieve_desc": "Breve descrição", "sieve_desc": "Breve descrição",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)",
"subscribeall": "Assine todas as pastas",
"syncjob": "Adicionar tarefa de sincronização",
"syncjob_hint": "Esteja ciente de que as senhas precisam ser salvas em texto simples!",
"tags": "Etiquetas", "tags": "Etiquetas",
"target_address": "Vá para endereços", "target_address": "Vá para endereços",
"target_address_info": "<small>Endereço (s) de e-mail completo (separado por vírgula).</small>", "target_address_info": "<small>Endereço (s) de e-mail completo (separado por vírgula).</small>",
"target_domain": "Domínio de destino", "target_domain": "Domínio de destino",
"timeout1": "Tempo limite para conexão com o host remoto",
"timeout2": "Tempo limite para conexão com o host local",
"username": "Nome de usuário", "username": "Nome de usuário",
"validate": "Validar", "validate": "Validar",
"validation_success": "Validado com sucesso", "validation_success": "Validado com sucesso",
"dry": "Simular sincronização",
"internal": "Interno", "internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio.", "internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio.",
"sender_allowed": "Permitir enviar como este alias" "sender_allowed": "Permitir enviar como este alias"
@@ -633,6 +648,7 @@
"app_name": "Nome do aplicativo", "app_name": "Nome do aplicativo",
"app_passwd": "Senha do aplicativo", "app_passwd": "Senha do aplicativo",
"app_passwd_protocols": "Protocolos permitidos para a senha do aplicativo", "app_passwd_protocols": "Protocolos permitidos para a senha do aplicativo",
"automap": "Tente mapear pastas automaticamente (“Itens enviados”, “Enviados” => “Enviados” etc.)",
"backup_mx_options": "Opções de relé", "backup_mx_options": "Opções de relé",
"bcc_dest_format": "O destino do BCC deve ser um único endereço de e-mail válido. <br>Se precisar enviar uma cópia para vários endereços, crie um alias e use-o aqui.", "bcc_dest_format": "O destino do BCC deve ser um único endereço de e-mail válido. <br>Se precisar enviar uma cópia para vários endereços, crie um alias e use-o aqui.",
"client_id": "ID Cliente", "client_id": "ID Cliente",
@@ -640,6 +656,9 @@
"comment_info": "Um comentário privado não é visível para o usuário, enquanto um comentário público é mostrado como dica de ferramenta ao passar o mouse sobre ele na visão geral do usuário", "comment_info": "Um comentário privado não é visível para o usuário, enquanto um comentário público é mostrado como dica de ferramenta ao passar o mouse sobre ele na visão geral do usuário",
"created_on": "Criado em", "created_on": "Criado em",
"custom_attributes": "Atributos personalizados", "custom_attributes": "Atributos personalizados",
"delete1": "Excluir da fonte quando concluído",
"delete2": "Excluir mensagens no destino que não estão na origem",
"delete2duplicates": "Excluir duplicatas no destino",
"delete_ays": "Confirme o processo de exclusão.", "delete_ays": "Confirme o processo de exclusão.",
"description": "Descrição", "description": "Descrição",
"disable_login": "Não permitir login (e-mails recebidos ainda são aceitos)", "disable_login": "Não permitir login (e-mails recebidos ainda são aceitos)",
@@ -663,6 +682,7 @@
"dont_check_sender_acl": "Desativar a verificação de remetente para o domínio %s (+ domínios de alias)", "dont_check_sender_acl": "Desativar a verificação de remetente para o domínio %s (+ domínios de alias)",
"edit_alias_domain": "Editar domínio Alias", "edit_alias_domain": "Editar domínio Alias",
"encryption": "Criptografia", "encryption": "Criptografia",
"exclude": "Excluir objetos (regex)",
"extended_sender_acl": "Endereços de remetentes externos", "extended_sender_acl": "Endereços de remetentes externos",
"extended_sender_acl_info": "Uma chave de domínio DKIM deve ser importada, se disponível. <br>\r\n Lembre-se de adicionar esse servidor ao registro TXT SPF correspondente. <br>\r\n Sempre que um domínio ou domínio de alias é adicionado a esse servidor, que se sobrepõe a um endereço externo, o endereço externo é removido. <br>\r\n Use @domain .tld para permitir o envio como * @domain .tld.", "extended_sender_acl_info": "Uma chave de domínio DKIM deve ser importada, se disponível. <br>\r\n Lembre-se de adicionar esse servidor ao registro TXT SPF correspondente. <br>\r\n Sempre que um domínio ou domínio de alias é adicionado a esse servidor, que se sobrepõe a um endereço externo, o endereço externo é removido. <br>\r\n Use @domain .tld para permitir o envio como * @domain .tld.",
"force_pw_update": "Forçar a atualização da senha no próximo login", "force_pw_update": "Forçar a atualização da senha no próximo login",
@@ -684,7 +704,10 @@
"max_aliases": "Máximo de aliases", "max_aliases": "Máximo de aliases",
"max_mailboxes": "Número máximo de mailboxes possíveis", "max_mailboxes": "Número máximo de mailboxes possíveis",
"max_quota": "Cota máxima por mailbox (MiB)", "max_quota": "Cota máxima por mailbox (MiB)",
"maxage": "Duração máxima das mensagens em dias que serão pesquisadas remotamente <br><small>(0 = ignorar a idade</small>)",
"maxbytespersecond": "Máximo de bytes por segundo <br><small>(0 = ilimitado</small>)",
"mbox_rl_info": "Esse limite de taxa é aplicado ao nome de login do SASL e corresponde a qualquer endereço “de” usado pelo usuário conectado. Um limite de taxa de mailbox substitui um limite de taxa em todo o domínio.", "mbox_rl_info": "Esse limite de taxa é aplicado ao nome de login do SASL e corresponde a qualquer endereço “de” usado pelo usuário conectado. Um limite de taxa de mailbox substitui um limite de taxa em todo o domínio.",
"mins_interval": "Intervalo (min)",
"multiple_bookings": "Várias reservas", "multiple_bookings": "Várias reservas",
"none_inherit": "Nenhum/Herdar", "none_inherit": "Nenhum/Herdar",
"nexthop": "Próximo salto", "nexthop": "Próximo salto",
@@ -725,6 +748,7 @@
"sender_acl_info": "Se o usuário A da mailbox tiver permissão para enviar como usuário B da mailbox, o endereço do remetente não será exibido automaticamente como campo “de” selecionável no SoGo. <br>\n O usuário B da mailbox precisa criar uma delegação no SoGo para permitir que o usuário A da mailbox selecione seu endereço como remetente. Para delegar uma mailbox no SoGo, use o menu (três pontos) à direita do nome da sua caixa de correio no canto superior esquerdo enquanto estiver na visualização de e-mail. Esse comportamento não se aplica a endereços de alias.", "sender_acl_info": "Se o usuário A da mailbox tiver permissão para enviar como usuário B da mailbox, o endereço do remetente não será exibido automaticamente como campo “de” selecionável no SoGo. <br>\n O usuário B da mailbox precisa criar uma delegação no SoGo para permitir que o usuário A da mailbox selecione seu endereço como remetente. Para delegar uma mailbox no SoGo, use o menu (três pontos) à direita do nome da sua caixa de correio no canto superior esquerdo enquanto estiver na visualização de e-mail. Esse comportamento não se aplica a endereços de alias.",
"sieve_desc": "Breve descrição", "sieve_desc": "Breve descrição",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)",
"sogo_access": "Encaminhamento direto para o SOGo", "sogo_access": "Encaminhamento direto para o SOGo",
"sogo_access_info": "Após o login, o usuário é automaticamente redirecionado para o SOGo.", "sogo_access_info": "Após o login, o usuário é automaticamente redirecionado para o SOGo.",
"sogo_visible": "O alias é visível no SoGo", "sogo_visible": "O alias é visível no SoGo",
@@ -733,8 +757,12 @@
"spam_filter": "Filtro de spam", "spam_filter": "Filtro de spam",
"spam_policy": "Adicionar ou remover itens da lista de permissões/bloqueio", "spam_policy": "Adicionar ou remover itens da lista de permissões/bloqueio",
"spam_score": "Defina uma pontuação de spam personalizada", "spam_score": "Defina uma pontuação de spam personalizada",
"subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)",
"syncjob": "Editar tarefa de sincronização",
"target_address": "Ir para o(s) endereço(s) <small>(separados por vírgula)</small>", "target_address": "Ir para o(s) endereço(s) <small>(separados por vírgula)</small>",
"target_domain": "Domínio de destino", "target_domain": "Domínio de destino",
"timeout1": "Tempo limite para conexão com o host remoto",
"timeout2": "Tempo limite para conexão com o host local",
"title": "Editar objeto", "title": "Editar objeto",
"unchanged_if_empty": "Se inalterado, deixe em branco", "unchanged_if_empty": "Se inalterado, deixe em branco",
"username": "Nome de usuário", "username": "Nome de usuário",
@@ -974,6 +1002,16 @@
"spam_aliases": "Apelido temporário", "spam_aliases": "Apelido temporário",
"stats": "Estatísticas", "stats": "Estatísticas",
"status": "Status", "status": "Status",
"sync_jobs": "Trabalhos de sincronização",
"syncjob_check_log": "Registro de verificação",
"syncjob_last_run_result": "Resultado da última corrida",
"syncjob_EX_OK": "Sucesso",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema de conexão",
"syncjob_EXIT_TLS_FAILURE": "Problema com conexão criptografada",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema de autenticação",
"syncjob_EXIT_OVERQUOTA": "A mailbox de destino está acima da cota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos",
"table_size": "Tamanho da mesa", "table_size": "Tamanho da mesa",
"table_size_show_n": "Mostrar itens %s", "table_size_show_n": "Mostrar itens %s",
"target_address": "Vá para o endereço", "target_address": "Vá para o endereço",
@@ -1213,37 +1251,6 @@
"setup_required": "Sua conta exige autenticação de dois fatores. Configure um método de 2FA para continuar.", "setup_required": "Sua conta exige autenticação de dois fatores. Configure um método de 2FA para continuar.",
"cancel_setup": "Cancelar e sair" "cancel_setup": "Cancelar e sair"
}, },
"syncjobs": {
"sync_jobs": "Trabalhos de sincronização",
"syncjob_check_log": "Registro de verificação",
"syncjob_last_run_result": "Resultado da última corrida",
"syncjob_EX_OK": "Sucesso",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema de conexão",
"syncjob_EXIT_TLS_FAILURE": "Problema com conexão criptografada",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema de autenticação",
"syncjob_EXIT_OVERQUOTA": "A mailbox de destino está acima da cota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos",
"add": "Adicionar tarefa de sincronização",
"add_hint": "Esteja ciente de que as senhas precisam ser salvas em texto simples!",
"edit": "Editar tarefa de sincronização",
"create": "Criar um novo trabalho de sincronização",
"dry": "Simular sincronização",
"subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)",
"mins_interval": "Intervalo de votação (minutos)",
"maxage": "Duração máxima das mensagens em dias que serão pesquisadas remotamente <br><small>(0 = ignorar a idade</small>)",
"exclude": "Excluir objetos (regex)",
"automap": "Tente mapear pastas automaticamente (“Itens enviados”, “Enviados” => “Enviados” etc.)",
"delete1": "Excluir da fonte quando concluído",
"delete2": "Excluir mensagens no destino que não estão na origem",
"delete2duplicates": "Excluir duplicatas no destino",
"skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)",
"subscribeall": "Assine todas as pastas",
"timeout1": "Tempo limite para conexão com o host remoto",
"timeout2": "Tempo limite para conexão com o host local",
"maxbytespersecond": "Máximo de bytes por segundo <br><small>(0 = ilimitado</small>)",
"custom_params": "Parâmetros personalizados"
},
"user": { "user": {
"action": "Ação", "action": "Ação",
"active": "Ativo", "active": "Ativo",
@@ -1273,6 +1280,7 @@
"clear_recent_successful_connections": "Conexões bem-sucedidas e claras", "clear_recent_successful_connections": "Conexões bem-sucedidas e claras",
"client_configuration": "Mostrar guias de configuração para clientes de e-mail e smartphones", "client_configuration": "Mostrar guias de configuração para clientes de e-mail e smartphones",
"create_app_passwd": "Crie a senha do aplicativo", "create_app_passwd": "Crie a senha do aplicativo",
"create_syncjob": "Criar um novo trabalho de sincronização",
"created_on": "Criado em", "created_on": "Criado em",
"daily": "Diariamente", "daily": "Diariamente",
"day": "dia", "day": "dia",
@@ -1376,6 +1384,16 @@
"spamfilter_wl_desc": "Endereços de e-mail na lista de permissões são configurados para <b>nunca</b> serem classificados como spam. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_wl_desc": "Endereços de e-mail na lista de permissões são configurados para <b>nunca</b> serem classificados como spam. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.",
"spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico", "spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico",
"status": "Status", "status": "Status",
"sync_jobs": "Trabalhos de sincronização",
"syncjob_check_log": "Registro de verificação",
"syncjob_last_run_result": "Resultado da última corrida",
"syncjob_EX_OK": "Sucesso",
"syncjob_EXIT_CONNECTION_FAILURE": "Problema de conexão",
"syncjob_EXIT_TLS_FAILURE": "Problema com conexão criptografada",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema de autenticação",
"syncjob_EXIT_OVERQUOTA": "A mailbox de destino está acima da cota",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos",
"tag_handling": "Definir o tratamento para e-mails marcados", "tag_handling": "Definir o tratamento para e-mails marcados",
"tag_help_example": "Exemplo de endereço de e-mail com tag: eu+Facebook</b>@exemplo.org", "tag_help_example": "Exemplo de endereço de e-mail com tag: eu+Facebook</b>@exemplo.org",
"tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”). <br>\r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.", "tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”). <br>\r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.",
+2 -4
View File
@@ -36,6 +36,8 @@
"public_comment": "Comentário público", "public_comment": "Comentário público",
"sieve_desc": "Breve descrição", "sieve_desc": "Breve descrição",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"subscribeall": "Subscrever todas as pastas",
"syncjob": "Adicionar sincronização",
"validate": "Validar", "validate": "Validar",
"validation_success": "Validado com sucesso", "validation_success": "Validado com sucesso",
"app_passwd_protocols": "Protocolos permitidos para senha de aplicação" "app_passwd_protocols": "Protocolos permitidos para senha de aplicação"
@@ -280,10 +282,6 @@
"mailbox_removed": "Conta %s removida com sucesso", "mailbox_removed": "Conta %s removida com sucesso",
"resource_modified": "A conta %s foi alterada com sucesso" "resource_modified": "A conta %s foi alterada com sucesso"
}, },
"syncjobs": {
"add": "Adicionar sincronização",
"subscribeall": "Subscrever todas as pastas"
},
"user": { "user": {
"action": "Ação", "action": "Ação",
"active": "Ativo", "active": "Ativo",
+48 -30
View File
@@ -43,9 +43,15 @@
"app_name": "Nume aplicație", "app_name": "Nume aplicație",
"app_password": "Adaugă parolă aplicație", "app_password": "Adaugă parolă aplicație",
"app_passwd_protocols": "Protocoale permise pentru parola aplicației", "app_passwd_protocols": "Protocoale permise pentru parola aplicației",
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
"backup_mx_options": "Opțiuni backup MX", "backup_mx_options": "Opțiuni backup MX",
"bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.", "bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.",
"comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor", "comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor",
"custom_params": "Parametri personalizați",
"custom_params_hint": "Corect: --param=xy, greşit: --param xy",
"delete1": "Șterge de la sursă când ai terminat",
"delete2": "Șterge mesajele la destinație care nu sunt la sursă",
"delete2duplicates": "Șterge duplicate la destinație",
"description": "Descriere", "description": "Descriere",
"destination": "Destinaţie", "destination": "Destinaţie",
"disable_login": "Dezactivează autentificarea", "disable_login": "Dezactivează autentificarea",
@@ -53,6 +59,7 @@
"domain_matches_hostname": "Domeniul %s se potrivește cu numele de gazdă", "domain_matches_hostname": "Domeniul %s se potrivește cu numele de gazdă",
"domain_quota_m": "Cota totală domeniu (MiB)", "domain_quota_m": "Cota totală domeniu (MiB)",
"enc_method": "Metodă de criptare", "enc_method": "Metodă de criptare",
"exclude": "Exclude obiectele (regex)",
"full_name": "Nume complet", "full_name": "Nume complet",
"gal": "Lista adreselor globale", "gal": "Lista adreselor globale",
"gal_info": "GAL conține toate obiectele unui domeniu și nu poate fi editat de niciun utilizator. Informația liber/ocupat din SOGo lipsește dacă este dezactivat! <b>Reporniți SOGo pentru a aplica modificările.</b>", "gal_info": "GAL conține toate obiectele unui domeniu și nu poate fi editat de niciun utilizator. Informația liber/ocupat din SOGo lipsește dacă este dezactivat! <b>Reporniți SOGo pentru a aplica modificările.</b>",
@@ -68,6 +75,7 @@
"mailbox_username": "Numele de utilizator (partea stângă a unei adrese de email)", "mailbox_username": "Numele de utilizator (partea stângă a unei adrese de email)",
"max_aliases": "Număr maxim posibil de aliasuri", "max_aliases": "Număr maxim posibil de aliasuri",
"max_mailboxes": "Număr maxim posibil de cutii poștale", "max_mailboxes": "Număr maxim posibil de cutii poștale",
"mins_interval": "Interval de interogare (minute)",
"multiple_bookings": "Rezervări multiple", "multiple_bookings": "Rezervări multiple",
"nexthop": "Următorul hop", "nexthop": "Următorul hop",
"password": "Parolă", "password": "Parolă",
@@ -87,9 +95,15 @@
"select_domain": "Selectează mai întâi un domeniu", "select_domain": "Selectează mai întâi un domeniu",
"sieve_desc": "Descriere scurtă", "sieve_desc": "Descriere scurtă",
"sieve_type": "Tip filtru", "sieve_type": "Tip filtru",
"skipcrossduplicates": "Sari peste mesajele duplicate din toate folderele (primul venit, primul servit)",
"subscribeall": "Abonare la toate dosarele",
"syncjob": "Adaugă o lucrare de sincronizare",
"syncjob_hint": "Reține că parolele trebuie să fie salvate în text simplu!",
"target_address": "Adrese goto", "target_address": "Adrese goto",
"target_address_info": "<small>Adresă/e de email completă/e (separate prin virgulă).</small>", "target_address_info": "<small>Adresă/e de email completă/e (separate prin virgulă).</small>",
"target_domain": "Domeniu țintă", "target_domain": "Domeniu țintă",
"timeout1": "Timeout pentru conectarea la gazda la distanță",
"timeout2": "Timeout pentru conectarea la gazda locală",
"username": "Nume de utilizator", "username": "Nume de utilizator",
"validate": "Validează", "validate": "Validează",
"validation_success": "Validat cu succes", "validation_success": "Validat cu succes",
@@ -506,12 +520,16 @@
"app_name": "Nume aplicație", "app_name": "Nume aplicație",
"app_passwd": "Parolă apicație", "app_passwd": "Parolă apicație",
"app_passwd_protocols": "Protocoale permise pentru parola aplicației", "app_passwd_protocols": "Protocoale permise pentru parola aplicației",
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
"backup_mx_options": "Opțiuni backup MX", "backup_mx_options": "Opțiuni backup MX",
"bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.<br>Dacă trebuie să trimiteți o copie la mai multe adrese, creați un alias și utilizați-l aici.", "bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.<br>Dacă trebuie să trimiteți o copie la mai multe adrese, creați un alias și utilizați-l aici.",
"client_id": "ID Client", "client_id": "ID Client",
"client_secret": "Secret client", "client_secret": "Secret client",
"comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor", "comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor",
"created_on": "Creat în", "created_on": "Creat în",
"delete1": "Șterge de la sursă când ai terminat",
"delete2": "Șterge mesajele la destinație care nu sunt la sursă",
"delete2duplicates": "Șterge duplicate la destinație",
"delete_ays": "Confirmați procesul de ștergere.", "delete_ays": "Confirmați procesul de ștergere.",
"description": "Descriere", "description": "Descriere",
"disable_login": "Nu permiteți autentificarea", "disable_login": "Nu permiteți autentificarea",
@@ -522,6 +540,7 @@
"dont_check_sender_acl": "Dezactivează verificarea expeditorului pentru domeniul %s (+ domenii alias)", "dont_check_sender_acl": "Dezactivează verificarea expeditorului pentru domeniul %s (+ domenii alias)",
"edit_alias_domain": "Editează domeniul alias", "edit_alias_domain": "Editează domeniul alias",
"encryption": "Criptare", "encryption": "Criptare",
"exclude": "Exclude obiecte (regex)",
"extended_sender_acl": "Adrese de expeditor externe", "extended_sender_acl": "Adrese de expeditor externe",
"extended_sender_acl_info": "O cheie de domeniu DKIM ar trebui importată, dacă este disponibilă.<br>\r\n Nu uitați să adăugați acest server la înregistrările corespunzătoare SPF TXT.<br>\r\n De fiecare dată când un domeniu sau un domeniu alias este adăugat pe server, care se suprapune cu o adresă externă, adresa externă este eliminată.<br>\r\n Utilizați @domain.tld pentru a permite trimiterea ca *@domain.tld.", "extended_sender_acl_info": "O cheie de domeniu DKIM ar trebui importată, dacă este disponibilă.<br>\r\n Nu uitați să adăugați acest server la înregistrările corespunzătoare SPF TXT.<br>\r\n De fiecare dată când un domeniu sau un domeniu alias este adăugat pe server, care se suprapune cu o adresă externă, adresa externă este eliminată.<br>\r\n Utilizați @domain.tld pentru a permite trimiterea ca *@domain.tld.",
"force_pw_update": "Forțează o actualizare a parolei la următoarea conectare", "force_pw_update": "Forțează o actualizare a parolei la următoarea conectare",
@@ -542,7 +561,10 @@
"max_aliases": "Număr maxim de aliasuri", "max_aliases": "Număr maxim de aliasuri",
"max_mailboxes": "Număr maxim de cutii poștale", "max_mailboxes": "Număr maxim de cutii poștale",
"max_quota": "Cotă maximă pentru cutia poștală (MiB)", "max_quota": "Cotă maximă pentru cutia poștală (MiB)",
"maxage": "Vârsta maximă, în zile, a mesajelor care vor fi interogate de la distanță<br><small>(0 = ignoră vârsta)</small>",
"maxbytespersecond": "Max. bytes pe secundă <br><small>(0 = nelimitat)</small>",
"mbox_rl_info": "Această limitare de rată se aplică pe numele de conectare SASL, se potrivește cu orice adresă „de la” folosită de către utilizatorul autentificat. O limitare a ratei căsuței poștale înlocuiește limitarea ratei la nivel de domeniu.", "mbox_rl_info": "Această limitare de rată se aplică pe numele de conectare SASL, se potrivește cu orice adresă „de la” folosită de către utilizatorul autentificat. O limitare a ratei căsuței poștale înlocuiește limitarea ratei la nivel de domeniu.",
"mins_interval": "Interval (min)",
"multiple_bookings": "Rezervări multiple", "multiple_bookings": "Rezervări multiple",
"none_inherit": "Nici una / Moștenește", "none_inherit": "Nici una / Moștenește",
"nexthop": "Următorul hop", "nexthop": "Următorul hop",
@@ -581,6 +603,7 @@
"sender_acl_info": "Dacă utilizatorului A de căsuță poștală îi este permis să trimită ca utilizatorul B, adresa expeditorului nu este afișată automat ca fiind câmp selectabil \"de la\" în SOGo.<br>\r\n Utilizatorul căsuței poștale B trebuie să creeze o delegație în SOGo pentru a permite utilizatorul cutiei poștale A să selecteze adresa ca expeditor. Pentru a delega o cutie poștală în SOGo, utilizați meniul (trei puncte) din dreapta numelui căsuței poștale în stânga sus, în timp ce vă aflați în vizualizarea e-mailului. Acest comportament nu se aplică adreselor alias.", "sender_acl_info": "Dacă utilizatorului A de căsuță poștală îi este permis să trimită ca utilizatorul B, adresa expeditorului nu este afișată automat ca fiind câmp selectabil \"de la\" în SOGo.<br>\r\n Utilizatorul căsuței poștale B trebuie să creeze o delegație în SOGo pentru a permite utilizatorul cutiei poștale A să selecteze adresa ca expeditor. Pentru a delega o cutie poștală în SOGo, utilizați meniul (trei puncte) din dreapta numelui căsuței poștale în stânga sus, în timp ce vă aflați în vizualizarea e-mailului. Acest comportament nu se aplică adreselor alias.",
"sieve_desc": "Descriere scurtă", "sieve_desc": "Descriere scurtă",
"sieve_type": "Tip filtru", "sieve_type": "Tip filtru",
"skipcrossduplicates": "Sari peste mesajele duplicate din toate folderele (primul venit, primul servit)",
"sogo_access": "Redirecționare directă către SOGo", "sogo_access": "Redirecționare directă către SOGo",
"sogo_access_info": "După logare, utilizatorul este redirecționat automat către SOGo.", "sogo_access_info": "După logare, utilizatorul este redirecționat automat către SOGo.",
"sogo_visible": "Aliasul este vizibil în SOGo", "sogo_visible": "Aliasul este vizibil în SOGo",
@@ -589,8 +612,12 @@
"spam_filter": "Filtru spam", "spam_filter": "Filtru spam",
"spam_policy": "Adaugă sau elimină elemente în lista albă/neagră", "spam_policy": "Adaugă sau elimină elemente în lista albă/neagră",
"spam_score": "Stabilește un scor de spam personalizat", "spam_score": "Stabilește un scor de spam personalizat",
"subfolder2": "Sincronizează în subfolderul destinației<br><small>(gol = nu se utilizează subfolderul)</small>",
"syncjob": "Editează lucrarea de sincronizare",
"target_address": "Adresă/e goto <small>(separate prin virgulă)</small>", "target_address": "Adresă/e goto <small>(separate prin virgulă)</small>",
"target_domain": "Domeniu țintă", "target_domain": "Domeniu țintă",
"timeout1": "Timeout pentru conectarea la gazda la distanță",
"timeout2": "Timeout pentru conectarea la gazda locală",
"title": "Editează obiectul", "title": "Editează obiectul",
"unchanged_if_empty": "Dacă rămâne neschimbat se lasă necompletat", "unchanged_if_empty": "Dacă rămâne neschimbat se lasă necompletat",
"username": "Nume de utilizator", "username": "Nume de utilizator",
@@ -789,6 +816,16 @@
"spam_aliases": "Alias temporar", "spam_aliases": "Alias temporar",
"stats": "Statistici", "stats": "Statistici",
"status": "Stare", "status": "Stare",
"sync_jobs": "Lucrări de sincronizare",
"syncjob_check_log": "Verificați jurnalul",
"syncjob_last_run_result": "Rezultatul ultimei rulări",
"syncjob_EX_OK": "Succes",
"syncjob_EXIT_CONNECTION_FAILURE": "Problemă de conectare",
"syncjob_EXIT_TLS_FAILURE": "Problemă cu conexiunea criptată",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problemă de autentificare",
"syncjob_EXIT_OVERQUOTA": "Cutia poștală țintă depășește cota maximă",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nu se poate conecta la server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nume de utilizator sau parolă greșite",
"table_size": "Mărime tabel", "table_size": "Mărime tabel",
"table_size_show_n": "Arată %s articole", "table_size_show_n": "Arată %s articole",
"target_address": "Adresă goto", "target_address": "Adresă goto",
@@ -990,36 +1027,6 @@
"waiting_usb_register": "<i>În așteptarea dispozitivului USB...</i><br><br>Introdu parola ta mai sus și confirmă înregistrarea ta WebAuthn atingând butonul de pe dispozitivul tău USB WebAuthn.", "waiting_usb_register": "<i>În așteptarea dispozitivului USB...</i><br><br>Introdu parola ta mai sus și confirmă înregistrarea ta WebAuthn atingând butonul de pe dispozitivul tău USB WebAuthn.",
"yubi_otp": "Autentificare Yubico OTP" "yubi_otp": "Autentificare Yubico OTP"
}, },
"syncjobs": {
"sync_jobs": "Lucrări de sincronizare",
"syncjob_check_log": "Verificați jurnalul",
"syncjob_last_run_result": "Rezultatul ultimei rulări",
"syncjob_EX_OK": "Succes",
"syncjob_EXIT_CONNECTION_FAILURE": "Problemă de conectare",
"syncjob_EXIT_TLS_FAILURE": "Problemă cu conexiunea criptată",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problemă de autentificare",
"syncjob_EXIT_OVERQUOTA": "Cutia poștală țintă depășește cota maximă",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nu se poate conecta la server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nume de utilizator sau parolă greșite",
"add": "Adaugă o lucrare de sincronizare",
"add_hint": "Reține că parolele trebuie să fie salvate în text simplu!",
"edit": "Editează lucrarea de sincronizare",
"create": "Crează o nouă lucrare de sincronizare",
"subfolder2": "Sincronizează în subfolderul destinației<br><small>(gol = nu se utilizează subfolderul)</small>",
"mins_interval": "Interval de interogare (minute)",
"maxage": "Vârsta maximă, în zile, a mesajelor care vor fi interogate de la distanță<br><small>(0 = ignoră vârsta)</small>",
"exclude": "Exclude obiectele (regex)",
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
"delete1": "Șterge de la sursă când ai terminat",
"delete2": "Șterge mesajele la destinație care nu sunt la sursă",
"delete2duplicates": "Șterge duplicate la destinație",
"skipcrossduplicates": "Sari peste mesajele duplicate din toate folderele (primul venit, primul servit)",
"subscribeall": "Abonare la toate dosarele",
"timeout1": "Timeout pentru conectarea la gazda la distanță",
"timeout2": "Timeout pentru conectarea la gazda locală",
"maxbytespersecond": "Max. bytes pe secundă <br><small>(0 = nelimitat)</small>",
"custom_params": "Parametri personalizați"
},
"user": { "user": {
"action": "Acțiune", "action": "Acțiune",
"active": "Activ", "active": "Activ",
@@ -1048,6 +1055,7 @@
"clear_recent_successful_connections": "Ștergeți conexiunile reușite văzute", "clear_recent_successful_connections": "Ștergeți conexiunile reușite văzute",
"client_configuration": "Afișează ghidurile de configurare pentru clienții de email și smartphone-uri", "client_configuration": "Afișează ghidurile de configurare pentru clienții de email și smartphone-uri",
"create_app_passwd": "Crează parola aplicației", "create_app_passwd": "Crează parola aplicației",
"create_syncjob": "Crează o nouă lucrare de sincronizare",
"created_on": "Creat în", "created_on": "Creat în",
"daily": "Zilnic", "daily": "Zilnic",
"day": "zi", "day": "zi",
@@ -1148,6 +1156,16 @@
"spamfilter_wl_desc": "Adrese de email de pe lista albă care nu trebuie <b>niciodată</b> să fie clasificate ca spam. Se pot folosi carduri speciale. Un filtru se aplică numai în cazul aliasurilor directe (aliasuri cu o singură căsuță poștală țintă), cu excepția aliasurilor catch-all și a cutiei poștale în sine.", "spamfilter_wl_desc": "Adrese de email de pe lista albă care nu trebuie <b>niciodată</b> să fie clasificate ca spam. Se pot folosi carduri speciale. Un filtru se aplică numai în cazul aliasurilor directe (aliasuri cu o singură căsuță poștală țintă), cu excepția aliasurilor catch-all și a cutiei poștale în sine.",
"spamfilter_yellow": "Galben: acest mesaj poate fi spam, va fi etichetat ca spam și va fi mutat în dosarul de junk", "spamfilter_yellow": "Galben: acest mesaj poate fi spam, va fi etichetat ca spam și va fi mutat în dosarul de junk",
"status": "Stare", "status": "Stare",
"sync_jobs": "Lucrări de sincronizare",
"syncjob_check_log": "Verificați jurnalul",
"syncjob_last_run_result": "Rezultatul ultimei rulări",
"syncjob_EX_OK": "Succes",
"syncjob_EXIT_CONNECTION_FAILURE": "Problemă de conectare",
"syncjob_EXIT_TLS_FAILURE": "Problemă cu conexiunea criptată",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problemă de autentificare",
"syncjob_EXIT_OVERQUOTA": "Cutia poștală țintă depășește cota maximă",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nu se poate conecta la server",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nume de utilizator sau parolă greșite",
"tag_handling": "Setează manevrarea pentru mailurile etichetate", "tag_handling": "Setează manevrarea pentru mailurile etichetate",
"tag_help_example": "Exemplu pentru o adresă de email etichetată: me<b>+Facebook</b>@example.org", "tag_help_example": "Exemplu pentru o adresă de email etichetată: me<b>+Facebook</b>@example.org",
"tag_help_explain": "În subfolder: un nou subfolder numit după etichetă va fi creat sub INBOX (\"INBOX/Facebook\").<br>\nÎn subiect: numele etichetelor va fi prefixat la subiectul mailurilor, de exemplu: \"[Facebook] My News\".", "tag_help_explain": "În subfolder: un nou subfolder numit după etichetă va fi creat sub INBOX (\"INBOX/Facebook\").<br>\nÎn subiect: numele etichetelor va fi prefixat la subiectul mailurilor, de exemplu: \"[Facebook] My News\".",
+49 -31
View File
@@ -44,16 +44,24 @@
"app_name": "Название приложения", "app_name": "Название приложения",
"app_passwd_protocols": "Разрешенные протоколы для пароля приложения", "app_passwd_protocols": "Разрешенные протоколы для пароля приложения",
"app_password": "Добавить пароль приложения", "app_password": "Добавить пароль приложения",
"automap": "Автоматическое слияние папок (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Параметры резервного MX", "backup_mx_options": "Параметры резервного MX",
"bcc_dest_format": "Место назначения BCC должно быть единственным действительным адресом электронной почты.<br>Если вам нужно отправить копию на несколько адресов - используйте псевдоним.", "bcc_dest_format": "Место назначения BCC должно быть единственным действительным адресом электронной почты.<br>Если вам нужно отправить копию на несколько адресов - используйте псевдоним.",
"comment_info": "Приватный комментарий не виден пользователям, а публичный - отображается рядом с псевдонимом в личном кабинете пользователя.", "comment_info": "Приватный комментарий не виден пользователям, а публичный - отображается рядом с псевдонимом в личном кабинете пользователя.",
"custom_params": "Пользовательские параметры",
"custom_params_hint": "Верно: --param=xy, неверно: --param xy",
"delete1": "Удаление из источника после завершения",
"delete2": "Удаление писем по месту назначения, которые не находятся на исходном",
"delete2duplicates": "Удаление дубликатов по назначению",
"description": "Описание", "description": "Описание",
"destination": "Назначение", "destination": "Назначение",
"disable_login": "Вход в систему запрещён", "disable_login": "Вход в систему запрещён",
"domain": "Домен", "domain": "Домен",
"domain_matches_hostname": "Домен %s соответствует имени хоста", "domain_matches_hostname": "Домен %s соответствует имени хоста",
"domain_quota_m": "Квота домена (MiB)", "domain_quota_m": "Квота домена (MiB)",
"dry": "Имитировать синхронизацию",
"enc_method": "Метод шифрования", "enc_method": "Метод шифрования",
"exclude": "Исключить объекты (regex)",
"full_name": "Полное имя", "full_name": "Полное имя",
"gal": "GAL - Глобальная адресная книга", "gal": "GAL - Глобальная адресная книга",
"gal_info": "GAL содержит все объекты домена и не подлежит редактированию. Информация о занятости в SOGo будет отсутствовать для домена, если эта функция не будет включена! <b>Требуется перезапустить SOGo, чтобы изменения применились.</b>", "gal_info": "GAL содержит все объекты домена и не подлежит редактированию. Информация о занятости в SOGo будет отсутствовать для домена, если эта функция не будет включена! <b>Требуется перезапустить SOGo, чтобы изменения применились.</b>",
@@ -69,6 +77,7 @@
"mailbox_username": "Имя пользователя (часть адреса электронной почты слева)", "mailbox_username": "Имя пользователя (часть адреса электронной почты слева)",
"max_aliases": "Максимум псевдонимов", "max_aliases": "Максимум псевдонимов",
"max_mailboxes": "Максимум почтовых ящиков", "max_mailboxes": "Максимум почтовых ящиков",
"mins_interval": "Интервал (в минутах)",
"multiple_bookings": "Несколько бронирований", "multiple_bookings": "Несколько бронирований",
"nexthop": "Следующий хост", "nexthop": "Следующий хост",
"password": "Пароль", "password": "Пароль",
@@ -88,10 +97,16 @@
"select_domain": "Пожалуйста, сначала выберите домен", "select_domain": "Пожалуйста, сначала выберите домен",
"sieve_desc": "Краткое описание", "sieve_desc": "Краткое описание",
"sieve_type": "Тип фильтра", "sieve_type": "Тип фильтра",
"skipcrossduplicates": "Пропускать повторяющиеся сообщения в папках",
"subscribeall": "Подписаться на все папки и подпапки",
"syncjob": "Добавить задание синхронизации",
"syncjob_hint": "Пароли к вашему аккаунту будут сохранены на сервере в виде простого текста!",
"tags": "Теги", "tags": "Теги",
"target_address": "Владельцы псевдонима", "target_address": "Владельцы псевдонима",
"target_address_info": "<small>Адреса почтовых ящиков, разделенные запятыми.</small>", "target_address_info": "<small>Адреса почтовых ящиков, разделенные запятыми.</small>",
"target_domain": "Целевой домен", "target_domain": "Целевой домен",
"timeout1": "Тайм-аут для подключения к удаленному хосту",
"timeout2": "Тайм-аут для подключения к локальному хосту",
"username": "Имя пользователя", "username": "Имя пользователя",
"validate": "Проверить", "validate": "Проверить",
"validation_success": "Проверка прошла успешно", "validation_success": "Проверка прошла успешно",
@@ -631,6 +646,7 @@
"app_name": "Название приложения", "app_name": "Название приложения",
"app_passwd": "Пароль приложения", "app_passwd": "Пароль приложения",
"app_passwd_protocols": "Разрешенные протоколы для пароля приложения", "app_passwd_protocols": "Разрешенные протоколы для пароля приложения",
"automap": "Автоматическое слияние папок (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"backup_mx_options": "Параметры резервного копирования MX", "backup_mx_options": "Параметры резервного копирования MX",
"bcc_dest_format": "Назначением для правила BCC должен быть единственный действительный адрес электронной почты.", "bcc_dest_format": "Назначением для правила BCC должен быть единственный действительный адрес электронной почты.",
"client_id": "ID клиента", "client_id": "ID клиента",
@@ -638,6 +654,9 @@
"comment_info": "Приватный комментарий не виден пользователям, а публичный - отображается рядом с псевдонимом в личном кабинете пользователя", "comment_info": "Приватный комментарий не виден пользователям, а публичный - отображается рядом с псевдонимом в личном кабинете пользователя",
"created_on": "Дата создания", "created_on": "Дата создания",
"custom_attributes": "Пользовательские атрибуты", "custom_attributes": "Пользовательские атрибуты",
"delete1": "Удаление из источника после завершения",
"delete2": "Удаление писем по месту назначения, которые не находятся на исходном",
"delete2duplicates": "Удаление дубликатов по назначению",
"delete_ays": "Пожалуйста, подтвердите удаление", "delete_ays": "Пожалуйста, подтвердите удаление",
"description": "Описание", "description": "Описание",
"disable_login": "Вход в систему запрещен", "disable_login": "Вход в систему запрещен",
@@ -661,6 +680,7 @@
"dont_check_sender_acl": "Отключить проверку отправителя для домена %s и псевдонимов домена", "dont_check_sender_acl": "Отключить проверку отправителя для домена %s и псевдонимов домена",
"edit_alias_domain": "Изменить псевдоним домена", "edit_alias_domain": "Изменить псевдоним домена",
"encryption": "Шифрование", "encryption": "Шифрование",
"exclude": "Исключить объекты (regex)",
"extended_sender_acl": "Внешние адреса почты", "extended_sender_acl": "Внешние адреса почты",
"extended_sender_acl_info": "Для внешних доменов должен быть импортирован или сгенерирован доменный ключ DKIM с соответствующей записью TXT в домене, если внешний домен использует DMARC.<br>\r\n Не забудьте добавить этот сервер к соответствующей записи SPF TXT внешнего домена.<br>\r\n Добавление домена из списка внешних адресов в mailcow автоматически удалит соответствующие записи из внешних адресов пользователей.<br>\r\n Чтобы разрешить пользователю отправку от имени *@domain.tld, укажите @domain.tld.", "extended_sender_acl_info": "Для внешних доменов должен быть импортирован или сгенерирован доменный ключ DKIM с соответствующей записью TXT в домене, если внешний домен использует DMARC.<br>\r\n Не забудьте добавить этот сервер к соответствующей записи SPF TXT внешнего домена.<br>\r\n Добавление домена из списка внешних адресов в mailcow автоматически удалит соответствующие записи из внешних адресов пользователей.<br>\r\n Чтобы разрешить пользователю отправку от имени *@domain.tld, укажите @domain.tld.",
"footer_exclude": "Исключить из нижнего колонтитула", "footer_exclude": "Исключить из нижнего колонтитула",
@@ -687,7 +707,10 @@
"max_aliases": "Максимум псевдонимов", "max_aliases": "Максимум псевдонимов",
"max_mailboxes": "Максимум почтовых ящиков", "max_mailboxes": "Максимум почтовых ящиков",
"max_quota": "Максимальная квота почтового аккаунта (MiB)", "max_quota": "Максимальная квота почтового аккаунта (MiB)",
"maxage": "Макс. возраст писем в днях, для загрузки с сервера<br><small>(0 = игнорировать возраст)</small>",
"maxbytespersecond": "Макс. байт в сек<br><small>(0 = без лимита)</small>",
"mbox_rl_info": "Этот лимит применяется к SASL логину пользователя и соответствует любому адресу отправителя, используемому зарегистрированным пользователем. Лимит скорости почтового аккаунта перекрывает лимит скорости для всего домена.", "mbox_rl_info": "Этот лимит применяется к SASL логину пользователя и соответствует любому адресу отправителя, используемому зарегистрированным пользователем. Лимит скорости почтового аккаунта перекрывает лимит скорости для всего домена.",
"mins_interval": "Интервал (в минутах)",
"multiple_bookings": "Несколько бронирований", "multiple_bookings": "Несколько бронирований",
"nexthop": "Следующий хост", "nexthop": "Следующий хост",
"none_inherit": "Отсутствует / Наследуется", "none_inherit": "Отсутствует / Наследуется",
@@ -728,6 +751,7 @@
"sender_acl_info": "Учтите, что если пользователю почтового аккаунта A разрешено отправлять от имени пользователя B, то адрес пользователя B не отобразится автоматически в списке \"Отправитель\" при написании писем в SOGo.<br>\r\n Пользователь почтового аккаунта B должен создать делегирование в SOGo, чтобы пользователь почтового аккаунта A мог выбрать его адрес в качестве отправителя. Делегирование находится в меню (троеточие) справа от имени почтового аккаунта в окне почты SOGo. Это поведение не относится к псевдонимам.", "sender_acl_info": "Учтите, что если пользователю почтового аккаунта A разрешено отправлять от имени пользователя B, то адрес пользователя B не отобразится автоматически в списке \"Отправитель\" при написании писем в SOGo.<br>\r\n Пользователь почтового аккаунта B должен создать делегирование в SOGo, чтобы пользователь почтового аккаунта A мог выбрать его адрес в качестве отправителя. Делегирование находится в меню (троеточие) справа от имени почтового аккаунта в окне почты SOGo. Это поведение не относится к псевдонимам.",
"sieve_desc": "Краткое описание", "sieve_desc": "Краткое описание",
"sieve_type": "Тип фильтра", "sieve_type": "Тип фильтра",
"skipcrossduplicates": "Пропускать повторяющиеся сообщения в папках",
"sogo_access": "Прямая переадресация в SOGo", "sogo_access": "Прямая переадресация в SOGo",
"sogo_access_info": "После входа в систему пользователь автоматически перенаправляется в SOGo.", "sogo_access_info": "После входа в систему пользователь автоматически перенаправляется в SOGo.",
"sogo_visible": "Отображать псевдоним в SOGo", "sogo_visible": "Отображать псевдоним в SOGo",
@@ -736,8 +760,12 @@
"spam_filter": "Спам фильтр", "spam_filter": "Спам фильтр",
"spam_policy": "Добавить или удалить элементы белого/черного списка", "spam_policy": "Добавить или удалить элементы белого/черного списка",
"spam_score": "Задать индивидуальное определение спама", "spam_score": "Задать индивидуальное определение спама",
"subfolder2": "Синхронизировать в подпапку<br><small>(пусто = в корень)</small>",
"syncjob": "Изменить задание синхронизации",
"target_address": "Владельцы псевдонима, <small>(разделённые запятыми)</small>", "target_address": "Владельцы псевдонима, <small>(разделённые запятыми)</small>",
"target_domain": "Целевой домен", "target_domain": "Целевой домен",
"timeout1": "Тайм-аут для подключения к удаленному хосту",
"timeout2": "Тайм-аут для подключения к локальному хосту",
"title": "Изменение объекта", "title": "Изменение объекта",
"unchanged_if_empty": "Если без изменений - оставьте пустым", "unchanged_if_empty": "Если без изменений - оставьте пустым",
"username": "Имя пользователя", "username": "Имя пользователя",
@@ -971,6 +999,16 @@
"spam_aliases": "Временный псевдоним", "spam_aliases": "Временный псевдоним",
"stats": "Статистика", "stats": "Статистика",
"status": "Статус", "status": "Статус",
"sync_jobs": "Задания синхронизации",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Ошибка авторизации",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Неправильное имя пользователя или пароль",
"syncjob_EXIT_CONNECTION_FAILURE": "Ошибка связи с сервером",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Не удалось подключиться к удаленному серверу",
"syncjob_EXIT_OVERQUOTA": "Целевой почтовый ящик превысил квоту",
"syncjob_EXIT_TLS_FAILURE": "Ошибка установки шифрованного соединения",
"syncjob_EX_OK": "Успешно",
"syncjob_check_log": "Проверить журнал",
"syncjob_last_run_result": "Результат последнего запуска",
"table_size": "Размер таблицы", "table_size": "Размер таблицы",
"table_size_show_n": "Отображать %s полей", "table_size_show_n": "Отображать %s полей",
"target_address": "Владельцы псевдонима", "target_address": "Владельцы псевдонима",
@@ -1204,37 +1242,6 @@
"webauthn": "WebAuthn аутентификация", "webauthn": "WebAuthn аутентификация",
"yubi_otp": "Yubico OTP аутентификация" "yubi_otp": "Yubico OTP аутентификация"
}, },
"syncjobs": {
"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": "Неправильное имя пользователя или пароль",
"add": "Добавить задание синхронизации",
"add_hint": "Пароли к вашему аккаунту будут сохранены на сервере в виде простого текста!",
"edit": "Изменить задание синхронизации",
"create": "Создание нового задания синхронизации",
"dry": "Имитировать синхронизацию",
"subfolder2": "Синхронизировать в подпапку<br><small>(пусто = в корень)</small>",
"mins_interval": "Интервал (в минутах)",
"maxage": "Макс. возраст писем в днях, для загрузки с сервера<br><small>(0 = игнорировать возраст)</small>",
"exclude": "Исключить объекты (regex)",
"automap": "Автоматическое слияние папок (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"delete1": "Удаление из источника после завершения",
"delete2": "Удаление писем по месту назначения, которые не находятся на исходном",
"delete2duplicates": "Удаление дубликатов по назначению",
"skipcrossduplicates": "Пропускать повторяющиеся сообщения в папках",
"subscribeall": "Подписаться на все папки и подпапки",
"timeout1": "Тайм-аут для подключения к удаленному хосту",
"timeout2": "Тайм-аут для подключения к локальному хосту",
"maxbytespersecond": "Макс. байт в сек<br><small>(0 = без лимита)</small>",
"custom_params": "Пользовательские параметры"
},
"user": { "user": {
"action": "Действия", "action": "Действия",
"active": "Активный", "active": "Активный",
@@ -1264,6 +1271,7 @@
"clear_recent_successful_connections": "Очистить историю успешных подключений", "clear_recent_successful_connections": "Очистить историю успешных подключений",
"client_configuration": "Показать руководство по настройке почтовых клиентов и смартфонов", "client_configuration": "Показать руководство по настройке почтовых клиентов и смартфонов",
"create_app_passwd": "Создать новый пароль", "create_app_passwd": "Создать новый пароль",
"create_syncjob": "Создание нового задания синхронизации",
"created_on": "Дата создания", "created_on": "Дата создания",
"daily": "Раз в день", "daily": "Раз в день",
"day": "день", "day": "день",
@@ -1367,6 +1375,16 @@
"spamfilter_wl_desc": "Белый список адресов электронной почты позволяет <b>никогда</b> не классифицировать все полученные письма с указанных адресов как спам. Можно добавлять маски адресов. Фильтр применяется только к личным псевдонимам (с одним получателем), исключая catch-all псевдонимы и сам почтовый аккаунт.", "spamfilter_wl_desc": "Белый список адресов электронной почты позволяет <b>никогда</b> не классифицировать все полученные письма с указанных адресов как спам. Можно добавлять маски адресов. Фильтр применяется только к личным псевдонимам (с одним получателем), исключая catch-all псевдонимы и сам почтовый аккаунт.",
"spamfilter_yellow": "Жёлтый: эти письма могут быть спамом, будут доставлены в папку \"Спам\"", "spamfilter_yellow": "Жёлтый: эти письма могут быть спамом, будут доставлены в папку \"Спам\"",
"status": "Статус", "status": "Статус",
"sync_jobs": "Задания синхронизации",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Ошибка авторизации",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Неправильное имя пользователя или пароль",
"syncjob_EXIT_CONNECTION_FAILURE": "Ошибка связи с сервером",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Не удалось подключиться к удаленному серверу",
"syncjob_EXIT_OVERQUOTA": "Целевой почтовый ящик превысил квоту",
"syncjob_EXIT_TLS_FAILURE": "Ошибка установки шифрованного соединения",
"syncjob_EX_OK": "Успешно",
"syncjob_check_log": "Проверить журнал",
"syncjob_last_run_result": "Результат",
"tag_handling": "Обработка тегированной почты", "tag_handling": "Обработка тегированной почты",
"tag_help_example": "Пример тегированного адреса электронной почты: <code>ich<b>+Facebook</b>@example.org</code>", "tag_help_example": "Пример тегированного адреса электронной почты: <code>ich<b>+Facebook</b>@example.org</code>",
"tag_help_explain": "Переместить в подпапку: будет создана новая подпапка в INBOX с именем тега, например: \"INBOX/Facebook\".<br>\r\n Добавить к теме письма: имя тега будет добавлено к теме письма, например: \"[Facebook] My News\".", "tag_help_explain": "Переместить в подпапку: будет создана новая подпапка в INBOX с именем тега, например: \"INBOX/Facebook\".<br>\r\n Добавить к теме письма: имя тега будет добавлено к теме письма, например: \"[Facebook] My News\".",
+49 -31
View File
@@ -43,14 +43,21 @@
"app_name": "Ime aplikacije", "app_name": "Ime aplikacije",
"app_password": "Dodaj geslo aplikacije", "app_password": "Dodaj geslo aplikacije",
"app_passwd_protocols": "Dovoljeni protokoli za geslo aplikacije", "app_passwd_protocols": "Dovoljeni protokoli za geslo aplikacije",
"automap": "Poskusi samodejno preslikati mape (\"Poslani elementi\", \"Poslano\" => \"Poslano\" ipd.)",
"backup_mx_options": "Možnosti posredovanja (relay)", "backup_mx_options": "Možnosti posredovanja (relay)",
"comment_info": "Zasebni komentarji niso vidni uporabnikom, javni komentarji pa so prikazani kot opis, ko se z miško postavimo nad uporabnika v pregledu", "comment_info": "Zasebni komentarji niso vidni uporabnikom, javni komentarji pa so prikazani kot opis, ko se z miško postavimo nad uporabnika v pregledu",
"custom_params": "Parametri po meri",
"custom_params_hint": "Pravilno: --param=xy, napačno: --param xy",
"delete1": "Izbriši na viru, ko je končano",
"delete2": "Izbriši sporočila na cilju, ki niso na viru",
"delete2duplicates": "Izbriši dvojnike na cilju",
"description": "Opis", "description": "Opis",
"destination": "Cilj", "destination": "Cilj",
"domain": "Domena", "domain": "Domena",
"domain_matches_hostname": "Domena %s se ujema z nazivom gostitelja (hostname)", "domain_matches_hostname": "Domena %s se ujema z nazivom gostitelja (hostname)",
"domain_quota_m": "Kvota za celotno domeno (MiB)", "domain_quota_m": "Kvota za celotno domeno (MiB)",
"enc_method": "Metoda kriptiranja", "enc_method": "Metoda kriptiranja",
"exclude": "Izključi objekte (regex)",
"full_name": "Polno ime", "full_name": "Polno ime",
"gal": "Globalni seznam stikov (GAL)", "gal": "Globalni seznam stikov (GAL)",
"generate": "generiraj", "generate": "generiraj",
@@ -64,6 +71,7 @@
"mailbox_username": "Uporabniško ime (levi del e-poštnega naslova)", "mailbox_username": "Uporabniško ime (levi del e-poštnega naslova)",
"max_aliases": "Največje število dovoljenih vzdevkov", "max_aliases": "Največje število dovoljenih vzdevkov",
"max_mailboxes": "Največje dovoljeno število poštnih predalov", "max_mailboxes": "Največje dovoljeno število poštnih predalov",
"mins_interval": "Interval preverjanja (minute)",
"multiple_bookings": "Več rezervacij", "multiple_bookings": "Več rezervacij",
"nexthop": "Naslednji korak", "nexthop": "Naslednji korak",
"password_repeat": "Potrditev gesla (ponovi)", "password_repeat": "Potrditev gesla (ponovi)",
@@ -80,10 +88,14 @@
"select_domain": "Prosim najprej izberite domeno", "select_domain": "Prosim najprej izberite domeno",
"sieve_desc": "Kratek opis", "sieve_desc": "Kratek opis",
"sieve_type": "Vrsta filtra", "sieve_type": "Vrsta filtra",
"skipcrossduplicates": "Preskoči podvojena sporočila po mapah (prvi pride, prvi melje)",
"subscribeall": "Prijavi vse mape",
"syncjob": "Dodaj sinhronizacijsko opravilo",
"tags": "Oznake", "tags": "Oznake",
"target_address": "Pojdi na naslov", "target_address": "Pojdi na naslov",
"target_address_info": "<small>Polni e-poštni naslov/i (ločeni z vejico).</small>", "target_address_info": "<small>Polni e-poštni naslov/i (ločeni z vejico).</small>",
"target_domain": "Ciljna domena", "target_domain": "Ciljna domena",
"timeout1": "Časovna omejitev za povezavo do oddaljenega gostitelja",
"username": "Uporabniško ime", "username": "Uporabniško ime",
"validate": "Potrditev", "validate": "Potrditev",
"validation_success": "Uspešno potrjeno", "validation_success": "Uspešno potrjeno",
@@ -96,6 +108,9 @@
"password": "Geslo", "password": "Geslo",
"post_domain_add": "SOGo zabojnik\"sogo-mailcow\" mora biti ponovno zagnan po dodajanju nove domene!<br><br>Dodatno se mora preveriti DNS konfiguracija domene. Ko je DNS konfiguracija domene odobrena, ponovno zaženite \"acme-mailcow\" za samodejno generiranje certifikatov za novo domeno (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Ta korak je opcijski in se ponovno poskuša vsakih 24 ur.", "post_domain_add": "SOGo zabojnik\"sogo-mailcow\" mora biti ponovno zagnan po dodajanju nove domene!<br><br>Dodatno se mora preveriti DNS konfiguracija domene. Ko je DNS konfiguracija domene odobrena, ponovno zaženite \"acme-mailcow\" za samodejno generiranje certifikatov za novo domeno (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Ta korak je opcijski in se ponovno poskuša vsakih 24 ur.",
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Definirate lahko preslikave transportov za cilj po meri za to domeno. Če ni nastavljena, se ustvari MX poizvedba.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Definirate lahko preslikave transportov za cilj po meri za to domeno. Če ni nastavljena, se ustvari MX poizvedba.",
"syncjob_hint": "Pozor! Gesla se morajo shraniti v golo besedilo!",
"timeout2": "Časovna omejitev za povezavo do lokalnega gostitelja",
"dry": "Simuliraj sinhronizacijo",
"internal": "Notranje", "internal": "Notranje",
"internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.", "internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.",
"sender_allowed": "Dovoli pošiljanje kot ta vzdevek" "sender_allowed": "Dovoli pošiljanje kot ta vzdevek"
@@ -633,6 +648,7 @@
"active": "Aktivno", "active": "Aktivno",
"allow_from_smtp": "Dovoli samo tem IP naslovom uporabo <b>SMTP</b>", "allow_from_smtp": "Dovoli samo tem IP naslovom uporabo <b>SMTP</b>",
"bcc_dest_format": "Ciljna stran za polje SKP (BCC) mora biti en veljaven e-poštni naslov.<br>Če morate kopijo poslati na več naslovov, ustvarite vzdevek in ga uporabite tukaj.", "bcc_dest_format": "Ciljna stran za polje SKP (BCC) mora biti en veljaven e-poštni naslov.<br>Če morate kopijo poslati na več naslovov, ustvarite vzdevek in ga uporabite tukaj.",
"automap": "Poskusite samodejno preslikati mape (\"Poslani predmeti\", \"Poslano\" => \"Poslano\" itd.)",
"admin": "Uredi skrbnika", "admin": "Uredi skrbnika",
"domain_footer_info_vars": { "domain_footer_info_vars": {
"custom": "{= foo =} - Če ima poštni predal atribut po meri \"foo\" z vrednostjo \"bar\", vrne \"bar\"", "custom": "{= foo =} - Če ima poštni predal atribut po meri \"foo\" z vrednostjo \"bar\", vrne \"bar\"",
@@ -647,6 +663,7 @@
"domains": "Domene", "domains": "Domene",
"extended_sender_acl_info": "Uvoziti je treba ključ domene DKIM, če je na voljo.<br>\n Ne pozabite dodati tega strežnika v ustrezni zapis SPF TXT.<br>\n Kadar koli je temu strežniku dodana domena ali vzdevek domene, ki se prekriva z zunanjim naslovom, se zunanji naslov odstrani.<br>\n Uporabite @domain.tld, da omogočite pošiljanje kot *@domain.tld.", "extended_sender_acl_info": "Uvoziti je treba ključ domene DKIM, če je na voljo.<br>\n Ne pozabite dodati tega strežnika v ustrezni zapis SPF TXT.<br>\n Kadar koli je temu strežniku dodana domena ali vzdevek domene, ki se prekriva z zunanjim naslovom, se zunanji naslov odstrani.<br>\n Uporabite @domain.tld, da omogočite pošiljanje kot *@domain.tld.",
"lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (<code>.*\\.google\\.com</code> za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka)", "lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (<code>.*\\.google\\.com</code> za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka)",
"maxbytespersecond": "Največ bajtov na sekundo <br><small>(0 = neomejeno)</small>",
"pushover_sender_array": "Upoštevaj samo sledeče e-poštne naslove pošiljateljev <small>(ločeni z vejico)</small>", "pushover_sender_array": "Upoštevaj samo sledeče e-poštne naslove pošiljateljev <small>(ločeni z vejico)</small>",
"mbox_rl_info": "Ta omejitev se uporabi za prijavno ime SASL in se ujema z naslovom \"od\", ki ga uporablja prijavljeni uporabnik. Omejitev poštnega nabiralnika preglasi omejitev za celotno domeno.", "mbox_rl_info": "Ta omejitev se uporabi za prijavno ime SASL in se ujema z naslovom \"od\", ki ga uporablja prijavljeni uporabnik. Omejitev poštnega nabiralnika preglasi omejitev za celotno domeno.",
"kind": "Tip", "kind": "Tip",
@@ -654,6 +671,9 @@
"comment_info": "Zasebni komentar ni viden uporabniku, javni komentar pa se prikaže kot opis orodja, ko nanj v pregledu uporabnika zadržite miško", "comment_info": "Zasebni komentar ni viden uporabniku, javni komentar pa se prikaže kot opis orodja, ko nanj v pregledu uporabnika zadržite miško",
"created_on": "Ustvarjeno", "created_on": "Ustvarjeno",
"custom_attributes": "Atributi po meri", "custom_attributes": "Atributi po meri",
"delete1": "Izbriši iz vira, ko je končano",
"delete2": "Izbriši sporočila na cilju, ki niso na izvoru",
"delete2duplicates": "Izbriši dvojnike na cilju",
"delete_ays": "Prosim potrdite proces izbrisa.", "delete_ays": "Prosim potrdite proces izbrisa.",
"description": "Opis", "description": "Opis",
"disable_login": "Onemogoči prijavo (dohodna pošta je še vedno sprejeta)", "disable_login": "Onemogoči prijavo (dohodna pošta je še vedno sprejeta)",
@@ -680,6 +700,7 @@
"domain_footer_skip_replies": "Prezri nogo v odgovorih na e-poštna sporočila", "domain_footer_skip_replies": "Prezri nogo v odgovorih na e-poštna sporočila",
"domain_quota": "Kvota domene", "domain_quota": "Kvota domene",
"edit_alias_domain": "Uredi vzdevek domene", "edit_alias_domain": "Uredi vzdevek domene",
"exclude": "Izključi objekte (regex)",
"extended_sender_acl": "Naslovi zunanjih pošiljateljev", "extended_sender_acl": "Naslovi zunanjih pošiljateljev",
"force_pw_update": "Obvezna zamenjava gesla ob naslednji prijavi", "force_pw_update": "Obvezna zamenjava gesla ob naslednji prijavi",
"force_pw_update_info": "Ta uporabnik se bo lahko prijavil samo v %s. Gesla aplikacij ostajajo v rabi.", "force_pw_update_info": "Ta uporabnik se bo lahko prijavil samo v %s. Gesla aplikacij ostajajo v rabi.",
@@ -698,6 +719,8 @@
"max_aliases": "Največje število vzdevkov", "max_aliases": "Največje število vzdevkov",
"max_mailboxes": "Največje možno število poštnih predalov", "max_mailboxes": "Največje možno število poštnih predalov",
"max_quota": "Največja kvota na poštni predal (MiB)", "max_quota": "Največja kvota na poštni predal (MiB)",
"maxage": "Najvišja starost sporočil v dnevih, ki bodo prebrana z oddaljenega strežnika<br><small>(0 = prezri starost)</small>",
"mins_interval": "Interval (min)",
"multiple_bookings": "Več rezervacij", "multiple_bookings": "Več rezervacij",
"none_inherit": "Brez / podeduj", "none_inherit": "Brez / podeduj",
"nexthop": "Naslednji skok", "nexthop": "Naslednji skok",
@@ -733,6 +756,7 @@
"sender_acl": "Dovoli pošiljanje kot", "sender_acl": "Dovoli pošiljanje kot",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Preverjanje pošiljatelja je onemogočeno</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Preverjanje pošiljatelja je onemogočeno</span>",
"sieve_type": "Vrsta filtra", "sieve_type": "Vrsta filtra",
"skipcrossduplicates": "Preskoči podvojena sporočila med mapami (po principu \"kdor prej pride, prej melje\")",
"sogo_access": "Neposredno posredovanje na SOGo", "sogo_access": "Neposredno posredovanje na SOGo",
"sogo_access_info": "Po prijavi je uporabnik samodejno preusmerjen na SOGo.", "sogo_access_info": "Po prijavi je uporabnik samodejno preusmerjen na SOGo.",
"sogo_visible": "Vzdevek je viden v SOGo", "sogo_visible": "Vzdevek je viden v SOGo",
@@ -741,8 +765,12 @@
"spam_filter": "Filter neželene pošte", "spam_filter": "Filter neželene pošte",
"spam_policy": "Dodajanje ali odstranjevanje elementov na seznam dovoljenih/zavrnjenih", "spam_policy": "Dodajanje ali odstranjevanje elementov na seznam dovoljenih/zavrnjenih",
"spam_score": "Nastavite oceno neželene pošte po meri", "spam_score": "Nastavite oceno neželene pošte po meri",
"subfolder2": "Sinhroniziraj v podmapo na cilju<br><small>(prazno = ne uporabi podmape)</small>",
"syncjob": "Urejanje sinhronizacijskega opravila",
"target_address": "Pojdi na naslov/e <small>(ločeno z vejico)</small>", "target_address": "Pojdi na naslov/e <small>(ločeno z vejico)</small>",
"target_domain": "Ciljna domena", "target_domain": "Ciljna domena",
"timeout1": "Časovna omejitev za povezavo z oddaljenim gostiteljem",
"timeout2": "Časovna omejitev za povezavo z lokalnim gostiteljem",
"title": "Urejanje predmeta", "title": "Urejanje predmeta",
"unchanged_if_empty": "Če ni spremenjeno, pustite prazno", "unchanged_if_empty": "Če ni spremenjeno, pustite prazno",
"username": "Uporabniško ime", "username": "Uporabniško ime",
@@ -937,6 +965,8 @@
"last_run": "Zadnji zagon", "last_run": "Zadnji zagon",
"sieve_preset_2": "Vedno označi e-pošto določenega pošiljatelja kot videno", "sieve_preset_2": "Vedno označi e-pošto določenega pošiljatelja kot videno",
"target_address": "Pojdi na naslov", "target_address": "Pojdi na naslov",
"syncjob_EXIT_TLS_FAILURE": "Težava s šifrirano povezavo",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Ni mogoče vzpostaviti povezave z oddaljenim strežnikom",
"tls_map_parameters_info": "Prazno ali parametri, na primer: protocols=!SSLv2 ciphers=medium exclude=3DES", "tls_map_parameters_info": "Prazno ali parametri, na primer: protocols=!SSLv2 ciphers=medium exclude=3DES",
"tls_policy_maps_enforced_tls": "Ti pravilniki bodo preglasili tudi vedenje uporabnikov poštnih predalov, ki vsiljujejo odhodne povezave TLS. Če spodaj ni pravilnika, bodo ti uporabniki uporabili privzete vrednosti, določene kot <code>smtp_tls_mandatory_protocols</code> in <code>smtp_tls_mandatory_ciphers</code>.", "tls_policy_maps_enforced_tls": "Ti pravilniki bodo preglasili tudi vedenje uporabnikov poštnih predalov, ki vsiljujejo odhodne povezave TLS. Če spodaj ni pravilnika, bodo ti uporabniki uporabili privzete vrednosti, določene kot <code>smtp_tls_mandatory_protocols</code> in <code>smtp_tls_mandatory_ciphers</code>.",
"spam_aliases": "Začasni vzdevek", "spam_aliases": "Začasni vzdevek",
@@ -952,6 +982,13 @@
"sogo_visible_y": "Prikaži vzdevek v SOGo", "sogo_visible_y": "Prikaži vzdevek v SOGo",
"stats": "Statistika", "stats": "Statistika",
"status": "Stanje", "status": "Stanje",
"sync_jobs": "Sinhronizacija opravil",
"syncjob_check_log": "Preveri dnevnik",
"syncjob_last_run_result": "Rezultat zadnjega zagona",
"syncjob_EX_OK": "Uspešno",
"syncjob_EXIT_CONNECTION_FAILURE": "Težava s povezavo",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Težava z overjanjem",
"syncjob_EXIT_OVERQUOTA": "Ciljni poštni predal presega kvoto",
"table_size_show_n": "Prikaži %s elementov", "table_size_show_n": "Prikaži %s elementov",
"target_domain": "Ciljna domena", "target_domain": "Ciljna domena",
"template": "Predloga", "template": "Predloga",
@@ -962,6 +999,7 @@
"tls_map_parameters": "Parametri", "tls_map_parameters": "Parametri",
"tls_map_policy": "Politika", "tls_map_policy": "Politika",
"tls_policy_maps": "Zemljevidi pravilnikov TLS", "tls_policy_maps": "Zemljevidi pravilnikov TLS",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Napačno uporabniško ime ali geslo",
"templates": "Predloge", "templates": "Predloge",
"tls_policy_maps_long": "Preglasitve zemljevidov pravilnikov odhodnega TLS", "tls_policy_maps_long": "Preglasitve zemljevidov pravilnikov odhodnega TLS",
"table_size": "Velikost tabele", "table_size": "Velikost tabele",
@@ -1221,38 +1259,8 @@
"hour": "sporočil / uro", "hour": "sporočil / uro",
"day": "sporočil / dan" "day": "sporočil / dan"
}, },
"syncjobs": {
"sync_jobs": "Sinhronizacija opravil",
"syncjob_check_log": "Preveri dnevnik",
"syncjob_last_run_result": "Rezultat zadnjega zagona",
"syncjob_EX_OK": "Uspeh",
"syncjob_EXIT_CONNECTION_FAILURE": "Težava s povezavo",
"syncjob_EXIT_TLS_FAILURE": "Težava s šifrirano povezavo",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Težava z overjanjem",
"syncjob_EXIT_OVERQUOTA": "Ciljni poštni predal presega kvoto",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Ni mogoče vzpostaviti povezave z oddaljenim strežnikom",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Napačno uporabniško ime ali geslo",
"add": "Dodaj sinhronizacijsko opravilo",
"add_hint": "Pozor! Gesla se morajo shraniti v golo besedilo!",
"edit": "Urejanje sinhronizacijskega opravila",
"create": "Ustvari novo sinhronizacijsko opravilo",
"dry": "Simuliraj sinhronizacijo",
"subfolder2": "Sinhroniziraj v podmapo na cilju<br><small>(prazno = ne uporabi podmape)</small>",
"mins_interval": "Interval preverjanja (minute)",
"maxage": "Najvišja starost sporočil v dnevih, ki bodo prebrana z oddaljenega strežnika<br><small>(0 = prezri starost)</small>",
"exclude": "Izključi objekte (regex)",
"automap": "Poskusi samodejno preslikati mape (\"Poslani elementi\", \"Poslano\" => \"Poslano\" ipd.)",
"delete1": "Izbriši na viru, ko je končano",
"delete2": "Izbriši sporočila na cilju, ki niso na viru",
"delete2duplicates": "Izbriši dvojnike na cilju",
"skipcrossduplicates": "Preskoči podvojena sporočila po mapah (prvi pride, prvi melje)",
"subscribeall": "Prijavi vse mape",
"timeout1": "Časovna omejitev za povezavo do oddaljenega gostitelja",
"timeout2": "Časovna omejitev za povezavo do lokalnega gostitelja",
"maxbytespersecond": "Največ bajtov na sekundo <br><small>(0 = neomejeno)</small>",
"custom_params": "Parametri po meri"
},
"user": { "user": {
"create_syncjob": "Ustvari novo sinhronizacijsko opravilo",
"open_webmail_sso": "Spletna pošta", "open_webmail_sso": "Spletna pošta",
"attribute": "Atribut", "attribute": "Atribut",
"description": "Opis", "description": "Opis",
@@ -1390,6 +1398,15 @@
"spamfilter_wl": "Seznam dovoljenih", "spamfilter_wl": "Seznam dovoljenih",
"spamfilter_yellow": "Rumena: to sporočilo je morda neželena pošta, označeno bo kot neželena pošta in premaknjeno v mapo z neželeno pošto", "spamfilter_yellow": "Rumena: to sporočilo je morda neželena pošta, označeno bo kot neželena pošta in premaknjeno v mapo z neželeno pošto",
"status": "Stanje", "status": "Stanje",
"sync_jobs": "Sinhronizacija opravil",
"syncjob_check_log": "Preveri dnevnik",
"syncjob_last_run_result": "Rezultat zadnjega zagona",
"syncjob_EXIT_CONNECTION_FAILURE": "Težava s povezavo",
"syncjob_EXIT_TLS_FAILURE": "Težava s šifrirano povezavo",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Težava z overjanjem",
"syncjob_EXIT_OVERQUOTA": "Ciljni poštni predal presega kvoto",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Ni mogoče vzpostaviti povezave z oddaljenim strežnikom",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Napačno uporabniško ime ali geslo",
"tag_handling": "Nastavitev obravnave označene pošte", "tag_handling": "Nastavitev obravnave označene pošte",
"tag_help_example": "Primer označenega e-poštnega naslova: jaz<b>+Facebook</b>@example.org", "tag_help_example": "Primer označenega e-poštnega naslova: jaz<b>+Facebook</b>@example.org",
"tag_in_none": "Ne naredi ničesar", "tag_in_none": "Ne naredi ničesar",
@@ -1411,6 +1428,7 @@
"years": "leta", "years": "leta",
"waiting": "Čakanje", "waiting": "Čakanje",
"q_all": "Vse kategorije", "q_all": "Vse kategorije",
"syncjob_EX_OK": "Uspeh",
"expire_never": "Nikoli ne poteče", "expire_never": "Nikoli ne poteče",
"forever": "Za vedno", "forever": "Za vedno",
"spam_aliases_info": "Vzdevek za neželeno pošto je začasni e-poštni naslov, ki ga je mogoče uporabiti za zaščito pravih e-poštnih naslovov. <br>Po želji je mogoče nastaviti čas poteka veljavnosti, tako da se vzdevek po določenem obdobju samodejno deaktivira, s čimer se učinkovito znebite zlorabljenih ali razkritih naslovov.", "spam_aliases_info": "Vzdevek za neželeno pošto je začasni e-poštni naslov, ki ga je mogoče uporabiti za zaščito pravih e-poštnih naslovov. <br>Po želji je mogoče nastaviti čas poteka veljavnosti, tako da se vzdevek po določenem obdobju samodejno deaktivira, s čimer se učinkovito znebite zlorabljenih ali razkritih naslovov.",
+50 -32
View File
@@ -43,9 +43,15 @@
"app_name": "Meno aplikácie", "app_name": "Meno aplikácie",
"app_passwd_protocols": "Povolené protokoly k heslu aplikácie", "app_passwd_protocols": "Povolené protokoly k heslu aplikácie",
"app_password": "Pridať heslo aplikácie", "app_password": "Pridať heslo aplikácie",
"automap": "Skúsiť automaticky mapovať priečinky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
"backup_mx_options": "Možnosti preposielania", "backup_mx_options": "Možnosti preposielania",
"bcc_dest_format": "Cieľ kópie musí byť jedna platná emailová adresa. Pokiaľ potrebujete posielať kópie na viac adries, vytvorte Alias a použite ho tu.", "bcc_dest_format": "Cieľ kópie musí byť jedna platná emailová adresa. Pokiaľ potrebujete posielať kópie na viac adries, vytvorte Alias a použite ho tu.",
"comment_info": "Súkromný komentár nie je viditeľný používateľovi, na rozdiel od verejného komentára, ktorý je prezentovaný ako popis v prehľade používateľov", "comment_info": "Súkromný komentár nie je viditeľný používateľovi, na rozdiel od verejného komentára, ktorý je prezentovaný ako popis v prehľade používateľov",
"custom_params": "Voliteľné parametre",
"custom_params_hint": "Správne: --param=xy, nesprávne: --param xy",
"delete1": "Vymazať zo zdrojovej schránky, po dokončení prenosu",
"delete2": "Vymazať správy v cieľovej schránke, ak nie sú v zdrojovej",
"delete2duplicates": "Vymazať duplikáty v cieľovej schránke",
"description": "Popis", "description": "Popis",
"destination": "Cieľ", "destination": "Cieľ",
"disable_login": "Zablokovať prihlásenie (nevzťahuje sa na prichádzajúcu poštu)", "disable_login": "Zablokovať prihlásenie (nevzťahuje sa na prichádzajúcu poštu)",
@@ -53,6 +59,7 @@
"domain_matches_hostname": "Doména %s sa zhoduje s hostiteľom", "domain_matches_hostname": "Doména %s sa zhoduje s hostiteľom",
"domain_quota_m": "Celkové kvóta domény (MiB)", "domain_quota_m": "Celkové kvóta domény (MiB)",
"enc_method": "Metóda zašifrovania", "enc_method": "Metóda zašifrovania",
"exclude": "Vylúčiť objekty (regex)",
"full_name": "Celé meno", "full_name": "Celé meno",
"gal": "Globálny zoznam adries", "gal": "Globálny zoznam adries",
"gal_info": "Globálny zoznam adries obsahuje všetky objekty v doméne a nemôže byť upravená žiadnym používateľom. Informácie o Free/Busy stave budú chýbať ak bude vypnutý! <b>Reštartujte SOGo na aplikáciu zmien.</b>", "gal_info": "Globálny zoznam adries obsahuje všetky objekty v doméne a nemôže byť upravená žiadnym používateľom. Informácie o Free/Busy stave budú chýbať ak bude vypnutý! <b>Reštartujte SOGo na aplikáciu zmien.</b>",
@@ -68,6 +75,7 @@
"mailbox_username": "Prihlasovacie meno (ľavá časť emailovej adresy)", "mailbox_username": "Prihlasovacie meno (ľavá časť emailovej adresy)",
"max_aliases": "Max. počet aliasov", "max_aliases": "Max. počet aliasov",
"max_mailboxes": "Max. počet mailových schránok", "max_mailboxes": "Max. počet mailových schránok",
"mins_interval": "Interval (min)",
"multiple_bookings": "Viaceré rezervácie", "multiple_bookings": "Viaceré rezervácie",
"nexthop": "Ďalší skok", "nexthop": "Ďalší skok",
"password": "Heslo", "password": "Heslo",
@@ -87,13 +95,20 @@
"select_domain": "Najskôr vyberte doménu prosím", "select_domain": "Najskôr vyberte doménu prosím",
"sieve_desc": "Krátky popis", "sieve_desc": "Krátky popis",
"sieve_type": "Typ filtru", "sieve_type": "Typ filtru",
"skipcrossduplicates": "Preskočiť duplikované správy naprieč priečinkami (akceptuje sa prvý nález)",
"subscribeall": "Synchronizovať všetky priečinky",
"syncjob": "Pridať novú synchronizačnú úlohu",
"syncjob_hint": "Upozornenie, heslá budú uložené v obyčajnom texte!",
"target_address": "Cieľové adresy", "target_address": "Cieľové adresy",
"target_address_info": "<small>Celá emailová adresa/y (oddelené čiarkou).</small>", "target_address_info": "<small>Celá emailová adresa/y (oddelené čiarkou).</small>",
"target_domain": "Cieľová doména", "target_domain": "Cieľová doména",
"timeout1": "Časový limit pre spojenie so vzdialeným serverom",
"timeout2": "Časový limit pre spojenie s lokálnym serverom",
"username": "Používateľské meno", "username": "Používateľské meno",
"validate": "Overiť", "validate": "Overiť",
"validation_success": "Úspešne overené", "validation_success": "Úspešne overené",
"tags": "Štítky" "tags": "Štítky",
"dry": "Simulovať synchronizáciu"
}, },
"admin": { "admin": {
"access": "Prístup", "access": "Prístup",
@@ -523,12 +538,16 @@
"app_name": "Meno aplikácie", "app_name": "Meno aplikácie",
"app_passwd": "Heslo aplikácie", "app_passwd": "Heslo aplikácie",
"app_passwd_protocols": "Povolené protokoly", "app_passwd_protocols": "Povolené protokoly",
"automap": "Skúsiť automapovať priečinky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
"backup_mx_options": "Možnosti preposielania", "backup_mx_options": "Možnosti preposielania",
"bcc_dest_format": "Cieľ kópie musí byť jedna platná emailová adresa. Pokiaľ potrebujete posielať kópie na viac adries, vytvorte Alias a použite ho tu.", "bcc_dest_format": "Cieľ kópie musí byť jedna platná emailová adresa. Pokiaľ potrebujete posielať kópie na viac adries, vytvorte Alias a použite ho tu.",
"client_id": "ID klienta", "client_id": "ID klienta",
"client_secret": "Klientský tajný kľúč", "client_secret": "Klientský tajný kľúč",
"comment_info": " Súkromný komentár nie je viditeľný používateľovi, na rozdiel od verejného komentára, ktorý je prezentovaný ako popis v prehľade používateľov", "comment_info": " Súkromný komentár nie je viditeľný používateľovi, na rozdiel od verejného komentára, ktorý je prezentovaný ako popis v prehľade používateľov",
"created_on": "Vytvorené", "created_on": "Vytvorené",
"delete1": "Vymazať zo zdrojovej schránky, po dokončení prenosu",
"delete2": "Vymazať správy v cieľovej schránke, ak nie sú v zdrojovej",
"delete2duplicates": "Vymazať duplikáty v cieľovej schránke",
"delete_ays": "Potvrďte zmazanie.", "delete_ays": "Potvrďte zmazanie.",
"description": "Popis", "description": "Popis",
"disable_login": "Zablokovať prihlásenie (nevzťahuje sa na prichádzajúcu poštu)", "disable_login": "Zablokovať prihlásenie (nevzťahuje sa na prichádzajúcu poštu)",
@@ -539,6 +558,7 @@
"dont_check_sender_acl": "Vypnúť kontrolu odosielateľa pre doménu %s (+ alias domény)", "dont_check_sender_acl": "Vypnúť kontrolu odosielateľa pre doménu %s (+ alias domény)",
"edit_alias_domain": "Upraviť alias domény", "edit_alias_domain": "Upraviť alias domény",
"encryption": "Zašifrovanie", "encryption": "Zašifrovanie",
"exclude": "Vyradiť objekty (regex)",
"extended_sender_acl": "Externé adresy odosielateľa", "extended_sender_acl": "Externé adresy odosielateľa",
"extended_sender_acl_info": "DKIM doménový kľúč by mal byť importovaný, ak je dostupný.<br>\r\n Nezabudnite pridať tento server prislúchajúcemu SPF TXT záznamu.<br>\r\n Kedykoľvek je doména alebo alias doména pridaná na server, ktorá sa zhoduje s externou adresou, táto externá adresa je odstránená.<br>\r\n Použite @domain.tld pre schopnosť poslať ako *@domain.tld.", "extended_sender_acl_info": "DKIM doménový kľúč by mal byť importovaný, ak je dostupný.<br>\r\n Nezabudnite pridať tento server prislúchajúcemu SPF TXT záznamu.<br>\r\n Kedykoľvek je doména alebo alias doména pridaná na server, ktorá sa zhoduje s externou adresou, táto externá adresa je odstránená.<br>\r\n Použite @domain.tld pre schopnosť poslať ako *@domain.tld.",
"force_pw_update": "Vynútiť zmenu hesla pri ďalšom prihlásení", "force_pw_update": "Vynútiť zmenu hesla pri ďalšom prihlásení",
@@ -559,7 +579,10 @@
"max_aliases": "Max. počet aliasov", "max_aliases": "Max. počet aliasov",
"max_mailboxes": "Max. počet schránok", "max_mailboxes": "Max. počet schránok",
"max_quota": "Max. kvóta pre mailovú schránku (MiB)", "max_quota": "Max. kvóta pre mailovú schránku (MiB)",
"maxage": "Maximálne obdobie správ v dňoch, ktoré sa budú vzdialene dotazovať<br><small>(0 = ignorovať obdobie age)</small>",
"maxbytespersecond": "Max. bajtov za sekundu <br><small>(0 = bez limitu)</small>",
"mbox_rl_info": "Tento limit je aplikovaný na báze prihlasovacieho mena SASL, je zhodný s \"from\" adresou používanou prihláseným používateľom. Prenosový limit u mailovej adresy má prednosť pred domain-wide prenosovým limitom.", "mbox_rl_info": "Tento limit je aplikovaný na báze prihlasovacieho mena SASL, je zhodný s \"from\" adresou používanou prihláseným používateľom. Prenosový limit u mailovej adresy má prednosť pred domain-wide prenosovým limitom.",
"mins_interval": "Interval (min)",
"multiple_bookings": "Viaceré rezervácie", "multiple_bookings": "Viaceré rezervácie",
"nexthop": "Ďalší skok", "nexthop": "Ďalší skok",
"none_inherit": "Žiadne / Zdediť", "none_inherit": "Žiadne / Zdediť",
@@ -598,6 +621,7 @@
"sender_acl_info": "Ak mailový používateľ A má povolenie poslať ako mailový používateľ B, adresa odosielateľa nieje automaticky viditeľná ako voliteľné \"from\" pole v SOGo.<br>\r\n Mailový používateľ B potrebuje vytvoriť delegáciu v SOGo, aby bol schopný mailový používateľ A vybrať jeho adresu ako odosielateľ. Na delegovanie mailovej adresy v SOGo, použite menu (tri bodky) napravo vášho mailového mena v hornom ľavom rohu, v prehľade správ. Toto neplatí pre alias adresy.", "sender_acl_info": "Ak mailový používateľ A má povolenie poslať ako mailový používateľ B, adresa odosielateľa nieje automaticky viditeľná ako voliteľné \"from\" pole v SOGo.<br>\r\n Mailový používateľ B potrebuje vytvoriť delegáciu v SOGo, aby bol schopný mailový používateľ A vybrať jeho adresu ako odosielateľ. Na delegovanie mailovej adresy v SOGo, použite menu (tri bodky) napravo vášho mailového mena v hornom ľavom rohu, v prehľade správ. Toto neplatí pre alias adresy.",
"sieve_desc": "Krátky popis", "sieve_desc": "Krátky popis",
"sieve_type": "Typ filtru", "sieve_type": "Typ filtru",
"skipcrossduplicates": "Preskočiť duplikované správy naprieč priečinkami (akceptuje sa prvý nález)",
"sogo_access": "Priame presmerovanie na SOGo", "sogo_access": "Priame presmerovanie na SOGo",
"sogo_access_info": "Po prihlásení je používateľ automaticky presmerovaný na službu SOGo.", "sogo_access_info": "Po prihlásení je používateľ automaticky presmerovaný na službu SOGo.",
"sogo_visible": "Alias je viditeľný v SOGo", "sogo_visible": "Alias je viditeľný v SOGo",
@@ -606,8 +630,12 @@
"spam_filter": "Spam filter", "spam_filter": "Spam filter",
"spam_policy": "Pridať alebo odstrániť položky do bielej/čiernej listiny", "spam_policy": "Pridať alebo odstrániť položky do bielej/čiernej listiny",
"spam_score": "Stanoviť vlastnú spam hodnotu", "spam_score": "Stanoviť vlastnú spam hodnotu",
"subfolder2": "Synchronizovať do podadresára na cieli<br><small>(prázdne = nevytvárať podpriečinok)</small>",
"syncjob": "Upraviť synchronizačnú úlohu",
"target_address": "Cieľová adresa/y <small>(oddelené čiarkou)</small>", "target_address": "Cieľová adresa/y <small>(oddelené čiarkou)</small>",
"target_domain": "Cieľová doména", "target_domain": "Cieľová doména",
"timeout1": "Timeout pre spojenie s vzdialeným serverom",
"timeout2": "Timeout pre spojenie s lokálnym serverom",
"title": "Upraviť objekt", "title": "Upraviť objekt",
"unchanged_if_empty": "Ak nemeníte, nechajte prázdne", "unchanged_if_empty": "Ak nemeníte, nechajte prázdne",
"username": "Používateľské meno", "username": "Používateľské meno",
@@ -821,6 +849,16 @@
"spam_aliases": "Dočasný alias", "spam_aliases": "Dočasný alias",
"stats": "Štatistika", "stats": "Štatistika",
"status": "Status", "status": "Status",
"sync_jobs": "Synchronizačné úlohy",
"syncjob_check_log": "Skontrolujte záznam",
"syncjob_last_run_result": "Výsledok posledného spustenia",
"syncjob_EX_OK": "Úspech",
"syncjob_EXIT_CONNECTION_FAILURE": "Problém so spojením",
"syncjob_EXIT_TLS_FAILURE": "Problém so šifrovaným spojením",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikáciou",
"syncjob_EXIT_OVERQUOTA": "Cieľová schránka je plná",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nedá sa pripojiť k vzdialenému serveru",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživateľské meno alebo heslo",
"table_size": "Veľkosť tabuľky", "table_size": "Veľkosť tabuľky",
"table_size_show_n": "Zobraziť %s položiek", "table_size_show_n": "Zobraziť %s položiek",
"target_address": "Cieľová adresa", "target_address": "Cieľová adresa",
@@ -1041,37 +1079,6 @@
"u2f_deprecated_important": "Zaregistrujte si svoj Kľúč v paneli správcu pomocou novej metódy WebAuthn.", "u2f_deprecated_important": "Zaregistrujte si svoj Kľúč v paneli správcu pomocou novej metódy WebAuthn.",
"u2f_deprecated": "Zdá sa, že váš kľúč bol zaregistrovaný pomocou zastaranej metódy U2F. Deaktivujeme vám dvojfaktorovú autentifikáciu a odstránime váš Kľúč." "u2f_deprecated": "Zdá sa, že váš kľúč bol zaregistrovaný pomocou zastaranej metódy U2F. Deaktivujeme vám dvojfaktorovú autentifikáciu a odstránime váš Kľúč."
}, },
"syncjobs": {
"sync_jobs": "Synchronizačné úlohy",
"syncjob_check_log": "Skontrolujte záznam",
"syncjob_last_run_result": "Výsledok posledného spustenia",
"syncjob_EX_OK": "Úspech",
"syncjob_EXIT_CONNECTION_FAILURE": "Problém so spojením",
"syncjob_EXIT_TLS_FAILURE": "Problém so šifrovaným spojením",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikáciou",
"syncjob_EXIT_OVERQUOTA": "Cieľová schránka je plná",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nedá sa pripojiť k vzdialenému serveru",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživateľské meno alebo heslo",
"add": "Pridať novú synchronizačnú úlohu",
"add_hint": "Upozornenie, heslá budú uložené v obyčajnom texte!",
"edit": "Upraviť synchronizačnú úlohu",
"create": "Vytvoriť novú synchronizačnú úlohu",
"dry": "Simulovať synchronizáciu",
"subfolder2": "Synchronizovať do podadresára na cieli<br><small>(prázdne = nevytvárať podpriečinok)</small>",
"mins_interval": "Interval (min)",
"maxage": "Maximálne obdobie správ v dňoch, ktoré sa budú vzdialene dotazovať<br><small>(0 = ignorovať obdobie age)</small>",
"exclude": "Vylúčiť objekty (regex)",
"automap": "Skúsiť automaticky mapovať priečinky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
"delete1": "Vymazať zo zdrojovej schránky, po dokončení prenosu",
"delete2": "Vymazať správy v cieľovej schránke, ak nie sú v zdrojovej",
"delete2duplicates": "Vymazať duplikáty v cieľovej schránke",
"skipcrossduplicates": "Preskočiť duplikované správy naprieč priečinkami (akceptuje sa prvý nález)",
"subscribeall": "Synchronizovať všetky priečinky",
"timeout1": "Časový limit pre spojenie so vzdialeným serverom",
"timeout2": "Časový limit pre spojenie s lokálnym serverom",
"maxbytespersecond": "Max. bajtov za sekundu <br><small>(0 = bez limitu)</small>",
"custom_params": "Voliteľné parametre"
},
"user": { "user": {
"action": "Akcia", "action": "Akcia",
"active": "Aktívny", "active": "Aktívny",
@@ -1100,6 +1107,7 @@
"clear_recent_successful_connections": "Vymazať nedávne úspešné prihlásenia", "clear_recent_successful_connections": "Vymazať nedávne úspešné prihlásenia",
"client_configuration": "Zobraziť konfiguračné pokyny pre emailových klientov a smartfóny", "client_configuration": "Zobraziť konfiguračné pokyny pre emailových klientov a smartfóny",
"create_app_passwd": "Vytvoriť heslo aplikácie", "create_app_passwd": "Vytvoriť heslo aplikácie",
"create_syncjob": "Vytvoriť novú synchronizačnú úlohu",
"created_on": "Vytvorené", "created_on": "Vytvorené",
"daily": "Denne", "daily": "Denne",
"day": "deň", "day": "deň",
@@ -1200,6 +1208,16 @@
"spamfilter_wl_desc": "Adresy na tomto zozname nebudú <b>nikdy klasifikované ako spam</b>. Môže byť použitý wildcard záznam (*). Filter je aplikovaný len na priame aliasy (aliasy s jednou cieľovou schránkou) s výnimkou doménových košov a samotnou schránkou.", "spamfilter_wl_desc": "Adresy na tomto zozname nebudú <b>nikdy klasifikované ako spam</b>. Môže byť použitý wildcard záznam (*). Filter je aplikovaný len na priame aliasy (aliasy s jednou cieľovou schránkou) s výnimkou doménových košov a samotnou schránkou.",
"spamfilter_yellow": "Žltá: Táto správa môže byť spam, bude označená ako spam a presunutá do priečinku nevyžiadanej pošty", "spamfilter_yellow": "Žltá: Táto správa môže byť spam, bude označená ako spam a presunutá do priečinku nevyžiadanej pošty",
"status": "Status", "status": "Status",
"sync_jobs": "Synchronizačné úlohy",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikáciou",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživateľské meno alebo heslo",
"syncjob_EXIT_CONNECTION_FAILURE": "Problém so spojením",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nedá sa pripojiť k vzdialenému serveru",
"syncjob_EXIT_OVERQUOTA": "Cieľová schránka je plná",
"syncjob_EXIT_TLS_FAILURE": "Problém so šifrovaným spojením",
"syncjob_EX_OK": "Úspech",
"syncjob_check_log": "Skontrolujte záznam",
"syncjob_last_run_result": "Výsledok posledného spustenia",
"tag_handling": "Zaobchádzanie s označenou poštou", "tag_handling": "Zaobchádzanie s označenou poštou",
"tag_help_example": "Príklad tagu e-mailovej adresy: me<b>+Facebook</b>@example.org", "tag_help_example": "Príklad tagu e-mailovej adresy: me<b>+Facebook</b>@example.org",
"tag_help_explain": "V podadresári: nový podadresár s menom tag-u bude vytvorený nižšie INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: meno štítka bude pridané pred predmet pošty, napríklad: \"[Facebook] My News\".", "tag_help_explain": "V podadresári: nový podadresár s menom tag-u bude vytvorený nižšie INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: meno štítka bude pridané pred predmet pošty, napríklad: \"[Facebook] My News\".",
+30 -21
View File
@@ -42,8 +42,14 @@
"alias_domain_info": "<small>Endast giltiga domännamn (kommaavgränsade).</small>", "alias_domain_info": "<small>Endast giltiga domännamn (kommaavgränsade).</small>",
"app_name": "Applikationsnamn", "app_name": "Applikationsnamn",
"app_password": "Lägg till applikationslösenord", "app_password": "Lägg till applikationslösenord",
"automap": "Försök att automatiskt mappa mappar (\"Skickade objekt\", \"Skickat\" => \"Skickat\" etc.)",
"backup_mx_options": "Reläalternativ", "backup_mx_options": "Reläalternativ",
"comment_info": "En privat kommentar är inte synlig för användaren, medan en offentlig kommentar visas som verktygstips när du hovrar den i en användares översikt", "comment_info": "En privat kommentar är inte synlig för användaren, medan en offentlig kommentar visas som verktygstips när du hovrar den i en användares översikt",
"custom_params": "Egna parametrar",
"custom_params_hint": "Korrekt: --param=xy, Felaktigt: --param xy",
"delete1": "Ta bort från källan när du är klar",
"delete2": "Ta bort meddelanden på målet som inte finns på källan",
"delete2duplicates": "Ta bort dubbletter på destinationsservern",
"description": "Beskrivning", "description": "Beskrivning",
"destination": "Destination", "destination": "Destination",
"disable_login": "Inaktivera inloggning (inkommande post kommer fortfarande tas emot)", "disable_login": "Inaktivera inloggning (inkommande post kommer fortfarande tas emot)",
@@ -51,6 +57,7 @@
"domain_matches_hostname": "Domänen %s kan inte matcha värdnamnet", "domain_matches_hostname": "Domänen %s kan inte matcha värdnamnet",
"domain_quota_m": "Total kvot per postlåda (MiB)", "domain_quota_m": "Total kvot per postlåda (MiB)",
"enc_method": "Krypteringsmetod", "enc_method": "Krypteringsmetod",
"exclude": "Exkludera objekt (regex-filter)",
"full_name": "Fullständiga namn", "full_name": "Fullständiga namn",
"gal": "Global adressbok", "gal": "Global adressbok",
"gal_info": "Den global adressboken innehåller alla objekt i en domän och kan inte redigeras av någon användare. Informationen om tillgänglighet i SOGo är endast synlig när den globala adressboken är påslagen. <b>Starta om SOGo för att tillämpa ändringar.</b>", "gal_info": "Den global adressboken innehåller alla objekt i en domän och kan inte redigeras av någon användare. Informationen om tillgänglighet i SOGo är endast synlig när den globala adressboken är påslagen. <b>Starta om SOGo för att tillämpa ändringar.</b>",
@@ -66,6 +73,7 @@
"mailbox_username": "Användarnamn (första delen av en e-postadress)", "mailbox_username": "Användarnamn (första delen av en e-postadress)",
"max_aliases": "Max antal alias", "max_aliases": "Max antal alias",
"max_mailboxes": "Max antal postlådor", "max_mailboxes": "Max antal postlådor",
"mins_interval": "Intervall (minuter)",
"multiple_bookings": "Bokningsmöjligheter", "multiple_bookings": "Bokningsmöjligheter",
"nexthop": "Reläserver", "nexthop": "Reläserver",
"password": "Lösenord", "password": "Lösenord",
@@ -85,9 +93,15 @@
"select_domain": "Välj först en domän", "select_domain": "Välj först en domän",
"sieve_desc": "Kort beskrivning", "sieve_desc": "Kort beskrivning",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
"skipcrossduplicates": "Hoppa över duplicerade meddelanden i katalogerna (först till kvarn)",
"subscribeall": "Prenumerera på alla kataloger",
"syncjob": "Lägg till synkroniseringsjobb",
"syncjob_hint": "Observera att lösenord lagras i klartext!",
"target_address": "Destinationsadresser", "target_address": "Destinationsadresser",
"target_address_info": "<small>Fullständig e-postadress/adresser (separerade med komma).</small>", "target_address_info": "<small>Fullständig e-postadress/adresser (separerade med komma).</small>",
"target_domain": "Måldomän", "target_domain": "Måldomän",
"timeout1": "Timeout för anslutning till fjärrservern",
"timeout2": "Timeout för anslutning till lokalserver",
"username": "Användarnamn", "username": "Användarnamn",
"validate": "Validera", "validate": "Validera",
"validation_success": "Korrekt validerad", "validation_success": "Korrekt validerad",
@@ -473,12 +487,16 @@
"allowed_protocols": "Tillåtna protokoll", "allowed_protocols": "Tillåtna protokoll",
"app_name": "Applikationsnamn", "app_name": "Applikationsnamn",
"app_passwd": "Applikationslösenord", "app_passwd": "Applikationslösenord",
"automap": "Koppla automatiskt de vanliga e-post katalogerna (\"Skickade meddelanden\", \"Skickat\" => \"Skickat\" mm)",
"backup_mx_options": "Inställningar för vidarebefordran", "backup_mx_options": "Inställningar för vidarebefordran",
"bcc_dest_format": "Destinationen för att skicka hemlig kopia måste vara en giltig e-postadress.", "bcc_dest_format": "Destinationen för att skicka hemlig kopia måste vara en giltig e-postadress.",
"client_id": "Klient-ID", "client_id": "Klient-ID",
"client_secret": "Klienthemlighet", "client_secret": "Klienthemlighet",
"comment_info": "En privat kommentar är inte synlig för användaren, medan en offentlig kommentar visas i användaröversikten", "comment_info": "En privat kommentar är inte synlig för användaren, medan en offentlig kommentar visas i användaröversikten",
"created_on": "Skapad vid", "created_on": "Skapad vid",
"delete1": "Ta bort meddelande från källservern när överföringen är slutförd.",
"delete2": "Ta bort meddelanden från destinationsservern som inte finns på källservern",
"delete2duplicates": "Ta bort dubbletter på destinationsservern",
"delete_ays": "Är du säker att du vill ta bort det här objektet?", "delete_ays": "Är du säker att du vill ta bort det här objektet?",
"description": "Beskrivning", "description": "Beskrivning",
"disable_login": "Inaktivera inloggning (inkommande post kommer fortfarande tas emot)", "disable_login": "Inaktivera inloggning (inkommande post kommer fortfarande tas emot)",
@@ -489,6 +507,7 @@
"dont_check_sender_acl": "Inaktivera avsändarkontrollen för domänen %s (+alias-domäner)", "dont_check_sender_acl": "Inaktivera avsändarkontrollen för domänen %s (+alias-domäner)",
"edit_alias_domain": "Ändra alias-domän", "edit_alias_domain": "Ändra alias-domän",
"encryption": "Kryptering", "encryption": "Kryptering",
"exclude": "Exkludera objekt (regex-filter)",
"extended_sender_acl": "Externa avsändaradresser", "extended_sender_acl": "Externa avsändaradresser",
"extended_sender_acl_info": "En DKIM-domännyckel bör importeras, om det är möjligt.<br>\r\n Kom ihåg att även lägga till motsvarande SPF TXT-uppslag på den här servern.<br>\r\n När en domän eller aliasdomän läggs till på den här servern som överlappar en extern adress, tas den externa adressen bort.<br>\r\n Använd @domain.tld för att kunna skicka som *@domain.tld.", "extended_sender_acl_info": "En DKIM-domännyckel bör importeras, om det är möjligt.<br>\r\n Kom ihåg att även lägga till motsvarande SPF TXT-uppslag på den här servern.<br>\r\n När en domän eller aliasdomän läggs till på den här servern som överlappar en extern adress, tas den externa adressen bort.<br>\r\n Använd @domain.tld för att kunna skicka som *@domain.tld.",
"force_pw_update": "Kräv uppdatering av lösenordet vid nästa inloggning", "force_pw_update": "Kräv uppdatering av lösenordet vid nästa inloggning",
@@ -507,7 +526,10 @@
"max_aliases": "Max antal alias", "max_aliases": "Max antal alias",
"max_mailboxes": "Max antal möjliga postlådor", "max_mailboxes": "Max antal möjliga postlådor",
"max_quota": "Max. kvot per postlåda (MiB)", "max_quota": "Max. kvot per postlåda (MiB)",
"maxage": "Hur många antal dagar bakåt som meddelanden ska hämtas <br><small>(0 = ignorera ålder)</small>",
"maxbytespersecond": "Max antal bitar per sekund <br><small>(0 = obegränsat)</small>",
"mbox_rl_info": "Hastighetsgränsen tillämpas på SASL-inloggningsnamnet och påverkar därför alla avsändaradresser som den inloggade användaren använder. Gränsvärde på en enskild postlåda åsidosätter gränsvärdet för domänen.", "mbox_rl_info": "Hastighetsgränsen tillämpas på SASL-inloggningsnamnet och påverkar därför alla avsändaradresser som den inloggade användaren använder. Gränsvärde på en enskild postlåda åsidosätter gränsvärdet för domänen.",
"mins_interval": "Intervall (min)",
"multiple_bookings": "Flera bokningar", "multiple_bookings": "Flera bokningar",
"nexthop": "Reläserver", "nexthop": "Reläserver",
"password": "Lösenord", "password": "Lösenord",
@@ -544,14 +566,19 @@
"sender_acl_info": "Om användaren A tillåts skicka som användaren B visas inte avsändaradressen automatiskt i \"från\" fältet i SOGo.<br>\r\n Användaren B måste skapa en delegation i SOGo för att låta användaren A välja Användaren B's adress som avsändare. För att delegera en postlåda i SOGo, gå till menyn (tre punkter) till höger om ditt namn uppe till vänster i postvyn. Detta gäller inte för aliasadresser.", "sender_acl_info": "Om användaren A tillåts skicka som användaren B visas inte avsändaradressen automatiskt i \"från\" fältet i SOGo.<br>\r\n Användaren B måste skapa en delegation i SOGo för att låta användaren A välja Användaren B's adress som avsändare. För att delegera en postlåda i SOGo, gå till menyn (tre punkter) till höger om ditt namn uppe till vänster i postvyn. Detta gäller inte för aliasadresser.",
"sieve_desc": "Kort beskrivning", "sieve_desc": "Kort beskrivning",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
"skipcrossduplicates": "Hoppa över duplicerade meddelanden i katalogerna (först till kvarn)",
"sogo_visible": "Alias visas i SOGo", "sogo_visible": "Alias visas i SOGo",
"sogo_visible_info": "Det här alternativet påverkar endast objekt som kan visas i SOGo (delade eller icke-delade aliasadresser med destination för minst en lokal postlåda).", "sogo_visible_info": "Det här alternativet påverkar endast objekt som kan visas i SOGo (delade eller icke-delade aliasadresser med destination för minst en lokal postlåda).",
"spam_alias": "Skapa eller ändra tillfälliga e-postalias", "spam_alias": "Skapa eller ändra tillfälliga e-postalias",
"spam_filter": "Spamfilter", "spam_filter": "Spamfilter",
"spam_policy": "Lägg till eller ta bort objekt på vit- eller svartlista", "spam_policy": "Lägg till eller ta bort objekt på vit- eller svartlista",
"spam_score": "Ange ett anpassad skräppostvärde", "spam_score": "Ange ett anpassad skräppostvärde",
"subfolder2": "Synkronisera till en undermapp <br><small>(lämna tomt = använd inte undermapp)</small>",
"syncjob": "Ändra synkroniseringsjobb",
"target_address": "Vidarbefodringsadresser", "target_address": "Vidarbefodringsadresser",
"target_domain": "Måldomän", "target_domain": "Måldomän",
"timeout1": "Timeout för anslutning till fjärrservern",
"timeout2": "Timeout för anslutning till lokalserver",
"title": "Ändra objekt", "title": "Ändra objekt",
"unchanged_if_empty": "Lämna blakt, om oförändrat", "unchanged_if_empty": "Lämna blakt, om oförändrat",
"username": "Användarnamn", "username": "Användarnamn",
@@ -725,6 +752,7 @@
"spam_aliases": "Tillfällig alias", "spam_aliases": "Tillfällig alias",
"stats": "Statistik", "stats": "Statistik",
"status": "Status", "status": "Status",
"sync_jobs": "Synkroniseringsjobb",
"table_size": "Tabellstorlek", "table_size": "Tabellstorlek",
"table_size_show_n": "Visa %s objekt", "table_size_show_n": "Visa %s objekt",
"target_address": "Vidarbefodringsadress", "target_address": "Vidarbefodringsadress",
@@ -932,27 +960,6 @@
"fido2_success": "Enheten har registrerats", "fido2_success": "Enheten har registrerats",
"fido2_validation_failed": "Verifiering misslyckades" "fido2_validation_failed": "Verifiering misslyckades"
}, },
"syncjobs": {
"sync_jobs": "Synkroniseringsjobb",
"add": "Lägg till synkroniseringsjobb",
"add_hint": "Observera att lösenord lagras i klartext!",
"edit": "Ändra synkroniseringsjobb",
"create": "Skapa ett nytt synkroniseringsjobb",
"subfolder2": "Synkronisera till en undermapp <br><small>(lämna tomt = använd inte undermapp)</small>",
"mins_interval": "Intervall (minuter)",
"maxage": "Hur många antal dagar bakåt som meddelanden ska hämtas <br><small>(0 = ignorera ålder)</small>",
"exclude": "Exkludera objekt (regex-filter)",
"automap": "Försök att automatiskt mappa mappar (\"Skickade objekt\", \"Skickat\" => \"Skickat\" etc.)",
"delete1": "Ta bort från källan när du är klar",
"delete2": "Ta bort meddelanden på målet som inte finns på källan",
"delete2duplicates": "Ta bort dubbletter på destinationsservern",
"skipcrossduplicates": "Hoppa över duplicerade meddelanden i katalogerna (först till kvarn)",
"subscribeall": "Prenumerera på alla kataloger",
"timeout1": "Timeout för anslutning till fjärrservern",
"timeout2": "Timeout för anslutning till lokalserver",
"maxbytespersecond": "Max antal bitar per sekund <br><small>(0 = obegränsat)</small>",
"custom_params": "Egna parametrar"
},
"user": { "user": {
"action": "Åtgärd", "action": "Åtgärd",
"active": "Aktiv", "active": "Aktiv",
@@ -977,6 +984,7 @@
"change_password": "Ändra lösenordet", "change_password": "Ändra lösenordet",
"client_configuration": "Visa konfigurationsguider för e-postklienter och smartatelefoner", "client_configuration": "Visa konfigurationsguider för e-postklienter och smartatelefoner",
"create_app_passwd": "Skapa applikationslösenord", "create_app_passwd": "Skapa applikationslösenord",
"create_syncjob": "Skapa ett nytt synkroniseringsjobb",
"created_on": "Skapad vid", "created_on": "Skapad vid",
"daily": "Dagligen", "daily": "Dagligen",
"day": "dag", "day": "dag",
@@ -1064,6 +1072,7 @@
"spamfilter_wl_desc": "Vitlistade e-postadresser kommer <b>aldrig</b> ska klassificeras som skräppost. Jokertecken kan användas. Ett filter kan bara används mot direkta aliasadresser (aliasadresser med en postlåda som destination) exklusive fånga-allt-aliasadresser och postlådan själv.", "spamfilter_wl_desc": "Vitlistade e-postadresser kommer <b>aldrig</b> ska klassificeras som skräppost. Jokertecken kan användas. Ett filter kan bara används mot direkta aliasadresser (aliasadresser med en postlåda som destination) exklusive fånga-allt-aliasadresser och postlådan själv.",
"spamfilter_yellow": "Gult: Det här medelandet kan vara spam, det kommer taggas som spam och flyttas till katalogen för skräppost", "spamfilter_yellow": "Gult: Det här medelandet kan vara spam, det kommer taggas som spam och flyttas till katalogen för skräppost",
"status": "Status", "status": "Status",
"sync_jobs": "Synkroniseringsjobb",
"tag_handling": "Ange åtgärd för taggade medelanden", "tag_handling": "Ange åtgärd för taggade medelanden",
"tag_help_example": "Exempel på en taggad e-postadress: me<b>+Facebook</b>@example.org", "tag_help_example": "Exempel på en taggad e-postadress: me<b>+Facebook</b>@example.org",
"tag_help_explain": "I en undermapp: en ny undermapp namngiven efter taggen kommer skapas under INKORGEN (\"INKORGEN/Facebook\").<br>\r\nI ämnesraden: taggens namn kommer infogas i medelandets ämnesrad, exempelvis: \"[Facebook] Mina Nyheter\".", "tag_help_explain": "I en undermapp: en ny undermapp namngiven efter taggen kommer skapas under INKORGEN (\"INKORGEN/Facebook\").<br>\r\nI ämnesraden: taggens namn kommer infogas i medelandets ämnesrad, exempelvis: \"[Facebook] Mina Nyheter\".",
+50 -32
View File
@@ -44,9 +44,15 @@
"app_name": "Uygulama adı", "app_name": "Uygulama adı",
"app_password": "Uygulama şifresi ekle", "app_password": "Uygulama şifresi ekle",
"app_passwd_protocols": "Uygulama şifresi için izin verilen protokoller", "app_passwd_protocols": "Uygulama şifresi için izin verilen protokoller",
"automap": "Klasörleri otomatik eşleştirmeyi deneyin (\"Gönderilen postalar\", \"Gönderilen\" => \"Gönderilen\" vb.)",
"backup_mx_options": "Relay ayarları", "backup_mx_options": "Relay ayarları",
"bcc_dest_format": "BCC hedefi tek bir geçerli e-posta adresi olmalıdır.<br>Bir kopyayı birden fazla adrese göndermeniz gerekiyorsa, bir takma ad oluşturun ve bunu burada kullanın.", "bcc_dest_format": "BCC hedefi tek bir geçerli e-posta adresi olmalıdır.<br>Bir kopyayı birden fazla adrese göndermeniz gerekiyorsa, bir takma ad oluşturun ve bunu burada kullanın.",
"comment_info": "Özel bir yorum kullanıcı tarafından görülemezken, herkese açık bir yorum kullanıcının genel görünümünde üzerine gelindiğinde tooltip olarak gösterilir", "comment_info": "Özel bir yorum kullanıcı tarafından görülemezken, herkese açık bir yorum kullanıcının genel görünümünde üzerine gelindiğinde tooltip olarak gösterilir",
"custom_params": "Özel parametreler",
"custom_params_hint": "Doğru: --param=xy, yanlış: --param xy",
"delete1": "Tamamlandığında kaynaktan sil",
"delete2": "Eğer kaynakta yoksa hedefteki mesajları sil",
"delete2duplicates": "Hedefteki kopyaları sil",
"description": "Açıklama", "description": "Açıklama",
"destination": "Hedef", "destination": "Hedef",
"disable_login": "Giriş yapmaya izin verme (gelen mailler yine de kabul edilir)", "disable_login": "Giriş yapmaya izin verme (gelen mailler yine de kabul edilir)",
@@ -54,6 +60,7 @@
"domain_matches_hostname": "Alan adı %s ana bilgisayar adıyla eşleşiyor", "domain_matches_hostname": "Alan adı %s ana bilgisayar adıyla eşleşiyor",
"domain_quota_m": "Toplam alan adı kotası (MiB)", "domain_quota_m": "Toplam alan adı kotası (MiB)",
"enc_method": "Şifreleme yöntemi", "enc_method": "Şifreleme yöntemi",
"exclude": "Hariç tutma kuralı (regex)",
"full_name": "Tam isim", "full_name": "Tam isim",
"gal": "Global Adres Listesi", "gal": "Global Adres Listesi",
"gal_info": "GAL bir alan alanının tüm nesnelerini içerir ve herhangi bir kullanıcı tarafından düzenlenemez. Eğer devre dışı bırakırsanız SOGo üzerindeki free/busy bilgileri kaybolur! <b>Değişiklikleri uygulamak için SOGo'yu yeniden başlatın.</b>", "gal_info": "GAL bir alan alanının tüm nesnelerini içerir ve herhangi bir kullanıcı tarafından düzenlenemez. Eğer devre dışı bırakırsanız SOGo üzerindeki free/busy bilgileri kaybolur! <b>Değişiklikleri uygulamak için SOGo'yu yeniden başlatın.</b>",
@@ -69,6 +76,7 @@
"mailbox_username": "Kullanıcı adı (e-posta adresinin sol kısmı)", "mailbox_username": "Kullanıcı adı (e-posta adresinin sol kısmı)",
"max_aliases": "Maksimum takma adı limiti", "max_aliases": "Maksimum takma adı limiti",
"max_mailboxes": "Maksimum e-posta hesabı", "max_mailboxes": "Maksimum e-posta hesabı",
"mins_interval": "Sorgulama döngüsü (dakika)",
"multiple_bookings": "Birden fazla rezervasyon", "multiple_bookings": "Birden fazla rezervasyon",
"nexthop": "Sonraki atlama", "nexthop": "Sonraki atlama",
"password": "Şifre", "password": "Şifre",
@@ -88,13 +96,20 @@
"select_domain": "Lütfen önce alan adı seçin", "select_domain": "Lütfen önce alan adı seçin",
"sieve_desc": "Kısa açıklama", "sieve_desc": "Kısa açıklama",
"sieve_type": "Filtre türü", "sieve_type": "Filtre türü",
"skipcrossduplicates": "Klasörler arasında yinelenen(kopya) mesajları es geçin (ilk gelen mail baz alınır)",
"subscribeall": "Tüm klasörlere abone ol",
"syncjob": "Senkronizasyon görevi ekle",
"syncjob_hint": "Parolaların düz metin olarak kaydedilmesi gerektiğini unutmayın!",
"tags": "Etiketler", "tags": "Etiketler",
"target_address": "Adreslere git", "target_address": "Adreslere git",
"target_address_info": "<small>Tam e-posta adresleri (virgülle ayrılmış).</small>", "target_address_info": "<small>Tam e-posta adresleri (virgülle ayrılmış).</small>",
"target_domain": "Hedef alan adı", "target_domain": "Hedef alan adı",
"timeout1": "Uzak ana bilgisayara bağlantı için zaman aşımı",
"timeout2": "Yerel ana bilgisayara bağlantı için zaman aşımı",
"username": "Kullanıcı Adı", "username": "Kullanıcı Adı",
"validate": "Doğrula", "validate": "Doğrula",
"validation_success": "Doğrulama başarılı" "validation_success": "Doğrulama başarılı",
"dry": "Senkronizasyonu simüle et"
}, },
"admin": { "admin": {
"action": "İşlem", "action": "İşlem",
@@ -399,9 +414,12 @@
"sender_acl_info": "Posta kutusu A kullanıcısının posta kutusu kullanıcısı B olarak göndermesine izin veriliyorsa, gönderen adresi SOGo'da seçilebilir \\\"gönderen\\\" alanı olarak otomatik olarak görüntülenmez.<br>\\r\\n Posta kutusu B kullanıcısının izin vermesi için SOGo'da bir yetkilendirme oluşturması gerekir. posta kutusu kullanıcısı A, adresini gönderen olarak seçmek için. SOGo'da bir posta kutusuna yetki vermek için, posta görünümündeyken sol üstteki posta kutusu adınızın sağındaki menüyü (üç nokta) kullanın. Bu davranış, diğer ad adresleri için geçerli değildir.", "sender_acl_info": "Posta kutusu A kullanıcısının posta kutusu kullanıcısı B olarak göndermesine izin veriliyorsa, gönderen adresi SOGo'da seçilebilir \\\"gönderen\\\" alanı olarak otomatik olarak görüntülenmez.<br>\\r\\n Posta kutusu B kullanıcısının izin vermesi için SOGo'da bir yetkilendirme oluşturması gerekir. posta kutusu kullanıcısı A, adresini gönderen olarak seçmek için. SOGo'da bir posta kutusuna yetki vermek için, posta görünümündeyken sol üstteki posta kutusu adınızın sağındaki menüyü (üç nokta) kullanın. Bu davranış, diğer ad adresleri için geçerli değildir.",
"sogo_visible_info": "Bu seçenek yalnızca SOGo'da görüntülenebilen nesneleri etkiler (en az bir yerel posta kutusuna işaret eden paylaşılan veya paylaşılmayan diğer ad adresleri). Gizliyse, bir takma ad SOGo'da seçilebilir gönderici olarak görünmez.", "sogo_visible_info": "Bu seçenek yalnızca SOGo'da görüntülenebilen nesneleri etkiler (en az bir yerel posta kutusuna işaret eden paylaşılan veya paylaşılmayan diğer ad adresleri). Gizliyse, bir takma ad SOGo'da seçilebilir gönderici olarak görünmez.",
"kind": "Nitelik", "kind": "Nitelik",
"maxage": "Uzaktan sorgulanacak mesajların gün cinsinden maksimum ileti yaşı<br><small>(0 = yaşı yoksay)</small>",
"maxbytespersecond": "Maks. saniye başına bayt <br><small>(0 = sınırsız)</small>",
"redirect_uri": "Yönlendirme URI'i", "redirect_uri": "Yönlendirme URI'i",
"relay_all": "Tüm alıcıları aktar", "relay_all": "Tüm alıcıları aktar",
"domain_footer_skip_replies": "Yanıt postasındaki altbilgiyi yoksay", "domain_footer_skip_replies": "Yanıt postasındaki altbilgiyi yoksay",
"exclude": "Nesneleri hariç tut (normal ifade)",
"extended_sender_acl": "Harici gönderen adresleri", "extended_sender_acl": "Harici gönderen adresleri",
"force_pw_update": "Bir sonraki girişte şifre güncellemesini zorla", "force_pw_update": "Bir sonraki girişte şifre güncellemesini zorla",
"password": "Şifre", "password": "Şifre",
@@ -413,6 +431,7 @@
"pushover_info": "Anında iletme bildirimi ayarları, takma adlar (paylaşılan, paylaşılmayan, etiketli) dahil <b>%s</b> adresine teslim edilen tüm temiz (spam olmayan) postalara uygulanacaktır.", "pushover_info": "Anında iletme bildirimi ayarları, takma adlar (paylaşılan, paylaşılmayan, etiketli) dahil <b>%s</b> adresine teslim edilen tüm temiz (spam olmayan) postalara uygulanacaktır.",
"pushover_sender_array": "Yalnızca aşağıdaki gönderen e-posta adreslerini göz önünde bulundurun <small>(virgülle ayrılmış)</small>", "pushover_sender_array": "Yalnızca aşağıdaki gönderen e-posta adreslerini göz önünde bulundurun <small>(virgülle ayrılmış)</small>",
"scope": "Kapsam", "scope": "Kapsam",
"skipcrossduplicates": "Klasörler arasında yinelenen mesajları atlayın (ilk gelen, ilk hizmet)",
"sogo_visible": "Takma ad SOGo'da görünür", "sogo_visible": "Takma ad SOGo'da görünür",
"relay_unknown_only": "Yalnızca var olmayan posta kutularını aktarın. Mevcut posta kutuları yerel olarak teslim edilecektir.", "relay_unknown_only": "Yalnızca var olmayan posta kutularını aktarın. Mevcut posta kutuları yerel olarak teslim edilecektir.",
"relayhost": "Göndericiye bağlı aktarımlar", "relayhost": "Göndericiye bağlı aktarımlar",
@@ -421,8 +440,12 @@
"save": "Değişiklikleri Kaydet", "save": "Değişiklikleri Kaydet",
"spam_policy": "Beyaz/kara listeye öğe ekleyin veya kaldırın", "spam_policy": "Beyaz/kara listeye öğe ekleyin veya kaldırın",
"spam_score": "Özel bir spam puanı ayarlayın", "spam_score": "Özel bir spam puanı ayarlayın",
"subfolder2": "Hedefteki alt klasörle senkronize et<br><small>(boş = alt klasörü kullanmayın)</small>",
"syncjob": "Senkronizasyon işini düzenle",
"target_address": "Adrese git <small>(virgülle ayrılmış)</small>", "target_address": "Adrese git <small>(virgülle ayrılmış)</small>",
"target_domain": "Hedef alan", "target_domain": "Hedef alan",
"timeout1": "Uzak ana bilgisayara bağlantı için zaman aşımı",
"timeout2": "Yerel ana bilgisayara bağlantı için zaman aşımı",
"unchanged_if_empty": "Değişmediyse boş bırakın", "unchanged_if_empty": "Değişmediyse boş bırakın",
"username": "Kullanıcı adı", "username": "Kullanıcı adı",
"validate_save": "Doğrula ve kaydet", "validate_save": "Doğrula ve kaydet",
@@ -445,6 +468,7 @@
"delete_ays": "Lütfen silme işlemini onaylayın.", "delete_ays": "Lütfen silme işlemini onaylayın.",
"description": "Açıklama", "description": "Açıklama",
"pushover_only_x_prio": "Yalnızca yüksek öncelikli postayı dikkate alın [<code>X-Priority: 1</code>]", "pushover_only_x_prio": "Yalnızca yüksek öncelikli postayı dikkate alın [<code>X-Priority: 1</code>]",
"delete2duplicates": "Hedefteki kopyaları sil",
"domain_footer": "Domain footer", "domain_footer": "Domain footer",
"domain_footer_html": "HTML footer", "domain_footer_html": "HTML footer",
"edit_alias_domain": "Takma ad alanını düzenle", "edit_alias_domain": "Takma ad alanını düzenle",
@@ -462,11 +486,14 @@
"app_name": "Uygulama adı", "app_name": "Uygulama adı",
"app_passwd": "Uygulama şifresi", "app_passwd": "Uygulama şifresi",
"app_passwd_protocols": "Uygulama şifresi için izin verilen protokoller", "app_passwd_protocols": "Uygulama şifresi için izin verilen protokoller",
"automap": "Klasörleri otomatik eşlemeyi deneyin (\\\"Gönderilmiş öğeler\\\", \\\"Gönderildi\\\" => \\\"Gönderildi\\\" vb.)",
"backup_mx_options": "Relay seçenekleri", "backup_mx_options": "Relay seçenekleri",
"bcc_dest_format": "BCC, tek bir geçerli e-posta adresi olmalıdır.<br>Bir kopyayı birden fazla adrese göndermeniz gerekiyorsa, bir takma ad oluşturun ve burada kullanın.", "bcc_dest_format": "BCC, tek bir geçerli e-posta adresi olmalıdır.<br>Bir kopyayı birden fazla adrese göndermeniz gerekiyorsa, bir takma ad oluşturun ve burada kullanın.",
"client_id": "Müşteri Kimliği", "client_id": "Müşteri Kimliği",
"client_secret": "Müşteri sırrı", "client_secret": "Müşteri sırrı",
"created_on": "Oluşturuldu", "created_on": "Oluşturuldu",
"delete1": "Tamamlandığında kaynaktan silin",
"delete2": "Kaynakta olmayan hedefteki mesajları sil",
"domain_footer_plain": "PLAIN footer", "domain_footer_plain": "PLAIN footer",
"multiple_bookings": "Çoklu rezervasyon", "multiple_bookings": "Çoklu rezervasyon",
"none_inherit": "Yok / Miras", "none_inherit": "Yok / Miras",
@@ -480,6 +507,7 @@
"grant_types": "Yetki türleri", "grant_types": "Yetki türleri",
"hostname": "Ana bilgisayar adı", "hostname": "Ana bilgisayar adı",
"mbox_rl_info": "Bu hız sınırı SASL oturum açma adına uygulanır, oturum açmış kullanıcı tarafından kullanılan herhangi bir \\\"gönderen\\\" adresiyle eşleşir. Posta kutusu hız sınırı, alan genelindeki hız sınırını geçersiz kılar.", "mbox_rl_info": "Bu hız sınırı SASL oturum açma adına uygulanır, oturum açmış kullanıcı tarafından kullanılan herhangi bir \\\"gönderen\\\" adresiyle eşleşir. Posta kutusu hız sınırı, alan genelindeki hız sınırını geçersiz kılar.",
"mins_interval": "Aralık(dakika)",
"pushover_title": "Bildirim başlığı", "pushover_title": "Bildirim başlığı",
"pushover_sound": "Ses", "pushover_sound": "Ses",
"pushover_vars": "Gönderici filtresi tanımlanmadığında, tüm postalar dikkate alınacaktır.<br>Regex filtreleri ve tam gönderen kontrolleri ayrı ayrı tanımlanabilir ve sıralı olarak değerlendirilecektir. Birbirlerine bağlı değildirler.<br>Metin ve başlık için kullanılabilir değişkenler (lütfen veri koruma politikalarını dikkate alın)", "pushover_vars": "Gönderici filtresi tanımlanmadığında, tüm postalar dikkate alınacaktır.<br>Regex filtreleri ve tam gönderen kontrolleri ayrı ayrı tanımlanabilir ve sıralı olarak değerlendirilecektir. Birbirlerine bağlı değildirler.<br>Metin ve başlık için kullanılabilir değişkenler (lütfen veri koruma politikalarını dikkate alın)",
@@ -694,6 +722,7 @@
"tls_map_parameters_info": "Boş veya parametreler, örneğin: protokoller=!SSLv2 şifreleri=orta hariç tutma=3DES", "tls_map_parameters_info": "Boş veya parametreler, örneğin: protokoller=!SSLv2 şifreleri=orta hariç tutma=3DES",
"recipient_map": "Alıcı haritası", "recipient_map": "Alıcı haritası",
"sieve_info": "Kullanıcı başına birden fazla filtre saklayabilirsiniz, ancak aynı anda yalnızca bir ön filtre ve bir son filtre etkin olabilir.<br>\\r\\nHer filtre açıklanan sırayla işlenir. Ne başarısız bir komut dosyası ne de verilen bir \\\"keep;\\\", başka komut dosyalarının işlenmesini durdurmayacaktır. Global elek komut dosyalarında yapılan değişiklikler, Dovecot'un yeniden başlatılmasını tetikleyecektir.<br><br>Global elek ön filtresi &#8226; Ön filtre &#8226; Kullanıcı komut dosyaları &#8226; Son filtre &#8226; Küresel elek son filtresi", "sieve_info": "Kullanıcı başına birden fazla filtre saklayabilirsiniz, ancak aynı anda yalnızca bir ön filtre ve bir son filtre etkin olabilir.<br>\\r\\nHer filtre açıklanan sırayla işlenir. Ne başarısız bir komut dosyası ne de verilen bir \\\"keep;\\\", başka komut dosyalarının işlenmesini durdurmayacaktır. Global elek komut dosyalarında yapılan değişiklikler, Dovecot'un yeniden başlatılmasını tetikleyecektir.<br><br>Global elek ön filtresi &#8226; Ön filtre &#8226; Kullanıcı komut dosyaları &#8226; Son filtre &#8226; Küresel elek son filtresi",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Kimlik doğrulama sorunu",
"tls_enforce_in": "TLS gelenini uygula", "tls_enforce_in": "TLS gelenini uygula",
"tls_policy_maps_info": "Bu politika haritası, bir kullanıcının TLS politika ayarlarından bağımsız olarak giden TLS aktarım kurallarını geçersiz kılar.<br>\\r\\n Lütfen <a href=\\\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\\\" adresini kontrol edin. daha fazla bilgi için target=\\\"_blank\\\">\\\"smtp_tls_policy_maps\\\" dokümanları</a>.", "tls_policy_maps_info": "Bu politika haritası, bir kullanıcının TLS politika ayarlarından bağımsız olarak giden TLS aktarım kurallarını geçersiz kılar.<br>\\r\\n Lütfen <a href=\\\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\\\" adresini kontrol edin. daha fazla bilgi için target=\\\"_blank\\\">\\\"smtp_tls_policy_maps\\\" dokümanları</a>.",
"remove": "Kaldır", "remove": "Kaldır",
@@ -828,6 +857,15 @@
"sogo_visible": "Takma ad SOGo'da görünür", "sogo_visible": "Takma ad SOGo'da görünür",
"sogo_visible_n": "SOGo'da takma adı gizle", "sogo_visible_n": "SOGo'da takma adı gizle",
"sogo_visible_y": "SOGo'da takma adı göster", "sogo_visible_y": "SOGo'da takma adı göster",
"sync_jobs": "İşleri senkronize et",
"syncjob_check_log": "Günlüğü kontrol et",
"syncjob_last_run_result": "Son çalıştırma sonucu",
"syncjob_EX_OK": "Başarılı",
"syncjob_EXIT_CONNECTION_FAILURE": "Bağlantı sorunu",
"syncjob_EXIT_TLS_FAILURE": "Şifreli bağlantıyla ilgili sorun",
"syncjob_EXIT_OVERQUOTA": "Hedef posta kutusu kotayı aştı",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Uzak sunucuya bağlanılamıyor",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Yanlış kullanıcı adı veya şifre",
"table_size": "Tablo boyutu", "table_size": "Tablo boyutu",
"table_size_show_n": "%s öğeleri göster", "table_size_show_n": "%s öğeleri göster",
"target_address": "Adrese git", "target_address": "Adrese git",
@@ -1106,37 +1144,6 @@
"hour": "mesaj / saat", "hour": "mesaj / saat",
"day": "mesaj / gün" "day": "mesaj / gün"
}, },
"syncjobs": {
"sync_jobs": "İşleri senkronize et",
"syncjob_check_log": "İşleri senkronize et",
"syncjob_last_run_result": "Son çalıştırma sonucu",
"syncjob_EX_OK": "Başarılı",
"syncjob_EXIT_CONNECTION_FAILURE": "Bağlantı sorunu",
"syncjob_EXIT_TLS_FAILURE": "Şifreli bağlantı ile ilgili sorun",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Kimlik doğrulama sorunu",
"syncjob_EXIT_OVERQUOTA": "Hedef posta kutusu kotayı aştı",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Uzak sunucuya bağlanılamıyor",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Yanlış kullanıcı adı veya şifre",
"add": "Senkronizasyon görevi ekle",
"add_hint": "Parolaların düz metin olarak kaydedilmesi gerektiğini unutmayın!",
"edit": "Senkronizasyon işini düzenle",
"create": "Yeni senkronizasyon işi oluştur",
"dry": "Senkronizasyonu simüle et",
"subfolder2": "Hedefteki alt klasörle senkronize et<br><small>(boş = alt klasörü kullanmayın)</small>",
"mins_interval": "Sorgulama döngüsü (dakika)",
"maxage": "Uzaktan sorgulanacak mesajların gün cinsinden maksimum ileti yaşı<br><small>(0 = yaşı yoksay)</small>",
"exclude": "Hariç tutma kuralı (regex)",
"automap": "Klasörleri otomatik eşleştirmeyi deneyin (\"Gönderilen postalar\", \"Gönderilen\" => \"Gönderilen\" vb.)",
"delete1": "Tamamlandığında kaynaktan sil",
"delete2": "Eğer kaynakta yoksa hedefteki mesajları sil",
"delete2duplicates": "Hedefteki kopyaları sil",
"skipcrossduplicates": "Klasörler arasında yinelenen(kopya) mesajları es geçin (ilk gelen mail baz alınır)",
"subscribeall": "Tüm klasörlere abone ol",
"timeout1": "Uzak ana bilgisayara bağlantı için zaman aşımı",
"timeout2": "Yerel ana bilgisayara bağlantı için zaman aşımı",
"maxbytespersecond": "Maks. saniye başına bayt <br><small>(0 = sınırsız)</small>",
"custom_params": "Özel parametreler"
},
"user": { "user": {
"advanced_settings": "Gelişmiş ayarlar", "advanced_settings": "Gelişmiş ayarlar",
"aliases_also_send_as": "Kullanıcı olarak göndermeye de izin verilir", "aliases_also_send_as": "Kullanıcı olarak göndermeye de izin verilir",
@@ -1154,6 +1161,7 @@
"shared_aliases_desc": "Paylaşılan takma adlar, spam filtresi veya şifreleme politikası gibi kullanıcıya özel ayarlardan etkilenmez. Karşılık gelen spam filtreleri yalnızca bir yönetici tarafından etki alanı çapında bir politika olarak yapılabilir.", "shared_aliases_desc": "Paylaşılan takma adlar, spam filtresi veya şifreleme politikası gibi kullanıcıya özel ayarlardan etkilenmez. Karşılık gelen spam filtreleri yalnızca bir yönetici tarafından etki alanı çapında bir politika olarak yapılabilir.",
"action": "Eylem", "action": "Eylem",
"create_app_passwd": "Uygulama parolası oluştur", "create_app_passwd": "Uygulama parolası oluştur",
"create_syncjob": "Yeni senkronizasyon işi oluştur",
"change_password": "Şifre değiştirme", "change_password": "Şifre değiştirme",
"clear_recent_successful_connections": "Görünen başarılı bağlantıları temizle", "clear_recent_successful_connections": "Görünen başarılı bağlantıları temizle",
"client_configuration": "E-posta istemcileri ve akıllı telefonlar için yapılandırma kılavuzlarını göster", "client_configuration": "E-posta istemcileri ve akıllı telefonlar için yapılandırma kılavuzlarını göster",
@@ -1260,6 +1268,7 @@
"spamfilter": "Spam filtresi", "spamfilter": "Spam filtresi",
"spamfilter_behavior": "Derecelendirme", "spamfilter_behavior": "Derecelendirme",
"spamfilter_bl": "Kara liste", "spamfilter_bl": "Kara liste",
"sync_jobs": "İşleri senkronize et",
"spamfilter_bl_desc": "Spam olarak sınıflandırılacak ve reddedilecek <b>her zaman</b> kara listeye alınan e-posta adresleri. Reddedilen postalar karantinaya <b>kopyalanmaz</b>. Joker karakterler kullanılabilir. Bir filtre yalnızca şunlara uygulanır tüm takma adları ve bir posta kutusunun kendisi hariç doğrudan takma adlar (tek bir hedef posta kutusuna sahip takma adlar).", "spamfilter_bl_desc": "Spam olarak sınıflandırılacak ve reddedilecek <b>her zaman</b> kara listeye alınan e-posta adresleri. Reddedilen postalar karantinaya <b>kopyalanmaz</b>. Joker karakterler kullanılabilir. Bir filtre yalnızca şunlara uygulanır tüm takma adları ve bir posta kutusunun kendisi hariç doğrudan takma adlar (tek bir hedef posta kutusuna sahip takma adlar).",
"spamfilter_green": "Yeşil: bu mesaj spam değil", "spamfilter_green": "Yeşil: bu mesaj spam değil",
"spamfilter_hint": "İlk değer \\\"düşük spam puanını\\\" tanımlar, ikincisi \\\"yüksek spam puanını\\\" temsil eder.", "spamfilter_hint": "İlk değer \\\"düşük spam puanını\\\" tanımlar, ikincisi \\\"yüksek spam puanını\\\" temsil eder.",
@@ -1269,6 +1278,15 @@
"spamfilter_wl": "Beyaz liste", "spamfilter_wl": "Beyaz liste",
"spamfilter_wl_desc": "Beyaz listeye alınan e-posta adresleri spam olarak <b>asla</b> sınıflandırılmayacak şekilde programlanmıştır. Joker karakterler kullanılabilir. Bir filtre yalnızca, tümünü yakalama takma adları ve bir posta kutusunun kendisi.", "spamfilter_wl_desc": "Beyaz listeye alınan e-posta adresleri spam olarak <b>asla</b> sınıflandırılmayacak şekilde programlanmıştır. Joker karakterler kullanılabilir. Bir filtre yalnızca, tümünü yakalama takma adları ve bir posta kutusunun kendisi.",
"spamfilter_yellow": "Sarı: bu mesaj spam olabilir, spam olarak etiketlenecek ve gereksiz klasörünüze taşınacak", "spamfilter_yellow": "Sarı: bu mesaj spam olabilir, spam olarak etiketlenecek ve gereksiz klasörünüze taşınacak",
"syncjob_check_log": "İşleri senkronize et",
"syncjob_last_run_result": "Son çalıştırma sonucu",
"syncjob_EX_OK": "Başarılı",
"syncjob_EXIT_CONNECTION_FAILURE": "Bağlantı sorunu",
"syncjob_EXIT_TLS_FAILURE": "Şifreli bağlantı ile ilgili sorun",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Kimlik doğrulama sorunu",
"syncjob_EXIT_OVERQUOTA": "Hedef posta kutusu kotayı aştı",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Uzak sunucuya bağlanılamıyor",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Yanlış kullanıcı adı veya şifre",
"tag_handling": "Etiketli postalar için işlemeyi ayarla", "tag_handling": "Etiketli postalar için işlemeyi ayarla",
"tag_help_example": "Etiketli bir e-posta adresi örneği: ben<b>+Facebook</b>@example.org", "tag_help_example": "Etiketli bir e-posta adresi örneği: ben<b>+Facebook</b>@example.org",
"tag_help_explain": "Alt klasörde: INBOX (\\\"INBOX/Facebook\\\") altında etiketten sonra adlandırılan yeni bir alt klasör oluşturulacak.<br>\\r\\nKonuda: etiketlerin adı posta konusunun önüne eklenecek, örnek: \\\"[Facebook] Haberlerim\\\".", "tag_help_explain": "Alt klasörde: INBOX (\\\"INBOX/Facebook\\\") altında etiketten sonra adlandırılan yeni bir alt klasör oluşturulacak.<br>\\r\\nKonuda: etiketlerin adı posta konusunun önüne eklenecek, örnek: \\\"[Facebook] Haberlerim\\\".",
+50 -32
View File
@@ -36,10 +36,12 @@
"app_password": "Додати пароль додатка", "app_password": "Додати пароль додатка",
"app_passwd_protocols": "Дозволені протоколи для пароля додатка", "app_passwd_protocols": "Дозволені протоколи для пароля додатка",
"comment_info": "Приватний коментар не видно користувачам, а публічний – відображається поряд із псевдонімом в особистому кабінеті користувача", "comment_info": "Приватний коментар не видно користувачам, а публічний – відображається поряд із псевдонімом в особистому кабінеті користувача",
"custom_params": "Налаштування користувача",
"gal": "GAL - Глобальна адресна книга", "gal": "GAL - Глобальна адресна книга",
"mailbox_quota_m": "Максимальна квота поштового акаунту (MiB)", "mailbox_quota_m": "Максимальна квота поштового акаунту (MiB)",
"max_aliases": "Максимум псевдонімів", "max_aliases": "Максимум псевдонімів",
"max_mailboxes": "Максимум поштових скриньок", "max_mailboxes": "Максимум поштових скриньок",
"mins_interval": "Інтервал (у хвилинах)",
"multiple_bookings": "Декілька бронювань", "multiple_bookings": "Декілька бронювань",
"nexthop": "Наступний хост", "nexthop": "Наступний хост",
"password": "Пароль", "password": "Пароль",
@@ -54,15 +56,20 @@
"select_domain": "Будь ласка, спочатку виберіть домен", "select_domain": "Будь ласка, спочатку виберіть домен",
"sieve_desc": "Короткий опис", "sieve_desc": "Короткий опис",
"sieve_type": "Тип фільтра", "sieve_type": "Тип фільтра",
"skipcrossduplicates": "Пропускати в папках повідомлення, що повторюються",
"subscribeall": "Підписатися на всі папки та підпапки",
"syncjob": "Додати завдання синхронізації",
"tags": "Мітки", "tags": "Мітки",
"target_address": "Власники псевдоніму", "target_address": "Власники псевдоніму",
"target_address_info": "<small>Адреси поштових скриньок, розділені комами.</small>", "target_address_info": "<small>Адреси поштових скриньок, розділені комами.</small>",
"target_domain": "Цільовий домен", "target_domain": "Цільовий домен",
"timeout2": "Тайм-аут для підключення до локального хоста",
"username": "Ім'я користувача", "username": "Ім'я користувача",
"validate": "Перевірити", "validate": "Перевірити",
"validation_success": "Перевірка пройшла успішно", "validation_success": "Перевірка пройшла успішно",
"activate_filter_warn": "Активація цього фільтра відключить решту фільтрів цього типу.", "activate_filter_warn": "Активація цього фільтра відключить решту фільтрів цього типу.",
"alias_address_info": "<small>Вкажіть поштові адреси розділені комами або, якщо хочете пересилати всі повідомлення для домену власникам псевдоніму: <code>@example.com</code>. <b>Дозволені тільки домени mailcow</b>.</small>", "alias_address_info": "<small>Вкажіть поштові адреси розділені комами або, якщо хочете пересилати всі повідомлення для домену власникам псевдоніму: <code>@example.com</code>. <b>Дозволені тільки домени mailcow</b>.</small>",
"automap": "Автоматичне зіставлення папок (\"Надіслані листи\", \"Надіслані\" => \"Надіслані\" тощо)",
"backup_mx_options": "Параметри резервного MX", "backup_mx_options": "Параметри резервного MX",
"bcc_dest_format": "Призначення BCC має бути єдиною дійсною адресою електронної пошти.<br>Якщо вам потрібно надіслати копію на декілька адрес - використовуйте псевдонім.", "bcc_dest_format": "Призначення BCC має бути єдиною дійсною адресою електронної пошти.<br>Якщо вам потрібно надіслати копію на декілька адрес - використовуйте псевдонім.",
"gal_info": "GAL містить усі об'єкти домену і не може бути відредагований. Інформація про зайнятість у SOGo буде відсутня для домену, якщо ця функція не буде увімкнена! <b>Щоб зміни застосувалися, потрібно перезапустити SOGo.</b>", "gal_info": "GAL містить усі об'єкти домену і не може бути відредагований. Інформація про зайнятість у SOGo буде відсутня для домену, якщо ця функція не буде увімкнена! <b>Щоб зміни застосувалися, потрібно перезапустити SOGo.</b>",
@@ -73,6 +80,8 @@
"relay_all_info": "Якщо ви вирішите <b>не</b> ретранслювати всіх одержувачів, вам потрібно буде додати (\"сліпу\") поштову адресу для кожного одержувача, якого слід ретранслювати.", "relay_all_info": "Якщо ви вирішите <b>не</b> ретранслювати всіх одержувачів, вам потрібно буде додати (\"сліпу\") поштову адресу для кожного одержувача, якого слід ретранслювати.",
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Інфо</div> Ви можете налаштувати власний транспорт для домену. Якщо такої установки немає, то доставка буде виконана на основі MX-записів.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Інфо</div> Ви можете налаштувати власний транспорт для домену. Якщо такої установки немає, то доставка буде виконана на основі MX-записів.",
"relayhost_wrapped_tls_info": "Будь ласка, <b>не</b> використовуйте TLS порти (в основному це 465 порт).<br>\nВикористовуйте будь-який <b>не</b> порт TLS, який підтримує STARTTLS. А для захисту від downgrate атак - налаштуйте примусову політику TLS.", "relayhost_wrapped_tls_info": "Будь ласка, <b>не</b> використовуйте TLS порти (в основному це 465 порт).<br>\nВикористовуйте будь-який <b>не</b> порт TLS, який підтримує STARTTLS. А для захисту від downgrate атак - налаштуйте примусову політику TLS.",
"syncjob_hint": "Паролі до вашого акаунту будуть збережені на сервері у вигляді простого тексту!",
"timeout1": "Тайм-аут для підключення до віддаленого хоста",
"active": "Активний", "active": "Активний",
"add": "Додати", "add": "Додати",
"add_domain_only": "Додати лише домен", "add_domain_only": "Додати лише домен",
@@ -80,12 +89,15 @@
"alias_address": "Псевдонім/и", "alias_address": "Псевдонім/и",
"alias_domain": "Псевдонім домена", "alias_domain": "Псевдонім домена",
"alias_domain_info": "<small>Лише дійсні доменні імена (розділені комами).</small>", "alias_domain_info": "<small>Лише дійсні доменні імена (розділені комами).</small>",
"custom_params_hint": "Правильно: --param=xy, неправильно: --param xy",
"delete2": "Видалити повідомлення на місці призначення, які не є в джерелі",
"description": "Опис", "description": "Опис",
"destination": "Призначення", "destination": "Призначення",
"disable_login": "Заборонити вхід (вхідна пошта все ще приймається)", "disable_login": "Заборонити вхід (вхідна пошта все ще приймається)",
"domain": "Домен", "domain": "Домен",
"domain_matches_hostname": "Домен %s відповідає імені хосту", "domain_matches_hostname": "Домен %s відповідає імені хосту",
"enc_method": "Метод шифрування", "enc_method": "Метод шифрування",
"exclude": "Виключити об'єкти (regex)",
"full_name": "Повне ім'я", "full_name": "Повне ім'я",
"generate": "згенерувати", "generate": "згенерувати",
"goto_ham": "Запам'ятати як <span class=\"text-success\"><b>корисну пошту</b></span>", "goto_ham": "Запам'ятати як <span class=\"text-success\"><b>корисну пошту</b></span>",
@@ -94,7 +106,10 @@
"hostname": "Ім'я хоста", "hostname": "Ім'я хоста",
"inactive": "Неактивний", "inactive": "Неактивний",
"kind": "Вид", "kind": "Вид",
"domain_quota_m": "Загальна квота домену (МіБ)" "delete1": "Видалити з джерела після завершення",
"delete2duplicates": "Видалити дублікати на місці призначення",
"domain_quota_m": "Загальна квота домену (МіБ)",
"dry": "Імітувати синхронізацію"
}, },
"admin": { "admin": {
"access": "Налаштування доступу", "access": "Налаштування доступу",
@@ -532,10 +547,14 @@
"app_name": "Назва додатка", "app_name": "Назва додатка",
"app_passwd": "Пароль додатку", "app_passwd": "Пароль додатку",
"app_passwd_protocols": "Дозволені протоколи для пароля додатку", "app_passwd_protocols": "Дозволені протоколи для пароля додатку",
"automap": "Автоматичне зіставлення папок (\"Надіслані листи\", \"Надіслані\" => \"Надіслані\" тощо)",
"backup_mx_options": "Параметри резервного MX", "backup_mx_options": "Параметри резервного MX",
"client_id": "ID клієнта", "client_id": "ID клієнта",
"client_secret": "Секретний ключ користувача", "client_secret": "Секретний ключ користувача",
"created_on": "Дата створення", "created_on": "Дата створення",
"delete1": "Видалити з джерела після завершення",
"delete2": "Видалити листи за місцем призначення, яких не має в джерелі",
"delete2duplicates": "Видалити дублікати за місцем призначення",
"delete_ays": "Будь ласка, підтвердіть видалення.", "delete_ays": "Будь ласка, підтвердіть видалення.",
"description": "Опис", "description": "Опис",
"disable_login": "Заборонити вхід (вхідна пошта все ще приймається)", "disable_login": "Заборонити вхід (вхідна пошта все ще приймається)",
@@ -544,6 +563,7 @@
"domains": "Домени", "domains": "Домени",
"edit_alias_domain": "Редагувати псевдонім домену", "edit_alias_domain": "Редагувати псевдонім домену",
"encryption": "Шифрування", "encryption": "Шифрування",
"exclude": "Виключити об'єкти (regex)",
"extended_sender_acl": "Зовнішні адреси пошти", "extended_sender_acl": "Зовнішні адреси пошти",
"force_pw_update": "Вимагати зміну пароля при наступному вході до системи", "force_pw_update": "Вимагати зміну пароля при наступному вході до системи",
"force_pw_update_info": "Цей користувач зможе увійти лише в %s. Паролі додатків залишаються придатними для використання.", "force_pw_update_info": "Цей користувач зможе увійти лише в %s. Паролі додатків залишаються придатними для використання.",
@@ -560,6 +580,8 @@
"max_aliases": "Максимум псевдонімів", "max_aliases": "Максимум псевдонімів",
"max_mailboxes": "Максимум поштових скриньок", "max_mailboxes": "Максимум поштових скриньок",
"max_quota": "Максимальна квота поштового акаунту (МіБ)", "max_quota": "Максимальна квота поштового акаунту (МіБ)",
"maxbytespersecond": "Макс. байт у сек<br><small>(0 = без ліміту)</small>",
"mins_interval": "Інтервал (у хвилинах)",
"multiple_bookings": "Декілька бронювань", "multiple_bookings": "Декілька бронювань",
"none_inherit": "Відсутня / Успадковується", "none_inherit": "Відсутня / Успадковується",
"nexthop": "Наступний хост", "nexthop": "Наступний хост",
@@ -591,6 +613,7 @@
"sender_acl": "Дозволити надсилати листи від імені", "sender_acl": "Дозволити надсилати листи від імені",
"sieve_desc": "Короткий опис", "sieve_desc": "Короткий опис",
"sieve_type": "Тип фільтра", "sieve_type": "Тип фільтра",
"skipcrossduplicates": "Пропускати в папках повідомлення, що повторюються",
"sogo_access": "Пряме перенаправлення до SOGo", "sogo_access": "Пряме перенаправлення до SOGo",
"sogo_access_info": "Після входу користувач автоматично перенаправляється на SOGo.", "sogo_access_info": "Після входу користувач автоматично перенаправляється на SOGo.",
"sogo_visible": "Відображати псевдонім у SOGo", "sogo_visible": "Відображати псевдонім у SOGo",
@@ -598,7 +621,10 @@
"spam_filter": "Спам фільтр", "spam_filter": "Спам фільтр",
"spam_policy": "Додати або видалити елементи до білого/чорного списка", "spam_policy": "Додати або видалити елементи до білого/чорного списка",
"spam_score": "Задати індивідуальне визначення спаму", "spam_score": "Задати індивідуальне визначення спаму",
"subfolder2": "Синхронізувати в підпапку <small>(порожньо = в корінь)</small>",
"syncjob": "Редагувати завдання синхронізації",
"target_domain": "Цільовий домен", "target_domain": "Цільовий домен",
"timeout1": "Тайм-аут для підключення до віддаленого хоста",
"title": "Редагувати об’єкт", "title": "Редагувати об’єкт",
"unchanged_if_empty": "Якщо не змінено, залиште порожнім", "unchanged_if_empty": "Якщо не змінено, залиште порожнім",
"username": "Ім'я користувача", "username": "Ім'я користувача",
@@ -615,6 +641,7 @@
"lookup_mx": "Призначення на основі резолвінгу MX-запису за регулярним виразом (<code>.*\\.example\\.com$</code> для маршрутизації всієї пошти через цей хост, якщо MX закінчується на example.com)", "lookup_mx": "Призначення на основі резолвінгу MX-запису за регулярним виразом (<code>.*\\.example\\.com$</code> для маршрутизації всієї пошти через цей хост, якщо MX закінчується на example.com)",
"mailbox_quota_def": "Квота за замовчуванням", "mailbox_quota_def": "Квота за замовчуванням",
"mailbox_relayhost_info": "Застосовується лише до поштової скриньки та особистих псевдонімів, незалежно від налаштувань маршрутизації на рівні домену.", "mailbox_relayhost_info": "Застосовується лише до поштової скриньки та особистих псевдонімів, незалежно від налаштувань маршрутизації на рівні домену.",
"maxage": "Макс. вік листів у днях для завантаження з сервера<br><small>(0 = ігнорувати вік)</small>",
"mbox_rl_info": "Цей ліміт застосовується до SASL логіну користувача і відповідає будь-якій адресі відправника, який використовується зареєстрованим користувачем. Ліміт швидкості поштового облікового запису перекриває ліміт швидкості для всього домену.", "mbox_rl_info": "Цей ліміт застосовується до SASL логіну користувача і відповідає будь-якій адресі відправника, який використовується зареєстрованим користувачем. Ліміт швидкості поштового облікового запису перекриває ліміт швидкості для всього домену.",
"pushover_info": "Установки Push-повідомлення будуть застосовуватися до всієї пошти <b>%s</b> (за винятком спаму), включаючи псевдоніми (особисті, загальні та теговані).", "pushover_info": "Установки Push-повідомлення будуть застосовуватися до всієї пошти <b>%s</b> (за винятком спаму), включаючи псевдоніми (особисті, загальні та теговані).",
"pushover_sender_array": "Отримувати повідомлення від списку адрес електронної пошти <small>(розділені комами)</small>:", "pushover_sender_array": "Отримувати повідомлення від списку адрес електронної пошти <small>(розділені комами)</small>:",
@@ -625,6 +652,7 @@
"sender_acl_info": "Врахуйте, що якщо користувачеві поштового облікового запису А дозволено відправляти від імені користувача Б, то адреса користувача Б не з'явиться автоматично у списку \"Відправник\" при написанні листів у SOGo.<br>\n Користувач поштового облікового запису Б повинен створити делегування в SOGo, щоб користувач поштового облікового запису А міг вибрати його адресу як відправника. Делегування знаходиться в меню (три крапки) праворуч від імені поштового облікового запису у вікні пошти SOGo. Ця поведінка не відноситься до псевдонімів.", "sender_acl_info": "Врахуйте, що якщо користувачеві поштового облікового запису А дозволено відправляти від імені користувача Б, то адреса користувача Б не з'явиться автоматично у списку \"Відправник\" при написанні листів у SOGo.<br>\n Користувач поштового облікового запису Б повинен створити делегування в SOGo, щоб користувач поштового облікового запису А міг вибрати його адресу як відправника. Делегування знаходиться в меню (три крапки) праворуч від імені поштового облікового запису у вікні пошти SOGo. Ця поведінка не відноситься до псевдонімів.",
"sogo_visible_info": "Впливає лише на об'єкти, які можуть відображатися в SOGo (персональні або загальні псевдоніми, що вказують щонайменше на один локальний поштовий обліковий запис). Зверніть увагу, що якщо функцію вимкнено, користувач не зможе вибрати адресу псевдоніма як відправника в SOGo.", "sogo_visible_info": "Впливає лише на об'єкти, які можуть відображатися в SOGo (персональні або загальні псевдоніми, що вказують щонайменше на один локальний поштовий обліковий запис). Зверніть увагу, що якщо функцію вимкнено, користувач не зможе вибрати адресу псевдоніма як відправника в SOGo.",
"target_address": "Власники псевдоніма, <small>(розділені комами)</small>", "target_address": "Власники псевдоніма, <small>(розділені комами)</small>",
"timeout2": "Тайм-аут для підключення до локального хоста",
"pushover_sound": "Звук", "pushover_sound": "Звук",
"domain_footer": "Нижній колонтитул домену", "domain_footer": "Нижній колонтитул домену",
"domain_footer_info": "Нижні колонтитули для всього домену додаються до всіх вихідних електронних листів, пов’язаних з адресою в цьому домені. <br> Наступні змінні можна використовувати для нижнього колонтитула:", "domain_footer_info": "Нижні колонтитули для всього домену додаються до всіх вихідних електронних листів, пов’язаних з адресою в цьому домені. <br> Наступні змінні можна використовувати для нижнього колонтитула:",
@@ -814,6 +842,13 @@
"spam_aliases": "Тимчасовий псевдонім", "spam_aliases": "Тимчасовий псевдонім",
"stats": "Статистика", "stats": "Статистика",
"status": "Статус", "status": "Статус",
"syncjob_check_log": "Перевірити журнал",
"syncjob_last_run_result": "Результат останнього запуску",
"syncjob_EX_OK": "Успішно",
"syncjob_EXIT_TLS_FAILURE": "Помилка встановлення шифрованого з'єднання",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Помилка авторизації",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Не вдалося підключитися до віддаленого сервера",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Неправильне ім'я користувача або пароль",
"table_size": "Розмір таблиці", "table_size": "Розмір таблиці",
"table_size_show_n": "Показувати %s полів", "table_size_show_n": "Показувати %s полів",
"target_domain": "Цільовий домен", "target_domain": "Цільовий домен",
@@ -851,6 +886,9 @@
"sieve_info": "Ви можете зберегти декілька фільтрів для кожного користувача, але тільки один попередній фільтр та один постфільтр можуть бути активними одночасно.<br>\nКожен фільтр буде оброблено в описаному порядку. Ані зламаний скрипт, ані <code>keep;</code> не зупинить обробку подальших скриптів.<br><br>Global sieve prefilter &#8226; Prefilter &#8226; User scripts &#8226; Postfilter &#8226; Global sieve postfilter", "sieve_info": "Ви можете зберегти декілька фільтрів для кожного користувача, але тільки один попередній фільтр та один постфільтр можуть бути активними одночасно.<br>\nКожен фільтр буде оброблено в описаному порядку. Ані зламаний скрипт, ані <code>keep;</code> не зупинить обробку подальших скриптів.<br><br>Global sieve prefilter &#8226; Prefilter &#8226; User scripts &#8226; Postfilter &#8226; Global sieve postfilter",
"sieve_preset_8": "Відхилити повідомлення, надіслане на псевдонім, частиною якого є відправник", "sieve_preset_8": "Відхилити повідомлення, надіслане на псевдонім, частиною якого є відправник",
"sieve_preset_header": "Будь ласка, ознайомтесь із прикладами нижче. Для більш детальної інформації прочитайте <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Sieve Wikipedia</a>.", "sieve_preset_header": "Будь ласка, ознайомтесь із прикладами нижче. Для більш детальної інформації прочитайте <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Sieve Wikipedia</a>.",
"sync_jobs": "Завдання синхронізації",
"syncjob_EXIT_CONNECTION_FAILURE": "Помилка зв'язку із сервером",
"syncjob_EXIT_OVERQUOTA": "Цільова поштова скринька перевищила квоту",
"target_address": "Власники псевдоніму", "target_address": "Власники псевдоніму",
"tls_map_dest_info": "Приклади: example.org, .example.org, [mail.example.org]:25", "tls_map_dest_info": "Приклади: example.org, .example.org, [mail.example.org]:25",
"tls_map_parameters_info": "Залиште поле порожнім або вкажіть параметри, наприклад: protocols=!SSLv2 ciphers=medium exclude=3DES", "tls_map_parameters_info": "Залиште поле порожнім або вкажіть параметри, наприклад: protocols=!SSLv2 ciphers=medium exclude=3DES",
@@ -1065,37 +1103,6 @@
"waiting_usb_register": "<i>Очікування USB-пристрою...</i><br><br>Будь ласка, введіть пароль вище та підтвердіть реєстрацію, натиснувши кнопку на USB пристрої.", "waiting_usb_register": "<i>Очікування USB-пристрою...</i><br><br>Будь ласка, введіть пароль вище та підтвердіть реєстрацію, натиснувши кнопку на USB пристрої.",
"authenticators": "Аутентифікатори" "authenticators": "Аутентифікатори"
}, },
"syncjobs": {
"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": "Неправильне ім'я користувача або пароль",
"add": "Додати завдання синхронізації",
"add_hint": "Паролі до вашого акаунту будуть збережені на сервері у вигляді простого тексту!",
"edit": "Редагувати завдання синхронізації",
"create": "Створити нове завдання синхронізації",
"dry": "Імітувати синхронізацію",
"subfolder2": "Синхронізувати в підпапку <small>(порожньо = в корінь)</small>",
"mins_interval": "Інтервал (у хвилинах)",
"maxage": "Макс. вік листів у днях для завантаження з сервера<br><small>(0 = ігнорувати вік)</small>",
"exclude": "Виключити об'єкти (regex)",
"automap": "Автоматичне зіставлення папок (\"Надіслані листи\", \"Надіслані\" => \"Надіслані\" тощо)",
"delete1": "Видалити з джерела після завершення",
"delete2": "Видалити повідомлення на місці призначення, які не є в джерелі",
"delete2duplicates": "Видалити дублікати на місці призначення",
"skipcrossduplicates": "Пропускати в папках повідомлення, що повторюються",
"subscribeall": "Підписатися на всі папки та підпапки",
"timeout1": "Тайм-аут для підключення до віддаленого хоста",
"timeout2": "Тайм-аут для підключення до локального хоста",
"maxbytespersecond": "Макс. байт у сек<br><small>(0 = без ліміту)</small>",
"custom_params": "Налаштування користувача"
},
"user": { "user": {
"action": "Дії", "action": "Дії",
"active": "Активний", "active": "Активний",
@@ -1119,6 +1126,7 @@
"change_password_hint_app_passwords": "Ваш обліковий запис містить %d паролів додатків, які не змінюватимуться. Щоб керувати ними, перейдіть на вкладку \"Паролі додатків\".", "change_password_hint_app_passwords": "Ваш обліковий запис містить %d паролів додатків, які не змінюватимуться. Щоб керувати ними, перейдіть на вкладку \"Паролі додатків\".",
"clear_recent_successful_connections": "Очистити історію успішних підключень", "clear_recent_successful_connections": "Очистити історію успішних підключень",
"create_app_passwd": "Створити новий пароль", "create_app_passwd": "Створити новий пароль",
"create_syncjob": "Створити нове завдання синхронізації",
"created_on": "Дата створення", "created_on": "Дата створення",
"daily": "Раз на день", "daily": "Раз на день",
"day": "день", "day": "день",
@@ -1197,6 +1205,14 @@
"spamfilter_wl": "Білий список", "spamfilter_wl": "Білий список",
"spamfilter_yellow": "Жовтий: ці листи можуть бути спамом, будуть доставлені до папки \"Спам\"", "spamfilter_yellow": "Жовтий: ці листи можуть бути спамом, будуть доставлені до папки \"Спам\"",
"status": "Статус", "status": "Статус",
"sync_jobs": "Завдання синхронізації",
"syncjob_check_log": "Перевірити журнал",
"syncjob_last_run_result": "Результат",
"syncjob_EX_OK": "Успішно",
"syncjob_EXIT_CONNECTION_FAILURE": "Помилка зв'язку із сервером",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Проблема аутентифікації",
"syncjob_EXIT_OVERQUOTA": "Цільова поштова скринька перевищила квоту",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Не вдалося підключитися до віддаленого сервера",
"tag_handling": "Обробка тегованої пошти", "tag_handling": "Обробка тегованої пошти",
"tag_help_example": "Приклад тегованої адреси електронної пошти: <code>me<b>+Facebook</b>@example.org</code>", "tag_help_example": "Приклад тегованої адреси електронної пошти: <code>me<b>+Facebook</b>@example.org</code>",
"tag_in_none": "Нічого не робити", "tag_in_none": "Нічого не робити",
@@ -1242,6 +1258,8 @@
"spamfilter_behavior": "Фільтрування спаму", "spamfilter_behavior": "Фільтрування спаму",
"spamfilter_bl_desc": "<b>Всі</b> листи, отримані від адрес електронної пошти, доданих до чорного списку, будуть відхилені та <b>не</b> будуть скопійовані до карантину. Використання <code>*</code> дозволено. Фільтр застосовується тільки до особистих псевдонімів (з одним одержувачем), за винятком catch-all псевдонімів та самого поштового облікового запису.", "spamfilter_bl_desc": "<b>Всі</b> листи, отримані від адрес електронної пошти, доданих до чорного списку, будуть відхилені та <b>не</b> будуть скопійовані до карантину. Використання <code>*</code> дозволено. Фільтр застосовується тільки до особистих псевдонімів (з одним одержувачем), за винятком catch-all псевдонімів та самого поштового облікового запису.",
"spamfilter_wl_desc": "Адреси електронної пошти з білого списку запрограмовані так, щоб <b>ніколи</b> не класифікуватися як спам. Можна використовувати символи підстановки (*). Фільтр застосовується лише до прямих псевдонімів (псевдонімів з однією цільовою поштовою скринькою), за винятком псевдонімів загального доступу та самої поштової скриньки.", "spamfilter_wl_desc": "Адреси електронної пошти з білого списку запрограмовані так, щоб <b>ніколи</b> не класифікуватися як спам. Можна використовувати символи підстановки (*). Фільтр застосовується лише до прямих псевдонімів (псевдонімів з однією цільовою поштовою скринькою), за винятком псевдонімів загального доступу та самої поштової скриньки.",
"syncjob_EXIT_TLS_FAILURE": "Помилка встановлення шифрованого з'єднання",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Неправильне ім'я користувача або пароль",
"tag_help_explain": "Перемістити до підпапки: буде створено нову підпапку в INBOX з ім'ям тега, наприклад: \"INBOX/Facebook\".<br>\n Додати до теми листа: ім'я тега буде додано до теми листа, наприклад: \"[Facebook] My News\".", "tag_help_explain": "Перемістити до підпапки: буде створено нову підпапку в INBOX з ім'ям тега, наприклад: \"INBOX/Facebook\".<br>\n Додати до теми листа: ім'я тега буде додано до теми листа, наприклад: \"[Facebook] My News\".",
"tls_policy_warning": "<strong>Попередження:</strong> якщо ви увімкнете примусове шифрування пошти, ви можете зіткнутися з втратою листів.<br>Повідомлення, які не відповідають політиці, будуть відкидатися з повідомленням поштовим сервером про серйозний збій.<br>Цей параметр застосовується до вашої основної адреси електронної пошти (логіну), усім особистим псевдонімам та псевдонімам доменів. Маються на увазі лише псевдоніми <b>з однією поштовою скринькою</b>, як одержувач.", "tls_policy_warning": "<strong>Попередження:</strong> якщо ви увімкнете примусове шифрування пошти, ви можете зіткнутися з втратою листів.<br>Повідомлення, які не відповідають політиці, будуть відкидатися з повідомленням поштовим сервером про серйозний збій.<br>Цей параметр застосовується до вашої основної адреси електронної пошти (логіну), усім особистим псевдонімам та псевдонімам доменів. Маються на увазі лише псевдоніми <b>з однією поштовою скринькою</b>, як одержувач.",
"year": "рік", "year": "рік",
+49 -31
View File
@@ -44,16 +44,24 @@
"app_name": "Ilova nomi", "app_name": "Ilova nomi",
"app_password": "Ilova paroli qoʻshish", "app_password": "Ilova paroli qoʻshish",
"app_passwd_protocols": "Ilova paroli uchun ruxsat etilgan protokollar", "app_passwd_protocols": "Ilova paroli uchun ruxsat etilgan protokollar",
"automap": "Papkalarni avtomatik xaritalashga harakat qilish (\"Sent items\", \"Sent\" => \"Sent\" va h.k.)",
"backup_mx_options": "Relay sozlamalari", "backup_mx_options": "Relay sozlamalari",
"bcc_dest_format": "BCC manzili bitta haqiqiy e-pochta manzili boʻlishi kerak.<br>Agar bir nechta manzilga nusxa joʻnatish kerak boʻlsa, taxallus yarating va shu yerda ishlating.", "bcc_dest_format": "BCC manzili bitta haqiqiy e-pochta manzili boʻlishi kerak.<br>Agar bir nechta manzilga nusxa joʻnatish kerak boʻlsa, taxallus yarating va shu yerda ishlating.",
"comment_info": "Maxfiy izoh foydalanuvchiga koʻrinmaydi, ommaviy izoh esa foydalanuvchi koʻrinishida ustiga sichqoncha kelganda maslahat sifatida koʻrsatiladi", "comment_info": "Maxfiy izoh foydalanuvchiga koʻrinmaydi, ommaviy izoh esa foydalanuvchi koʻrinishida ustiga sichqoncha kelganda maslahat sifatida koʻrsatiladi",
"custom_params": "Maxsus parametrlar",
"custom_params_hint": "Toʻgʻri: --param=xy, notoʻgʻri: --param xy",
"delete1": "Tugatilgach manbadan oʻchirish",
"delete2": "Manbada boʻlmagan xabarlarni manzildan oʻchirish",
"delete2duplicates": "Manzildagi dublikatlarni oʻchirish",
"description": "Tavsif", "description": "Tavsif",
"destination": "Manzil", "destination": "Manzil",
"disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)", "disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)",
"domain": "Domen", "domain": "Domen",
"domain_matches_hostname": "%s domeni xost nomiga mos keladi", "domain_matches_hostname": "%s domeni xost nomiga mos keladi",
"domain_quota_m": "Domenning umumiy kvotasi (MiB)", "domain_quota_m": "Domenning umumiy kvotasi (MiB)",
"dry": "Sinxronizatsiyani simulyatsiya qilish",
"enc_method": "Shifrlash usuli", "enc_method": "Shifrlash usuli",
"exclude": "Obyektlarni istisno qilish (regex)",
"full_name": "Toʻliq ism", "full_name": "Toʻliq ism",
"gal": "Global manzillar roʻyxati", "gal": "Global manzillar roʻyxati",
"gal_info": "GAL domenning barcha obyektlarini oʻz ichiga oladi va hech bir foydalanuvchi tomonidan tahrirlanmaydi. Oʻchirilsa, SOGodagi bandlik maʼlumotlari yoʻqoladi! <b>Oʻzgarishlarni qoʻllash uchun SOGoni qayta ishga tushiring.</b>", "gal_info": "GAL domenning barcha obyektlarini oʻz ichiga oladi va hech bir foydalanuvchi tomonidan tahrirlanmaydi. Oʻchirilsa, SOGodagi bandlik maʼlumotlari yoʻqoladi! <b>Oʻzgarishlarni qoʻllash uchun SOGoni qayta ishga tushiring.</b>",
@@ -72,6 +80,7 @@
"mailbox_username": "Foydalanuvchi nomi (e-pochta manzilining chap qismi)", "mailbox_username": "Foydalanuvchi nomi (e-pochta manzilining chap qismi)",
"max_aliases": "Mumkin boʻlgan maks. taxalluslar", "max_aliases": "Mumkin boʻlgan maks. taxalluslar",
"max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari", "max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari",
"mins_interval": "Soʻrov oraligʻi (daqiqa)",
"multiple_bookings": "Bir nechta bandlovlar", "multiple_bookings": "Bir nechta bandlovlar",
"nexthop": "Keyingi nuqta", "nexthop": "Keyingi nuqta",
"password": "Parol", "password": "Parol",
@@ -91,10 +100,16 @@
"select_domain": "Avval domenni tanlang", "select_domain": "Avval domenni tanlang",
"sieve_desc": "Qisqa tavsif", "sieve_desc": "Qisqa tavsif",
"sieve_type": "Filtr turi", "sieve_type": "Filtr turi",
"skipcrossduplicates": "Papkalararo dublikat xabarlarni oʻtkazib yuborish (birinchi kelgan, birinchi xizmatlanadi)",
"subscribeall": "Barcha papkalarga obuna boʻlish",
"syncjob": "Sinxronizatsiya vazifasi qoʻshish",
"syncjob_hint": "Parollar oddiy matn sifatida saqlanishini unutmang!",
"tags": "Teglar", "tags": "Teglar",
"target_address": "Yoʻnaltirish manzillari", "target_address": "Yoʻnaltirish manzillari",
"target_address_info": "<small>Toʻliq e-pochta manzil(lar)i (vergul bilan ajratilgan).</small>", "target_address_info": "<small>Toʻliq e-pochta manzil(lar)i (vergul bilan ajratilgan).</small>",
"target_domain": "Maqsad domen", "target_domain": "Maqsad domen",
"timeout1": "Masofaviy xostga ulanish vaqtining tugashi",
"timeout2": "Mahalliy xostga ulanish vaqtining tugashi",
"username": "Foydalanuvchi nomi", "username": "Foydalanuvchi nomi",
"validate": "Tekshirish", "validate": "Tekshirish",
"validation_success": "Muvaffaqiyatli tekshirildi" "validation_success": "Muvaffaqiyatli tekshirildi"
@@ -633,6 +648,7 @@
"app_name": "Ilova nomi", "app_name": "Ilova nomi",
"app_passwd": "Ilova paroli", "app_passwd": "Ilova paroli",
"app_passwd_protocols": "Ilova paroli uchun ruxsat etilgan protokollar", "app_passwd_protocols": "Ilova paroli uchun ruxsat etilgan protokollar",
"automap": "Papkalarni avtomatik xaritalashga harakat qilish (\"Sent items\", \"Sent\" => \"Sent\" va h.k.)",
"backup_mx_options": "Relay sozlamalari", "backup_mx_options": "Relay sozlamalari",
"bcc_dest_format": "BCC manzili bitta haqiqiy e-pochta manzili boʻlishi kerak.<br>Agar bir nechta manzilga nusxa joʻnatish kerak boʻlsa, taxallus yarating va shu yerda ishlating.", "bcc_dest_format": "BCC manzili bitta haqiqiy e-pochta manzili boʻlishi kerak.<br>Agar bir nechta manzilga nusxa joʻnatish kerak boʻlsa, taxallus yarating va shu yerda ishlating.",
"client_id": "Mijoz ID", "client_id": "Mijoz ID",
@@ -640,6 +656,9 @@
"comment_info": "Maxfiy izoh foydalanuvchiga koʻrinmaydi, ommaviy izoh esa foydalanuvchi koʻrinishida ustiga sichqoncha kelganda maslahat sifatida koʻrsatiladi", "comment_info": "Maxfiy izoh foydalanuvchiga koʻrinmaydi, ommaviy izoh esa foydalanuvchi koʻrinishida ustiga sichqoncha kelganda maslahat sifatida koʻrsatiladi",
"created_on": "Yaratilgan sana", "created_on": "Yaratilgan sana",
"custom_attributes": "Maxsus atributlar", "custom_attributes": "Maxsus atributlar",
"delete1": "Tugatilgach manbadan oʻchirish",
"delete2": "Manbada boʻlmagan xabarlarni manzildan oʻchirish",
"delete2duplicates": "Manzildagi dublikatlarni oʻchirish",
"delete_ays": "Iltimos, oʻchirish jarayonini tasdiqlang.", "delete_ays": "Iltimos, oʻchirish jarayonini tasdiqlang.",
"description": "Tavsif", "description": "Tavsif",
"disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)", "disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)",
@@ -663,6 +682,7 @@
"dont_check_sender_acl": "%s domeni uchun joʻnatuvchi tekshiruvini oʻchirish (+ taxallus domenlari)", "dont_check_sender_acl": "%s domeni uchun joʻnatuvchi tekshiruvini oʻchirish (+ taxallus domenlari)",
"edit_alias_domain": "Taxallus domenini tahrirlash", "edit_alias_domain": "Taxallus domenini tahrirlash",
"encryption": "Shifrlash", "encryption": "Shifrlash",
"exclude": "Obyektlarni istisno qilish (regex)",
"extended_sender_acl": "Tashqi joʻnatuvchi manzillari", "extended_sender_acl": "Tashqi joʻnatuvchi manzillari",
"extended_sender_acl_info": "DKIM domen kaliti mavjud boʻlsa, import qilinishi kerak.<br>\r\n Ushbu serverni mos SPF TXT yozuviga qoʻshishni unutmang.<br>\r\n Ushbu serverga tashqi manzil bilan ust-ust tushadigan domen yoki taxallus domeni qoʻshilganda, tashqi manzil olib tashlanadi.<br>\r\n *@domain.tld sifatida joʻnatishga ruxsat berish uchun @domain.tld dan foydalaning.", "extended_sender_acl_info": "DKIM domen kaliti mavjud boʻlsa, import qilinishi kerak.<br>\r\n Ushbu serverni mos SPF TXT yozuviga qoʻshishni unutmang.<br>\r\n Ushbu serverga tashqi manzil bilan ust-ust tushadigan domen yoki taxallus domeni qoʻshilganda, tashqi manzil olib tashlanadi.<br>\r\n *@domain.tld sifatida joʻnatishga ruxsat berish uchun @domain.tld dan foydalaning.",
"force_pw_update": "Keyingi kirishda parolni majburiy yangilash", "force_pw_update": "Keyingi kirishda parolni majburiy yangilash",
@@ -693,7 +713,10 @@
"max_aliases": "Maks. taxalluslar", "max_aliases": "Maks. taxalluslar",
"max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari", "max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari",
"max_quota": "Bitta pochta qutisining maks. kvotasi (MiB)", "max_quota": "Bitta pochta qutisining maks. kvotasi (MiB)",
"maxage": "Masofadan soʻraladigan xabarlarning maksimal yoshi (kunlarda)<br><small>(0 = yoshni e'tiborsiz qoldirish)</small>",
"maxbytespersecond": "Maks. soniyaga bayt <br><small>(0 = cheksiz)</small>",
"mbox_rl_info": "Bu tezlik chegarasi SASL kirish nomiga qoʻllaniladi, u kirgan foydalanuvchi tomonidan ishlatiladigan har qanday \"from\" manziliga mos keladi. Pochta qutisi tezlik chegarasi domen boʻyicha tezlik chegarasini bekor qiladi.", "mbox_rl_info": "Bu tezlik chegarasi SASL kirish nomiga qoʻllaniladi, u kirgan foydalanuvchi tomonidan ishlatiladigan har qanday \"from\" manziliga mos keladi. Pochta qutisi tezlik chegarasi domen boʻyicha tezlik chegarasini bekor qiladi.",
"mins_interval": "Oraliq (daqiqa)",
"mta_sts": "MTA-STS", "mta_sts": "MTA-STS",
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> — pochta serverlari oʻrtasida xat yetkazib berishni haqiqiy sertifikatli TLS dan foydalanishga majburlovchi standart. <br>U DNSSEC mavjud emasligi yoki qoʻllab-quvvatlanmasligi sababli <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> imkoni boʻlmaganda ishlatiladi.<br><b>Eslatma</b>: Agar qabul qiluvchi domen DNSSEC bilan DANE ni qoʻllab-quvvatlasa, DANE <b>har doim</b> afzal koʻriladi — MTA-STS faqat zaxira sifatida ishlaydi.", "mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> — pochta serverlari oʻrtasida xat yetkazib berishni haqiqiy sertifikatli TLS dan foydalanishga majburlovchi standart. <br>U DNSSEC mavjud emasligi yoki qoʻllab-quvvatlanmasligi sababli <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> imkoni boʻlmaganda ishlatiladi.<br><b>Eslatma</b>: Agar qabul qiluvchi domen DNSSEC bilan DANE ni qoʻllab-quvvatlasa, DANE <b>har doim</b> afzal koʻriladi — MTA-STS faqat zaxira sifatida ishlaydi.",
"mta_sts_version": "Versiya", "mta_sts_version": "Versiya",
@@ -745,6 +768,7 @@
"sender_acl_info": "Agar A pochta qutisi foydalanuvchisi B pochta qutisi foydalanuvchisi sifatida joʻnatishga ruxsat etilgan boʻlsa, joʻnatuvchi manzili SOGo da tanlanadigan \"from\" maydoni sifatida avtomatik koʻrsatilmaydi.<br>\r\n B pochta qutisi foydalanuvchisi A pochta qutisi foydalanuvchisi oʻz manzilini joʻnatuvchi sifatida tanlay olishi uchun SOGo da delegatsiya yaratishi kerak. SOGo da pochta qutisini delegatsiya qilish uchun pochta koʻrinishida yuqori chap qismdagi pochta qutisi nomingiz oʻng tomonidagi menyudan (uch nuqta) foydalaning. Bu xatti-harakat taxallus manzillariga qoʻllanmaydi.", "sender_acl_info": "Agar A pochta qutisi foydalanuvchisi B pochta qutisi foydalanuvchisi sifatida joʻnatishga ruxsat etilgan boʻlsa, joʻnatuvchi manzili SOGo da tanlanadigan \"from\" maydoni sifatida avtomatik koʻrsatilmaydi.<br>\r\n B pochta qutisi foydalanuvchisi A pochta qutisi foydalanuvchisi oʻz manzilini joʻnatuvchi sifatida tanlay olishi uchun SOGo da delegatsiya yaratishi kerak. SOGo da pochta qutisini delegatsiya qilish uchun pochta koʻrinishida yuqori chap qismdagi pochta qutisi nomingiz oʻng tomonidagi menyudan (uch nuqta) foydalaning. Bu xatti-harakat taxallus manzillariga qoʻllanmaydi.",
"sieve_desc": "Qisqa tavsif", "sieve_desc": "Qisqa tavsif",
"sieve_type": "Filtr turi", "sieve_type": "Filtr turi",
"skipcrossduplicates": "Papkalararo dublikat xabarlarni oʻtkazib yuborish (birinchi kelgan, birinchi xizmatlanadi)",
"sogo_access": "SOGo ga toʻgʻridan-toʻgʻri yoʻnaltirish", "sogo_access": "SOGo ga toʻgʻridan-toʻgʻri yoʻnaltirish",
"sogo_access_info": "Tizimga kirgandan keyin foydalanuvchi avtomatik ravishda SOGo ga yoʻnaltiriladi.", "sogo_access_info": "Tizimga kirgandan keyin foydalanuvchi avtomatik ravishda SOGo ga yoʻnaltiriladi.",
"sogo_visible": "Taxallus SOGo da koʻrinadi", "sogo_visible": "Taxallus SOGo da koʻrinadi",
@@ -753,8 +777,12 @@
"spam_filter": "Spam filtri", "spam_filter": "Spam filtri",
"spam_policy": "Elementlarni ruxsat etilgan/qora roʻyxatga qoʻshish yoki olib tashlash", "spam_policy": "Elementlarni ruxsat etilgan/qora roʻyxatga qoʻshish yoki olib tashlash",
"spam_score": "Maxsus spam bahosini oʻrnatish", "spam_score": "Maxsus spam bahosini oʻrnatish",
"subfolder2": "Manzilning subpapkasiga sinxronizatsiya qilish<br><small>(boʻsh = subpapkadan foydalanmang)</small>",
"syncjob": "Sinxronizatsiya vazifasini tahrirlash",
"target_address": "Yoʻnaltirish manzil(lar)i <small>(vergul bilan ajratilgan)</small>", "target_address": "Yoʻnaltirish manzil(lar)i <small>(vergul bilan ajratilgan)</small>",
"target_domain": "Maqsad domen", "target_domain": "Maqsad domen",
"timeout1": "Masofaviy xostga ulanish vaqtining tugashi",
"timeout2": "Mahalliy xostga ulanish vaqtining tugashi",
"title": "Obyektni tahrirlash", "title": "Obyektni tahrirlash",
"unchanged_if_empty": "Oʻzgartirilmagan boʻlsa, boʻsh qoldiring", "unchanged_if_empty": "Oʻzgartirilmagan boʻlsa, boʻsh qoldiring",
"username": "Foydalanuvchi nomi", "username": "Foydalanuvchi nomi",
@@ -975,6 +1003,16 @@
"spam_aliases": "Vaqt. taxallus", "spam_aliases": "Vaqt. taxallus",
"stats": "Statistika", "stats": "Statistika",
"status": "Holat", "status": "Holat",
"sync_jobs": "Sinxronizatsiya vazifalari",
"syncjob_check_log": "Jurnalni tekshirish",
"syncjob_last_run_result": "Oxirgi ishga tushirilgan natija",
"syncjob_EX_OK": "Muvaffaqiyatli",
"syncjob_EXIT_CONNECTION_FAILURE": "Ulanish muammosi",
"syncjob_EXIT_TLS_FAILURE": "Shifrlangan ulanish bilan muammo",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Autentifikatsiya muammosi",
"syncjob_EXIT_OVERQUOTA": "Maqsad pochta qutisi kvotadan oshib ketdi",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Masofaviy serverga ulanib boʻlmadi",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Notoʻgʻri foydalanuvchi nomi yoki parol",
"table_size": "Jadval oʻlchami", "table_size": "Jadval oʻlchami",
"table_size_show_n": "%s ta element koʻrsatish", "table_size_show_n": "%s ta element koʻrsatish",
"target_address": "Yoʻnaltirish manzili", "target_address": "Yoʻnaltirish manzili",
@@ -1211,37 +1249,6 @@
"waiting_usb_register": "<i>USB qurilmasi kutilmoqda...</i><br><br>Iltimos, yuqorida parolingizni kiriting va USB qurilmangizdagi tugmani bosib roʻyxatga olinishingizni tasdiqlang.", "waiting_usb_register": "<i>USB qurilmasi kutilmoqda...</i><br><br>Iltimos, yuqorida parolingizni kiriting va USB qurilmangizdagi tugmani bosib roʻyxatga olinishingizni tasdiqlang.",
"yubi_otp": "Yubico OTP autentifikatsiya" "yubi_otp": "Yubico OTP autentifikatsiya"
}, },
"syncjobs": {
"sync_jobs": "Sinxronizatsiya vazifalari",
"syncjob_check_log": "Jurnalni tekshirish",
"syncjob_last_run_result": "Oxirgi ishga tushirilgan natija",
"syncjob_EX_OK": "Muvaffaqiyatli",
"syncjob_EXIT_CONNECTION_FAILURE": "Ulanish muammosi",
"syncjob_EXIT_TLS_FAILURE": "Shifrlangan ulanish bilan muammo",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Autentifikatsiya muammosi",
"syncjob_EXIT_OVERQUOTA": "Maqsad pochta qutisi kvotadan oshib ketdi",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Masofaviy serverga ulanib boʻlmadi",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Notoʻgʻri foydalanuvchi nomi yoki parol",
"add": "Sinxronizatsiya vazifasi qoʻshish",
"add_hint": "Parollar oddiy matn sifatida saqlanishini unutmang!",
"edit": "Sinxronizatsiya vazifasini tahrirlash",
"create": "Yangi sinxronizatsiya vazifasi yaratish",
"dry": "Sinxronizatsiyani simulyatsiya qilish",
"subfolder2": "Manzilning subpapkasiga sinxronizatsiya qilish<br><small>(boʻsh = subpapkadan foydalanmang)</small>",
"mins_interval": "Soʻrov oraligʻi (daqiqa)",
"maxage": "Masofadan soʻraladigan xabarlarning maksimal yoshi (kunlarda)<br><small>(0 = yoshni e'tiborsiz qoldirish)</small>",
"exclude": "Obyektlarni istisno qilish (regex)",
"automap": "Papkalarni avtomatik xaritalashga harakat qilish (\"Sent items\", \"Sent\" => \"Sent\" va h.k.)",
"delete1": "Tugatilgach manbadan oʻchirish",
"delete2": "Manbada boʻlmagan xabarlarni manzildan oʻchirish",
"delete2duplicates": "Manzildagi dublikatlarni oʻchirish",
"skipcrossduplicates": "Papkalararo dublikat xabarlarni oʻtkazib yuborish (birinchi kelgan, birinchi xizmatlanadi)",
"subscribeall": "Barcha papkalarga obuna boʻlish",
"timeout1": "Masofaviy xostga ulanish vaqtining tugashi",
"timeout2": "Mahalliy xostga ulanish vaqtining tugashi",
"maxbytespersecond": "Maks. soniyaga bayt <br><small>(0 = cheksiz)</small>",
"custom_params": "Maxsus parametrlar"
},
"user": { "user": {
"action": "Amal", "action": "Amal",
"active": "Faol", "active": "Faol",
@@ -1272,6 +1279,7 @@
"clear_recent_successful_connections": "Koʻrilgan muvaffaqiyatli ulanishlarni tozalash", "clear_recent_successful_connections": "Koʻrilgan muvaffaqiyatli ulanishlarni tozalash",
"client_configuration": "E-pochta mijozlari va smartfonlar uchun konfiguratsiya qoʻllanmalarini koʻrsatish", "client_configuration": "E-pochta mijozlari va smartfonlar uchun konfiguratsiya qoʻllanmalarini koʻrsatish",
"create_app_passwd": "Ilova paroli yaratish", "create_app_passwd": "Ilova paroli yaratish",
"create_syncjob": "Yangi sinxronizatsiya vazifasi yaratish",
"created_on": "Yaratilgan sana", "created_on": "Yaratilgan sana",
"daily": "Kunlik", "daily": "Kunlik",
"day": "kun", "day": "kun",
@@ -1381,6 +1389,16 @@
"spamfilter_wl_desc": "Oq roʻyxatdagi e-pochta manzillari <b>hech qachon</b> spam sifatida tasniflanmaydi. Joker belgilar ishlatilishi mumkin. Filtr faqat toʻgʻridan-toʻgʻri taxalluslarga (bitta maqsad pochta qutisiga ega taxalluslarga) qoʻllaniladi, hammasini-qabul qilish taxalluslari va pochta qutisining oʻzi istisno qilinadi.", "spamfilter_wl_desc": "Oq roʻyxatdagi e-pochta manzillari <b>hech qachon</b> spam sifatida tasniflanmaydi. Joker belgilar ishlatilishi mumkin. Filtr faqat toʻgʻridan-toʻgʻri taxalluslarga (bitta maqsad pochta qutisiga ega taxalluslarga) qoʻllaniladi, hammasini-qabul qilish taxalluslari va pochta qutisining oʻzi istisno qilinadi.",
"spamfilter_yellow": "Sariq: bu xabar spam boʻlishi mumkin, spam sifatida belgilanadi va Junk papkangizga koʻchiriladi", "spamfilter_yellow": "Sariq: bu xabar spam boʻlishi mumkin, spam sifatida belgilanadi va Junk papkangizga koʻchiriladi",
"status": "Holat", "status": "Holat",
"sync_jobs": "Sinxronizatsiya vazifalari",
"syncjob_check_log": "Jurnalni tekshirish",
"syncjob_last_run_result": "Oxirgi ishga tushirilgan natija",
"syncjob_EX_OK": "Muvaffaqiyatli",
"syncjob_EXIT_CONNECTION_FAILURE": "Ulanish muammosi",
"syncjob_EXIT_TLS_FAILURE": "Shifrlangan ulanish bilan muammo",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Autentifikatsiya muammosi",
"syncjob_EXIT_OVERQUOTA": "Maqsad pochta qutisi kvotadan oshib ketdi",
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Masofaviy serverga ulanib boʻlmadi",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Notoʻgʻri foydalanuvchi nomi yoki parol",
"tag_handling": "Teglangan xat uchun ishlovni oʻrnatish", "tag_handling": "Teglangan xat uchun ishlovni oʻrnatish",
"tag_help_example": "Teglangan e-pochta manzili misoli: me<b>+Facebook</b>@example.org", "tag_help_example": "Teglangan e-pochta manzili misoli: me<b>+Facebook</b>@example.org",
"tag_help_explain": "Subpapkada: tegga qarab nomlangan yangi subpapka INBOX ostida yaratiladi (\"INBOX/Facebook\").<br>\r\nMavzuda: teg nomi xatning mavzusiga oldindan qoʻshiladi, misol: \"[Facebook] Mening yangiliklarim\".", "tag_help_explain": "Subpapkada: tegga qarab nomlangan yangi subpapka INBOX ostida yaratiladi (\"INBOX/Facebook\").<br>\r\nMavzuda: teg nomi xatning mavzusiga oldindan qoʻshiladi, misol: \"[Facebook] Mening yangiliklarim\".",
+20 -16
View File
@@ -44,16 +44,24 @@
"app_name": "Tên ứng dụng", "app_name": "Tên ứng dụng",
"app_password": "Thêm mật khẩu ứng dụng", "app_password": "Thêm mật khẩu ứng dụng",
"app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng", "app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng",
"automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)",
"backup_mx_options": "Tùy chọn chuyển tiếp", "backup_mx_options": "Tùy chọn chuyển tiếp",
"bcc_dest_format": "Địa chỉ BCC phải là một địa chỉ email hợp lệ duy nhất.<br>Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.", "bcc_dest_format": "Địa chỉ BCC phải là một địa chỉ email hợp lệ duy nhất.<br>Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.",
"comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng", "comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng",
"custom_params": "Tham số tùy chỉnh",
"custom_params_hint": "Đúng: --param=xy, sai: --param xy",
"delete1": "Xóa từ nguồn khi hoàn thành",
"delete2": "Xóa thư ở đích không có ở nguồn",
"delete2duplicates": "Xóa các bản sao ở đích",
"description": "Mô tả", "description": "Mô tả",
"destination": "Đích", "destination": "Đích",
"disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)", "disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)",
"domain": "Tên miền", "domain": "Tên miền",
"domain_matches_hostname": "Tên miền %s khớp với tên máy chủ", "domain_matches_hostname": "Tên miền %s khớp với tên máy chủ",
"domain_quota_m": "Tổng hạn ngạch tên miền (MiB)", "domain_quota_m": "Tổng hạn ngạch tên miền (MiB)",
"dry": "Mô phỏng đồng bộ hóa",
"enc_method": "Phương thức mã hóa", "enc_method": "Phương thức mã hóa",
"exclude": "Loại trừ đối tượng (biểu thức chính quy)",
"full_name": "Tên đầy đủ", "full_name": "Tên đầy đủ",
"gal": "Danh sách địa chỉ toàn cục", "gal": "Danh sách địa chỉ toàn cục",
"gal_info": "GAL chứa tất cả các đối tượng của một tên miền và không thể được chỉnh sửa bởi bất kỳ người dùng nào. Thông tin rảnh/bận trong SOGo sẽ bị thiếu nếu vô hiệu hóa! <b>Khởi động lại SOGo để áp dụng thay đổi.</b>", "gal_info": "GAL chứa tất cả các đối tượng của một tên miền và không thể được chỉnh sửa bởi bất kỳ người dùng nào. Thông tin rảnh/bận trong SOGo sẽ bị thiếu nếu vô hiệu hóa! <b>Khởi động lại SOGo để áp dụng thay đổi.</b>",
@@ -71,6 +79,7 @@
"mailbox_username": "Tên người dùng (phần bên trái của địa chỉ email)", "mailbox_username": "Tên người dùng (phần bên trái của địa chỉ email)",
"max_aliases": "Số lượng bí danh tối đa có thể tạo", "max_aliases": "Số lượng bí danh tối đa có thể tạo",
"max_mailboxes": "Số lượng hộp thư tối đa có thể tạo", "max_mailboxes": "Số lượng hộp thư tối đa có thể tạo",
"mins_interval": "Khoảng thời gian kiểm tra (phút)",
"multiple_bookings": "Đặt chỗ nhiều lần", "multiple_bookings": "Đặt chỗ nhiều lần",
"nexthop": "Bước nhảy tiếp theo", "nexthop": "Bước nhảy tiếp theo",
"password": "Mật khẩu", "password": "Mật khẩu",
@@ -90,10 +99,16 @@
"select_domain": "Vui lòng chọn tên miền trước", "select_domain": "Vui lòng chọn tên miền trước",
"sieve_desc": "Mô tả ngắn", "sieve_desc": "Mô tả ngắn",
"sieve_type": "Loại bộ lọc", "sieve_type": "Loại bộ lọc",
"skipcrossduplicates": "Bỏ qua tin nhắn trùng lặp giữa các thư mục (ai đến trước được phục vụ trước)",
"subscribeall": "Đăng ký tất cả thư mục",
"syncjob": "Thêm công việc đồng bộ",
"syncjob_hint": "Lưu ý rằng mật khẩu cần được lưu dưới dạng văn bản thuần!",
"tags": "Thẻ", "tags": "Thẻ",
"target_address": "Địa chỉ chuyển tiếp", "target_address": "Địa chỉ chuyển tiếp",
"target_address_info": "<small>Địa chỉ email đầy đủ (phân cách bằng dấu phẩy).</small>", "target_address_info": "<small>Địa chỉ email đầy đủ (phân cách bằng dấu phẩy).</small>",
"target_domain": "Tên miền đích", "target_domain": "Tên miền đích",
"timeout1": "Thời gian chờ kết nối đến máy chủ từ xa",
"timeout2": "Thời gian chờ kết nối đến máy chủ cục bộ",
"username": "Tên người dùng", "username": "Tên người dùng",
"validate": "Xác thực", "validate": "Xác thực",
"validation_success": "Xác thực thành công", "validation_success": "Xác thực thành công",
@@ -631,6 +646,7 @@
"app_name": "Tên ứng dụng", "app_name": "Tên ứng dụng",
"app_passwd": "Mật khẩu ứng dụng", "app_passwd": "Mật khẩu ứng dụng",
"app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng", "app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng",
"automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)",
"backup_mx_options": "Tùy chọn chuyển tiếp", "backup_mx_options": "Tùy chọn chuyển tiếp",
"bcc_dest_format": "Đích BCC phải là một địa chỉ email hợp lệ duy nhất.<br>Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.", "bcc_dest_format": "Đích BCC phải là một địa chỉ email hợp lệ duy nhất.<br>Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.",
"client_id": "ID khách hàng", "client_id": "ID khách hàng",
@@ -638,6 +654,9 @@
"comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng", "comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng",
"created_on": "Được tạo vào", "created_on": "Được tạo vào",
"custom_attributes": "Thuộc tính tùy chỉnh", "custom_attributes": "Thuộc tính tùy chỉnh",
"delete1": "Xóa từ nguồn khi hoàn thành",
"delete2": "Xóa thư ở đích không có ở nguồn",
"delete2duplicates": "Xóa các bản sao ở đích",
"delete_ays": "Vui lòng xác nhận quá trình xóa.", "delete_ays": "Vui lòng xác nhận quá trình xóa.",
"description": "Mô tả", "description": "Mô tả",
"disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)", "disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)",
@@ -661,6 +680,7 @@
"dont_check_sender_acl": "Tắt kiểm tra người gửi cho tên miền %s (+ tên miền bí danh)", "dont_check_sender_acl": "Tắt kiểm tra người gửi cho tên miền %s (+ tên miền bí danh)",
"edit_alias_domain": "Chỉnh sửa tên miền bí danh", "edit_alias_domain": "Chỉnh sửa tên miền bí danh",
"encryption": "Mã hóa", "encryption": "Mã hóa",
"exclude": "Loại trừ đối tượng (biểu thức chính quy)",
"extended_sender_acl": "Địa chỉ người gửi bên ngoài", "extended_sender_acl": "Địa chỉ người gửi bên ngoài",
"extended_sender_acl_info": "Khóa tên miền DKIM nên được nhập vào, nếu có sẵn.<br>\n Nhớ thêm máy chủ này vào bản ghi SPF TXT tương ứng.<br>\n Khi một tên miền hoặc tên miền bí danh được thêm vào máy chủ này, mà trùng với một địa chỉ bên ngoài, địa chỉ bên ngoài sẽ bị xóa.<br>\n Sử dụng @domain.tld để cho phép gửi dưới dạng *@domain.tld.", "extended_sender_acl_info": "Khóa tên miền DKIM nên được nhập vào, nếu có sẵn.<br>\n Nhớ thêm máy chủ này vào bản ghi SPF TXT tương ứng.<br>\n Khi một tên miền hoặc tên miền bí danh được thêm vào máy chủ này, mà trùng với một địa chỉ bên ngoài, địa chỉ bên ngoài sẽ bị xóa.<br>\n Sử dụng @domain.tld để cho phép gửi dưới dạng *@domain.tld.",
"force_pw_update": "Bắt buộc cập nhật mật khẩu trong lần đăng nhập tiếp theo", "force_pw_update": "Bắt buộc cập nhật mật khẩu trong lần đăng nhập tiếp theo",
@@ -691,21 +711,5 @@
"max_aliases": "Số lượng Bí danh tối đa", "max_aliases": "Số lượng Bí danh tối đa",
"max_mailboxes": "Số lượng hộp thư tối đa có thể có", "max_mailboxes": "Số lượng hộp thư tối đa có thể có",
"max_quota": "Dung lượng tối đa cho mỗi hộp thư (MiB)" "max_quota": "Dung lượng tối đa cho mỗi hộp thư (MiB)"
},
"syncjobs": {
"add": "Thêm công việc đồng bộ",
"add_hint": "Lưu ý rằng mật khẩu cần được lưu dưới dạng văn bản thuần!",
"dry": "Mô phỏng đồng bộ hóa",
"mins_interval": "Khoảng thời gian kiểm tra (phút)",
"exclude": "Loại trừ đối tượng (biểu thức chính quy)",
"automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)",
"delete1": "Xóa từ nguồn khi hoàn thành",
"delete2": "Xóa thư ở đích không có ở nguồn",
"delete2duplicates": "Xóa các bản sao ở đích",
"skipcrossduplicates": "Bỏ qua tin nhắn trùng lặp giữa các thư mục (ai đến trước được phục vụ trước)",
"subscribeall": "Đăng ký tất cả thư mục",
"timeout1": "Thời gian chờ kết nối đến máy chủ từ xa",
"timeout2": "Thời gian chờ kết nối đến máy chủ cục bộ",
"custom_params": "Tham số tùy chỉnh"
} }
} }
+49 -31
View File
@@ -45,9 +45,15 @@
"app_name": "应用名称", "app_name": "应用名称",
"app_password": "添加应用密码", "app_password": "添加应用密码",
"app_passwd_protocols": "应用密码允许的协议", "app_passwd_protocols": "应用密码允许的协议",
"automap": "将尝试自动映射文件夹 (例如将 \"已发送消息\" 和 \"已发送\" 均映射到 \"已发送\" 文件夹)",
"backup_mx_options": "中继选项", "backup_mx_options": "中继选项",
"bcc_dest_format": "BCC 的目标地址必须是一个有效的电子邮箱地址。<br>如果你需要向多个地址发送副本,请创建一个别名并在此使用。", "bcc_dest_format": "BCC 的目标地址必须是一个有效的电子邮箱地址。<br>如果你需要向多个地址发送副本,请创建一个别名并在此使用。",
"comment_info": "私密评论对用户不可见,公开评论将会在鼠标悬停时作为用户提示显示", "comment_info": "私密评论对用户不可见,公开评论将会在鼠标悬停时作为用户提示显示",
"custom_params": "自定义参数",
"custom_params_hint": "正确的格式: --param=xy ,错误的格式: --param xy",
"delete1": "在完成后删除源邮件",
"delete2": "删除在目标邮箱中存在但在源邮箱中不存在的邮件",
"delete2duplicates": "删除目标邮箱中的重复邮件",
"description": "描述", "description": "描述",
"destination": "目标邮箱", "destination": "目标邮箱",
"disable_login": "不允许登录 (但仍然会接收邮件)", "disable_login": "不允许登录 (但仍然会接收邮件)",
@@ -55,6 +61,7 @@
"domain_matches_hostname": "域名 %s 与主机名称匹配", "domain_matches_hostname": "域名 %s 与主机名称匹配",
"domain_quota_m": "域名总配额 (MiB)", "domain_quota_m": "域名总配额 (MiB)",
"enc_method": "加密方法", "enc_method": "加密方法",
"exclude": "拒绝对象 (Regex)",
"full_name": "全称", "full_name": "全称",
"gal": "全球地址簿", "gal": "全球地址簿",
"gal_info": "全球地址簿包含了域名下的所有对象,并且此行为不能被用户更改。如果关闭,用户的 \"空闲/繁忙\" 的状态将无法在 SOGo 中显示。 <b>重启 SOGo 服务以应用更改。</b>", "gal_info": "全球地址簿包含了域名下的所有对象,并且此行为不能被用户更改。如果关闭,用户的 \"空闲/繁忙\" 的状态将无法在 SOGo 中显示。 <b>重启 SOGo 服务以应用更改。</b>",
@@ -70,6 +77,7 @@
"mailbox_username": "用户名 (邮箱地址左侧的部分)", "mailbox_username": "用户名 (邮箱地址左侧的部分)",
"max_aliases": "最大允许的地址别名数", "max_aliases": "最大允许的地址别名数",
"max_mailboxes": "最大允许的邮箱数", "max_mailboxes": "最大允许的邮箱数",
"mins_interval": "轮询间隔 (分钟)",
"multiple_bookings": "登记限制", "multiple_bookings": "登记限制",
"nexthop": "下一跳", "nexthop": "下一跳",
"password": "密码", "password": "密码",
@@ -89,13 +97,20 @@
"select_domain": "请先选择一个域名", "select_domain": "请先选择一个域名",
"sieve_desc": "简短描述", "sieve_desc": "简短描述",
"sieve_type": "过滤器类型", "sieve_type": "过滤器类型",
"skipcrossduplicates": "跳过其他文件夹中已存在的邮件 (保留已经存在的邮件)",
"subscribeall": "订阅所有文件夹",
"syncjob": "添加同步任务",
"syncjob_hint": "注意密码将以明文存储!",
"tags": "标签", "tags": "标签",
"target_address": "目标地址", "target_address": "目标地址",
"target_address_info": "<small>完整的邮箱地址 (存在多个地址时使用英文逗号分隔)</small>", "target_address_info": "<small>完整的邮箱地址 (存在多个地址时使用英文逗号分隔)</small>",
"target_domain": "目标域名", "target_domain": "目标域名",
"timeout1": "远程主机连接超时时间",
"timeout2": "本地主机连接超时时间",
"username": "用户名", "username": "用户名",
"validate": "验证", "validate": "验证",
"validation_success": "验证成功", "validation_success": "验证成功",
"dry": "模拟同步(Dry run)",
"internal_info": "内部的别名只能在域内部或者别名域内部访问。", "internal_info": "内部的别名只能在域内部或者别名域内部访问。",
"internal": "内部的", "internal": "内部的",
"sender_allowed": "允许以该别名发送" "sender_allowed": "允许以该别名发送"
@@ -608,11 +623,15 @@
"app_name": "应用名称", "app_name": "应用名称",
"app_passwd": "应用密码", "app_passwd": "应用密码",
"app_passwd_protocols": "应用密码允许的协议", "app_passwd_protocols": "应用密码允许的协议",
"automap": "将尝试自动映射文件夹 (例如将 \"已发送消息\" 和 \"已发送\" 均映射到 \"已发送\" 文件夹)",
"backup_mx_options": "中继选项", "backup_mx_options": "中继选项",
"bcc_dest_format": "BCC 的目标必须是一个有效的电子邮箱地址。<br>如果你需要向多个地址发送副本,请创建一个别名并在此使用。", "bcc_dest_format": "BCC 的目标必须是一个有效的电子邮箱地址。<br>如果你需要向多个地址发送副本,请创建一个别名并在此使用。",
"client_id": "客户端 ID", "client_id": "客户端 ID",
"client_secret": "客户端 secret", "client_secret": "客户端 secret",
"comment_info": "私密评论对用户不可见,公开评论将会在鼠标悬停时作为用户提示显示", "comment_info": "私密评论对用户不可见,公开评论将会在鼠标悬停时作为用户提示显示",
"delete1": "在完成后删除源邮件",
"delete2": "删除在目标邮箱中存在但在源邮箱中不存在的邮件",
"delete2duplicates": "删除目标邮箱中的重复邮件",
"delete_ays": "请确认删除。", "delete_ays": "请确认删除。",
"description": "描述", "description": "描述",
"disable_login": "不允许登录 (但仍然会接收邮件)", "disable_login": "不允许登录 (但仍然会接收邮件)",
@@ -623,6 +642,7 @@
"dont_check_sender_acl": "为域名 %s (+ 域名别名) 关闭发件人检查", "dont_check_sender_acl": "为域名 %s (+ 域名别名) 关闭发件人检查",
"edit_alias_domain": "编辑域名别名", "edit_alias_domain": "编辑域名别名",
"encryption": "加密", "encryption": "加密",
"exclude": "排除对象 (Regex)",
"extended_sender_acl": "外部发件人地址", "extended_sender_acl": "外部发件人地址",
"extended_sender_acl_info": "当可用时请导入域名的 DKIM 密钥。<br>\r\n 请注意将此服务器添加到相应的 SPF TXT 记录中。<br>\r\n 当域名或域名别名被添加时,若其与此外部发件人地址交叠,则外部发件人地址将会被移除。<br>\r\n 填入 @domain.tld 以允许作为 *@domain.tld 发送邮件。", "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": "在下一次登录时强制要求更新密码",
@@ -642,7 +662,10 @@
"max_aliases": "最大允许的地址别名数", "max_aliases": "最大允许的地址别名数",
"max_mailboxes": "最大允许的邮箱数", "max_mailboxes": "最大允许的邮箱数",
"max_quota": "每个邮箱的最大配额 (MiB)", "max_quota": "每个邮箱的最大配额 (MiB)",
"maxage": "从远程拉取消息的最大时间间隔限制<br><small>(0表示忽略)</small>",
"maxbytespersecond": "最大速率 (Bytes/s) <br><small>(0表示无限制)</small>",
"mbox_rl_info": "此频率限制应用于 SASL 登录名,它会匹配邮件的 \"from\" 地址。邮箱的频率限制设置会覆盖域名的频率限制值设置。", "mbox_rl_info": "此频率限制应用于 SASL 登录名,它会匹配邮件的 \"from\" 地址。邮箱的频率限制设置会覆盖域名的频率限制值设置。",
"mins_interval": "轮询间隔 (分钟)",
"multiple_bookings": "登记限制", "multiple_bookings": "登记限制",
"none_inherit": "无/继承", "none_inherit": "无/继承",
"nexthop": "下一跳", "nexthop": "下一跳",
@@ -681,6 +704,7 @@
"sender_acl_info": "如果允许邮箱用户 A 作为邮箱用户 B 发送邮件,发件人的地址不会在 SOGo 中的 \"from\" 区域中作为下拉项显示。<br>\r\n 邮箱用户 B 需要添加授权以允许邮箱用户 A 选择 B 的地址作为发件人;授权方法为,在 SOGo 中点击左上方邮箱地址右边的菜单按钮 (三个点) 并授权。", "sender_acl_info": "如果允许邮箱用户 A 作为邮箱用户 B 发送邮件,发件人的地址不会在 SOGo 中的 \"from\" 区域中作为下拉项显示。<br>\r\n 邮箱用户 B 需要添加授权以允许邮箱用户 A 选择 B 的地址作为发件人;授权方法为,在 SOGo 中点击左上方邮箱地址右边的菜单按钮 (三个点) 并授权。",
"sieve_desc": "简短描述", "sieve_desc": "简短描述",
"sieve_type": "过滤器类型", "sieve_type": "过滤器类型",
"skipcrossduplicates": "跳过其他文件夹中已存在的邮件(保留已经存在的邮件)",
"sogo_access": "直接转到 SOGo", "sogo_access": "直接转到 SOGo",
"sogo_access_info": "登录后,用户会自动跳转到 SOGo。", "sogo_access_info": "登录后,用户会自动跳转到 SOGo。",
"sogo_visible": "SOGo 显示的别名", "sogo_visible": "SOGo 显示的别名",
@@ -689,8 +713,12 @@
"spam_filter": "垃圾邮件过滤器", "spam_filter": "垃圾邮件过滤器",
"spam_policy": "将项目添加到白/黑名单或从其中移除", "spam_policy": "将项目添加到白/黑名单或从其中移除",
"spam_score": "自定义垃圾邮件分数", "spam_score": "自定义垃圾邮件分数",
"subfolder2": "同步到目标邮箱的子文件夹<br><small>(留空表示不使用子文件夹)</small>",
"syncjob": "编辑同步任务",
"target_address": "目标地址 <small>(存在多个地址时使用英文逗号分隔)</small>", "target_address": "目标地址 <small>(存在多个地址时使用英文逗号分隔)</small>",
"target_domain": "目标域名", "target_domain": "目标域名",
"timeout1": "远程主机连接超时时间",
"timeout2": "本地主机连接超时时间",
"title": "编辑对象", "title": "编辑对象",
"unchanged_if_empty": "如果不更改则留空", "unchanged_if_empty": "如果不更改则留空",
"username": "用户名", "username": "用户名",
@@ -936,6 +964,16 @@
"spam_aliases": "临时别名", "spam_aliases": "临时别名",
"stats": "统计", "stats": "统计",
"status": "状态", "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": "表格尺寸",
"table_size_show_n": "显示 %s 个项目", "table_size_show_n": "显示 %s 个项目",
"target_address": "目标地址", "target_address": "目标地址",
@@ -1188,37 +1226,6 @@
"setup_required": "你的账户需要设置两步验证。请添加一个两步验证方法以继续。", "setup_required": "你的账户需要设置两步验证。请添加一个两步验证方法以继续。",
"cancel_setup": "取消并注销" "cancel_setup": "取消并注销"
}, },
"syncjobs": {
"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": "用户名或密码错误",
"add": "添加同步任务",
"add_hint": "注意密码将以明文存储!",
"edit": "编辑同步任务",
"create": "添加同步任务",
"dry": "模拟同步(Dry run)",
"subfolder2": "同步到目标邮箱的子文件夹<br><small>(留空表示不使用子文件夹)</small>",
"mins_interval": "轮询间隔 (分钟)",
"maxage": "从远程拉取消息的最大时间间隔限制<br><small>(0表示忽略)</small>",
"exclude": "拒绝对象 (Regex)",
"automap": "将尝试自动映射文件夹 (例如将 \"已发送消息\" 和 \"已发送\" 均映射到 \"已发送\" 文件夹)",
"delete1": "在完成后删除源邮件",
"delete2": "删除在目标邮箱中存在但在源邮箱中不存在的邮件",
"delete2duplicates": "删除目标邮箱中的重复邮件",
"skipcrossduplicates": "跳过其他文件夹中已存在的邮件 (保留已经存在的邮件)",
"subscribeall": "订阅所有文件夹",
"timeout1": "远程主机连接超时时间",
"timeout2": "本地主机连接超时时间",
"maxbytespersecond": "最大速率 (Bytes/s) <br><small>(0表示无限制)</small>",
"custom_params": "自定义参数"
},
"user": { "user": {
"action": "操作", "action": "操作",
"active": "启用", "active": "启用",
@@ -1247,6 +1254,7 @@
"clear_recent_successful_connections": "清除成功匹配的连接", "clear_recent_successful_connections": "清除成功匹配的连接",
"client_configuration": "显示邮箱客户端和智能手机的配置指南", "client_configuration": "显示邮箱客户端和智能手机的配置指南",
"create_app_passwd": "添加应用密码", "create_app_passwd": "添加应用密码",
"create_syncjob": "添加同步任务",
"created_on": "添加于", "created_on": "添加于",
"daily": "每日", "daily": "每日",
"day": "日", "day": "日",
@@ -1347,6 +1355,16 @@
"spamfilter_wl_desc": "允许名单中地址<b>永远不会</b>被标记为垃圾邮件。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", "spamfilter_wl_desc": "允许名单中地址<b>永远不会</b>被标记为垃圾邮件。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。",
"spamfilter_yellow": "黄色: 此为垃圾邮件,会被标记为垃圾邮件并且移入垃圾邮件文件夹", "spamfilter_yellow": "黄色: 此为垃圾邮件,会被标记为垃圾邮件并且移入垃圾邮件文件夹",
"status": "状态", "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_handling": "处理带有标签的邮件",
"tag_help_example": "带有标签的邮箱地址示例: me<b>+Facebook</b>@example.org", "tag_help_example": "带有标签的邮箱地址示例: me<b>+Facebook</b>@example.org",
"tag_help_explain": "置于子文件夹: 在 INBOX (收件箱) 下创建一个以标签名命名的子文件夹 (\"INBOX/Facebook\")。<br>\r\n置于主题: 标签名会被前置到邮件主题, 例如: \"[Facebook] My News\"。", "tag_help_explain": "置于子文件夹: 在 INBOX (收件箱) 下创建一个以标签名命名的子文件夹 (\"INBOX/Facebook\")。<br>\r\n置于主题: 标签名会被前置到邮件主题, 例如: \"[Facebook] My News\"。",
+50 -32
View File
@@ -44,9 +44,15 @@
"app_name": "應用程式名稱", "app_name": "應用程式名稱",
"app_password": "新增應用程式密碼", "app_password": "新增應用程式密碼",
"app_passwd_protocols": "此應用程式密碼所允許的協定", "app_passwd_protocols": "此應用程式密碼所允許的協定",
"automap": "自動歸類資料夾規則表 (如:\"已發送\", \"Sent\" => \"Sent\" 等...)",
"backup_mx_options": "中繼選項", "backup_mx_options": "中繼選項",
"bcc_dest_format": "密件副本地址必須是單一的有效地址。<br>如果你需要傳送到多個地址,請幫它們建立一個別名然後設定於此欄。", "bcc_dest_format": "密件副本地址必須是單一的有效地址。<br>如果你需要傳送到多個地址,請幫它們建立一個別名然後設定於此欄。",
"comment_info": "隱密備註不會被使用者看到,公開備註則會在使用者游標懸停於概述頁時顯示於提示框", "comment_info": "隱密備註不會被使用者看到,公開備註則會在使用者游標懸停於概述頁時顯示於提示框",
"custom_params": "訂製參數",
"custom_params_hint": "正確的用法:--param=xy,錯誤的用法:--param xy",
"delete1": "完成後將來源郵件刪除",
"delete2": "刪除目的地信箱中存在但來源信箱中不存在的郵件",
"delete2duplicates": "刪除目的地信箱中的重複郵件",
"description": "描述", "description": "描述",
"destination": "目的地信箱", "destination": "目的地信箱",
"disable_login": "不允許登入 (但郵件仍然會正常接收)", "disable_login": "不允許登入 (但郵件仍然會正常接收)",
@@ -54,6 +60,7 @@
"domain_matches_hostname": "域名 %s 與主機名稱匹配", "domain_matches_hostname": "域名 %s 與主機名稱匹配",
"domain_quota_m": "域名總容量配額 (MiB)", "domain_quota_m": "域名總容量配額 (MiB)",
"enc_method": "加密方式", "enc_method": "加密方式",
"exclude": "拒絕物件 (regex)",
"full_name": "全名", "full_name": "全名",
"gal": "全域地址清單", "gal": "全域地址清單",
"gal_info": "<b>全域地址清單</b>包含了域名下所有的物件,且它們不能被使用者更改。如果關閉,使用者的 閒置/忙碌中 狀態將無法在 SOGo 中顯示。 <b>需重新啟動 SOGo 來更改。</b>", "gal_info": "<b>全域地址清單</b>包含了域名下所有的物件,且它們不能被使用者更改。如果關閉,使用者的 閒置/忙碌中 狀態將無法在 SOGo 中顯示。 <b>需重新啟動 SOGo 來更改。</b>",
@@ -69,6 +76,7 @@
"mailbox_username": "使用者名稱 (郵件地址的左側部分)", "mailbox_username": "使用者名稱 (郵件地址的左側部分)",
"max_aliases": "地址別名上限", "max_aliases": "地址別名上限",
"max_mailboxes": "信箱數量上限", "max_mailboxes": "信箱數量上限",
"mins_interval": "輪詢間隔 (分鐘)",
"multiple_bookings": "允許重複預約", "multiple_bookings": "允許重複預約",
"nexthop": "下一個跳躍點", "nexthop": "下一個跳躍點",
"password": "密碼", "password": "密碼",
@@ -88,13 +96,20 @@
"select_domain": "請先選擇一個域名", "select_domain": "請先選擇一個域名",
"sieve_desc": "簡短描述", "sieve_desc": "簡短描述",
"sieve_type": "過濾器類型", "sieve_type": "過濾器類型",
"skipcrossduplicates": "跳過在其他資料夾中重複的郵件 (優先使用先找到的郵件)",
"subscribeall": "訂閱所有資料夾",
"syncjob": "新增同步任務",
"syncjob_hint": "請注意此密碼會以明文儲存!",
"tags": "標籤", "tags": "標籤",
"target_address": "目標地址", "target_address": "目標地址",
"target_address_info": "<small>完整的信箱地址 (以英文逗號分隔多個地址)。</small>", "target_address_info": "<small>完整的信箱地址 (以英文逗號分隔多個地址)。</small>",
"target_domain": "目標域名", "target_domain": "目標域名",
"timeout1": "遠端主機連線逾時時間",
"timeout2": "本地主機連線逾時時間",
"username": "使用者名稱", "username": "使用者名稱",
"validate": "驗證", "validate": "驗證",
"validation_success": "驗證成功" "validation_success": "驗證成功",
"dry": "模擬同步"
}, },
"admin": { "admin": {
"access": "存取", "access": "存取",
@@ -542,12 +557,16 @@
"app_name": "應用程式名稱", "app_name": "應用程式名稱",
"app_passwd": "應用程式密碼", "app_passwd": "應用程式密碼",
"app_passwd_protocols": "應用程式密碼允許的協定", "app_passwd_protocols": "應用程式密碼允許的協定",
"automap": "自動分類資料夾規則 (如:\"已發送\", \"寄件備份\" => \"Sent\")",
"backup_mx_options": "中繼選項", "backup_mx_options": "中繼選項",
"bcc_dest_format": "密件副本地址必須是單一的有效地址。<br>如果你需要傳送到多個地址,請幫它們建立一個別名然後設定於此欄。", "bcc_dest_format": "密件副本地址必須是單一的有效地址。<br>如果你需要傳送到多個地址,請幫它們建立一個別名然後設定於此欄。",
"client_id": "用戶端 ID", "client_id": "用戶端 ID",
"client_secret": "用戶端金鑰", "client_secret": "用戶端金鑰",
"comment_info": "隱密備註不會被使用者看到,公開備註則會在使用者游標懸停於概述頁時顯示於提示框", "comment_info": "隱密備註不會被使用者看到,公開備註則會在使用者游標懸停於概述頁時顯示於提示框",
"created_on": "建立於", "created_on": "建立於",
"delete1": "完成後將來源郵件刪除",
"delete2": "刪除目的地信箱中存在但來源信箱中不存在的郵件",
"delete2duplicates": "刪除目的地信箱中的重複郵件",
"delete_ays": "確定要刪除?", "delete_ays": "確定要刪除?",
"description": "描述", "description": "描述",
"disable_login": "不允許登入 (但郵件仍然會正常接收)", "disable_login": "不允許登入 (但郵件仍然會正常接收)",
@@ -558,6 +577,7 @@
"dont_check_sender_acl": "關閉域名 %s (及其域名別名) 的寄件人檢查", "dont_check_sender_acl": "關閉域名 %s (及其域名別名) 的寄件人檢查",
"edit_alias_domain": "編輯域名別名", "edit_alias_domain": "編輯域名別名",
"encryption": "加密", "encryption": "加密",
"exclude": "拒絕物件 (正規表示式)",
"extended_sender_acl": "外部寄件人地址", "extended_sender_acl": "外部寄件人地址",
"extended_sender_acl_info": "如果可以的話,請匯入 DKIM 域名金鑰。<br>\r\n別忘記將此伺服器新增到相應的 SPF TXT 中。<br>\r\n當域名或域名別名被新增時,若其與此外部寄件人地址交疊,則外部寄件人地址會被移除。<br>\r\n填入 @domain.tld 以允許作為 *@domain.tld 發送郵件。", "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": "在下一次登入時強制要求更新密碼",
@@ -579,7 +599,10 @@
"max_aliases": "最大允許地址別名數", "max_aliases": "最大允許地址別名數",
"max_mailboxes": "最大允許信箱數", "max_mailboxes": "最大允許信箱數",
"max_quota": "每個信箱的最大容量配額 (MiB)", "max_quota": "每個信箱的最大容量配額 (MiB)",
"maxage": "從遠端拉取消息的最大消息年齡限制<br><small>(0 表示忽略)</small>",
"maxbytespersecond": "最大速率 (Bytes/s) <br><small>(0 表示不限)</small>",
"mbox_rl_info": "此頻率限制應用於 SASL 登入名,它會匹配任何郵件的\"寄件人\"地址。信箱的頻率限制設定會覆蓋域名的速率限制值設定。", "mbox_rl_info": "此頻率限制應用於 SASL 登入名,它會匹配任何郵件的\"寄件人\"地址。信箱的頻率限制設定會覆蓋域名的速率限制值設定。",
"mins_interval": "輪詢間隔 (分鐘)",
"multiple_bookings": "多重登記限制", "multiple_bookings": "多重登記限制",
"none_inherit": "無 / 繼承", "none_inherit": "無 / 繼承",
"nexthop": "下一跳", "nexthop": "下一跳",
@@ -618,6 +641,7 @@
"sender_acl_info": "如果信箱使用者 A 被允許以信箱使用者 B 發送郵件,該寄件人地址不會出現在 SOGo 中\"發送自\"的下拉選項中。<br>\r\n信箱使用者 B 需要新增授權以允許信箱使用者 A 選擇 B 的地址作為寄件人;授權方法為,在 SOGo 中點擊右上方信箱名稱左邊的選項按鈕(三個點)並授權。此行為不會套用於信箱別名。", "sender_acl_info": "如果信箱使用者 A 被允許以信箱使用者 B 發送郵件,該寄件人地址不會出現在 SOGo 中\"發送自\"的下拉選項中。<br>\r\n信箱使用者 B 需要新增授權以允許信箱使用者 A 選擇 B 的地址作為寄件人;授權方法為,在 SOGo 中點擊右上方信箱名稱左邊的選項按鈕(三個點)並授權。此行為不會套用於信箱別名。",
"sieve_desc": "簡短描述", "sieve_desc": "簡短描述",
"sieve_type": "過濾器類型", "sieve_type": "過濾器類型",
"skipcrossduplicates": "跳過在其他資料夾中重複的郵件 (優先使用先找到的郵件)",
"sogo_access": "直接轉寄至SOGo", "sogo_access": "直接轉寄至SOGo",
"sogo_access_info": "登入後,使用者會自動重新導向至SOGo。", "sogo_access_info": "登入後,使用者會自動重新導向至SOGo。",
"sogo_visible": "別名會在 SOGo 中顯示", "sogo_visible": "別名會在 SOGo 中顯示",
@@ -626,8 +650,12 @@
"spam_filter": "垃圾郵件過濾器", "spam_filter": "垃圾郵件過濾器",
"spam_policy": "將項目新增到白/黑名單或從其中移除", "spam_policy": "將項目新增到白/黑名單或從其中移除",
"spam_score": "自訂垃圾郵件分數", "spam_score": "自訂垃圾郵件分數",
"subfolder2": "同步到目標信箱子資料夾<br><small>(留空表示不使用子資料夾)</small>",
"syncjob": "編輯同步任務",
"target_address": "目標地址 <small>(以英文逗號分隔多個地址)</small>", "target_address": "目標地址 <small>(以英文逗號分隔多個地址)</small>",
"target_domain": "目標域名", "target_domain": "目標域名",
"timeout1": "遠端主機連接超時時間",
"timeout2": "本地主機連接超時時間",
"title": "編輯物件", "title": "編輯物件",
"unchanged_if_empty": "如果不更改則留空", "unchanged_if_empty": "如果不更改則留空",
"username": "使用者名稱", "username": "使用者名稱",
@@ -849,6 +877,16 @@
"spam_aliases": "臨時別名", "spam_aliases": "臨時別名",
"stats": "統計", "stats": "統計",
"status": "狀態", "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": "表格尺寸",
"table_size_show_n": "顯示 %s 個項目", "table_size_show_n": "顯示 %s 個項目",
"target_address": "目標地址", "target_address": "目標地址",
@@ -1065,37 +1103,6 @@
"yubi_otp": "Yubico OTP 認證", "yubi_otp": "Yubico OTP 認證",
"authenticators": "驗證器(Authenticators)" "authenticators": "驗證器(Authenticators)"
}, },
"syncjobs": {
"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": "錯誤的使用者名稱或密碼",
"add": "新增同步任務",
"add_hint": "請注意此密碼會以明文儲存!",
"edit": "編輯同步任務",
"create": "新增同步任務",
"dry": "模擬同步",
"subfolder2": "同步到目標信箱子資料夾<br><small>(留空表示不使用子資料夾)</small>",
"mins_interval": "輪詢間隔 (分鐘)",
"maxage": "從遠端拉取消息的最大消息年齡限制<br><small>(0 表示忽略)</small>",
"exclude": "拒絕物件 (regex)",
"automap": "自動歸類資料夾規則表 (如:\"已發送\", \"Sent\" => \"Sent\" 等...)",
"delete1": "完成後將來源郵件刪除",
"delete2": "刪除目的地信箱中存在但來源信箱中不存在的郵件",
"delete2duplicates": "刪除目的地信箱中的重複郵件",
"skipcrossduplicates": "跳過在其他資料夾中重複的郵件 (優先使用先找到的郵件)",
"subscribeall": "訂閱所有資料夾",
"timeout1": "遠端主機連線逾時時間",
"timeout2": "本地主機連線逾時時間",
"maxbytespersecond": "最大速率 (Bytes/s) <br><small>(0 表示不限)</small>",
"custom_params": "訂製參數"
},
"user": { "user": {
"action": "操作", "action": "操作",
"active": "啟用", "active": "啟用",
@@ -1124,6 +1131,7 @@
"clear_recent_successful_connections": "中斷成功的連線", "clear_recent_successful_connections": "中斷成功的連線",
"client_configuration": "顯示電子信箱程式和智慧型手機的設定指南", "client_configuration": "顯示電子信箱程式和智慧型手機的設定指南",
"create_app_passwd": "新增應用程式密碼", "create_app_passwd": "新增應用程式密碼",
"create_syncjob": "新增同步任務",
"created_on": "建立於", "created_on": "建立於",
"daily": "每日", "daily": "每日",
"day": "日", "day": "日",
@@ -1224,6 +1232,16 @@
"spamfilter_wl_desc": "白名單中地址<b>永遠不會</b>被標記為垃圾郵件。可以使用萬用匹配字元\"*\"。此過濾器只會套用於直接別名 (只有一個目標信箱),而不會應用到\"接收所有信件\"別名和信箱地址本身。", "spamfilter_wl_desc": "白名單中地址<b>永遠不會</b>被標記為垃圾郵件。可以使用萬用匹配字元\"*\"。此過濾器只會套用於直接別名 (只有一個目標信箱),而不會應用到\"接收所有信件\"別名和信箱地址本身。",
"spamfilter_yellow": "黃色: 此信件可能是垃圾消息,會被標記為垃圾信件並且移入垃圾信件資料夾", "spamfilter_yellow": "黃色: 此信件可能是垃圾消息,會被標記為垃圾信件並且移入垃圾信件資料夾",
"status": "狀態", "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_handling": "設定被標籤的郵件處理方式",
"tag_help_example": "標籤的信箱地址範例: me<b>+Facebook</b>@example.org", "tag_help_example": "標籤的信箱地址範例: me<b>+Facebook</b>@example.org",
"tag_help_explain": "置於子資料夾: 在收件夾下創建一個以標籤名命名的子資料夾 (\"INBOX/Facebook\")。<br>\r\n主旨: 標籤名會被前綴於郵件主旨, 如: \"[Facebook] 我的新聞\"。", "tag_help_explain": "置於子資料夾: 在收件夾下創建一個以標籤名命名的子資料夾 (\"INBOX/Facebook\")。<br>\r\n主旨: 標籤名會被前綴於郵件主旨, 如: \"[Facebook] 我的新聞\"。",
-103
View File
@@ -1,103 +0,0 @@
<?php
// Per-user OAuth2 authorization_code flow for IMAP sync sources.
// Opened as a popup from the sync-job modal:
// ?action=start&source_id=N&mailbox=M -> redirect the logged-in user to the provider consent screen
// (callback, ?code&state) -> exchange code, store the token for mailbox M, message the opener
// The token is bound to the local mailbox M (owner) and the authenticated remote identity (syncjob user1).
// The redirect URI is always https://MAILCOW_HOSTNAME/syncjob-oauth, so the UI must be used on that host.
// postMessage only carries short error codes, the UI translates them:
// access_denied, mailbox_denied, source_unavailable, provider_denied, invalid_state, token_failed, no_identity
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
function syncjob_oauth_finish($ok, $payload) {
// Renders a tiny page that hands the result back to the opener modal and closes the popup.
global $lang;
header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: no-store');
$json = json_encode(array_merge(array('type' => 'syncjob_oauth', 'ok' => $ok), $payload),
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
$title = $lang['syncjobs']['oauth_popup_title'] ?? 'Sign in with the provider';
$text = $ok
? ($lang['syncjobs']['oauth_popup_success'] ?? 'Connected. You can close this window.')
: ($lang['syncjobs']['oauth_popup_failed'] ?? 'Authentication failed. You can close this window.');
echo '<!doctype html><html><head><meta charset="utf-8"><title>' . htmlspecialchars($title) . '</title></head>'
. '<body><p>' . htmlspecialchars($text) . '</p><script>'
. 'try { if (window.opener) window.opener.postMessage(' . $json . ', window.location.origin); } catch (e) {}'
. 'window.close();'
. '</script></body></html>';
exit;
}
// Must be an authenticated session with the syncjobs ACL (user_acl / da_acl; always set for admins).
if (empty($_SESSION['mailcow_cc_username']) || empty($_SESSION['mailcow_cc_role'])) {
header('Location: /');
exit;
}
$context = \Mailcow\Core\Context::fromSession($_SESSION);
$imapsync = \Mailcow\Imapsync\ImapsyncModule::fromGlobals($context);
if (!$context->hasAcl('syncjobs')) {
syncjob_oauth_finish(false, array('error' => 'access_denied'));
}
$action = isset($_GET['action']) ? $_GET['action'] : ((isset($_GET['code']) || isset($_GET['error'])) ? 'callback' : '');
if ($action == 'start') {
$source_id = intval($_GET['source_id'] ?? 0);
// C1: the token is stored for this local mailbox; default is the own mailbox (role user)
$mailbox = (isset($_GET['mailbox']) && is_string($_GET['mailbox']) && $_GET['mailbox'] !== '')
? strtolower(trim($_GET['mailbox'])) : $_SESSION['mailcow_cc_username'];
if (!filter_var($mailbox, FILTER_VALIDATE_EMAIL) || !$context->canAccessMailbox($mailbox)) {
syncjob_oauth_finish(false, array('error' => 'mailbox_denied'));
}
// findConnectable enforces visibility for the current session
$state = bin2hex(random_bytes(16));
$source = $imapsync->sources->findConnectable($source_id);
$url = $source ? $imapsync->oauth->authorizeUrl($source, $state) : null;
if ($url === null) {
syncjob_oauth_finish(false, array('error' => 'source_unavailable'));
}
$_SESSION['syncjob_oauth2state'] = $state;
$_SESSION['syncjob_oauth_source_id'] = $source_id;
$_SESSION['syncjob_oauth_mailbox'] = $mailbox;
header('Location: ' . $url);
exit;
}
// --- callback ---
// CSRF: state must match the one stored at 'start'
$state_ok = !empty($_GET['state']) && is_string($_GET['state'])
&& hash_equals((string)($_SESSION['syncjob_oauth2state'] ?? ''), $_GET['state']);
$source_id = intval($_SESSION['syncjob_oauth_source_id'] ?? 0);
$mailbox = (string)($_SESSION['syncjob_oauth_mailbox'] ?? '');
if ($state_ok) {
unset($_SESSION['syncjob_oauth2state'], $_SESSION['syncjob_oauth_source_id'], $_SESSION['syncjob_oauth_mailbox']);
}
if (isset($_GET['error'])) {
// Provider error codes are not passed through (only a fixed code)
syncjob_oauth_finish(false, array('error' => 'provider_denied'));
}
if (!$state_ok || empty($_GET['code']) || !is_string($_GET['code']) || $source_id <= 0 || $mailbox === '') {
syncjob_oauth_finish(false, array('error' => 'invalid_state'));
}
// Re-check access to the owner mailbox (session could have changed in between)
if (!$context->canAccessMailbox($mailbox)) {
syncjob_oauth_finish(false, array('error' => 'mailbox_denied'));
}
$source = $imapsync->sources->findConnectable($source_id);
if (!$source) {
syncjob_oauth_finish(false, array('error' => 'source_unavailable'));
}
// C1: the token is bound to the local owner mailbox and the remote identity (id_token claims, else userinfo).
// Sanitized details go to the php-fpm log only, the opener gets a fixed code.
try {
$user1 = $imapsync->oauth->connectUser($source, $mailbox, $_GET['code']);
}
catch (\Mailcow\Core\UserError $e) {
if ($e->args) {
error_log('syncjob-oauth: ' . $e->args[0]);
}
syncjob_oauth_finish(false, array('error' => $e->key));
}
syncjob_oauth_finish(true, array('source_id' => $source_id, 'mailbox' => $mailbox, 'user1' => $user1));
-2
View File
@@ -22,7 +22,6 @@
<li><button class="dropdown-item" data-bs-target="#tab-config-f2b" aria-selected="false" aria-controls="tab-config-f2b" role="tab" data-bs-toggle="tab">{{ lang.admin.f2b_parameters }}</button></li> <li><button class="dropdown-item" data-bs-target="#tab-config-f2b" aria-selected="false" aria-controls="tab-config-f2b" role="tab" data-bs-toggle="tab">{{ lang.admin.f2b_parameters }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-quarantine" aria-selected="false" aria-controls="tab-config-quarantine" role="tab" data-bs-toggle="tab">{{ lang.admin.quarantine }}</button></li> <li><button class="dropdown-item" data-bs-target="#tab-config-quarantine" aria-selected="false" aria-controls="tab-config-quarantine" role="tab" data-bs-toggle="tab">{{ lang.admin.quarantine }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-quota" aria-selected="false" aria-controls="tab-config-quota" role="tab" data-bs-toggle="tab">{{ lang.admin.quota_notifications }}</button></li> <li><button class="dropdown-item" data-bs-target="#tab-config-quota" aria-selected="false" aria-controls="tab-config-quota" role="tab" data-bs-toggle="tab">{{ lang.admin.quota_notifications }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-syncjob" aria-selected="false" aria-controls="tab-config-syncjob" role="tab" data-bs-toggle="tab">{{ lang.admin.syncjobs }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-rsettings" aria-selected="false" aria-controls="tab-config-rsettings" role="tab" data-bs-toggle="tab">{{ lang.admin.rspamd_settings_map }}</button></li> <li><button class="dropdown-item" data-bs-target="#tab-config-rsettings" aria-selected="false" aria-controls="tab-config-rsettings" role="tab" data-bs-toggle="tab">{{ lang.admin.rspamd_settings_map }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-password-settings" aria-selected="false" aria-controls="tab-config-password-settings" role="tab" data-bs-toggle="tab">{{ lang.admin.password_settings }}</button></li> <li><button class="dropdown-item" data-bs-target="#tab-config-password-settings" aria-selected="false" aria-controls="tab-config-password-settings" role="tab" data-bs-toggle="tab">{{ lang.admin.password_settings }}</button></li>
<li><button class="dropdown-item" data-bs-target="#tab-config-customize" aria-selected="false" aria-controls="tab-config-customize" role="tab" data-bs-toggle="tab">{{ lang.admin.customize }}</button></li> <li><button class="dropdown-item" data-bs-target="#tab-config-customize" aria-selected="false" aria-controls="tab-config-customize" role="tab" data-bs-toggle="tab">{{ lang.admin.customize }}</button></li>
@@ -52,7 +51,6 @@
{% include 'admin/tab-config-f2b.twig' %} {% include 'admin/tab-config-f2b.twig' %}
{% include 'admin/tab-config-quarantine.twig' %} {% include 'admin/tab-config-quarantine.twig' %}
{% include 'admin/tab-config-quota.twig' %} {% include 'admin/tab-config-quota.twig' %}
{% include 'admin/tab-config-syncjob.twig' %}
{% include 'admin/tab-config-rsettings.twig' %} {% include 'admin/tab-config-rsettings.twig' %}
{% include 'admin/tab-config-customize.twig' %} {% include 'admin/tab-config-customize.twig' %}
{% include 'admin/tab-config-password-settings.twig' %} {% include 'admin/tab-config-password-settings.twig' %}
@@ -1,35 +0,0 @@
<div class="tab-pane fade" id="tab-config-syncjob" role="tabpanel" aria-labelledby="tab-config-syncjob">
<div class="card mb-4">
<div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-syncjob" data-bs-toggle="collapse" aria-controls="collapse-tab-config-syncjob">
{{ lang.admin.syncjobs }}
</button>
<span class="d-none d-md-block">{{ lang.admin.syncjobs }}</span>
</div>
<div id="collapse-tab-config-syncjob" class="card-body collapse" data-bs-parent="#admin-content">
<form class="form" role="form" data-id="imapsync_settings" method="post">
<div class="row mb-4">
<div class="col-sm-6">
<label for="max_parallel">{{ lang.admin.syncjob_max_parallel }}:</label>
<input type="number" class="form-control" id="max_parallel" name="max_parallel" min="1" max="32" value="{{ imapsync_settings.max_parallel }}">
<small class="text-muted">{{ lang.admin.syncjob_max_parallel_info }}</small>
</div>
<div class="col-sm-6">
<label for="max_kb_per_second">{{ lang.admin.syncjob_max_kb_per_second }}:</label>
<input type="number" class="form-control" id="max_kb_per_second" name="max_kb_per_second" min="0" max="10485760" value="{{ (imapsync_settings.max_bytes_per_second / 1024)|round }}">
<small class="text-muted">{{ lang.admin.syncjob_max_kb_per_second_info }}</small>
</div>
</div>
<div class="row">
<div class="col-sm-10">
<a type="button" class="btn btn-sm d-block d-sm-inline btn-success" data-action="edit_selected"
data-item="imapsync_settings"
data-id="imapsync_settings"
data-api-url='edit/imapsync_settings'
data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.user.save_changes }}</a>
</div>
</div>
</form>
</div>
</div>
</div>
-1
View File
@@ -26,7 +26,6 @@
var lang_user = {{ lang_user|raw }}; var lang_user = {{ lang_user|raw }};
var lang_admin = {{ lang_admin|raw }}; var lang_admin = {{ lang_admin|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var lang_syncjobs = {{ lang_syncjobs|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = Math.trunc('{{ pagination_size }}'); var pagination_size = Math.trunc('{{ pagination_size }}');
var table_for_domain = '{{ domain }}'; var table_for_domain = '{{ domain }}';
@@ -1,233 +0,0 @@
{% extends 'edit.twig' %}
{% block inner_content %}
{% if result %}
{# XOAUTH2 sources are managed by admins and domain admins only #}
{% set ro = not result.can_edit or (mailcow_cc_role == 'user' and result.auth_type == 'XOAUTH2') %}
{% set show_oauth = mailcow_cc_role != 'user' or result.auth_type == 'XOAUTH2' %}
{% set is_cc = result.auth_type == 'XOAUTH2' and result.oauth_flow != 'authorization_code' %}
<h4 class="mb-4">{{ lang.syncjobs.source }}: {{ result.name }}</h4>
{% if ro %}
<div class="alert alert-info" role="alert"><i class="bi bi-eye"></i> {{ lang.syncjobs.source_readonly_hint }}</div>
{% endif %}
<form class="form-horizontal" data-id="edit_imapsync_source" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="row mb-2">
<label class="control-label col-sm-2" for="name">{{ lang.syncjobs.source_name }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="name" maxlength="100" value="{{ result.name }}" required{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="description">{{ lang.syncjobs.source_description }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="description" maxlength="255" value="{{ result.description }}"{% if ro %} disabled{% endif %}>
</div>
</div>
{% if result.created_by %}
<div class="row mb-2">
<label class="control-label col-sm-2" for="created_by_display">{{ lang.syncjobs.source_created_by }}</label>
<div class="col-sm-10">
<input type="text" readonly class="form-control-plaintext" id="created_by_display" value="{{ result.created_by }}">
</div>
</div>
{% endif %}
{% if (mailcow_cc_role == 'admin' or mailcow_cc_role == 'domainadmin') and not ro %}
<div class="imapsync-source-scope-block"{% if is_cc %} style="display:none;"{% endif %}>
<div class="row mb-2">
<label class="control-label col-sm-2" for="scope">{{ lang.syncjobs.source_scope }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-scope" name="scope" id="scope" data-width="100%">
{% if mailcow_cc_role == 'admin' %}
<option value="all"{% if result.scope == 'all' %} selected{% endif %}>{{ lang.syncjobs.source_scope_all }}</option>
{% endif %}
<option value="domain"{% if result.scope == 'domain' %} selected{% endif %}>{{ lang.syncjobs.source_scope_domain }}</option>
<option value="user"{% if result.scope == 'user' %} selected{% endif %}>{{ lang.syncjobs.source_scope_user }}</option>
</select>
</div>
</div>
<div class="row mb-2 imapsync-source-domains-row" style="display:{% if result.scope == 'domain' %}flex{% else %}none{% endif %};">
<label class="control-label col-sm-2" for="domains">{{ lang.syncjobs.source_domains }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-domains" name="domains" id="domains" data-live-search="true" data-width="100%" title="{{ lang.syncjobs.source_domains }}" multiple>
{% for d in result.domains %}<option value="{{ d }}" selected>{{ d }}</option>{% endfor %}
</select>
</div>
</div>
<div class="row mb-2 imapsync-source-users-row" style="display:{% if result.scope == 'user' %}flex{% else %}none{% endif %};">
<label class="control-label col-sm-2" for="users">{{ lang.syncjobs.source_users }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-users" name="users" id="users" data-live-search="true" data-width="100%" title="{{ lang.syncjobs.source_users }}" multiple>
{% for u in result.users %}<option value="{{ u }}" selected>{{ u }}</option>{% endfor %}
</select>
</div>
</div>
</div>
<div class="row mb-2 imapsync-source-scope-cc-hint"{% if not is_cc %} style="display:none;"{% endif %}>
<div class="offset-sm-2 col-sm-10">
<small class="text-muted"><i class="bi bi-lock"></i> {{ lang.syncjobs.source_scope_cc_hint }}</small>
</div>
</div>
{% else %}
<div class="row mb-2">
<label class="control-label col-sm-2" for="scope_display">{{ lang.syncjobs.source_scope }}</label>
<div class="col-sm-10">
{% set scope_targets = result.scope == 'domain' ? result.domains : (result.scope == 'user' ? result.users : []) %}
<input type="text" readonly class="form-control-plaintext" id="scope_display" value="{{ lang.syncjobs['source_scope_' ~ result.scope] }}{% if scope_targets is not empty %}: {{ scope_targets|join(', ') }}{% endif %}">
</div>
</div>
{% endif %}
<div class="row mb-2">
<label class="control-label col-sm-2" for="host">{{ lang.add.hostname }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="host" id="host" maxlength="253" value="{{ result.host1 }}" required{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="port">{{ lang.add.port }}</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="port" id="port" min="1" max="65535" value="{{ result.port1 }}" required{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="enc">{{ lang.add.enc_method }}</label>
<div class="col-sm-10">
<select name="enc" id="enc" data-width="100%"{% if ro %} disabled{% endif %}>
<option value="SSL"{% if result.enc1 == 'SSL' %} selected{% endif %}>SSL</option>
<option value="TLS"{% if result.enc1 == 'TLS' %} selected{% endif %}>STARTTLS</option>
<option value="PLAIN"{% if result.enc1 == 'PLAIN' %} selected{% endif %}>PLAIN</option>
</select>
</div>
</div>
<div class="row mb-4">
<label class="control-label col-sm-2" for="auth_type">{{ lang.syncjobs.source_auth_type }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-auth-type" name="auth_type" id="auth_type" data-width="100%" required{% if ro %} disabled{% endif %}>
<option value="PLAIN"{% if result.auth_type == 'PLAIN' %} selected{% endif %}>PLAIN</option>
<option value="LOGIN"{% if result.auth_type == 'LOGIN' %} selected{% endif %}>LOGIN</option>
<option value="CRAM-MD5"{% if result.auth_type == 'CRAM-MD5' %} selected{% endif %}>CRAM-MD5</option>
{% if show_oauth %}
<option value="XOAUTH2"{% if result.auth_type == 'XOAUTH2' %} selected{% endif %}>XOAUTH2 (OAuth2)</option>
{% endif %}
</select>
</div>
</div>
{% if show_oauth %}
<fieldset class="imapsync-source-oauth-block" style="display:{% if result.auth_type == 'XOAUTH2' %}block{% else %}none{% endif %};">
<legend class="fs-5">{{ lang.syncjobs.source_oauth_block }}</legend>
<div class="row mb-2">
<label class="control-label col-sm-2" for="oauth_flow">{{ lang.syncjobs.source_oauth_flow }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-oauth-flow" name="oauth_flow" id="oauth_flow" data-width="100%"{% if ro %} disabled{% endif %}>
<option value="client_credentials"{% if result.oauth_flow != 'authorization_code' %} selected{% endif %}>{{ lang.syncjobs.source_oauth_flow_app }}</option>
<option value="authorization_code"{% if result.oauth_flow == 'authorization_code' %} selected{% endif %}>{{ lang.syncjobs.source_oauth_flow_user }}</option>
</select>
</div>
</div>
<div class="row mb-2 imapsync-source-authcode-block" style="display:{% if result.oauth_flow == 'authorization_code' %}flex{% else %}none{% endif %};">
<label class="control-label col-sm-2" for="oauth_authorize_endpoint">{{ lang.syncjobs.source_authorize_endpoint }}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="oauth_authorize_endpoint" id="oauth_authorize_endpoint" value="{{ result.oauth_authorize_endpoint }}"{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="oauth_token_endpoint">{{ lang.syncjobs.source_oauth_token_endpoint }}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="oauth_token_endpoint" id="oauth_token_endpoint" value="{{ result.oauth_token_endpoint }}"{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="imapsync-source-authcode-block" style="display:{% if result.oauth_flow == 'authorization_code' %}block{% else %}none{% endif %};">
<div class="row mb-2">
<label class="control-label col-sm-2" for="oauth_userinfo_endpoint">{{ lang.syncjobs.source_userinfo_endpoint }}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="oauth_userinfo_endpoint" id="oauth_userinfo_endpoint" value="{{ result.oauth_userinfo_endpoint }}"{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="redirect_uri">{{ lang.syncjobs.source_redirect_uri }}</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="redirect_uri" readonly value="{{ result.oauth_redirect_uri ?: imapsync_redirect_uri }}">
<button type="button" class="btn btn-secondary imapsync-copy-btn" data-copy-target="redirect_uri" title="{{ lang.syncjobs.copy }}"><i class="bi bi-clipboard" aria-hidden="true"></i> {{ lang.syncjobs.copy }}</button>
</div>
{% if not ro %}
<small class="text-muted">{{ lang.syncjobs.source_redirect_uri_hint }}</small>
{% endif %}
</div>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="oauth_client_id">{{ lang.syncjobs.source_oauth_client_id }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="oauth_client_id" id="oauth_client_id" value="{{ result.oauth_client_id }}"{% if ro %} disabled{% endif %}>
</div>
</div>
{% if not ro %}
<div class="row mb-2">
<label class="control-label col-sm-2" for="oauth_client_secret">{{ lang.syncjobs.source_oauth_client_secret }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="oauth_client_secret" id="oauth_client_secret" autocomplete="new-password"{% if result.has_client_secret %} placeholder="••••••••"{% endif %}>
<small class="text-muted">{{ lang.syncjobs.source_secret_unchanged_hint }}</small>
</div>
</div>
{% endif %}
<div class="row mb-2">
<label class="control-label col-sm-2" for="oauth_scope">{{ lang.syncjobs.source_oauth_scope }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="oauth_scope" id="oauth_scope" value="{{ result.oauth_scope }}"{% if ro %} disabled{% endif %}>
</div>
</div>
{% if not ro %}
<div class="row mb-4">
<label class="control-label col-sm-2" for="oauth_extra_params">{{ lang.syncjobs.source_oauth_extra_params }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="oauth_extra_params" id="oauth_extra_params" value="{{ result.oauth_extra_params }}" placeholder='{"resource":"https://provider.example.com"}'>
<small class="text-muted">{{ lang.syncjobs.source_oauth_extra_params_hint }}</small>
</div>
</div>
{% endif %}
{% if is_cc %}
{# token state of the stored client credentials token (authorization_code tokens live per sync job) #}
<div class="row mb-4 imapsync-source-cc-block">
<div class="offset-sm-2 col-sm-10">
<p class="mb-1">
{% if result.oauth_last_refresh_error %}
<span class="badge bg-danger">{{ lang.syncjobs.token_state_failed }}</span>
{% elseif result.has_access_token %}
<span class="badge bg-success">{{ lang.syncjobs.token_state_issued }}</span>
{% if result.oauth_token_expires %}{{ lang.syncjobs.source_token_status }}: <code>{{ result.oauth_token_expires|date('Y-m-d H:i:s') }}</code>{% endif %}
{% else %}
<span class="badge bg-warning">{{ lang.syncjobs.token_state_none }}</span>
{% endif %}
</p>
{% if result.oauth_last_refresh_error %}
<p class="text-danger">{{ lang.syncjobs.source_token_error }}: <code>{{ result.oauth_last_refresh_error }}</code></p>
{% endif %}
{% if not ro %}
{# own data-id: must not harvest/post the form fields #}
<button type="button" class="btn btn-sm btn-secondary" data-action="edit_selected" data-id="imapsync_source_refresh" data-item="{{ result.id }}" data-api-url='edit/syncjob_source/refresh_token' data-api-attr='{}'><i class="bi bi-arrow-repeat" aria-hidden="true"></i> {{ lang.syncjobs.source_refresh_now }}</button>
{% endif %}
</div>
</div>
{% endif %}
</fieldset>
{% endif %}
<div class="row mb-4">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active"{% if result.active == '1' %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.edit.active }}</label>
</div>
</div>
</div>
{% if not ro %}
<div class="row mb-2">
<div class="offset-sm-2 col-sm-10">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="edit_imapsync_source" data-item="{{ result.id }}" data-api-url='edit/syncjob_source' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
</div>
</div>
{% endif %}
</form>
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
+46 -87
View File
@@ -2,8 +2,8 @@
{% block inner_content %} {% block inner_content %}
{% if result %} {% if result %}
<h4 class="mb-4">{{ lang.syncjobs.edit }}</h4> <h4 class="mb-4">{{ lang.edit.syncjob }}</h4>
<form class="form-horizontal" data-id="editsyncjob" data-mailbox="{{ result.user2 }}" role="form" method="post"> <form class="form-horizontal" data-id="editsyncjob" role="form" method="post">
<input type="hidden" value="0" name="delete2duplicates"> <input type="hidden" value="0" name="delete2duplicates">
<input type="hidden" value="0" name="delete1"> <input type="hidden" value="0" name="delete1">
<input type="hidden" value="0" name="delete2"> <input type="hidden" value="0" name="delete2">
@@ -13,180 +13,139 @@
<input type="hidden" value="0" name="subscribeall"> <input type="hidden" value="0" name="subscribeall">
<input type="hidden" value="0" name="dry"> <input type="hidden" value="0" name="dry">
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="user2_display">{{ lang.syncjobs.user2_label }}</label> <label class="control-label col-sm-2" for="host1">{{ lang.edit.hostname }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" readonly class="form-control-plaintext" id="user2_display" value="{{ result.user2 }}"> <input type="text" class="form-control" name="host1" id="host1" value="{{ result.host1 }}">
</div> </div>
</div> </div>
<div class="row mb-4"> <div class="row mb-4">
<label class="control-label col-sm-2" for="source_id">{{ lang.syncjobs.source }}</label> <label class="control-label col-sm-2" for="port1">Port</label>
<div class="col-sm-10 imapsync-select-wrap">
<select class="imapsync-source-select" name="source_id" id="source_id"
data-live-search="true" data-show-subtext="true" data-width="100%"
data-current-source-id="{{ result.source_id }}"
data-current-source-name="{{ result.source_name }}"
data-current-source-host="{{ result.source_host }}"
data-current-source-port="{{ result.source_port }}"
data-current-source-auth-type="{{ result.source_auth_type }}"
data-current-source-oauth-flow="{{ result.source_oauth_flow }}" title="{{ lang.syncjobs.source_select }}" required>
{% if result.source_id %}
<option value="{{ result.source_id }}" data-auth-type="{{ result.source_auth_type }}" data-oauth-flow="{{ result.source_oauth_flow }}" data-subtext="{{ result.source_host }}:{{ result.source_port }} · {{ result.source_auth_type }}" selected>{{ result.source_name }}</option>
{% endif %}
</select>
{% if not result.source_id %}
<div class="alert alert-warning mt-2 mb-0" role="alert"><i class="bi bi-exclamation-triangle" aria-hidden="true"></i> {{ lang.syncjobs.source_missing }}</div>
{% endif %}
<div class="imapsync-no-source d-none mt-2"><small class="text-muted">{{ lang.syncjobs.no_sources }}</small></div>
</div>
</div>
<div class="row mb-2 imapsync-user1-row" style="display:{% if result.source_oauth_flow == 'authorization_code' %}none{% else %}flex{% endif %};">
<label class="control-label col-sm-2" for="user1">{{ lang.syncjobs.user1_label }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" name="user1" id="user1" value="{{ result.user1 }}" autocomplete="off"> <input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="{{ result.port1 }}">
</div>
</div>
<div class="row mb-2 imapsync-oauth-connect-row" style="display:{% if result.source_oauth_flow == 'authorization_code' %}flex{% else %}none{% endif %};">
<div class="offset-sm-2 col-sm-10">
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn"><i class="bi bi-box-arrow-up-right" aria-hidden="true"></i> {{ lang.syncjobs.syncjob_oauth_connect }}</button>
{% if result.user_token_exists == 1 and not result.user_token_error %}
<span class="imapsync-oauth-connect-status ms-2 text-success" role="status" aria-live="polite">{{ lang.syncjobs.syncjob_oauth_connected }}: {{ result.user1 }}</span>
{% elseif result.user_token_error %}
<span class="imapsync-oauth-connect-status ms-2 text-danger" role="status" aria-live="polite">{{ lang.syncjobs.token_state_failed }}: {{ result.user1 }}</span>
{% else %}
<span class="imapsync-oauth-connect-status ms-2 text-warning" role="status" aria-live="polite">{{ lang.syncjobs.token_state_pending }}</span>
{% endif %}
<div><small class="text-muted">{{ lang.syncjobs.oauth_hint }}</small></div>
</div>
</div>
<div class="row mb-4 password1-row" style="display:{% if result.source_auth_type == 'XOAUTH2' %}none{% else %}flex{% endif %};">
<label class="control-label col-sm-2" for="password1">{{ lang.syncjobs.password1_label }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="{{ lang.edit.unchanged_if_empty }}" autocomplete="new-password">
<small class="text-muted">{{ lang.edit.unchanged_if_empty }}</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="mins_interval">{{ lang.syncjobs.mins_interval }}</label> <label class="control-label col-sm-2" for="user1">{{ lang.edit.username }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" id="mins_interval" min="1" max="43800" value="{{ result.mins_interval }}" required> <input type="text" class="form-control" name="user1" id="user1" value="{{ result.user1 }}">
</div>
</div>
<div class="row mb-4">
<label class="control-label col-sm-2" for="password1">{{ lang.edit.password }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password1" id="password1" value="{{ result.password1 }}">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="enc1">{{ lang.edit.encryption }}</label>
<div class="col-sm-10">
<select id="enc1" name="enc1">
<option value="SSL"{% if result.enc1 == 'SSL' %} selected{% endif %}>SSL</option>
<option value="TLS"{% if result.enc1 == 'TLS' %} selected{% endif %}>STARTTLS</option>
<option value="PLAIN"{% if result.enc1 == 'PLAIN' %} selected{% endif %}>PLAIN</option>
</select>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="mins_interval">{{ lang.edit.mins_interval }}</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="{{ result.mins_interval }}" required>
<small class="text-muted">1-43800</small> <small class="text-muted">1-43800</small>
</div> </div>
</div> </div>
{% if mailcow_cc_role == 'admin' %}
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="prio">{{ lang.syncjobs.prio }}</label> <label class="control-label col-sm-2" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="prio" id="prio" min="0" value="{{ result.prio }}">
<small class="text-muted">{{ lang.syncjobs.prio_hint }}</small>
</div>
</div>
{% endif %}
<div class="row mb-2">
<label class="control-label col-sm-2" for="subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" name="subfolder2" id="subfolder2" value="{{ result.subfolder2 }}"> <input type="text" class="form-control" name="subfolder2" id="subfolder2" value="{{ result.subfolder2 }}">
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="maxage">{{ lang.syncjobs.maxage|raw }}</label> <label class="control-label col-sm-2" for="maxage">{{ lang.edit.maxage|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="{{ result.maxage }}"> <input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="{{ result.maxage }}">
<small class="text-muted">0-32000</small> <small class="text-muted">0-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label> <label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="{{ result.maxbytespersecond }}"> <input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="{{ result.maxbytespersecond }}">
<small class="text-muted">0-125000000</small> <small class="text-muted">0-125000000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="timeout1">{{ lang.syncjobs.timeout1 }}</label> <label class="control-label col-sm-2" for="timeout1">{{ lang.add.timeout1 }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="{{ result.timeout1 }}"> <input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="{{ result.timeout1 }}">
<small class="text-muted">1-32000</small> <small class="text-muted">1-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="timeout2">{{ lang.syncjobs.timeout2 }}</label> <label class="control-label col-sm-2" for="timeout2">{{ lang.add.timeout2 }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="{{ result.timeout2 }}"> <input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="{{ result.timeout2 }}">
<small class="text-muted">1-32000</small> <small class="text-muted">1-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2" for="exclude">{{ lang.syncjobs.exclude }}</label> <label class="control-label col-sm-2" for="exclude">{{ lang.edit.exclude }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" name="exclude" id="exclude" value="{{ result.exclude }}"> <input type="text" class="form-control" name="exclude" id="exclude" value="{{ result.exclude }}">
</div> </div>
</div> </div>
<div class="row mb-4"> <div class="row mb-4">
<label class="control-label col-sm-2" id="custom_params_label">{{ lang.syncjobs.custom_params }}</label> <label class="control-label col-sm-2" for="custom_params">{{ lang.add.custom_params }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="imapsync-cp-editor" role="group" aria-labelledby="custom_params_label"> <input type="text" class="form-control" name="custom_params" id="custom_params" value="{{ result.custom_params }}" placeholder="--some-param=xy --other-param=yx">
<div class="imapsync-cp-rows"></div> <small class="text-muted">{{ lang.add.custom_params_hint }}</small>
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg" aria-hidden="true"></i> {{ lang.syncjobs.custom_param_add }}</button>
</div>
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="{{ result.custom_params }}">
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
</div> </div>
</div> </div>
{% if result.custom_params_legacy %}
<div class="row mb-4">
<label class="control-label col-sm-2" for="custom_params_legacy_display">{{ lang.syncjobs.custom_params_legacy }}</label>
<div class="col-sm-10">
<div class="alert alert-warning mb-2"><i class="bi bi-exclamation-triangle"></i> {{ lang.syncjobs.custom_params_legacy_hint }}</div>
<textarea class="form-control font-monospace" id="custom_params_legacy_display" rows="2" readonly>{{ result.custom_params_legacy }}</textarea>
</div>
</div>
{% endif %}
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates"{% if result.delete2duplicates == '1' %} checked{% endif %}> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates"{% if result.delete2duplicates == '1' %} checked{% endif %}> {{ lang.edit.delete2duplicates }} (--delete2duplicates)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"{% if result.delete1 == '1' %} checked{% endif %}> {{ lang.syncjobs.delete1 }} (--delete1)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete1"{% if result.delete1 == '1' %} checked{% endif %}> {{ lang.edit.delete1 }} (--delete1)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"{% if result.delete2 == '1' %} checked{% endif %}> {{ lang.syncjobs.delete2 }} (--delete2)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete2"{% if result.delete2 == '1' %} checked{% endif %}> {{ lang.edit.delete2 }} (--delete2)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="automap"{% if result.automap == '1' %} checked{% endif %}> {{ lang.syncjobs.automap }} (--automap)</label> <label><input type="checkbox" class="form-check-input" value="1" name="automap"{% if result.automap == '1' %} checked{% endif %}> {{ lang.edit.automap }} (--automap)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"{% if result.skipcrossduplicates == '1' %} checked{% endif %}> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label> <label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"{% if result.skipcrossduplicates == '1' %} checked{% endif %}> {{ lang.edit.skipcrossduplicates }} (--skipcrossduplicates)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall"{% if result.subscribeall == '1' %} checked{% endif %}> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label> <label><input type="checkbox" class="form-check-input" value="1" name="subscribeall"{% if result.subscribeall == '1' %} checked{% endif %}> {{ lang.add.subscribeall }} (--subscribeall)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="dry"{% if result.dry == '1' %} checked{% endif %}> {{ lang.syncjobs.dry }} (--dry)</label> <label><input type="checkbox" class="form-check-input" value="1" name="dry"{% if result.dry == '1' %} checked{% endif %}> {{ lang.add.dry }} (--dry)</label>
</div> </div>
</div> </div>
</div> </div>
+1 -2
View File
@@ -33,7 +33,7 @@
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-domain-aliases" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domain-aliases">{{ lang.mailbox.domain_aliases }}</button></li> <li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-domain-aliases" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domain-aliases">{{ lang.mailbox.domain_aliases }}</button></li>
</ul> </ul>
</li> </li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.mailbox.sync_jobs }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li>
<li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li> <li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li>
@@ -67,7 +67,6 @@
<script type='text/javascript'> <script type='text/javascript'>
var acl = '{{ acl_json|raw }}'; var acl = '{{ acl_json|raw }}';
var lang = {{ lang_mailbox|raw }}; var lang = {{ lang_mailbox|raw }};
lang.syncjobs = {{ lang_syncjobs|raw }};
var lang_rl = {{ lang_rl|raw }}; var lang_rl = {{ lang_rl|raw }};
var lang_edit = {{ lang_edit|raw }}; var lang_edit = {{ lang_edit|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
+6 -35
View File
@@ -1,35 +1,10 @@
<div class="tab-pane fade" id="tab-syncjobs" role="tabpanel" aria-labelledby="tab-syncjobs"> <div class="tab-pane fade" id="tab-syncjobs" role="tabpanel" aria-labelledby="tab-syncjobs">
<div class="card mb-4">
<div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-imapsync-sources" data-bs-toggle="collapse" aria-controls="collapse-tab-imapsync-sources">
{{ lang.syncjobs.sources_panel }} <span class="badge bg-info table-lines"></span>
</button>
<span class="d-none d-md-block">{{ lang.syncjobs.sources_panel }} <span class="badge bg-info table-lines"></span></span>
<div class="btn-group ms-auto d-flex">
<button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_imapsync_source_table" data-table="imapsync_source_table">{{ lang.admin.refresh }}</button>
</div>
</div>
<div id="collapse-tab-imapsync-sources" class="card-body collapse" data-bs-parent="#mail-content">
<p class="text-muted">{{ lang.syncjobs.sources_hint }}</p>
<div class="mass-actions-mailbox mb-4 d-none d-sm-block">
<div class="btn-group" data-acl="{{ acl.syncjobs }}">
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addImapsyncSourceModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.source_add }}</a>
</div>
</div>
<table id="imapsync_source_table" class="table table-striped dt-responsive w-100"></table>
<div class="mass-actions-mailbox mt-4">
<div class="btn-group" data-acl="{{ acl.syncjobs }}">
<a class="btn btn-sm btn-xs-lg btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addImapsyncSourceModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.source_add }}</a>
</div>
</div>
</div>
</div>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-syncjobs"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-syncjobs">
{{ lang.syncjobs.sync_jobs }} <span class="badge bg-info table-lines"></span> {{ lang.mailbox.sync_jobs }} <span class="badge bg-info table-lines"></span>
</button> </button>
<span class="d-none d-md-block">{{ lang.syncjobs.sync_jobs }} <span class="badge bg-info table-lines"></span></span> <span class="d-none d-md-block">{{ lang.mailbox.sync_jobs }} <span class="badge bg-info table-lines"></span></span>
<div class="btn-group ms-auto d-flex"> <div class="btn-group ms-auto d-flex">
<button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_sync_job_table" data-table="sync_job_table">{{ lang.admin.refresh }}</button> <button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_sync_job_table" data-table="sync_job_table">{{ lang.admin.refresh }}</button>
@@ -41,10 +16,8 @@
<a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a> <a class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a> <a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if mailcow_cc_role == 'admin' %} <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":"","success":""}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob/run_next' data-api-attr='{}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
{% endif %}
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
@@ -53,7 +26,7 @@
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li>
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a> <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
</div> </div>
</div> </div>
<table id="sync_job_table" class="table table-striped dt-responsive w-100"></table> <table id="sync_job_table" class="table table-striped dt-responsive w-100"></table>
@@ -62,10 +35,8 @@
<a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a> <a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="syncjob" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a> <a class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if mailcow_cc_role == 'admin' %} <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"last_run":"","success":""}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob/run_next' data-api-attr='{}' href="#">{{ lang.mailbox.last_run_reset }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
{% endif %}
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
@@ -74,7 +45,7 @@
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="sync_job_table">{{ lang.datatables.expand_all }}</a></li>
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li> <li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="sync_job_table">{{ lang.datatables.collapse_all }}</a></li>
</ul> </ul>
<a class="btn btn-sm btn-xs-lg btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a> <a class="btn btn-sm btn-xs-lg btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModalAdmin"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -1,207 +0,0 @@
<!-- add imapsync source modal (shared between user and admin views) -->
<div class="modal fade" id="addImapsyncSourceModal" tabindex="-1" role="dialog" aria-hidden="true" aria-labelledby="addImapsyncSourceModalLabel">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="addImapsyncSourceModalLabel">{{ lang.syncjobs.source_add }}</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ lang.footer.cancel }}"></button>
</div>
<div class="modal-body">
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_imapsync_source">
<input type="hidden" value="0" name="active">
<div class="row mb-3">
<label class="control-label col-sm-2 text-sm-end">{{ lang.syncjobs.source_presets }}</label>
<div class="col-sm-10 small">
{% if mailcow_cc_role != 'user' %}
<div class="d-flex flex-wrap column-gap-3">
<a href="#" class="imapsync-source-preset" data-preset="m365_app">Microsoft 365 ({{ lang.syncjobs.source_flow_badge_app }})</a>
<a href="#" class="imapsync-source-preset" data-preset="m365_user">Microsoft 365 ({{ lang.syncjobs.source_flow_badge_user }})</a>
<a href="#" class="imapsync-source-preset" data-preset="google_user">Google ({{ lang.syncjobs.source_flow_badge_user }})</a>
</div>
{% endif %}
<div class="d-flex flex-wrap column-gap-3">
<a href="#" class="imapsync-source-preset" data-preset="gmail">Gmail</a>
<a href="#" class="imapsync-source-preset" data-preset="gmx">GMX</a>
<a href="#" class="imapsync-source-preset" data-preset="webde">WEB.DE</a>
<a href="#" class="imapsync-source-preset" data-preset="tonline">T-Online</a>
<a href="#" class="imapsync-source-preset" data-preset="icloud">iCloud</a>
<a href="#" class="imapsync-source-preset" data-preset="yahoo">Yahoo</a>
<a href="#" class="imapsync-source-preset" data-preset="ionos">IONOS</a>
<a href="#" class="imapsync-source-preset" data-preset="strato">STRATO</a>
</div>
<span class="text-muted">{{ lang.syncjobs.source_presets_hint }}</span>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_name">{{ lang.syncjobs.source_name }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addsrc_name" name="name" maxlength="100" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_description">{{ lang.syncjobs.source_description }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addsrc_description" name="description" maxlength="255">
</div>
</div>
{% if mailcow_cc_role == 'admin' or mailcow_cc_role == 'domainadmin' %}
<div class="imapsync-source-scope-block">
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_scope">{{ lang.syncjobs.source_scope }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-scope" id="addsrc_scope" name="scope" data-width="100%">
{% if mailcow_cc_role == 'admin' %}
<option value="all">{{ lang.syncjobs.source_scope_all }}</option>
{% endif %}
<option value="domain">{{ lang.syncjobs.source_scope_domain }}</option>
<option value="user" selected>{{ lang.syncjobs.source_scope_user }}</option>
</select>
</div>
</div>
<div class="row mb-2 imapsync-source-domains-row" style="display:none;">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_domains">{{ lang.syncjobs.source_domains }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-domains" id="addsrc_domains" name="domains" data-live-search="true" data-width="100%" title="{{ lang.syncjobs.source_domains }}" multiple></select>
</div>
</div>
<div class="row mb-2 imapsync-source-users-row" style="display:none;">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_users">{{ lang.syncjobs.source_users }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-users" id="addsrc_users" name="users" data-live-search="true" data-width="100%" title="{{ lang.syncjobs.source_users }}" multiple></select>
</div>
</div>
</div>
<div class="row mb-2 imapsync-source-scope-cc-hint" style="display:none;">
<div class="offset-sm-2 col-sm-10">
<small class="text-muted"><i class="bi bi-lock"></i> {{ lang.syncjobs.source_scope_cc_hint }}</small>
</div>
</div>
{% endif %}
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_host">{{ lang.add.hostname }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addsrc_host" name="host" maxlength="253" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_port">{{ lang.add.port }}</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="addsrc_port" name="port" min="1" max="65535" value="993" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_enc">{{ lang.add.enc_method }}</label>
<div class="col-sm-10">
<select id="addsrc_enc" name="enc" title="{{ lang.add.select }}" data-width="100%" required>
<option value="SSL" selected>SSL</option>
<option value="TLS">STARTTLS</option>
<option value="PLAIN">PLAIN</option>
</select>
</div>
</div>
<div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_auth_type">{{ lang.syncjobs.source_auth_type }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-auth-type" id="addsrc_auth_type" name="auth_type" data-width="100%" required>
<option value="PLAIN" selected>PLAIN</option>
<option value="LOGIN">LOGIN</option>
<option value="CRAM-MD5">CRAM-MD5</option>
{% if mailcow_cc_role != 'user' %}
<option value="XOAUTH2">XOAUTH2 (OAuth2)</option>
{% endif %}
</select>
</div>
</div>
{% if mailcow_cc_role != 'user' %}
<fieldset class="imapsync-source-oauth-block" style="display:none;">
<legend class="fs-5">{{ lang.syncjobs.source_oauth_block }}</legend>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_flow">{{ lang.syncjobs.source_oauth_flow }}</label>
<div class="col-sm-10">
<select class="form-control imapsync-source-oauth-flow" id="addsrc_oauth_flow" name="oauth_flow" data-width="100%">
<option value="client_credentials" selected>{{ lang.syncjobs.source_oauth_flow_app }}</option>
<option value="authorization_code">{{ lang.syncjobs.source_oauth_flow_user }}</option>
</select>
</div>
</div>
<div class="row mb-2 imapsync-source-tenant-row" style="display:none;">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_tenant">{{ lang.syncjobs.source_tenant }}</label>
<div class="col-sm-10">
<input type="text" class="form-control imapsync-source-tenant" id="addsrc_tenant" placeholder="contoso.onmicrosoft.com">
<small class="text-muted">{{ lang.syncjobs.source_tenant_hint }}</small>
</div>
</div>
<div class="row mb-2 imapsync-source-authcode-block" style="display:none;">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_authorize_endpoint">{{ lang.syncjobs.source_authorize_endpoint }}</label>
<div class="col-sm-10">
<input type="url" class="form-control" id="addsrc_oauth_authorize_endpoint" name="oauth_authorize_endpoint" placeholder="https://provider.example.com/oauth2/authorize">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_token_endpoint">{{ lang.syncjobs.source_oauth_token_endpoint }}</label>
<div class="col-sm-10">
<input type="url" class="form-control" id="addsrc_oauth_token_endpoint" name="oauth_token_endpoint" placeholder="https://provider.example.com/oauth2/token">
</div>
</div>
<div class="imapsync-source-authcode-block" style="display:none;">
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_userinfo_endpoint">{{ lang.syncjobs.source_userinfo_endpoint }}</label>
<div class="col-sm-10">
<input type="url" class="form-control" id="addsrc_oauth_userinfo_endpoint" name="oauth_userinfo_endpoint" placeholder="https://provider.example.com/oauth2/userinfo">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_redirect_uri">{{ lang.syncjobs.source_redirect_uri }}</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="addsrc_redirect_uri" readonly value="{{ imapsync_redirect_uri }}" data-formcache-skip>
<button type="button" class="btn btn-secondary imapsync-copy-btn" data-copy-target="addsrc_redirect_uri" title="{{ lang.syncjobs.copy }}"><i class="bi bi-clipboard" aria-hidden="true"></i> {{ lang.syncjobs.copy }}</button>
</div>
<small class="text-muted">{{ lang.syncjobs.source_redirect_uri_hint }}</small>
</div>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_client_id">{{ lang.syncjobs.source_oauth_client_id }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addsrc_oauth_client_id" name="oauth_client_id">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_client_secret">{{ lang.syncjobs.source_oauth_client_secret }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="addsrc_oauth_client_secret" name="oauth_client_secret" autocomplete="new-password" data-formcache-skip>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_scope">{{ lang.syncjobs.source_oauth_scope }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addsrc_oauth_scope" name="oauth_scope" placeholder="openid email offline_access">
</div>
</div>
<div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end" for="addsrc_oauth_extra_params">{{ lang.syncjobs.source_oauth_extra_params }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="addsrc_oauth_extra_params" name="oauth_extra_params" placeholder='{"resource":"https://provider.example.com"}'>
<small class="text-muted">{{ lang.syncjobs.source_oauth_extra_params_hint }}</small>
</div>
</div>
</fieldset>
{% endif %}
<div class="row mb-4">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.add.active }}</label>
</div>
</div>
</div>
<div class="row mb-2">
<div class="offset-sm-2 col-sm-10">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="add_imapsync_source" data-api-url='add/syncjob_source' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
+61 -65
View File
@@ -933,20 +933,20 @@
</div> </div>
</div><!-- add domain alias modal --> </div><!-- add domain alias modal -->
<!-- add sync job modal --> <!-- add sync job modal -->
<div class="modal fade" id="addSyncJobModalAdmin" tabindex="-1" role="dialog" aria-hidden="true" aria-labelledby="addSyncJobModalAdminLabel"> <div class="modal fade" id="addSyncJobModalAdmin" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl"> <div class="modal-dialog modal-xl">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="addSyncJobModalAdminLabel">{{ lang.syncjobs.add }}</h3> <h3 class="modal-title">{{ lang.add.syncjob }}</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ lang.footer.cancel }}"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p class="text-muted">{{ lang.syncjobs.add_hint }}</p> <p class="text-muted">{{ lang.add.syncjob_hint }}</p>
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_syncjob"> <form class="form-horizontal" data-cached-form="false" role="form" data-id="add_syncjob">
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_username">{{ lang.syncjobs.user2_label }}</label> <label class="control-label col-sm-2 text-sm-end" for="username">{{ lang.add.username }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<select data-live-search="true" id="addsja_username" name="username" title="{{ lang.add.select }}" data-width="100%" required> <select data-live-search="true" name="username" title="{{ lang.add.select }}" required>
{% for mailbox in mailboxes %} {% for mailbox in mailboxes %}
<option>{{ mailbox }}</option> <option>{{ mailbox }}</option>
{% endfor %} {% endfor %}
@@ -954,142 +954,140 @@
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_source_id">{{ lang.syncjobs.source_select }}</label> <label class="control-label col-sm-2 text-sm-end" for="host1">{{ lang.add.hostname }}</label>
<div class="col-sm-10 imapsync-select-wrap">
<select class="imapsync-source-select" id="addsja_source_id" data-live-search="true" data-show-subtext="true" data-width="100%" name="source_id" title="{{ lang.syncjobs.source_select }}" required>
{# options are loaded via the API on modal open (js/site/imapsync.js) #}
</select>
<div class="imapsync-no-source d-none mt-2">
<small class="text-muted">{{ lang.syncjobs.no_sources }}</small>
{% if mailcow_cc_role == 'admin' or acl.syncjobs == 1 %}
<button type="button" class="btn btn-sm btn-success ms-2 imapsync-open-source-modal"><i class="bi bi-plus-lg" aria-hidden="true"></i> {{ lang.syncjobs.source_add }}</button>
{% endif %}
</div>
</div>
</div>
<div class="row mb-2 imapsync-user1-row">
<label class="control-label col-sm-2 text-sm-end" for="addsja_user1">{{ lang.syncjobs.user1_label }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="addsja_user1" name="user1" autocomplete="off" required> <input type="text" class="form-control" name="host1" required>
</div>
</div>
<div class="row mb-2 imapsync-oauth-connect-row" style="display:none;">
<div class="offset-sm-2 col-sm-10">
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn"><i class="bi bi-box-arrow-up-right" aria-hidden="true"></i> {{ lang.syncjobs.syncjob_oauth_connect }}</button>
<span class="imapsync-oauth-connect-status ms-2" role="status" aria-live="polite"></span>
<div><small class="text-muted">{{ lang.syncjobs.oauth_hint }}</small></div>
</div>
</div>
<div class="row mb-4 password1-row">
<label class="control-label col-sm-2 text-sm-end" for="addsja_password1">{{ lang.syncjobs.password1_label }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="addsja_password1" name="password1" autocomplete="new-password" data-formcache-skip>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_mins_interval">{{ lang.syncjobs.mins_interval }}</label> <label class="control-label col-sm-2 text-sm-end" for="port1">{{ lang.add.port }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsja_mins_interval" name="mins_interval" min="1" max="43800" value="20" required> <input type="number" class="form-control" name="port1" min="1" max="65535" value="143" required>
<small class="text-muted">1-65535</small>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="user1">{{ lang.add.username }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="user1" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="password1">{{ lang.add.password }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password1" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="enc1">{{ lang.add.enc_method }}</label>
<div class="col-sm-10">
<select name="enc1" title="{{ lang.add.select }}" required>
<option value="SSL" selected>SSL</option>
<option value="TLS">STARTTLS</option>
<option value="PLAIN">PLAIN</option>
</select>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="mins_interval">{{ lang.add.mins_interval }}</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="20" required>
<small class="text-muted">1-43800</small> <small class="text-muted">1-43800</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label> <label class="control-label col-sm-2 text-sm-end" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="addsja_subfolder2" name="subfolder2" value=""> <input type="text" class="form-control" name="subfolder2" value="">
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_maxage">{{ lang.syncjobs.maxage|raw }}</label> <label class="control-label col-sm-2 text-sm-end" for="maxage">{{ lang.edit.maxage|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsja_maxage" name="maxage" min="0" max="32000" value="0"> <input type="number" class="form-control" name="maxage" min="0" max="32000" value="0">
<small class="text-muted">0-32000</small> <small class="text-muted">0-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label> <label class="control-label col-sm-2 text-sm-end" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsja_maxbytespersecond" name="maxbytespersecond" min="0" max="125000000" value="0"> <input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0">
<small class="text-muted">0-125000000</small> <small class="text-muted">0-125000000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_timeout1">{{ lang.syncjobs.timeout1 }}</label> <label class="control-label col-sm-2 text-sm-end" for="timeout1">{{ lang.edit.timeout1 }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsja_timeout1" name="timeout1" min="1" max="32000" value="600"> <input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600">
<small class="text-muted">1-32000</small> <small class="text-muted">1-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_timeout2">{{ lang.syncjobs.timeout2 }}</label> <label class="control-label col-sm-2 text-sm-end" for="timeout2">{{ lang.edit.timeout2 }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsja_timeout2" name="timeout2" min="1" max="32000" value="600"> <input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600">
<small class="text-muted">1-32000</small> <small class="text-muted">1-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsja_exclude">{{ lang.syncjobs.exclude }}</label> <label class="control-label col-sm-2 text-sm-end" for="exclude">{{ lang.add.exclude }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="addsja_exclude" name="exclude" value="(?i)spam|(?i)junk"> <input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk">
</div> </div>
</div> </div>
<div class="row mb-4"> <div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end" id="addsja_custom_params_label">{{ lang.syncjobs.custom_params }}</label> <label class="control-label col-sm-2 text-sm-end" for="custom_params">{{ lang.add.custom_params }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="imapsync-cp-editor" role="group" aria-labelledby="addsja_custom_params_label"> <input type="text" class="form-control" name="custom_params" placeholder="--some-param=xy --other-param=yx">
<div class="imapsync-cp-rows"></div> <small class="text-muted">{{ lang.add.custom_params_hint }}</small>
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg" aria-hidden="true"></i> {{ lang.syncjobs.custom_param_add }}</button>
</div>
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="[]">
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.add.delete2duplicates }} (--delete2duplicates)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.syncjobs.delete1 }} (--delete1)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.add.delete1 }} (--delete1)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.syncjobs.delete2 }} (--delete2)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.add.delete2 }} (--delete2)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.syncjobs.automap }} (--automap)</label> <label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.add.automap }} (--automap)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label> <label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.add.skipcrossduplicates }} (--skipcrossduplicates)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label> <label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.add.subscribeall }} (--subscribeall)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-4"> <div class="row mb-4">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="dry"> {{ lang.syncjobs.dry }} (--dry)</label> <label><input type="checkbox" class="form-check-input" value="1" name="dry"> {{ lang.add.dry }} (--dry)</label>
</div> </div>
</div> </div>
</div> </div>
@@ -1349,5 +1347,3 @@
</div> </div>
</div> </div>
</div><!-- DNS info modal --> </div><!-- DNS info modal -->
{% include "modals/imapsync_source.twig" %}
+55 -59
View File
@@ -27,153 +27,150 @@
</div> </div>
</div><!-- add domain admin modal --> </div><!-- add domain admin modal -->
<!-- add sync job modal --> <!-- add sync job modal -->
<div class="modal fade" id="addSyncJobModal" tabindex="-1" role="dialog" aria-hidden="true" aria-labelledby="addSyncJobModalLabel"> <div class="modal fade" id="addSyncJobModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl"> <div class="modal-dialog modal-xl">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="addSyncJobModalLabel">{{ lang.syncjobs.add }}</h3> <h3 class="modal-title">{{ lang.add.syncjob }}</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ lang.footer.cancel }}"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>{{ lang.syncjobs.add_hint }}</p> <p>{{ lang.add.syncjob_hint }}</p>
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob"> <form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_source_id">{{ lang.syncjobs.source_select }}</label> <label class="control-label col-sm-2" for="host1">{{ lang.add.hostname }}</label>
<div class="col-sm-10 imapsync-select-wrap">
<select class="imapsync-source-select" id="addsju_source_id" data-live-search="true" data-show-subtext="true" data-width="100%" name="source_id" title="{{ lang.syncjobs.source_select }}" required>
{# options are loaded via the API on modal open (js/site/imapsync.js) #}
</select>
<div class="imapsync-no-source d-none mt-2">
<small class="text-muted">{{ lang.syncjobs.no_sources }}</small>
{% if mailcow_cc_role == 'admin' or acl.syncjobs == 1 %}
<button type="button" class="btn btn-sm btn-success ms-2 imapsync-open-source-modal"><i class="bi bi-plus-lg" aria-hidden="true"></i> {{ lang.syncjobs.source_add }}</button>
{% endif %}
</div>
</div>
</div>
<div class="row mb-2 imapsync-user1-row">
<label class="control-label col-sm-2 text-sm-end" for="addsju_user1">{{ lang.syncjobs.user1_label }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="addsju_user1" name="user1" autocomplete="off" required> <input type="text" class="form-control" name="host1" required>
</div> </div>
</div> </div>
<div class="row mb-2 imapsync-oauth-connect-row" style="display:none;"> <div class="row mb-4">
<div class="offset-sm-2 col-sm-10"> <label class="control-label col-sm-2" for="port1">{{ lang.add.port }}</label>
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn"><i class="bi bi-box-arrow-up-right" aria-hidden="true"></i> {{ lang.syncjobs.syncjob_oauth_connect }}</button>
<span class="imapsync-oauth-connect-status ms-2" role="status" aria-live="polite"></span>
<div><small class="text-muted">{{ lang.syncjobs.oauth_hint }}</small></div>
</div>
</div>
<div class="row mb-4 password1-row">
<label class="control-label col-sm-2 text-sm-end" for="addsju_password1">{{ lang.syncjobs.password1_label }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="password" class="form-control" id="addsju_password1" name="password1" autocomplete="new-password" data-formcache-skip data-hibp="true"> <input type="number" class="form-control" name="port1" min="1" max="65535" value="143" required>
<small class="text-muted">1-65535</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_mins_interval">{{ lang.syncjobs.mins_interval }}</label> <label class="control-label col-sm-2" for="user1">{{ lang.add.username }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsju_mins_interval" name="mins_interval" min="1" max="43800" value="20" required> <input type="text" class="form-control" name="user1" required>
</div>
</div>
<div class="row mb-4">
<label class="control-label col-sm-2" for="password1">{{ lang.add.password }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password1" data-hibp="true" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="enc1">{{ lang.add.enc_method }}</label>
<div class="col-sm-10">
<select name="enc1" title="{{ lang.add.select }}" required>
<option value="SSL" selected>SSL</option>
<option value="TLS">STARTTLS</option>
<option value="PLAIN">PLAIN</option>
</select>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="mins_interval">{{ lang.add.mins_interval }}</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="20" required>
<small class="text-muted">1-43800</small> <small class="text-muted">1-43800</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label> <label class="control-label col-sm-2" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="addsju_subfolder2" name="subfolder2" value=""> <input type="text" class="form-control" name="subfolder2" value="">
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_maxage">{{ lang.syncjobs.maxage|raw }}</label> <label class="control-label col-sm-2" for="maxage">{{ lang.edit.maxage|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsju_maxage" name="maxage" min="0" max="32000" value="0"> <input type="number" class="form-control" name="maxage" min="0" max="32000" value="0">
<small class="text-muted">0-32000</small> <small class="text-muted">0-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label> <label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsju_maxbytespersecond" name="maxbytespersecond" min="0" max="125000000" value="0"> <input type="number" class="form-control" name="maxbytespersecond" min="0" max="125000000" value="0">
<small class="text-muted">0-125000000</small> <small class="text-muted">0-125000000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_timeout1">{{ lang.syncjobs.timeout1 }}</label> <label class="control-label col-sm-2" for="timeout1">{{ lang.edit.timeout1 }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsju_timeout1" name="timeout1" min="1" max="32000" value="600"> <input type="number" class="form-control" name="timeout1" min="1" max="32000" value="600">
<small class="text-muted">1-32000</small> <small class="text-muted">1-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_timeout2">{{ lang.syncjobs.timeout2 }}</label> <label class="control-label col-sm-2" for="timeout2">{{ lang.edit.timeout2 }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="addsju_timeout2" name="timeout2" min="1" max="32000" value="600"> <input type="number" class="form-control" name="timeout2" min="1" max="32000" value="600">
<small class="text-muted">1-32000</small> <small class="text-muted">1-32000</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="addsju_exclude">{{ lang.syncjobs.exclude }}</label> <label class="control-label col-sm-2" for="exclude">{{ lang.add.exclude }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="addsju_exclude" name="exclude" value="(?i)spam|(?i)junk"> <input type="text" class="form-control" name="exclude" value="(?i)spam|(?i)junk">
</div> </div>
</div> </div>
<div class="row mb-4"> <div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end" id="addsju_custom_params_label">{{ lang.syncjobs.custom_params }}</label> <label class="control-label col-sm-2" for="custom_params">{{ lang.add.custom_params }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="imapsync-cp-editor" role="group" aria-labelledby="addsju_custom_params_label"> <input type="text" class="form-control" name="custom_params" placeholder="--delete2folders --otheroption">
<div class="imapsync-cp-rows"></div>
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg" aria-hidden="true"></i> {{ lang.syncjobs.custom_param_add }}</button>
</div>
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="[]">
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates" checked> {{ lang.add.delete2duplicates }} (--delete2duplicates)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.syncjobs.delete1 }} (--delete1)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete1"> {{ lang.add.delete1 }} (--delete1)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.syncjobs.delete2 }} (--delete2)</label> <label><input type="checkbox" class="form-check-input" value="1" name="delete2"> {{ lang.add.delete2 }} (--delete2)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.syncjobs.automap }} (--automap)</label> <label><input type="checkbox" class="form-check-input" value="1" name="automap" checked> {{ lang.add.automap }} (--automap)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label> <label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"> {{ lang.add.skipcrossduplicates }} (--skipcrossduplicates)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label> <label><input type="checkbox" class="form-check-input" value="1" name="subscribeall" checked> {{ lang.add.subscribeall }} (--subscribeall)</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="dry" checked> {{ lang.syncjobs.dry }} (--dry)</label> <label><input type="checkbox" class="form-check-input" value="1" name="dry" checked> {{ lang.add.dry }} (--dry)</label>
</div> </div>
</div> </div>
</div> </div>
@@ -194,7 +191,6 @@
</div> </div>
</div> </div>
</div><!-- add sync job modal --> </div><!-- add sync job modal -->
{% include 'modals/imapsync_source.twig' %}
<!-- app passwd modal --> <!-- app passwd modal -->
<div class="modal fade" id="addAppPasswdModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal fade" id="addAppPasswdModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
+1 -1
View File
@@ -19,7 +19,7 @@
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Spamfilter" role="tab" data-bs-toggle="tab" data-bs-target="#Spamfilter">{{ lang.user.spamfilter }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Spamfilter" role="tab" data-bs-toggle="tab" data-bs-target="#Spamfilter">{{ lang.user.spamfilter }}</button></li>
{% endif %} {% endif %}
{% if acl.syncjobs == 1 %} {% if acl.syncjobs == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#Syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#Syncjobs">{{ lang.user.sync_jobs }}</button></li>
{% endif %} {% endif %}
{% if acl.app_passwds == 1 %} {% if acl.app_passwds == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="AppPasswds" role="tab" data-bs-toggle="tab" data-bs-target="#AppPasswds">{{ lang.user.app_passwds }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="AppPasswds" role="tab" data-bs-toggle="tab" data-bs-target="#AppPasswds">{{ lang.user.app_passwds }}</button></li>
+4 -21
View File
@@ -1,27 +1,10 @@
<div class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs"> <div class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs">
<div class="card mb-4">
<div class="card-header d-flex fs-5">
<span class="d-none d-md-block">{{ lang.syncjobs.sources_panel }}</span>
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-imapsync-sources" data-bs-toggle="collapse" aria-controls="collapse-tab-imapsync-sources">
{{ lang.syncjobs.sources_panel }}
</button>
</div>
<div id="collapse-tab-imapsync-sources" class="card-body collapse" data-bs-parent="#user-content">
<p class="text-muted">{{ lang.syncjobs.sources_hint }}</p>
<div class="mass-actions-user mb-4">
<div class="btn-group" data-acl="{{ acl.syncjobs }}">
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addImapsyncSourceModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.source_add }}</a>
</div>
</div>
<table id="imapsync_source_table" class="table table-striped dt-responsive w-100"></table>
</div>
</div>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs">
{{ lang.syncjobs.sync_jobs }} {{ lang.user.sync_jobs }}
</button> </button>
<span class="d-none d-md-block">{{ lang.syncjobs.sync_jobs }}</span> <span class="d-none d-md-block">{{ lang.user.sync_jobs }}</span>
</div> </div>
<div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content">
<div class="mass-actions-user mb-4"> <div class="mass-actions-user mb-4">
@@ -40,7 +23,7 @@
</ul> </ul>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a> <a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -61,7 +44,7 @@
</ul> </ul>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.create }}</a> <a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addSyncJobModal"><i class="bi bi-plus-lg"></i> {{ lang.user.create_syncjob }}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -3,7 +3,6 @@
<script type='text/javascript'> <script type='text/javascript'>
var acl = '{{ acl_json|raw }}'; var acl = '{{ acl_json|raw }}';
var lang = {{ lang_user|raw }}; var lang = {{ lang_user|raw }};
lang.syncjobs = {{ lang_syncjobs|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = Math.trunc('{{ pagination_size }}'); var pagination_size = Math.trunc('{{ pagination_size }}');
var mailcow_cc_username = '{{ mailcow_cc_username }}'; var mailcow_cc_username = '{{ mailcow_cc_username }}';
-2
View File
@@ -73,10 +73,8 @@ $template_data = [
'lang_user' => json_encode($lang['user']), 'lang_user' => json_encode($lang['user']),
'number_of_app_passwords' => $number_of_app_passwords, 'number_of_app_passwords' => $number_of_app_passwords,
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
]; ];
$js_minifier->add('/web/js/site/imapsync.js');
$js_minifier->add('/web/js/site/user.js'); $js_minifier->add('/web/js/site/user.js');
$js_minifier->add('/web/js/site/pwgen.js'); $js_minifier->add('/web/js/site/pwgen.js');
+2 -5
View File
@@ -194,9 +194,6 @@ services:
ofelia.job-exec.phpfpm_ldap_sync.schedule: "0 * * * * *" ofelia.job-exec.phpfpm_ldap_sync.schedule: "0 * * * * *"
ofelia.job-exec.phpfpm_ldap_sync.no-overlap: "true" ofelia.job-exec.phpfpm_ldap_sync.no-overlap: "true"
ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /crons/ldap-sync.php || exit 0\"" ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /crons/ldap-sync.php || exit 0\""
ofelia.job-exec.phpfpm_imapsync_oauth_refresh.schedule: "0 * * * * *"
ofelia.job-exec.phpfpm_imapsync_oauth_refresh.no-overlap: "true"
ofelia.job-exec.phpfpm_imapsync_oauth_refresh.command: "/bin/bash -c \"php /crons/imapsync-oauth-refresh.php || exit 0\""
networks: networks:
mailcow-network: mailcow-network:
aliases: aliases:
@@ -255,7 +252,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: ghcr.io/mailcow/dovecot:2.3.21.1-3 image: ghcr.io/mailcow/dovecot:2.3.21.1-2
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
- netfilter-mailcow - netfilter-mailcow
@@ -315,7 +312,7 @@ services:
ofelia.enabled: "true" ofelia.enabled: "true"
ofelia.job-exec.dovecot_imapsync_runner.schedule: "0 * * * * *" ofelia.job-exec.dovecot_imapsync_runner.schedule: "0 * * * * *"
ofelia.job-exec.dovecot_imapsync_runner.no-overlap: "true" ofelia.job-exec.dovecot_imapsync_runner.no-overlap: "true"
ofelia.job-exec.dovecot_imapsync_runner.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.py || exit 0\"" ofelia.job-exec.dovecot_imapsync_runner.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.pl || exit 0\""
ofelia.job-exec.dovecot_trim_logs.schedule: "0 * * * * *" ofelia.job-exec.dovecot_trim_logs.schedule: "0 * * * * *"
ofelia.job-exec.dovecot_trim_logs.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\"" ofelia.job-exec.dovecot_trim_logs.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\""
ofelia.job-exec.dovecot_quarantine.schedule: "0 */20 * * * *" ofelia.job-exec.dovecot_quarantine.schedule: "0 */20 * * * *"
@@ -1,15 +0,0 @@
#!/bin/bash
# Runs the PHPUnit tests of data/web/inc/lib/mailcow (tests/php) in throwaway containers.
# Dev only: PHPUnit is installed into tests/php/vendor, never into the production vendor directory.
# Usage: helper-scripts/dev_tests/run_php_unit_tests.sh [phpunit arguments, e.g. --filter JobServiceTest]
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TESTS_DIR="${REPO_DIR}/tests/php"
docker run --rm -u "$(id -u):$(id -g)" -v "${TESTS_DIR}:/app" -w /app composer:2 \
install --no-interaction --no-progress --quiet
docker run --rm -u "$(id -u):$(id -g)" -v "${REPO_DIR}:/app" -w /app/tests/php php:8.2-cli \
php vendor/bin/phpunit "$@"
-92
View File
@@ -1,92 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Core;
use Mailcow\Core\AccessDenied;
use Mailcow\Core\ApiResponse;
use Mailcow\Core\UserError;
use PHPUnit\Framework\TestCase;
class ApiResponseTest extends TestCase
{
protected function setUp(): void
{
$_SESSION = [];
}
public function testMessagesUseTheMailcowFormat(): void
{
$response = new ApiResponse();
$this->assertTrue($response->success(['mailbox_modified', 'a@example.org'], ['syncjob', 'add', 'job', []]));
$this->assertFalse($response->danger('access_denied', ['syncjob', 'edit', 'job', []]));
$this->assertSame([
['type' => 'success', 'log' => ['syncjob', 'add', 'job', []], 'msg' => ['mailbox_modified', 'a@example.org']],
['type' => 'danger', 'log' => ['syncjob', 'edit', 'job', []], 'msg' => 'access_denied'],
], $response->messages());
}
public function testSecretsAreMaskedInArrayLogParts(): void
{
$response = new ApiResponse(['password1', 'oauth_client_secret']);
$response->success('ok', ['syncjob', 'add', 'job', [
'password1' => 'secret',
'oauth_client_secret' => '',
'user1' => 'remote',
'nested' => ['password1' => 'kept'],
]]);
$this->assertSame(['syncjob', 'add', 'job', [
'password1' => '*',
'oauth_client_secret' => '',
'user1' => 'remote',
'nested' => ['password1' => 'kept'],
]], $response->messages()[0]['log']);
}
public function testNullSecretStaysNull(): void
{
$response = new ApiResponse(['password1']);
$response->danger('x', ['syncjob', 'add', 'job', ['password1' => null]]);
$this->assertSame(['password1' => null], $response->messages()[0]['log'][3]);
}
public function testUserErrorBecomesDangerMessage(): void
{
$response = new ApiResponse();
$this->assertFalse($response->error(new UserError('object_exists', ['name']), ['l']));
$response->error(new AccessDenied(), ['l']);
$this->assertSame(['object_exists', 'name'], $response->messages()[0]['msg']);
$this->assertSame('danger', $response->messages()[0]['type']);
$this->assertSame('access_denied', $response->messages()[1]['msg']);
}
public function testFlushAppendsToSessionAndStartsOver(): void
{
$_SESSION['return'] = [['type' => 'success', 'log' => [], 'msg' => 'earlier']];
$response = new ApiResponse();
$response->success('first', ['l']);
$response->danger('second', ['l']);
$response->flush();
$response->flush();
$this->assertSame(['earlier', 'first', 'second'], array_column($_SESSION['return'], 'msg'));
$this->assertSame([], $response->messages());
}
public function testFlushWithoutMessagesLeavesSessionUntouched(): void
{
(new ApiResponse())->flush();
$this->assertArrayNotHasKey('return', $_SESSION);
}
}
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff. Show more