WIP: sieve filters

This commit is contained in:
Antoine Nguyen
2024-06-22 19:44:45 +02:00
committed by Spitfireap
parent 4020b2fe5c
commit d2ef09b7f2
70 changed files with 8939 additions and 27 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ repos:
name: eslint
language: node
types: [javascript]
entry: eslint --fix
entry: eslint -c frontend/.eslintrc.js --ignore-pattern modoboa/* --fix
additional_dependencies: ["eslint-plugin-vue"]
- repo: https://github.com/psf/black
rev: "24.2.0"
+10
View File
@@ -41,6 +41,7 @@ services:
python3 manage.py runserver 0:8000"
volumes:
- .:/code
- ../sievelib/sievelib:/opt/venv/lib/python3.12/site-packages/sievelib
front:
container_name: modoboa_front
@@ -57,3 +58,12 @@ services:
DOCKER: "yes"
depends_on:
- api
dovecot:
container_name: modoboa_dovecot
restart: unless-stopped
image: dovecot/dovecot:latest
depends_on:
- api
volumes:
- ./docker/dovecot_config:/etc/dovecot/conf.d
+12
View File
@@ -0,0 +1,12 @@
auth_mechanisms = oauthbearer xoauth2
# authentication debug logging
auth_debug = yes
auth_verbose = yes
auth_debug_passwords = yes
passdb {
driver = oauth2
mechanisms = xoauth2 oauthbearer
args = /etc/dovecot/conf.d/dovecot-oauth2.conf.ext
}
@@ -0,0 +1,6 @@
introspection_mode = post
introspection_url = http://b1r8hUce03fA3OK260MQBgvi96dsnXvdmSDCVoLJ:dX8ZxICUPCVSoOJiyNAWIozf58IjmlUNQ6PPbX8rqD90QAw0te8052PKI82IRzmeCxv1kvHjSFpCOPqwteiFDj3TBq1cxmrn0Do2AlyDWVD1p8lUeiloERNEcj1InXaR@api:8000/api/o/introspect/
username_attribute = username
tls_ca_cert_file = /etc/ssl/certs/ca-certificates.crt
active_attribute = active
active_value = true
+1
View File
@@ -21,6 +21,7 @@
"debounce": "^2.0.0",
"js-cookie": "^3.0.5",
"luxon": "^3.4.4",
"oidc-client-ts": "^3.0.1",
"pinia": "^2.1.7",
"qrcode.vue": "^3.4.1",
"roboto-fontface": "*",
+21
View File
@@ -50,4 +50,25 @@ export default {
setARMessage(data) {
return repository.put(`${resource}/armessage/`, data)
},
getFilterSets() {
return repository.get(`${resource}/filtersets/`)
},
createFilterSet(data) {
return repository.post(`${resource}/filtersets/`, data)
},
getFilterConditionTemplates() {
return repository.get(`${resource}/filtersets/condition_templates/`)
},
getFilterActionTemplates() {
return repository.get(`${resource}/filtersets/action_templates/`)
},
getFilters(filterSetName) {
return repository.get(`${resource}/filtersets/${filterSetName}/filters/`)
},
createFilter(filterSetName, data) {
return repository.post(
`${resource}/filtersets/${filterSetName}/filters/`,
data
)
},
}
@@ -0,0 +1,275 @@
<template>
<v-card
:title="title"
>
<v-card-text>
<v-form ref="formRef">
<v-text-field
v-model="form.name"
:label="$gettext('Name')"
variant="outlined"
density="compact"
:rules="[rules.required]"
/>
<div>
<h4>{{ $gettext('Conditions') }}</h4>
<v-radio-group
v-model="form.match_type"
color="primary"
inline
>
<v-radio
:label="$gettext('All of the following')"
value="allof"
/>
<v-radio
:label="$gettext('Any of the following')"
value="anyof"
/>
<v-radio
:label="$gettext('All messages')"
value="all"
/>
</v-radio-group>
<div v-for="(condition, index) in form.conditions" class="d-flex align-center">
<v-select
v-model="condition.name"
:label="$gettext('Choose a header')"
:items="conditionTemplates"
item-title="label"
item-value="name"
variant="outlined"
density="compact"
:rules="[rules.required]"
/>
<v-select
v-model="condition.operator"
:label="$gettext('Choose an operator')"
:items="getConditionOperators(condition.name)"
item-title="label"
item-value="name"
variant="outlined"
class="ml-2"
density="compact"
:rules="[rules.required]"
/>
<v-text-field
v-model="condition.value"
:label="$gettext('Value')"
variant="outlined"
class="ml-2"
density="compact"
:type="getConditionType(index)"
:rules="[rules.required]"
/>
<v-btn
v-if="index === 0"
icon="mdi-plus"
variant="text"
:title="$gettext('Add condition')"
color="primary"
@click="addCondition"
/>
<v-btn
v-else
icon="mdi-trash-can"
variant="text"
:title="$gettext('Remove condition')"
color="error"
@click="removeCondition(index)"
/>
</div>
</div>
<div>
<h4>{{ $gettext('Actions') }}</h4>
<div v-for="(action, index) in form.actions" class="d-flex align-start mt-2">
<v-select
v-model="action.name"
:label="$gettext('Choose an action')"
:items="actionTemplates"
item-title="label"
item-value="name"
variant="outlined"
density="compact"
class="flex-grow-0"
/>
<div class="mx-2 d-flex flex-column flex-grow-1">
<template v-for="arg in getActionArguments(action.name)">
<v-text-field
v-if="arg.type === 'string' || arg.type === 'list'"
v-model="action.args[arg.name]"
density="compact"
variant="outlined"
:rules="[rules.required]"
/>
<v-checkbox
v-if="arg.type === 'boolean'"
v-model="action.args[arg.name]"
:label="arg.label"
/>
</template>
</div>
<v-btn
v-if="index === 0"
icon="mdi-plus"
variant="text"
:title="$gettext('Add action')"
color="primary"
@click="addAction"
/>
<v-btn
v-else
icon="mdi-trash-can"
variant="text"
:title="$gettext('Remove action')"
color="error"
@click="removeAction(index)"
/>
</div>
</div>
</v-form>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
:text="$gettext('Cancel')"
variant="plain"
@click="close"
></v-btn>
<v-btn
color="primary"
:text="submitLabel"
@click="submit"
></v-btn>
</v-card-actions>
</v-card>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext'
import accountApi from '@/api/account'
import rules from '@/plugins/rules'
const props = defineProps({
filterSet: {
type: String,
default: null
},
filter: {
type: Object,
required: false,
default: null
}
})
const emit = defineEmits(['close'])
const { $gettext } = useGettext()
const conditionTemplates = ref([])
const actionTemplates = ref([])
const formRef = ref()
const form = ref(getInitialFormContent())
const title = computed(() => {
return (props.filter) ? $gettext('Edit filter') : $gettext('New filter')
})
const submitLabel = computed(() => {
return (props.filter) ? $gettext('Update') : $gettext('Create')
})
watch(() => props.filter, (value) => {
if (value) {
form.value = JSON.parse(JSON.stringify(props.filter))
} else {
form.value = getInitialFormContent()
}
}, { immediate: true })
function close() {
emit('close')
form.value = getInitialFormContent()
}
function getInitialFormContent() {
return {
match_type: 'anyof',
conditions: [{}],
actions: [{
args: {}
}]
}
}
function getConditionOperators(condition) {
for (const template of conditionTemplates.value) {
if (template.name === condition) {
return template.operators
}
}
return []
}
function getConditionType(index) {
for (const template of conditionTemplates.value) {
if (template.name === form.value.conditions[index].name) {
for (const operator of template.operators) {
if (operator.name === form.value.conditions[index].operator) {
return (operator.type === 'string') ? 'text' : 'number'
}
}
}
}
}
function getActionArguments(action) {
for (const template of actionTemplates.value) {
if (template.name === action) {
return template.args
}
}
return []
}
function addCondition() {
form.value.conditions.push({
name: conditionTemplates.value[0].name,
operator: conditionTemplates.value[0].operators[0]
})
}
function removeCondition(index) {
form.value.conditions.splice(index, 1)
}
function addAction() {
form.value.actions.push({
name: actionTemplates.value[0].name,
args: {}
})
}
function removeAction(index) {
form.value.actions.splice(index, 1)
}
async function submit() {
const { valid } = await formRef.value.validate()
if (!valid) {
return
}
accountApi.createFilter(props.filterSet, form.value).then(() => {
})
}
accountApi.getFilterConditionTemplates().then(resp => {
conditionTemplates.value = resp.data
form.value.conditions[0].name = conditionTemplates.value[0].name
form.value.conditions[0].operator = conditionTemplates.value[0].operators[0]
})
accountApi.getFilterActionTemplates().then(resp => {
actionTemplates.value = resp.data
form.value.actions[0].name = actionTemplates.value[0].name
})
</script>
@@ -0,0 +1,62 @@
<template>
<v-card
:title="$gettext('Create a new filter set')"
>
<v-card-text>
<v-form ref="formRef">
<v-text-field
v-model="form.name"
:label="$gettext('Name')"
variant="outlined"
:rules="[rules.required]"
/>
<v-switch
:label="$gettext('Active')"
v-model="form.active"
color="primary"
/>
</v-form>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
:text="$gettext('Cancel')"
variant="plain"
@click="close"
></v-btn>
<v-btn
color="primary"
:text="$gettext('Create')"
@click="submit"
></v-btn>
</v-card-actions>
</v-card>
</template>
<script setup>
import { ref } from 'vue'
import rules from '@/plugins/rules'
import accountApi from '@/api/account'
const emit = defineEmits(['close'])
const form = ref({})
const formRef = ref()
function close() {
formRef.value.reset()
form.value = {}
emit('close')
}
async function submit() {
const { valid } = await formRef.value.validate()
if (!valid) {
return
}
accountApi.createFilterSet(form.value).then(() => {
close()
})
}
</script>
@@ -257,6 +257,11 @@ const mainMenuItems = [
]
const userSettingsMenuItems = [
{
text: $gettext('Filters'),
to: { name: 'AccountFilters' },
icon: 'mdi-filter'
},
{
text: $gettext('Settings'),
to: { name: 'AccountSettings' },
+16 -10
View File
@@ -256,6 +256,15 @@ const routes = [
requiresAuth: true,
},
children: [
{
path: 'filters',
name: 'AccountFilters',
component: () => import('@/views/account/FiltersView.vue'),
meta: {
layout: 'account',
requiresAuth: true,
},
},
{
path: ':tab?',
name: 'AccountSettings',
@@ -283,18 +292,15 @@ router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth !== undefined) {
const authStore = useAuthStore()
authStore.initialize().then(() => {
if (!authStore.isAuthenticated) {
next({ name: 'Login' })
} else {
/*
authStore.validateAccess()
/*
if (to.meta.allowedRoles !== undefined) {
if (to.meta.allowedRoles.indexOf(authStore.authUser.role) === -1) {
next({ name: 'Dashboard' })
return
}
if (to.meta.allowedRoles.indexOf(authStore.authUser.role) === -1) {
next({ name: 'Dashboard' })
return
}
}*/
next()
}
next()
})
} else {
next()
+33 -15
View File
@@ -3,15 +3,28 @@ import Cookies from 'js-cookie'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import gettext from '@/plugins/gettext'
import { UserManager } from 'oidc-client-ts'
import repository from '@/api/repository'
import accountApi from '@/api/account'
import accountsApi from '@/api/accounts'
import authApi from '@/api/auth'
// import authApi from '@/api/auth'
export const useAuthStore = defineStore('auth', () => {
const authUser = ref({})
const isAuthenticated = ref(false)
const manager = new UserManager({
authority: '/api/o',
client_id: 'LVQbfIIX3khWR3nDvix1u9yEGHZUxcx53bhJ7FlD',
// Is client_secret necessary?
client_secret:
'fXzG7tq23eXrM7RK41pXHRseuRWnxe4gV0p4A4YXJlF9jva7QfsDft1g7ESYU9AXR35QE0lo2Ti696OXsqb9JHLjtrPxaYmgMSRmRwLCce5hVr3FB2QhfSOThFYhcuuR',
metadata: {
authorization_endpoint: '/api/o/authorize/',
token_endpoint: '/api/o/token/',
},
scope: 'read write',
})
const userHasMailbox = computed(() => {
return authUser.value.mailbox !== null
@@ -26,31 +39,35 @@ export const useAuthStore = defineStore('auth', () => {
}
async function initialize() {
const token = Cookies.get('token')
if (!token) {
if (isAuthenticated.value) {
return
}
repository.defaults.headers.common.Authorization = `Bearer ${token}`
const user = await manager.getUser()
if (!user) {
return
}
repository.defaults.headers.common.Authorization = `Bearer ${user.access_token}`
repository.defaults.headers.post['Content-Type'] = 'application/json'
return fetchUser()
}
async function login(payload) {
const resp = await authApi.requestToken(payload)
const cookiesAttributes = { sameSite: 'strict' }
if (payload.rememberMe) {
cookiesAttributes.expires = 90
async function validateAccess() {
const user = await manager.getUser()
if (!user || user.expired) {
manager.signinRedirect()
}
}
const cookie = Cookies.withAttributes(cookiesAttributes)
cookie.set('token', resp.data.access)
cookie.set('refreshToken', resp.data.refresh)
async function login(payload) {
await manager.signinResourceOwnerCredentials(payload)
return initialize()
// const cookiesAttributes = { sameSite: 'strict' }
// if (payload.rememberMe) {
// cookiesAttributes.expires = 90
// }
}
async function $reset() {
delete repository.defaults.headers.common.Authorization
Cookies.remove('token')
Cookies.remove('refreshToken')
authUser.value = {}
isAuthenticated.value = false
}
@@ -85,12 +102,13 @@ export const useAuthStore = defineStore('auth', () => {
return {
authUser,
isAuthenticated,
userHasMailbox,
validateAccess,
fetchUser,
initialize,
login,
$reset,
updateAccount,
finalizeTFASetup,
userHasMailbox,
}
})
+165
View File
@@ -0,0 +1,165 @@
<template>
<v-toolbar flat>
<v-toolbar-title>{{ $gettext('Message filters') }}</v-toolbar-title>
</v-toolbar>
<div class="d-flex bg-white align-center pa-4">
<v-autocomplete
v-model="currentFilterSet"
:label="$gettext('Select a filter set')"
:items="filterSets"
item-title="name"
return-object
variant="outlined"
hide-details
class="flex-grow-0"
style="width: 200px"
/>
<v-btn
color="primary"
icon="mdi-plus"
size="small"
class="ml-4"
@click="showCreationForm = true"
:title="$gettext('Add filter set')"
/>
</div>
<div
v-if="currentFilterSet"
class="bg-white mt-4 pa-2"
>
<div class="d-flex align-center">
<v-spacer />
<v-btn
color="primary"
:title="$gettext('Add new filter')"
@click="showFilterForm = true"
>
<v-icon icon="mdi-plus" />
{{ $gettext('Add') }}
</v-btn>
</div>
<v-data-table
:headers="headers"
:items="filters"
>
<template #[`item.enabled`]="{ item }">
{{ $yesno(item.enabled) }}
</template>
<template #[`item.filter_actions`]="{ item }">
<v-menu offset-y>
<template #activator="{ props }">
<v-btn
icon="mdi-dots-horizontal"
variant="text"
v-bind="props"
>
</v-btn>
</template>
<MenuItems :items="filterActions" :obj="item" />
</v-menu>
</template>
</v-data-table>
</div>
<v-dialog
v-model="showFilterSetForm"
max-width="600"
>
<FilterSetForm
@close="closeFilterSetForm"
/>
</v-dialog>
<v-dialog
v-model="showFilterForm"
max-width="800"
>
<FilterForm
v-if="currentFilterSet"
:filter-set="currentFilterSet.name"
:filter="selectedFilter"
@close="closeFilterForm"
/>
</v-dialog>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext'
import accountApi from '@/api/account'
import FilterSetForm from '@/components/account/FilterSetForm.vue'
import FilterForm from '@/components/account/FilterForm.vue'
import MenuItems from '@/components/tools/MenuItems.vue'
const { $gettext } = useGettext()
const filterSets = ref([])
const filters = ref([])
const currentFilterSet = ref()
const selectedFilter = ref(null)
const showFilterSetForm = ref(false)
const showFilterForm = ref(false)
const headers = [
{ title: $gettext('Name'), key: 'name' },
{ title: $gettext('Active'), key: 'enabled' },
{ title: '', key: 'filter_actions', align: 'end' }
]
watch(currentFilterSet, (value) => {
accountApi.getFilters(value.name).then(resp => {
filters.value = resp.data
})
})
function closeFilterSetForm() {
showFilterSetForm.value = false
fetchFilterSets()
}
function closeFilterForm() {
showFilterForm.value = false
selectedFilter.value = null
}
function fetchFilterSets() {
accountApi.getFilterSets().then(resp => {
filterSets.value = resp.data
})
}
function editFilter(filter) {
selectedFilter.value = filter
showFilterForm.value = true
}
function deleteFilter(filter) {
}
const filterActions = [
{
label: $gettext('Edit'),
icon: 'mdi-circle-edit-outline',
onClick: editFilter
},
{
label: $gettext('Delete'),
icon: 'mdi-delete-outline',
onClick: deleteFilter,
color: 'red'
}
]
fetchFilterSets()
</script>
<style scoped>
.v-toolbar {
background-color: #f7f8fa !important;
}
.v-tabs {
background-color: #f7f8fa !important;
}
</style>
+12
View File
@@ -1134,6 +1134,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
jwt-decode@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -1254,6 +1259,13 @@ nth-check@^2.1.1:
dependencies:
boolbase "^1.0.0"
oidc-client-ts@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz#be264fb87c89f74f73863646431c32cd06f5ceb7"
integrity sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==
dependencies:
jwt-decode "^4.0.0"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -92,6 +92,8 @@ MODOBOA_APPS = (
'modoboa.pdfcredentials',
'modoboa.dmarc',
'modoboa.imap_migration',
'modoboa.postfix_autoreply',
'modoboa.sievefilters',
# Modoboa extensions here.
{% for extension in extensions %} '{{ extension }}',
{% endfor %}
+16
View File
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
"""The sieve filters editor of Modoboa."""
from __future__ import unicode_literals
from pkg_resources import get_distribution, DistributionNotFound
try:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
# package is not installed
pass
default_app_config = "modoboa_sievefilters.apps.SieveFiltersConfig"
+145
View File
@@ -0,0 +1,145 @@
"""Sievefilters serializers."""
from sievelib import commands
from rest_framework import serializers
from modoboa.sievefilters import constants, lib
class FilterSetSerializer(serializers.Serializer):
name = serializers.CharField()
active = serializers.BooleanField(default=False)
class ConditionSerializer(serializers.Serializer):
name = serializers.CharField()
operator = serializers.CharField()
value = serializers.CharField()
class ActionSerializer(serializers.Serializer):
name = serializers.CharField()
args = serializers.DictField(child=serializers.CharField())
class FilterSerializer(serializers.Serializer):
name = serializers.CharField()
enabled = serializers.BooleanField(default=True)
match_type = serializers.ChoiceField(choices=constants.MATCH_TYPES)
conditions = ConditionSerializer(many=True)
actions = ActionSerializer(many=True)
@staticmethod
def from_filters(filters) -> "FilterSerializer":
result = []
for fobj in filters:
test = (
fobj["content"].children[0] if not fobj["enabled"] else fobj["content"]
)
item = {
"name": fobj["name"],
"enabled": fobj["enabled"],
"match_type": test["test"].name,
"conditions": [],
"actions": [],
}
for t in test["test"]["tests"]:
print(t)
if isinstance(t, commands.TrueCommand):
item["match_type"] = "all"
item["conditions"] += [
{name: "Subject", "operator": "contains", value: ""}
]
break
elif isinstance(t, commands.SizeCommand):
item["conditions"] += [
{
name: "size",
"operator": t["comparator"][1:],
"value": t["limit"],
}
]
else:
operator_prefix = ""
if isinstance(t, commands.NotCommand):
t = t["test"]
operator_prefix = "not"
item["conditions"] += [
{
"name": t["header-names"].strip('"'),
"operator": "{}{}".format(
operator_prefix, t["match-type"][1:]
),
"value": t["key-list"].strip('"'),
}
]
for c in test.children:
action = {"name": c.name, "args": {}}
tpl = lib.find_action_template(c.name)
for argtpl in tpl.get("args", []):
if argtpl["name"] not in c:
continue
value = c[argtpl["name"]]
if argtpl["type"] == "boolean":
value = True if value else False
else:
value = value.strip('"')
action["args"][argtpl["name"]] = value
item["actions"] += [action]
result.append(item)
return FilterSerializer(result, many=True)
def to_filter(self):
"""Convert serializer data to filter."""
conditions = []
actions = []
for condition in self.validated_data["conditions"]:
conditions.append(
[condition["name"], f":{condition['operator']}", condition["value"]]
)
for input_action in self.validated_data["actions"]:
action = [input_action["name"]]
tpl = lib.find_action_template(input_action["name"])
if "args_order" in tpl:
for name in tpl["args_order"]:
if name in input_action["args"]:
action.append(input_action["args"][name])
else:
action += [arg for arg in input_action["args"].values()]
actions.append(action)
return (conditions, actions)
class OperatorSerializer(serializers.Serializer):
name = serializers.CharField()
label = serializers.CharField()
type = serializers.CharField()
class ConditionTemplateSerializer(serializers.Serializer):
name = serializers.CharField()
label = serializers.CharField()
operators = OperatorSerializer(many=True)
class ActionArgumentSerializer(serializers.Serializer):
name = serializers.CharField()
type = serializers.CharField()
label = serializers.CharField(required=False)
value = serializers.CharField(required=False)
class ActionTemplateSerializer(serializers.Serializer):
name = serializers.CharField()
label = serializers.CharField()
args = ActionArgumentSerializer(many=True, required=False)
args_order = serializers.ListField(child=serializers.CharField(), required=False)
+10
View File
@@ -0,0 +1,10 @@
"""App related API urls."""
from rest_framework import routers
from modoboa.sievefilters.api.v2 import viewsets
router = routers.SimpleRouter()
router.register(r"account/filtersets", viewsets.FilterSetViewSet, basename="filterset")
urlpatterns = router.urls
+89
View File
@@ -0,0 +1,89 @@
"""Sievefilters viewsets."""
import copy
from rest_framework import response, viewsets
from rest_framework.decorators import action
from modoboa.lib.connections import ConnectionError
from modoboa.sievefilters import constants
from modoboa.sievefilters.lib import SieveClient, SieveClientError
from modoboa.sievefilters.api.v2 import serializers
class FilterSetViewSet(viewsets.ViewSet):
serializer_class = serializers.FilterSetSerializer
def get_sieve_client(self, request):
# try:
return SieveClient(user=request.user.username, password=str(request.auth))
# except ConnectionError as e:
# raise
def list(self, request):
sclient = self.get_sieve_client(request)
active_script, scripts = sclient.listscripts()
scripts = [
{"name": script, "active": active_script == script} for script in scripts
]
return response.Response(scripts)
def create(self, request):
serializer = serializers.FilterSetSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
sclient = self.get_sieve_client(request)
sclient.pushscript(
serializer.validated_data["name"],
"# Empty script",
serializer.validated_data["active"],
)
return response.Response(serializer.validated_data, 201)
@action(methods=["get"], detail=False)
def condition_templates(self, request):
templates = copy.deepcopy(constants.CONDITION_TEMPLATES)
for template in templates:
template["operators"] = [
{"name": operator[0], "label": operator[1], "type": operator[2]}
for operator in template["operators"]
]
serializer = serializers.ConditionTemplateSerializer(templates, many=True)
return response.Response(serializer.data)
@action(methods=["get"], detail=False)
def action_templates(self, request):
serializer = serializers.ActionTemplateSerializer(
constants.ACTION_TEMPLATES, many=True
)
return response.Response(serializer.data)
@action(methods=["get", "post"], detail=True)
def filters(self, request, pk=None):
sclient = self.get_sieve_client(request)
try:
fset = sclient.getscript(pk, format="gui")
except SieveClientError:
return response.Response(status=404)
if request.method == "GET":
serializer = serializers.FilterSerializer.from_filters(fset.filters)
return response.Response(serializer.data)
serializer = serializers.FilterSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
conditions, actions = serializer.to_filter()
match_type = serializer.validated_data["match_type"]
if match_type == "all":
match_type = "anyof"
conditions = [("true",)]
fltname = serializer.validated_data["name"].encode("utf-8")
try:
fset.addfilter(fltname, conditions, actions, match_type)
except (BadArgument, BadValue) as inst:
return response.Response(str(inst), status=400)
sclient.pushscript(fset.name, "{}".format(fset))
return response.Response(serializer.validated_data, status=201)
# @action(methods=["delete"], detail=True, url_path="filters/{filter}")
# def delete_filter(self, request, pk=None):
# sclient = self.get_sieve_client(request)
# return response.Response(status=204)
+27
View File
@@ -0,0 +1,27 @@
"""AppConfig for stats."""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy
def load_settings():
from modoboa.parameters import tools as param_tools
from modoboa.sievefilters import forms
param_tools.registry.add(
"global", forms.ParametersForm, gettext_lazy("Sieve filters")
)
param_tools.registry.add(
"user", forms.UserSettings, gettext_lazy("Message filters")
)
class SieveFiltersConfig(AppConfig):
"""App configuration."""
name = "modoboa.sievefilters"
verbose_name = "Sieve filters editor for Modoboa"
def ready(self):
load_settings()
from . import handlers
+72
View File
@@ -0,0 +1,72 @@
"""Sieve filters constants."""
from django.utils.translation import gettext_lazy as _
MATCH_TYPE_ALLOF = "allof"
MATCH_TYPE_ANYOF = "anyof"
MATCH_TYPE_ALL = "all"
MATCH_TYPES = [
(MATCH_TYPE_ALLOF, _("All of the following")),
(MATCH_TYPE_ANYOF, _("Any of the following")),
(MATCH_TYPE_ALL, _("All messages")),
]
HEADER_OPERATORS = [
("contains", _("contains"), "string"),
("notcontains", _("does not contain"), "string"),
("is", _("is"), "string"),
("isnot", _("is not"), "string"),
]
CONDITION_TEMPLATES = [
{"name": "Subject", "label": _("Subject"), "operators": HEADER_OPERATORS},
{"name": "From", "label": _("Sender"), "operators": HEADER_OPERATORS},
{"name": "To", "label": _("Recipient"), "operators": HEADER_OPERATORS},
{"name": "Cc", "label": _("Cc"), "operators": HEADER_OPERATORS},
{
"name": "size",
"label": _("Size"),
"operators": [
("over", _("is greater than"), "number"),
("under", _("is less than"), "number"),
],
},
]
ACTION_TEMPLATES = [
{
"name": "fileinto",
"label": _("Move message to"),
"args": [
{"name": "mailbox", "type": "list", "vloader": "userfolders"},
{
"name": "copy",
"type": "boolean",
"label": _("Keep local copy"),
"value": ":copy",
},
],
"args_order": ["copy", "mailbox"],
},
{
"name": "redirect",
"label": _("Redirect message to"),
"args": [
{"name": "address", "type": "string"},
{
"name": "copy",
"type": "boolean",
"label": _("Keep local copy"),
"value": ":copy",
},
],
"args_order": ["copy", "address"],
},
{
"name": "reject",
"label": _("Reject message"),
"args": [{"name": "text", "type": "string"}],
},
{"name": "stop", "label": _("Stop processing")},
]
+395
View File
@@ -0,0 +1,395 @@
"""Custom forms."""
from sievelib import commands
from sievelib.managesieve import SUPPORTED_AUTH_MECHS
from django import forms
from django.http import QueryDict
from django.utils.encoding import smart_str
from django.utils.translation import gettext as _, gettext_lazy
from modoboa.admin.templatetags.admin_tags import gender
from modoboa.lib import form_utils
from modoboa.parameters import forms as param_forms
from . import constants
from . import imaputils
from . import lib
class FiltersSetForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))
active = forms.BooleanField(
label=gender("Active", "m"),
required=False,
initial=False,
help_text=gettext_lazy("Check to activate this filters set"),
)
class FilterForm(forms.Form):
"""A dynamic form to edit a filter."""
def __init__(self, conditions, actions, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.field_widths = {"match_type": 8}
self.fields["name"] = forms.CharField(label=_("Name"))
self.fields["match_type"] = forms.ChoiceField(
label=_("Match type"),
choices=[
("allof", _("All of the following")),
("anyof", _("Any of the following")),
("all", _("All messages")),
],
initial="anyof",
widget=form_utils.HorizontalRadioSelect(),
)
self.conds_cnt = 0
for c in conditions:
getattr(self, "_build_%s_field" % c[0])(c[1], c[2])
self.actions_cnt = 0
for a in actions:
getattr(self, "_build_%s_field" % a[0])(request, *a[1:])
def clean_name(self):
"""Check that name does not contain strange chars."""
if "#" in self.cleaned_data["name"]:
raise forms.ValidationError(_("Wrong filter name"))
return self.cleaned_data["name"]
def _build_header_field(self, name, op, value):
"""Add a new header field to form."""
targets = []
ops = []
vfield = None
for tpl in constants.CONDITION_TEMPLATES:
targets += [
(tpl["name"], tpl["label"]),
]
if tpl["name"] != name:
continue
for opdef in tpl["operators"]:
ops += [opdef[:2]]
if op != opdef[0]:
continue
if opdef[2] in ["string", "number"]:
vfield = forms.CharField(max_length=255, initial=value)
self.fields["cond_target_%d" % self.conds_cnt] = forms.ChoiceField(
initial=name, choices=targets
)
self.fields["cond_operator_%d" % self.conds_cnt] = forms.ChoiceField(
initial=op, choices=ops
)
self.fields["cond_value_%d" % self.conds_cnt] = vfield
self.conds_cnt += 1
def _build_Subject_field(self, op, value):
self._build_header_field("Subject", op, value)
def _build_To_field(self, op, value):
self._build_header_field("To", op, value)
def _build_From_field(self, op, value):
self._build_header_field("From", op, value)
def _build_Cc_field(self, op, value):
self._build_header_field("Cc", op, value)
def _build_size_field(self, op, value):
self._build_header_field("size", op, value)
def _build_action_field(self, request, name, *values):
"""Add a new action field to form."""
actions = []
args = None
for tpl in constants.ACTION_TEMPLATES:
actions += [
(tpl["name"], tpl["label"]),
]
if name == tpl["name"]:
args = tpl.get("args", [])
self.fields["action_name_%d" % self.actions_cnt] = forms.ChoiceField(
initial=name, choices=actions
)
for cnt in range(len(args)):
arg = args[cnt]
value = values[cnt] if len(values) > cnt else None
aname = "action_arg_%d_%d" % (self.actions_cnt, cnt)
if arg["type"] == "string":
self.fields[aname] = forms.CharField(max_length=255, initial=value)
elif arg["type"] == "boolean":
self.fields[aname] = forms.BooleanField(
label=arg["label"], initial=value, required=False
)
elif arg["type"] == "list":
choices = getattr(self, arg["vloader"])(request)
self.fields[aname] = forms.ChoiceField(initial=value, choices=choices)
self.actions_cnt += 1
def _build_redirect_field(self, request, *values):
self._build_action_field(request, "redirect", *values)
def _build_reject_field(self, request, *values):
self._build_action_field(request, "reject", *values)
def _build_fileinto_field(self, request, *values):
self._build_action_field(request, "fileinto", *values)
def _build_stop_field(self, request):
self._build_action_field(request, "stop")
def __build_folders_list(self, folders, user, imapc, parentmb=None):
ret = []
for fd in folders:
value = fd["path"] if "path" in fd else fd["name"]
if parentmb:
ret += [
(
value,
fd["name"].replace("%s%s" % (parentmb, imapc.hdelimiter), ""),
)
]
else:
ret += [(value, fd["name"])]
if "sub" in fd:
submboxes = imapc.getmboxes(user, value)
ret += self.__build_folders_list(submboxes, user, imapc, value)
return ret
def userfolders(self, request):
mbc = imaputils.get_imapconnector(request)
ret = mbc.getmboxes(request.user)
folders = self.__build_folders_list(ret, request.user, mbc)
return folders
def tofilter(self):
"""Convert form values to filter values."""
conditions = []
actions = []
for cpt in range(self.conds_cnt):
conditions += [
(
self.cleaned_data["cond_target_%d" % cpt],
":" + self.cleaned_data["cond_operator_%d" % cpt],
self.cleaned_data["cond_value_%d" % cpt],
)
]
for cpt in range(self.actions_cnt):
action = self.cleaned_data["action_name_%d" % cpt]
tpl = lib.find_action_template(action)
naction = [action]
args = {}
for pos, argtpl in enumerate(tpl.get("args", [])):
fieldname = "action_arg_{}_{}".format(cpt, pos)
if fieldname not in self.cleaned_data:
continue
value = self.cleaned_data[fieldname]
if argtpl["type"] == "boolean" and isinstance(value, bool):
if not value:
continue
value = argtpl["value"]
args[argtpl["name"]] = value
if "args_order" in tpl:
# Corresponding command requires args to be in a special order
for name in tpl["args_order"]:
if name in args:
naction.append(args[name])
else:
naction += [arg for arg in args.values()]
actions += [naction]
return (conditions, actions)
def build_filter_form_from_qdict(request):
conditions = []
actions = []
qdict = QueryDict("", mutable=True)
qdict["name"] = request.POST["name"]
qdict["match_type"] = request.POST["match_type"]
cpt = 0
i = 0
if qdict["match_type"] != "all":
while True:
if cpt == int(request.POST["conds"]):
break
if "cond_target_%d" % i in request.POST:
qdict["cond_target_%d" % cpt] = request.POST["cond_target_%d" % i]
qdict["cond_operator_%d" % cpt] = request.POST["cond_operator_%d" % i]
qdict["cond_value_%d" % cpt] = request.POST["cond_value_%d" % i]
condtarget = request.POST["cond_target_%d" % i]
condop = request.POST["cond_operator_%d" % i]
condvalue = request.POST["cond_value_%d" % i]
conditions += [(condtarget, condop, condvalue)]
cpt += 1
i += 1
cpt = 0
i = 0
while True:
if cpt == int(request.POST["actions"]):
break
if "action_name_%d" % i in request.POST:
qdict["action_name_%d" % cpt] = request.POST["action_name_%d" % i]
action = request.POST["action_name_%d" % i]
argcpt = 0
args = []
while True:
try:
qdict["action_arg_%d_%d" % (cpt, argcpt)] = request.POST[
"action_arg_%d_%d" % (i, argcpt)
]
args += [request.POST["action_arg_%d_%d" % (i, argcpt)]]
except KeyError:
break
argcpt += 1
args = [action] + args
actions += [args]
cpt += 1
i += 1
return FilterForm(conditions, actions, request, qdict)
def build_filter_form_from_filter(request, name, fobj):
match_type = fobj["test"].name
conditions = []
for t in fobj["test"]["tests"]:
if isinstance(t, commands.TrueCommand):
match_type = "all"
conditions += [("Subject", "contains", "")]
break
elif isinstance(t, commands.SizeCommand):
conditions += [("size", t["comparator"][1:], t["limit"])]
else:
operator_prefix = ""
if isinstance(t, commands.NotCommand):
t = t["test"]
operator_prefix = "not"
conditions += [
(
smart_str(t["header-names"]).strip('"'),
"{}{}".format(operator_prefix, smart_str(t["match-type"])[1:]),
smart_str(t["key-list"]).strip('"'),
)
]
actions = []
for c in fobj.children:
action = (c.name,)
tpl = lib.find_action_template(c.name)
for argtpl in tpl.get("args", []):
if argtpl["name"] not in c:
continue
value = c[argtpl["name"]]
if argtpl["type"] == "boolean":
value = True if value else False
else:
value = value.strip('"')
action += (value,)
actions += [action]
form = FilterForm(conditions, actions, request)
form.fields["name"].initial = smart_str(name)
form.fields["match_type"].initial = match_type
return form
def supported_auth_mechs():
values = [("AUTO", "auto")]
for m in SUPPORTED_AUTH_MECHS:
values += [(m, m.lower())]
return values
class ParametersForm(param_forms.AdminParametersForm):
app = "sievefilters"
sep1 = form_utils.SeparatorField(label=gettext_lazy("ManageSieve settings"))
server = forms.CharField(
label=gettext_lazy("Server address"),
initial="127.0.0.1",
help_text=gettext_lazy("Address of your MANAGESIEVE server"),
widget=forms.TextInput(attrs={"class": "form-control"}),
)
port = forms.IntegerField(
label=gettext_lazy("Server port"),
initial=4190,
help_text=gettext_lazy("Listening port of your MANAGESIEVE server"),
widget=forms.TextInput(attrs={"class": "form-control"}),
)
starttls = form_utils.YesNoField(
label=gettext_lazy("Connect using STARTTLS"),
initial=False,
help_text=gettext_lazy("Use the STARTTLS extension"),
)
authentication_mech = forms.ChoiceField(
label=gettext_lazy("Authentication mechanism"),
choices=supported_auth_mechs(),
initial="auto",
help_text=gettext_lazy("Prefered authentication mechanism"),
widget=forms.Select(attrs={"class": "form-control"}),
)
sep2 = form_utils.SeparatorField(label=gettext_lazy("IMAP settings"))
imap_server = forms.CharField(
label=gettext_lazy("Server address"),
initial="127.0.0.1",
help_text=gettext_lazy("Address of your IMAP server"),
)
imap_secured = form_utils.YesNoField(
label=gettext_lazy("Use a secured connection"),
initial=False,
help_text=gettext_lazy("Use a secured connection to access IMAP server"),
)
imap_port = forms.IntegerField(
label=gettext_lazy("Server port"),
initial=143,
help_text=gettext_lazy("Listening port of your IMAP server"),
)
class UserSettings(param_forms.UserParametersForm):
app = "sievefilters"
sep1 = form_utils.SeparatorField(label=gettext_lazy("General"))
editor_mode = forms.ChoiceField(
initial="gui",
label=gettext_lazy("Editor mode"),
choices=[("raw", "raw"), ("gui", "simplified")],
help_text=gettext_lazy("Select the mode you want the editor to work in"),
widget=form_utils.HorizontalRadioSelect(),
)
sep2 = form_utils.SeparatorField(label=gettext_lazy("Mailboxes"))
trash_folder = forms.CharField(
initial="Trash",
label=gettext_lazy("Trash folder"),
help_text=gettext_lazy("Folder where deleted messages go"),
)
sent_folder = forms.CharField(
initial="Sent",
label=gettext_lazy("Sent folder"),
help_text=gettext_lazy("Folder where copies of sent messages go"),
)
drafts_folder = forms.CharField(
initial="Drafts",
label=gettext_lazy("Drafts folder"),
help_text=gettext_lazy("Folder where drafts go"),
)
@staticmethod
def has_access(**kwargs):
return hasattr(kwargs.get("user"), "mailbox")
+39
View File
@@ -0,0 +1,39 @@
"""Modoboa sievefilters handlers."""
from django.urls import reverse
from django.dispatch import receiver
from django.utils.translation import gettext as _
from modoboa.core import signals as core_signals
from . import lib
@receiver(core_signals.extra_user_menu_entries)
def menu(sender, location, user, **kwargs):
"""Return extra menu entry."""
if location != "options_menu" or not hasattr(user, "mailbox"):
return []
return [
{
"name": "sievefilters",
"label": _("Message filters"),
"url": reverse("sievefilters:index"),
"img": "fa fa-filter",
}
]
@receiver(core_signals.user_logout)
def userlogout(sender, request, **kwargs):
"""Close managesieve connection."""
if not hasattr(request.user, "mailbox"):
return
try:
sc = lib.SieveClient(
user=request.user.username, password=request.session["password"]
)
except Exception:
pass
else:
sc.logout()
+246
View File
@@ -0,0 +1,246 @@
# coding: utf-8
"""
:mod:`imaputils` --- Extra IMAPv4 utilities
-------------------------------------------
"""
import imaplib
import re
from operator import itemgetter
import socket
import ssl
import six
from django.utils.encoding import smart_bytes
from django.utils.translation import gettext as _
from modoboa.lib import imap_utf7 # NOQA
from modoboa.lib.connections import ConnectionsManager
from modoboa.lib.exceptions import ModoboaException, InternalError
from modoboa.parameters import tools as param_tools
class ImapError(ModoboaException):
def __init__(self, reason):
self.reason = reason
def __str__(self):
return str(self.reason)
@six.add_metaclass(ConnectionsManager)
class IMAPconnector(object):
"""The IMAPv4 connector."""
list_base_pattern = r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" "?(?P<name>[^"]*)"?'
list_response_pattern_literal = re.compile(
r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" \{(?P<namelen>\d+)\}'
)
list_response_pattern = re.compile(list_base_pattern)
listextended_response_pattern = re.compile(
list_base_pattern + r"\s*(?P<childinfo>.*)"
)
def __init__(self, user=None, password=None):
self.__hdelimiter = None
self.conf = dict(param_tools.get_global_parameters("sievefilters"))
self.address = self.conf["imap_server"]
self.port = self.conf["imap_port"]
self.login(user, password)
def _cmd(self, name, *args, **kwargs):
"""IMAP command wrapper
To simplify errors handling, this wrapper calls the
appropriate method (``uid`` or FIXME) and then check the
return code. If an error has occured, an ``ImapError``
exception is raised.
For specific commands commands (FETCH, ...), the result is
parsed using the IMAPclient module before being returned.
:param name: the command's name
:return: the command's result
"""
try:
typ, data = self.m._simple_command(name, *args)
except imaplib.IMAP4.error as e:
raise ImapError(e)
if typ == "NO":
raise ImapError(data)
if "responses" not in kwargs:
if name not in self.m.untagged_responses:
return None
return self.m.untagged_responses.pop(name)
res = []
for r in kwargs["responses"]:
if r not in self.m.untagged_responses:
return None
res.append(self.m.untagged_responses.pop(r))
return res
@property
def hdelimiter(self):
"""Return the default hierachy delimiter.
This is a simple way to retrieve the default delimiter (see
http://www.imapwiki.org/ClientImplementation/MailboxList).
:return: a string
"""
if self.__hdelimiter is None:
data = self._cmd("LIST", '""', '""')
m = self.list_response_pattern.match(data[0].decode())
if m is None:
raise InternalError(_("Failed to retrieve hierarchy delimiter"))
self.__hdelimiter = m.group("delimiter")
return self.__hdelimiter
def refresh(self, user, password):
"""Check if current connection needs a refresh
Is it really secure?
"""
if self.m is not None:
try:
self._cmd("NOOP")
except ImapError:
if hasattr(self, "current_mailbox"):
del self.current_mailbox
else:
return
self.login(user, password)
def login(self, user, passwd):
"""Custom login method
We connect to the server, issue a LOGIN command. If
successfull, we try to record a eventuel CAPABILITY untagged
response. Otherwise, we issue the command.
:param user: username
:param passwd: password
"""
try:
if self.conf["imap_secured"]:
self.m = imaplib.IMAP4_SSL(self.address, self.port)
else:
self.m = imaplib.IMAP4(self.address, self.port)
except (socket.error, imaplib.IMAP4.error, ssl.SSLError) as error:
raise ImapError(_("Connection to IMAP server failed: %s" % error))
passwd = self.m._quote(passwd)
data = self._cmd("LOGIN", smart_bytes(user), smart_bytes(passwd))
self.m.state = "AUTH"
if "CAPABILITY" in self.m.untagged_responses:
self.capabilities = (
self.m.untagged_responses.pop("CAPABILITY")[0].decode().split()
)
else:
data = self._cmd("CAPABILITY")
self.capabilities = data[0].decode().split()
def logout(self):
"""Logout from server."""
try:
self._cmd("CHECK")
except ImapError:
pass
self._cmd("LOGOUT")
del self.m
self.m = None
if hasattr(self, "current_mailbox"):
del self.current_mailbox
def _listmboxes(self, topmailbox, mailboxes):
"""Retrieve mailboxes list."""
pattern = (
'"{0}{1}%"'.format(
topmailbox.encode("imap4-utf-7").decode(), self.hdelimiter
)
if topmailbox
else "%"
)
resp = self._cmd("LIST", '""', pattern, "RETURN", "(CHILDREN)")
newmboxes = []
for mb in resp:
if not mb:
continue
if type(mb) in [list, tuple]:
flags, delimiter, namelen = self.list_response_pattern_literal.match(
mb[0].decode()
).groups()
name = mb[1][0 : int(namelen)]
else:
flags, delimiter, name, childinfo = (
self.listextended_response_pattern.match(mb.decode()).groups()
)
flags = flags.split(" ")
name = bytearray(name, "utf-8")
name = name.decode("imap4-utf-7")
mdm_found = False
for idx, mdm in enumerate(mailboxes):
if mdm["name"] == name:
mdm_found = True
descr = mailboxes[idx]
break
if not mdm_found:
descr = {"name": name}
newmboxes += [descr]
if "\\Marked" in flags or "\\UnMarked" not in flags:
descr["send_status"] = True
if "\\HasChildren" in flags:
if "\\NonExistent" in flags:
descr["removed"] = True
descr["path"] = name
descr["sub"] = []
mailboxes += sorted(newmboxes, key=itemgetter("name"))
def getmboxes(self, user, topmailbox=""):
"""Returns a list of mailboxes for a particular user
By default, only the first level of mailboxes under
``topmailbox`` is returned.
:param user: a ``User`` instance
:param topmailbox: the mailbox where to start in the tree
:return: a list
"""
if topmailbox:
md_mailboxes = []
else:
md_mailboxes = [
{"name": "INBOX", "class": "fa fa-inbox"},
{
"name": user.parameters.get_value("drafts_folder"),
"class": "fa fa-file",
},
{"name": "Junk", "class": "fa fa-fire"},
{
"name": user.parameters.get_value("sent_folder"),
"class": "fa fa-envelope",
},
{
"name": user.parameters.get_value("trash_folder"),
"class": "fa fa-trash",
},
]
self._listmboxes(topmailbox, md_mailboxes)
return md_mailboxes
def get_imapconnector(request):
"""Simple shortcut to create a connector
:param request: a ``Request`` object
"""
imapc = IMAPconnector(
user=request.user.username, password=request.session["password"]
)
return imapc
+115
View File
@@ -0,0 +1,115 @@
# coding: utf-8
"""Internal tools."""
import ssl
from sievelib.factory import FiltersSet
from sievelib import managesieve
from sievelib.parser import Parser
import six
from django.utils.translation import gettext as _
from modoboa.lib.connections import ConnectionsManager, ConnectionError
from modoboa.lib.exceptions import ModoboaException
from modoboa.parameters import tools as param_tools
from . import constants
class SieveClientError(ModoboaException):
http_code = 424
# @six.add_metaclass(ConnectionsManager)
class SieveClient:
"""Sieve client."""
def __init__(self, user=None, password=None):
try:
ret, msg = self.login(user, password)
except managesieve.Error as e:
raise ConnectionError(str(e))
if not ret:
raise ConnectionError(msg)
def login(self, user, password):
conf = dict(param_tools.get_global_parameters("sievefilters"))
self.msc = managesieve.Client(conf["server"], conf["port"], debug=True)
authmech = conf["authentication_mech"]
if authmech == "AUTO":
authmech = None
try:
ret = self.msc.connect(
user, password, starttls=conf["starttls"], authmech=authmech
)
except managesieve.Error as err:
print(err)
ret = False
if not ret:
return False, _(
"Connection to MANAGESIEVE server failed, check your " "configuration"
)
return True, None
def logout(self):
self.msc.logout()
self.msc = None
def refresh(self, user, password):
if self.msc is not None:
try:
self.msc.capability()
except managesieve.Error as e:
pass
else:
return
try:
ret, msg = self.login(user, password)
except (managesieve.Error, ssl.SSLError) as e:
raise ConnectionError(e)
if not ret:
raise ConnectionError(msg)
def listscripts(self):
return self.msc.listscripts()
def getscript(self, name: str, format: str = "raw") -> FiltersSet | str | None:
content = self.msc.getscript(name)
if content is None:
raise SieveClientError(self.msc.errmsg.decode())
if format == "raw":
return content
p = Parser()
if not p.parse(content):
print("Parse error????")
return None
fs = FiltersSet(name)
fs.from_parser_result(p)
return fs
def pushscript(self, name: str, content: str, active: bool = False) -> None:
if not self.msc.havespace(name, len(content)):
error = "%s (%s)" % (_("Not enough space on server"), self.msc.errmsg)
raise SieveClientError(error)
if not self.msc.putscript(name, content):
raise SieveClientError(self.msc.errmsg.decode())
if active and not self.msc.setactive(name):
raise SieveClientError(self.msc.errmsg)
def deletescript(self, name):
if not self.msc.deletescript(name):
raise SieveClientError(self.msc.errmsg.decode())
def activatescript(self, name):
if not self.msc.setactive(name):
raise SieveClientError(self.msc.errmsg.decode())
def find_action_template(action):
"""Find template corresponding to action."""
for tpl in constants.ACTION_TEMPLATES:
if tpl["name"] == action:
return tpl
raise RuntimeError("action {} not defined".format(action))
@@ -0,0 +1,364 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Peter Sveter, 2016-2017
# Jarda Tesar <intossh@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2018-08-10 11:55+0000\n"
"Last-Translator: Jarda Tesar <intossh@gmail.com>\n"
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/tonio/modoboa/language/cs_CZ/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs_CZ\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#: constants.py:6
msgid "contains"
msgstr "obsahuje"
#: constants.py:7
msgid "does not contain"
msgstr "neobsahuje"
#: constants.py:8
msgid "is"
msgstr "je"
#: constants.py:9
msgid "is not"
msgstr "není"
#: constants.py:14
msgid "Subject"
msgstr "Předmět"
#: constants.py:17
msgid "Sender"
msgstr "Odesílatel"
#: constants.py:20
msgid "Recipient"
msgstr "Příjemce"
#: constants.py:23
msgid "Cc"
msgstr "Kopie"
#: constants.py:25
msgid "Size"
msgstr "Velikost"
#: constants.py:26
msgid "is greater than"
msgstr "Je větší než"
#: constants.py:27
msgid "is less than"
msgstr "Je menší než"
#: constants.py:31
msgid "Move message to"
msgstr "Přesunout zprávu do"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Ponechat místní kopii"
#: constants.py:40
msgid "Redirect message to"
msgstr "Přesměrovat zprávu na"
#: constants.py:48
msgid "Reject message"
msgstr "Odmítnout zprávu"
#: constants.py:50
msgid "Stop processing"
msgstr "Ukončit zpracování"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Zatrhněte pole pro aktivaci této sady filtrů"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Jméno"
#: forms.py:89
msgid "Match type"
msgstr "Musí souhlasit"
#: forms.py:90
msgid "All of the following"
msgstr "Všechna pravidla"
#: forms.py:91
msgid "Any of the following"
msgstr "Alespoň jedno z pravidel"
#: forms.py:92
msgid "All messages"
msgstr "Všechny zprávy"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Chybný název filtru"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Nastavení ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Adresa serveru"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Adresa vašeho serveru ManegeSieve"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Port serveru"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Port, na kterém naslouchá váš ManageSieve server"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Připojit pomocí STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Použít rozšíření STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Režim ověření"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Preferovaný režim ověření"
#: forms.py:381
msgid "IMAP settings"
msgstr "Nastavení IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Adresa vašeho IMAP serveru"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Použít zabezpečené připojení"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Použít zabezpečené připojení pro přístup k IMAP serveru"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Port, na kterém naslouchá váš IMAP server"
#: forms.py:405
msgid "General"
msgstr "Obecné"
#: forms.py:409
msgid "Editor mode"
msgstr "Režim úpravy"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Zvolte režim editoru textu, ve kterém si přejete pracovat"
#: forms.py:415
msgid "Mailboxes"
msgstr "Schránky"
#: forms.py:419
msgid "Trash folder"
msgstr "Složka koše"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Složka pro smazané zprávy"
#: forms.py:425
msgid "Sent folder"
msgstr "Složka odeslané pošty"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Složka pro kopie odeslaných zpráv"
#: forms.py:431
msgid "Drafts folder"
msgstr "Složka konceptů"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Složka pro koncepty zpráv"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Filtry zpráv"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Nebylo možné zjistit hierarchický oddělovač"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Spojení se serverem IMAP selhalo: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Spojení se serverem ManageSieve selhalo, zkontrolujte nastavení"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Nedostatek prostoru na serveru"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Doplněk pro jednoduchou tvorbu filtrů zpráv na serveru"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Sieve filtry"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Podmínky"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Akce"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Aktivní"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Dosud nebyl vytvořen žádný filtr"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Sady filtrů"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "aktivní"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Dosud nebyla vytvořena žádná sada filtrů. Aby bylo možné vytvořit filtry, je nejprve potřeba vytvořit sadu filtrů."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Nová sada filtrů"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nový filtr"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Uložit sadu filtrů"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Aktitovat sadu filtrů"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Odstranit sadu filtrů"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Stáhnout"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Upravit filtr"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Odstranit tento filtr"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Posunout tento filtr nahoru"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Posunout tento filtr dolů"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Nebylo možné získat sadu filtrů"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Vytvořit"
#: views.py:161
msgid "Filter created"
msgstr "Filtr vytvořen"
#: views.py:179
msgid "Update"
msgstr "Aktualizovat"
#: views.py:186
msgid "Filter modified"
msgstr "Filtr upraven"
#: views.py:209
msgid "Filter removed"
msgstr "Filtr odstraněn"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Nezdařilo se odstranit filtr"
#: views.py:225
msgid "Filters set saved"
msgstr "Sada filtrů uložena"
#: views.py:241
msgid "Filters set created"
msgstr "Sada filtrů vytvořena"
#: views.py:246
msgid "Create a new filters set"
msgstr "Vytvořit novou sadu filtrů"
#: views.py:265
msgid "Filters set deleted"
msgstr "Sada filtrů vymazána"
#: views.py:278
msgid "Filters set activated"
msgstr "Sada filtrů aktivována"
#: views.py:307
msgid "yes"
msgstr "ano"
#: views.py:311
msgid "no"
msgstr "ne"
@@ -0,0 +1,40 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Miroslav Abrahám <miris@kunago.com>, 2013,2015
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/tonio/modoboa/language/cs_CZ/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs_CZ\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Odstranit tento filtr?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Uložit změny?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Smazat sadu filtrů?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Aktivovat tuto sadu filtrů?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "aktivní"
@@ -0,0 +1,368 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Alexander Dube <adube@layerwerks.net>, 2017
# Arledrian <hvuzgr@gmail.com>, 2017
# Arvedui <arvedui@posteo.de>, 2014
# Boris Andratzek <BAndratzek@LBV-net.de>, 2018
# controlcde, 2015
# Patrick Koetter <p@state-of-mind.de>, 2010
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2018-04-25 14:02+0000\n"
"Last-Translator: Boris Andratzek <BAndratzek@LBV-net.de>\n"
"Language-Team: German (http://www.transifex.com/tonio/modoboa/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "enthält"
#: constants.py:7
msgid "does not contain"
msgstr "enthält nicht"
#: constants.py:8
msgid "is"
msgstr "ist"
#: constants.py:9
msgid "is not"
msgstr "ist nicht"
#: constants.py:14
msgid "Subject"
msgstr "Betreff"
#: constants.py:17
msgid "Sender"
msgstr "Sender"
#: constants.py:20
msgid "Recipient"
msgstr "Empfänger"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Größe"
#: constants.py:26
msgid "is greater than"
msgstr "ist größer als"
#: constants.py:27
msgid "is less than"
msgstr "ist kleiner als"
#: constants.py:31
msgid "Move message to"
msgstr "Verschiebe Nachricht nach"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Behalte lokale Kopie"
#: constants.py:40
msgid "Redirect message to"
msgstr "Leite Nachricht um zu"
#: constants.py:48
msgid "Reject message"
msgstr "E-Mail ablehnen"
#: constants.py:50
msgid "Stop processing"
msgstr "Stoppe Bearbeitung"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Häkchen setzen, um den Filter-Satz zu aktivieren"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Name"
#: forms.py:89
msgid "Match type"
msgstr "Vergleichte Typ"
#: forms.py:90
msgid "All of the following"
msgstr "Alle der folgenden"
#: forms.py:91
msgid "Any of the following"
msgstr "Irgendeines der folgenden"
#: forms.py:92
msgid "All messages"
msgstr "Alle Nachrichten"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Falscher Filtername"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "ManageSieve-Einstellungen"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Server-Adresse"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Adresse Ihres MANAGESIEVE-Servers"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Server-Port"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "TCP-Port Ihres MANAGESIEVE-Servers"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "STARTTLS für Verbindungsaufbau nutzen"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "STARTTLS benutzen"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Authentifizierungsmechanismus"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Bevorzugte Authentifizierungsmethode"
#: forms.py:381
msgid "IMAP settings"
msgstr "IMAP-Einstellungen"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Addresse Ihres IMAP-Servers"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Benutze eine sichere Verbindung"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Benutze eine gesicherte Verbindung zum Zugriff auf den IMAP Server"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "TCPIP Port Ihres IMAP Servers"
#: forms.py:405
msgid "General"
msgstr "Allgemein"
#: forms.py:409
msgid "Editor mode"
msgstr "Editor-Modus"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Wählen Sie den Modus aus, in dem der Editor arbeiten soll"
#: forms.py:415
msgid "Mailboxes"
msgstr "E-Mail-Postfächer"
#: forms.py:419
msgid "Trash folder"
msgstr "Papierkorb-Ordner"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Ordner, in den gelöschte Nachrichten gehen"
#: forms.py:425
msgid "Sent folder"
msgstr "Gesendet-Ordner"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Ordner, in den Kopien gesendeter Nachrichten gehen"
#: forms.py:431
msgid "Drafts folder"
msgstr "Entwürfe-Ordner"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Ordner für Entwürfe"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Nachrichten-Filter"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr ""
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Verbindung zum IMAP-Server gescheitert: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Verbindung zu MANAGESIEVE-Server fehlgeschlagen. Überprüfen Sie Ihre Konfiguration."
#: lib.py:98
msgid "Not enough space on server"
msgstr "Kein weiterer Platz auf Server verfügbar"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Plugin, um auf einfache Weise serverseitige Filter zu erstellen"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "SIEVE-Filter"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Bedingungen"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Aktionen"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Aktiv"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Noch keine Filter definiert"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Filter-Sätze"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "aktiv"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Noch keine Filter-Sätze definiert. Sie müssen einen Satz erstellen, um diesem Filter hinzufügen zu können."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Neuer Filter-Satz"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Neuer Filter"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Filter-Satz speichern"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Filter-Satz aktivieren"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Filter-Satz entfernen"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Herunterladen"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Filter bearbeiten"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Filter entfernen"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Filter nach oben verschieben"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Filter nach unten verschieben"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Konnte Filter-Satz nicht laden"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Erstellen"
#: views.py:161
msgid "Filter created"
msgstr "Filter erstellt"
#: views.py:179
msgid "Update"
msgstr "Aktualisieren"
#: views.py:186
msgid "Filter modified"
msgstr "Filter verändert"
#: views.py:209
msgid "Filter removed"
msgstr "Filter entfernt"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Konnte Filter nicht entfernen"
#: views.py:225
msgid "Filters set saved"
msgstr "Filter-Satz gespeichert"
#: views.py:241
msgid "Filters set created"
msgstr "Filter-Satz erstellt"
#: views.py:246
msgid "Create a new filters set"
msgstr "Neuen Filter-Satz erstellen"
#: views.py:265
msgid "Filters set deleted"
msgstr "Filter-Satz entfernt"
#: views.py:278
msgid "Filters set activated"
msgstr "Filter-Satz aktiviert"
#: views.py:307
msgid "yes"
msgstr "Ja"
#: views.py:311
msgid "no"
msgstr "Nein"
@@ -0,0 +1,42 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Arvedui <arvedui@posteo.de>, 2014
# controlcde, 2015
# Patrick Koetter <p@state-of-mind.de>, 2010
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: controlcde\n"
"Language-Team: German (http://www.transifex.com/tonio/modoboa/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Diesen Filter entfernen?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Änderungen speichern?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Diesen Filter-Satz entfernen?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Diesen Filter-Satz aktivieren?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "Aktiv"
@@ -0,0 +1,368 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Giannis Kapetanakis <bilias@edu.physics.uoc.gr>, 2016
# Kostas Moumouris <moumouriskostas@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2017-10-12 13:12+0000\n"
"Last-Translator: Kostas Moumouris <moumouriskostas@gmail.com>\n"
"Language-Team: Greek (Greece) (http://www.transifex.com/tonio/modoboa/"
"language/el_GR/)\n"
"Language: el_GR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "περιέχει"
#: constants.py:7
msgid "does not contain"
msgstr "δεν περιέχει"
#: constants.py:8
msgid "is"
msgstr "είναι"
#: constants.py:9
msgid "is not"
msgstr "δεν είναι"
#: constants.py:14
msgid "Subject"
msgstr "Τίτλος"
#: constants.py:17
msgid "Sender"
msgstr "Αποστολέας"
#: constants.py:20
msgid "Recipient"
msgstr "Παραλήπτης"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Μέγεθος"
#: constants.py:26
msgid "is greater than"
msgstr "είναι μεγαλύτερο από"
#: constants.py:27
msgid "is less than"
msgstr "είναι μικρότερο από"
#: constants.py:31
msgid "Move message to"
msgstr "Μετακίνηση μηνυμάτων προς"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr ""
#: constants.py:40
msgid "Redirect message to"
msgstr "Ανακατεύθυνση μηνυμάτων προς"
#: constants.py:48
msgid "Reject message"
msgstr "Απόρριψη μηνύματος"
#: constants.py:50
msgid "Stop processing"
msgstr "Ακύρωση επεξεργασίας"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Επιλέξτε για να ενεργοποιηθεί αυτό το σετ φίλτρων"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Όνομα"
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr "Όλα τα παρακάτω"
#: forms.py:91
msgid "Any of the following"
msgstr "Οποιοδήποτε από τα παρακάτων"
#: forms.py:92
msgid "All messages"
msgstr "Όλα τα μηνύματα"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Λάθος όνομα φίλτρου"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Ρυθμίσεις ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Διεύθυνση εξυπηρετητή"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Η διεύθυνση του εξυπηρετητή MANAGESIEVE"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Πόρτα εξυπηρετητή"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Η πόρτα λειτουργίας του MANAGESIEVE εξυπηρετητή σας"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Σύνδεση με χρήση STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Χρήση του extension STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Μηχανισμός πιστοποίησης"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Επιλεγμένος μηχανισμός πιστοποίησης"
#: forms.py:381
msgid "IMAP settings"
msgstr "ρύθμισης IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Διεύθυνση του διακομιστή IMAP"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Χρησιμοποιήστε μια ασφαλή σύνδεση"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Χρησιμοποιήστε μια ασφαλή σύνδεση για πρόσβαση στον διακομιστή IMAP"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Η θύρα που ακούει ο IMAP server σας"
#: forms.py:405
msgid "General"
msgstr "Γενικά"
#: forms.py:409
msgid "Editor mode"
msgstr "Λειτουργία συντάκτη"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Επιλέξτε τη λειτουργία στην οποία θέλετε να εργάζεται ο συντάκτης"
#: forms.py:415
msgid "Mailboxes"
msgstr "Γραμματοκιβώτια"
#: forms.py:419
msgid "Trash folder"
msgstr "Φάκελος σκουπιδιών"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Φάκελος που πηγαίνουν τα σβησμένα μηνύματα"
#: forms.py:425
msgid "Sent folder"
msgstr "Φάκελος απεσταλμένων"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Φάκελος όπου πηγαίνουν αντίγραφα των απεσταλμένων μηνυμάτων"
#: forms.py:431
msgid "Drafts folder"
msgstr "Φάκελος πρόχειρων"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Ο φάκελος που θα πηγαίνουν τα πρόχειρα"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Φίλτρα μηνυμάτων"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Αποτυχία κατά την ανάκτηση της οροθετημένης ιεραρχίας"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Η σύνδεση στο διακομιστή IMAP απέτυχε:%s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr ""
"Η σύνδεση με τον εξυπηρετητή MANAGESIEVE απέτυχε, ελέγξτε τις ρυθμίσεις σας "
#: lib.py:98
msgid "Not enough space on server"
msgstr "Δεν υπάρχει διαθέσιμος χώρος στον εξυπηρετητή"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Πρόσθετο για εύκολη δημιουργία φίλτρων από την πλευρά του διακομιστή"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Φίλτρα Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Συνθήκες"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Ενέργειες"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Ενεργό"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Δεν υπάρχει καθορισμένο φίλτρο"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Σετ φίλτρων"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "ενεργό"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr ""
"Δεν έχουν οριστεί ακόμη φίλτρα. Πρέπει να δημιουργήσετε τουλάχιστον ένα για "
"να καθοριστούν τα φίλτρα"
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Νέο σετ φίλτρων"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Νέο φίλτρο"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Αποθήκευση του σετ φίλτρων"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Ενεργοποίηση του σετ φίλτρων"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Αφαίρεση του σετ φίλτρων"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Λήψη"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Επεξεργασία φίλτρου"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Αφαίρεση αυτού του φίλτρου"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Μετακίνηση αυτού του φίλτρου πάνω"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Μετακίνηση αυτού του φίλτρου κάτω"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Σφάλμα κατά την την λήψη του σετ φίλτρων"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Δημιουργία"
#: views.py:161
msgid "Filter created"
msgstr "Το φίλτρο δημιουργήθηκε"
#: views.py:179
msgid "Update"
msgstr "Ενημέρωση"
#: views.py:186
msgid "Filter modified"
msgstr "Το φίλτρο άλλαξε"
#: views.py:209
msgid "Filter removed"
msgstr "Το φίλτρο αφαιρέθηκε "
#: views.py:210
msgid "Failed to remove filter"
msgstr "Σφάλμα κατά την αφαίρεση του φίλτρου"
#: views.py:225
msgid "Filters set saved"
msgstr "Το σετ φίλτρων αποθηκεύτηκε"
#: views.py:241
msgid "Filters set created"
msgstr "Το σετ φίλτρων δημιουργήθηκε"
#: views.py:246
msgid "Create a new filters set"
msgstr "Δημιουργία νέου σετ φίλτρων"
#: views.py:265
msgid "Filters set deleted"
msgstr "Το σετ φίλτρων διαγράφηκε"
#: views.py:278
msgid "Filters set activated"
msgstr "Το σετ φίλτρων ενεργοποιήθηκε"
#: views.py:307
msgid "yes"
msgstr "ναι"
#: views.py:311
msgid "no"
msgstr "όχι"
@@ -0,0 +1,40 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Giannis Kapetanakis <bilias@edu.physics.uoc.gr>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-03 09:34+0100\n"
"PO-Revision-Date: 2017-09-22 22:21+0000\n"
"Last-Translator: Giannis Kapetanakis <bilias@edu.physics.uoc.gr>\n"
"Language-Team: Greek (Greece) (http://www.transifex.com/tonio/modoboa/language/el_GR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: el_GR\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Αφαίρεση αυτού του φίλτρου;"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Αποθήκευση αλλαγών;"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Αφαίρεση αυτού του σετ φίλτρων;"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Ενεργοποίηση αυτού του σετ φίλτρων;"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "ενεργό"
@@ -0,0 +1,362 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: constants.py:6
msgid "contains"
msgstr ""
#: constants.py:7
msgid "does not contain"
msgstr ""
#: constants.py:8
msgid "is"
msgstr ""
#: constants.py:9
msgid "is not"
msgstr ""
#: constants.py:14
msgid "Subject"
msgstr ""
#: constants.py:17
msgid "Sender"
msgstr ""
#: constants.py:20
msgid "Recipient"
msgstr ""
#: constants.py:23
msgid "Cc"
msgstr ""
#: constants.py:25
msgid "Size"
msgstr ""
#: constants.py:26
msgid "is greater than"
msgstr ""
#: constants.py:27
msgid "is less than"
msgstr ""
#: constants.py:31
msgid "Move message to"
msgstr ""
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr ""
#: constants.py:40
msgid "Redirect message to"
msgstr ""
#: constants.py:48
msgid "Reject message"
msgstr ""
#: constants.py:50
msgid "Stop processing"
msgstr ""
#: forms.py:33
msgid "Check to activate this filters set"
msgstr ""
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr ""
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr ""
#: forms.py:91
msgid "Any of the following"
msgstr ""
#: forms.py:92
msgid "All messages"
msgstr ""
#: forms.py:107
msgid "Wrong filter name"
msgstr ""
#: forms.py:351
msgid "ManageSieve settings"
msgstr ""
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr ""
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr ""
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr ""
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr ""
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr ""
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr ""
#: forms.py:374
msgid "Authentication mechanism"
msgstr ""
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr ""
#: forms.py:381
msgid "IMAP settings"
msgstr ""
#: forms.py:386
msgid "Address of your IMAP server"
msgstr ""
#: forms.py:390
msgid "Use a secured connection"
msgstr ""
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr ""
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr ""
#: forms.py:405
msgid "General"
msgstr ""
#: forms.py:409
msgid "Editor mode"
msgstr ""
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr ""
#: forms.py:415
msgid "Mailboxes"
msgstr ""
#: forms.py:419
msgid "Trash folder"
msgstr ""
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr ""
#: forms.py:425
msgid "Sent folder"
msgstr ""
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr ""
#: forms.py:431
msgid "Drafts folder"
msgstr ""
#: forms.py:432
msgid "Folder where drafts go"
msgstr ""
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr ""
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr ""
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr ""
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr ""
#: lib.py:98
msgid "Not enough space on server"
msgstr ""
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr ""
#: modo_extension.py:24
msgid "Sieve filters"
msgstr ""
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr ""
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr ""
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr ""
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr ""
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr ""
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr ""
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr ""
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr ""
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr ""
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr ""
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr ""
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr ""
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr ""
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr ""
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr ""
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr ""
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr ""
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr ""
#: views.py:156 views.py:248
msgid "Create"
msgstr ""
#: views.py:161
msgid "Filter created"
msgstr ""
#: views.py:179
msgid "Update"
msgstr ""
#: views.py:186
msgid "Filter modified"
msgstr ""
#: views.py:209
msgid "Filter removed"
msgstr ""
#: views.py:210
msgid "Failed to remove filter"
msgstr ""
#: views.py:225
msgid "Filters set saved"
msgstr ""
#: views.py:241
msgid "Filters set created"
msgstr ""
#: views.py:246
msgid "Create a new filters set"
msgstr ""
#: views.py:265
msgid "Filters set deleted"
msgstr ""
#: views.py:278
msgid "Filters set activated"
msgstr ""
#: views.py:307
msgid "yes"
msgstr ""
#: views.py:311
msgid "no"
msgstr ""
@@ -0,0 +1,34 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr ""
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr ""
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr ""
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr ""
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr ""
@@ -0,0 +1,364 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Evilham <github@evilham.com>, 2017
# Pedro Gracia <lasarux@neuroomante.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2017-12-01 17:05+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Spanish (http://www.transifex.com/tonio/modoboa/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "contiene"
#: constants.py:7
msgid "does not contain"
msgstr "no contiene"
#: constants.py:8
msgid "is"
msgstr "es"
#: constants.py:9
msgid "is not"
msgstr "no es"
#: constants.py:14
msgid "Subject"
msgstr "Tema"
#: constants.py:17
msgid "Sender"
msgstr "Remitente"
#: constants.py:20
msgid "Recipient"
msgstr "Destinatario"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Tamaño"
#: constants.py:26
msgid "is greater than"
msgstr "es mayor que"
#: constants.py:27
msgid "is less than"
msgstr "es menor que"
#: constants.py:31
msgid "Move message to"
msgstr "Mover el mensaje a"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr ""
#: constants.py:40
msgid "Redirect message to"
msgstr "Redirigir el mensaje a"
#: constants.py:48
msgid "Reject message"
msgstr "Bloquear mensaje"
#: constants.py:50
msgid "Stop processing"
msgstr "Detener procesamiento"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Marcar para activar este conjunto de filtros"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Nombre"
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr "Todos los siguientes"
#: forms.py:91
msgid "Any of the following"
msgstr "Cualquiera de los siguientes"
#: forms.py:92
msgid "All messages"
msgstr "Todos los mensajes"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Nombre de filtro erróneo"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Configuración de ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Dirección del servidor"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Dirección de su servidor MANAGESIEVE"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Puerto del servidor"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Puerto a la escucha de su servidor MANAGESIEVE"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Conectar usando STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Usar la extensión STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Mecanismos de autenticación"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Mecanismo de autenticación preferido"
#: forms.py:381
msgid "IMAP settings"
msgstr "Configuración de IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Dirección de su servidor IMAP"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Usar una conexión segura"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Usar una conexión segura para acceder al servidor IMAP"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Puerto de su servidor IMAP"
#: forms.py:405
msgid "General"
msgstr "General"
#: forms.py:409
msgid "Editor mode"
msgstr "Modo editor"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Seleccione el modo en el que desea que el editor trabaje"
#: forms.py:415
msgid "Mailboxes"
msgstr "Mailboxes"
#: forms.py:419
msgid "Trash folder"
msgstr "Carpeta de basura"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Carpeta dónde van los mensajes eliminados"
#: forms.py:425
msgid "Sent folder"
msgstr "Carpeta de enviados"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Carpeta dónde van las copias de los correos enviados"
#: forms.py:431
msgid "Drafts folder"
msgstr "Carpeta de borradores"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Carpeta dónde van los borradores"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Filtros de mensajes"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Fallo al recuperar el delimitador jerárquico"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Falló la conexión con el servidor IMAP: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "La conexión al servidor MANAGESIEVE falló, verifique su configuración."
#: lib.py:98
msgid "Not enough space on server"
msgstr "No hay suficiente espacio en el servidor"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Extensión para crear filtros en el servidor de forma sencilla"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "filtros Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Condiciones"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Acciones"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Activo"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "No hay un filtro definido aún"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Conjunto de filtros"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "activo"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "No hay un conjunto de filtros definido aún. Debe crear uno para poder definir filtros."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Nuevo conjunto de filtros"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nuevo filtro"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Guardar conjunto de filtros"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Activar conjunto de filtros"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Eliminar conjunto de filtros"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Descargar"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Editar filtro"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Eliminar este filtro"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Subir este filtro"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Bajar este filtro"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Fallo en la recuperación de un conjunto de filtros"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Crear"
#: views.py:161
msgid "Filter created"
msgstr "Filtro creado"
#: views.py:179
msgid "Update"
msgstr "Actualizar"
#: views.py:186
msgid "Filter modified"
msgstr "Filtro modificado"
#: views.py:209
msgid "Filter removed"
msgstr "Filtro eliminado"
#: views.py:210
msgid "Failed to remove filter"
msgstr "La eliminación del filtro falló"
#: views.py:225
msgid "Filters set saved"
msgstr "Filtros guardados"
#: views.py:241
msgid "Filters set created"
msgstr "Conjunto de filtros creado"
#: views.py:246
msgid "Create a new filters set"
msgstr "Crear un nuevo conjunto de filtros"
#: views.py:265
msgid "Filters set deleted"
msgstr "Conjunto de filtros eliminado"
#: views.py:278
msgid "Filters set activated"
msgstr "Conjunto de filtros activado"
#: views.py:307
msgid "yes"
msgstr "sí"
#: views.py:311
msgid "no"
msgstr "no"
@@ -0,0 +1,41 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Pedro Gracia <lasarux@neuroomante.com>, 2011,2013
# Pedro Gracia <pedro.gracia@impulzia.com>, 2010-2011
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Spanish (http://www.transifex.com/tonio/modoboa/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "¿Eliminar este filtro?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "¿Guardar cambios?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "¿Eliminar este conjunto de filtros?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "¿Activar este conjunto de filtros?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "activo"
@@ -0,0 +1,366 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Antoine Nguyen <tonio@ngyn.org>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2017-12-01 18:06+0100\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: French (http://www.transifex.com/tonio/modoboa/language/fr/)\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: constants.py:6
msgid "contains"
msgstr "contient"
#: constants.py:7
msgid "does not contain"
msgstr "ne contient pas"
#: constants.py:8
msgid "is"
msgstr "est"
#: constants.py:9
msgid "is not"
msgstr "n'est pas"
#: constants.py:14
msgid "Subject"
msgstr "Sujet"
#: constants.py:17
msgid "Sender"
msgstr "Expéditeur"
#: constants.py:20
msgid "Recipient"
msgstr "Destinataire"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Taille"
#: constants.py:26
msgid "is greater than"
msgstr "est supérieure à"
#: constants.py:27
msgid "is less than"
msgstr "est inférieure à"
#: constants.py:31
msgid "Move message to"
msgstr "Déplacer le message vers"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Conserver une copie locale"
#: constants.py:40
msgid "Redirect message to"
msgstr "Rediriger le message vers"
#: constants.py:48
msgid "Reject message"
msgstr "Rejeter le message"
#: constants.py:50
msgid "Stop processing"
msgstr "Arrêter le traitement"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Cocher pour activer ce jeu de filtres"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Nom"
#: forms.py:89
msgid "Match type"
msgstr "Correspondance"
#: forms.py:90
msgid "All of the following"
msgstr "Toutes les conditions suivantes"
#: forms.py:91
msgid "Any of the following"
msgstr "Au moins une des conditions suivantes"
#: forms.py:92
msgid "All messages"
msgstr "Tous les messages"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Nom de filtre invalide"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Paramètres ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Adresse du serveur"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Adresse de votre serveur ManageSieve"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Port utilisé par le serveur"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Port d'écoute de votre serveur ManageSieve"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Se connecter en utilisant STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Utiliser l'extension STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Méthode d'authentification"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Méthode d'authentification préférée"
#: forms.py:381
msgid "IMAP settings"
msgstr "Paramètres IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Adresse de votre serveur IMAP"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Utiliser une connexion sécurisée"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Utiliser une connexion sécurisée pour accéder au serveur IMAP"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Port d'écoute de votre serveur IMAP"
#: forms.py:405
msgid "General"
msgstr "Général"
#: forms.py:409
msgid "Editor mode"
msgstr "Mode de l'editeur"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Sélectionner le mode de fonctionnement de l'éditeur"
#: forms.py:415
msgid "Mailboxes"
msgstr "Boîtes aux lettres"
#: forms.py:419
msgid "Trash folder"
msgstr "Dossier corbeille"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Dossier où vont les messages supprimés"
#: forms.py:425
msgid "Sent folder"
msgstr "Dossier d'envoi"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Dossier où sont copiés les messages envoyés"
#: forms.py:431
msgid "Drafts folder"
msgstr "Dossier brouillon"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Dossier où sont stockés les brouillons"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Filtres de messages"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Impossible de récupérer le délimiteur de hierarchie"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Échec de connexion au serveur IMAP : %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr ""
"La connexion au serveur ManageSieve a échoué, vérifiez votre configuration"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Pas assez d'espace sur le serveur"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Extension pour créer simplement des filtres côté serveur"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Filtres Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Conditions"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Actions"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Actif"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Aucun filtre défini."
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Jeux de filtres"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "actif"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr ""
"Aucun jeu de filtres défini. Vous devez en créer un pour pouvoir définir des "
"filtres."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Nouveau jeu de filtres"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nouveau filtre"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Sauvegarder le jeu de filtres"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Activer le jeu de filtres"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Supprimer le jeu de filtres"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Télécharger"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Editer le filtre"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Supprimer ce filtre"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Déplacer ce filtre vers le haut"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Déplacer ce filtre vers le bas"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Impossible de récuperer le jeu de filtres"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Créer"
#: views.py:161
msgid "Filter created"
msgstr "Filtre créé"
#: views.py:179
msgid "Update"
msgstr "Mettre à jour"
#: views.py:186
msgid "Filter modified"
msgstr "Filtre modifié"
#: views.py:209
msgid "Filter removed"
msgstr "Filtre supprimé"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Impossible de supprimer le filtre"
#: views.py:225
msgid "Filters set saved"
msgstr "Jeu de filtres sauvegardé"
#: views.py:241
msgid "Filters set created"
msgstr "Jeu de filtres créé"
#: views.py:246
msgid "Create a new filters set"
msgstr "Créer un nouveau jeu de filtres"
#: views.py:265
msgid "Filters set deleted"
msgstr "Jeu de filtres supprimé"
#: views.py:278
msgid "Filters set activated"
msgstr "Jeu de filtres activé"
#: views.py:307
msgid "yes"
msgstr "oui"
#: views.py:311
msgid "no"
msgstr "non"
@@ -0,0 +1,39 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: French (http://www.transifex.com/tonio/modoboa/language/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Supprimer ce filtre?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Enregistrer les changements?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Supprimer ce jeu de filtres?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Activer ce jeu de filtres?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "actif"
@@ -0,0 +1,365 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Giancarlo Gaifas <lallo@artiemestieri.tn.it>, 2013
# marcotrevisan <mrctrevisan@yahoo.it>, 2018
# Rocco <mors10@hotmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2018-11-01 22:22+0000\n"
"Last-Translator: marcotrevisan <mrctrevisan@yahoo.it>\n"
"Language-Team: Italian (http://www.transifex.com/tonio/modoboa/language/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "contiene"
#: constants.py:7
msgid "does not contain"
msgstr "non contiene"
#: constants.py:8
msgid "is"
msgstr "è"
#: constants.py:9
msgid "is not"
msgstr "non è"
#: constants.py:14
msgid "Subject"
msgstr "Oggetto"
#: constants.py:17
msgid "Sender"
msgstr "Mittente"
#: constants.py:20
msgid "Recipient"
msgstr "Destinatario"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Dimensione"
#: constants.py:26
msgid "is greater than"
msgstr "è maggiore di"
#: constants.py:27
msgid "is less than"
msgstr "è minore di"
#: constants.py:31
msgid "Move message to"
msgstr "Sposta il messaggio in"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Mantieni copia locale"
#: constants.py:40
msgid "Redirect message to"
msgstr "Ridireziona il messaggio a"
#: constants.py:48
msgid "Reject message"
msgstr "Rifiuta il messaggio"
#: constants.py:50
msgid "Stop processing"
msgstr "Termina l'elaborazione"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Seleziona per attivare questo insieme di filtri"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Nome"
#: forms.py:89
msgid "Match type"
msgstr "Tipo di confronto"
#: forms.py:90
msgid "All of the following"
msgstr "Tutti i seguenti"
#: forms.py:91
msgid "Any of the following"
msgstr "Alcuni dei seguenti"
#: forms.py:92
msgid "All messages"
msgstr "Tutti i messaggi"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Nome del filtro errato"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Impostazioni ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Indirizzo del server"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Indirizzo del tuo server MANAGESIEVE"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Porta del server"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Porta di ascolto del tuo servere MANAGESIEVE"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Connetti utilizzando STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Usa l'estensione STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Meccanismo di autenticazione"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Meccanismo di autenticazione preferito"
#: forms.py:381
msgid "IMAP settings"
msgstr "Impostazioni IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Indirizzo del server IMAP"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Utilizza connessione sicura"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Utilizza una connessione sicura per l'accesso al server IMAP"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Porta in ascolto del server IMAP"
#: forms.py:405
msgid "General"
msgstr "Generale"
#: forms.py:409
msgid "Editor mode"
msgstr "Modo editor"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Seleziona la modalità di lavoro che desideri per l'editor"
#: forms.py:415
msgid "Mailboxes"
msgstr "Caselle di posta"
#: forms.py:419
msgid "Trash folder"
msgstr "Cestino"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Cartella dove finiscono i messaggi eliminati"
#: forms.py:425
msgid "Sent folder"
msgstr "Inviati"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Cartella dove finiscono le copie dei messaggi spediti"
#: forms.py:431
msgid "Drafts folder"
msgstr "Bozze"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Cartella dove finiscono le bozze"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Filtri messaggi"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Impossibile recuperare il delimitatore delle gerarchie"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Connessione al server IMAP fallita: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Connessione al server MANAGESIEVE fallita, controlla la tua configurazione"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Non c'è spazio sufficiente nel server"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Plugin per creare facilmente filtri lato-server"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Filtri Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Condizioni"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Azioni"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Attivo"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Non ci sono ancora filtri definiti"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Impostazioni dei filtri"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "attivo"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Non è ancora stato definito alcun gruppo di filtri. Devi crearne uno per definire dei filtri"
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Nuovo gruppo di filtri"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nuovo filtro"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Salva il gruppo di filtri"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Attiva gruppo di filtri"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Elimina gruppo di filtri"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Download"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Modifica filtro"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Elimina questo filtro"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Sposta questo filtro in alto"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Sposta questo filtro in basso "
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Recupero delle impostazioni di filtro fallito"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Crea"
#: views.py:161
msgid "Filter created"
msgstr "Filtro creato"
#: views.py:179
msgid "Update"
msgstr "Aggiorna"
#: views.py:186
msgid "Filter modified"
msgstr "Filtro modificato"
#: views.py:209
msgid "Filter removed"
msgstr "Filtro eliminato"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Rimozione del filtro fallita"
#: views.py:225
msgid "Filters set saved"
msgstr "Gruppo filtri salvato"
#: views.py:241
msgid "Filters set created"
msgstr "Gruppo filtri creato"
#: views.py:246
msgid "Create a new filters set"
msgstr "Crea un nuovo gruppo di filtri"
#: views.py:265
msgid "Filters set deleted"
msgstr "Gruppo filtri eliminato"
#: views.py:278
msgid "Filters set activated"
msgstr "Gruppo filtri attivato"
#: views.py:307
msgid "yes"
msgstr "si"
#: views.py:311
msgid "no"
msgstr "no"
@@ -0,0 +1,39 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Italian (http://www.transifex.com/tonio/modoboa/language/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Rimuovere questo filtro?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Salvare le modifiche?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Rimuovere il gruppo di filtri?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Attivare il gruppo di filtri?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "attivo"
@@ -0,0 +1,380 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Andrew <buddha@cryptolab.net>, 2014
# TuxBrother <vrijwilligerswerk@wij.rs>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2014-12-31 11:37+0000\n"
"Last-Translator: TuxBrother <vrijwilligerswerk@wij.rs>\n"
"Language-Team: Dutch (Netherlands) (http://www.transifex.com/projects/p/"
"modoboa/language/nl_NL/)\n"
"Language: nl_NL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "bevat"
#: constants.py:7
msgid "does not contain"
msgstr "bevat niet"
#: constants.py:8
msgid "is"
msgstr "is"
#: constants.py:9
msgid "is not"
msgstr "is niet"
#: constants.py:14
msgid "Subject"
msgstr "Onderwerp"
#: constants.py:17
msgid "Sender"
msgstr "Afzender"
#: constants.py:20
msgid "Recipient"
msgstr "Ontvanger"
#: constants.py:23
msgid "Cc"
msgstr "CC"
#: constants.py:25
msgid "Size"
msgstr "Grootte"
#: constants.py:26
msgid "is greater than"
msgstr "is groter dan"
#: constants.py:27
msgid "is less than"
msgstr "is minder dan"
#: constants.py:31
msgid "Move message to"
msgstr "Verplaats bericht naar"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr ""
#: constants.py:40
msgid "Redirect message to"
msgstr "Bericht doorsturen naar"
#: constants.py:48
#, fuzzy
#| msgid "Redirect message to"
msgid "Reject message"
msgstr "Bericht doorsturen naar"
#: constants.py:50
msgid "Stop processing"
msgstr ""
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Aanvinken om deze filterset te activeren"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Naam"
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr "Alles van de volgende"
#: forms.py:91
msgid "Any of the following"
msgstr "Elke van het volgende"
#: forms.py:92
msgid "All messages"
msgstr "Alle berichten"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Verkeerde filter naam"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "ManageSieve instellingen"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Server adres"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Adres van uw MANAGESIEVE server"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Server poort"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Poort van uw MANAGESIEVE server"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Verbind dmv. STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Gebruik de STARTTLS extensie (aanbevolen)"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Authenticatie mechanisme"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Geprefereerde authenticatie mechanisme"
#: forms.py:381
#, fuzzy
#| msgid "ManageSieve settings"
msgid "IMAP settings"
msgstr "ManageSieve instellingen"
#: forms.py:386
#, fuzzy
#| msgid "Address of your MANAGESIEVE server"
msgid "Address of your IMAP server"
msgstr "Adres van uw MANAGESIEVE server"
#: forms.py:390
msgid "Use a secured connection"
msgstr ""
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr ""
#: forms.py:398
#, fuzzy
#| msgid "Listening port of your MANAGESIEVE server"
msgid "Listening port of your IMAP server"
msgstr "Poort van uw MANAGESIEVE server"
#: forms.py:405
msgid "General"
msgstr "Algemeen"
#: forms.py:409
msgid "Editor mode"
msgstr "Editor mode"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Selecteer de mode waarin u de editor wilt hebben"
#: forms.py:415
msgid "Mailboxes"
msgstr ""
#: forms.py:419
msgid "Trash folder"
msgstr ""
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr ""
#: forms.py:425
msgid "Sent folder"
msgstr ""
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr ""
#: forms.py:431
msgid "Drafts folder"
msgstr ""
#: forms.py:432
msgid "Folder where drafts go"
msgstr ""
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Berichtfilters"
#: imaputils.py:99
#, fuzzy
#| msgid "Failed to retrieve filters set"
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Mislukt om de filterset te te laden"
#: imaputils.py:135
#, fuzzy, python-format
#| msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgid "Connection to IMAP server failed: %s"
msgstr "Verbinding naar MANAGESIEVE server mislukt, controleer uw configuratie"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Verbinding naar MANAGESIEVE server mislukt, controleer uw configuratie"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Niet genoeg ruimte beschikbaar op de server"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr ""
"Plugin om gemakkelijk filters op de server in te stellen (bijvoorbeeld om uw "
"mail te sorteren)"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Sieve filters"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Voorwaarden"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Acties"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Actief"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Nog geen filter aangemaakt"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Filtersets"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "actief"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr ""
"Nog geen filterset gedefinieerd. U moet een filterset aanmaken om filters in "
"te kunnen stellen."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Nieuwe filterset"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nieuw filter"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Filterset opslaan"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Filterset activeren"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Filterset verwijderen"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Download"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Filter bewerken"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Dit filter verwijderen"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Dit filter omhoogplaatsen"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Dit filter omlaagplaatsen"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Mislukt om de filterset te te laden"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Aanmaken"
#: views.py:161
msgid "Filter created"
msgstr "Filter aangemaakt"
#: views.py:179
msgid "Update"
msgstr "Toepassen"
#: views.py:186
msgid "Filter modified"
msgstr "Filter aangepast"
#: views.py:209
msgid "Filter removed"
msgstr "Filter verwijderd"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Mislukt om het filter te verwijderen"
#: views.py:225
msgid "Filters set saved"
msgstr "Filterset opgeslagen"
#: views.py:241
msgid "Filters set created"
msgstr "Filterset aangemaakt"
#: views.py:246
msgid "Create a new filters set"
msgstr "Maak een nieuwe filterset aan"
#: views.py:265
msgid "Filters set deleted"
msgstr "Filterset verwijderd"
#: views.py:278
msgid "Filters set activated"
msgstr "Filterset geactiveerd"
#: views.py:307
msgid "yes"
msgstr "ja"
#: views.py:311
msgid "no"
msgstr "nee"
@@ -0,0 +1,41 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ronald Otto <ronald@tuxis.nl>, 2017
# TuxBrother <vrijwilligerswerk@wij.rs>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Ronald Otto <ronald@tuxis.nl>\n"
"Language-Team: Dutch (Netherlands) (http://www.transifex.com/tonio/modoboa/language/nl_NL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl_NL\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Dit filter verwijderen?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Wijzigingen opslaan?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Deze filterset verwijderen?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Wilt u deze filterset activeren?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "actief"
@@ -0,0 +1,365 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Artur Slowik <artur@slowik.it>, 2017
# Dariusz Koryto <dariusz@koryto.eu>, 2016
# Tomasz Proć <tomasz.proc@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2017-12-02 10:16+0000\n"
"Last-Translator: Tomasz Proć <tomasz.proc@gmail.com>\n"
"Language-Team: Polish (Poland) (http://www.transifex.com/tonio/modoboa/language/pl_PL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl_PL\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
#: constants.py:6
msgid "contains"
msgstr "zawiera"
#: constants.py:7
msgid "does not contain"
msgstr "nie zawiera"
#: constants.py:8
msgid "is"
msgstr "jest"
#: constants.py:9
msgid "is not"
msgstr "nie jest"
#: constants.py:14
msgid "Subject"
msgstr "Temat"
#: constants.py:17
msgid "Sender"
msgstr "Nadawca"
#: constants.py:20
msgid "Recipient"
msgstr "Odbioraca"
#: constants.py:23
msgid "Cc"
msgstr "DW"
#: constants.py:25
msgid "Size"
msgstr "Rozmiar"
#: constants.py:26
msgid "is greater than"
msgstr "jest większe niż"
#: constants.py:27
msgid "is less than"
msgstr "jest mniejsze niż"
#: constants.py:31
msgid "Move message to"
msgstr "Przenieś wiadomość do"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Zachowaj kopię"
#: constants.py:40
msgid "Redirect message to"
msgstr "Przekieruj wiadomość do"
#: constants.py:48
msgid "Reject message"
msgstr "Odrzuć wiadomość"
#: constants.py:50
msgid "Stop processing"
msgstr "Zatrzymaj przetwarzanie"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Zaznacz aby uruchomić ten zestaw filtrów"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Nazwa"
#: forms.py:89
msgid "Match type"
msgstr "Typ dopasowania"
#: forms.py:90
msgid "All of the following"
msgstr "Wszystkie poniższe"
#: forms.py:91
msgid "Any of the following"
msgstr "Żadne z poniższych"
#: forms.py:92
msgid "All messages"
msgstr "Wszystkie wiadomości"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Zła nazwa filtru"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Ustawienia ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Adres serwera"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Addres twojego serwera MANAGESIEVE"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Port serwera"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Port twojego serwera MANAGESIEVE"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Połącz używając STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Użyj rozszerzenia STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Mechanizm autentykacji"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Preferowany mechanizm uwierzytelnienia"
#: forms.py:381
msgid "IMAP settings"
msgstr "Ustawienia IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Adres serwera IMAP"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Użyj bezpiecznego polączenia"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Użyj bezpiecznego połączenia do serwera IMAP"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Port, na którym nasłuchuje twój serwer IMAP"
#: forms.py:405
msgid "General"
msgstr "Ogólne"
#: forms.py:409
msgid "Editor mode"
msgstr "Tryb edycji"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Wybierz tryb w jakim ma działać edytor"
#: forms.py:415
msgid "Mailboxes"
msgstr "Skrzynki pocztowe"
#: forms.py:419
msgid "Trash folder"
msgstr "Folder kosza"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Folder, do którego trafiają usunięte wiadomości"
#: forms.py:425
msgid "Sent folder"
msgstr "Folder wysłane"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Folder, do którego trafiają kopie wysłanych wiadomości"
#: forms.py:431
msgid "Drafts folder"
msgstr "Folder kopie robocze"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Folder, do którego trafiają kopie robocze"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Filtry wiadomości"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Nie udało się rozpoznać hierarchii separatora"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Błąd połączenia do serwera IMAP: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Błąd połączenia do serwera MANAGESIEVE: %s, sprawdź konfigurację"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Brak miejsca na serwerze"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Wtyczka do łatwego tworzenia filtrów po stronie serwera"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Filtry Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Warunki"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Akcje"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Aktywne"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Brak zdefiniowanych filtrów"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Zestaw filtrów"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "aktywny"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Nie zdefiniowano zestawu filtrów. Musisz utworzyć przynajmniej jeden."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Nowy zestaw filtrów"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nowy filter"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Zapisz zestaw filtrów"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Uruchom zestaw filtrów"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Usuń zestaw filtrów"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Pobierz"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Edytuj filtr"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Usuń ten filtr"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Przenieś ten filtr wyżej"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Przenieś ten filtr niżej"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Nie udało się pobrać zestawu filtrów"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Utwórz"
#: views.py:161
msgid "Filter created"
msgstr "Filtr utworzony"
#: views.py:179
msgid "Update"
msgstr "Zaktualizuj"
#: views.py:186
msgid "Filter modified"
msgstr "Filtr zmodyfikowano"
#: views.py:209
msgid "Filter removed"
msgstr "Filtr usunięto"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Nie udało się usunąć filtru"
#: views.py:225
msgid "Filters set saved"
msgstr "Zestaw filtrów zapisano"
#: views.py:241
msgid "Filters set created"
msgstr "Zestaw filtrów utworzono"
#: views.py:246
msgid "Create a new filters set"
msgstr "Utwórz nowy zestaw filtrów"
#: views.py:265
msgid "Filters set deleted"
msgstr "Filtry skasowano"
#: views.py:278
msgid "Filters set activated"
msgstr "Filtry usunięto"
#: views.py:307
msgid "yes"
msgstr "tak"
#: views.py:311
msgid "no"
msgstr "nie"
@@ -0,0 +1,41 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Dariusz Koryto <dariusz@koryto.eu>, 2016
# Tomasz Proć <tomasz.proc@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-12-01 16:15+0000\n"
"Last-Translator: Tomasz Proć <tomasz.proc@gmail.com>\n"
"Language-Team: Polish (Poland) (http://www.transifex.com/tonio/modoboa/language/pl_PL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl_PL\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Usunać ten filter?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Zapisać zmiany?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Usunąć zestaw filtrów?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Aktywować ten zestaw filtrów? "
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "aktywne"
@@ -0,0 +1,367 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# csmk, 2014
# Felipe Morato <me@fmorato.com>, 2017
# Paulino Michelazzo <paulino@michelazzo.com.br>, 2014-2015
# Rafael Barretto <ch3yd@hotmail.com>, 2013
# Rafael Barretto <ch3yd@hotmail.com>, 2017-2018
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2018-09-25 03:25+0000\n"
"Last-Translator: Rafael Barretto <ch3yd@hotmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/tonio/modoboa/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: constants.py:6
msgid "contains"
msgstr "contém"
#: constants.py:7
msgid "does not contain"
msgstr "não contém"
#: constants.py:8
msgid "is"
msgstr "é"
#: constants.py:9
msgid "is not"
msgstr "não é"
#: constants.py:14
msgid "Subject"
msgstr "Assunto"
#: constants.py:17
msgid "Sender"
msgstr "Remetente"
#: constants.py:20
msgid "Recipient"
msgstr "Destinatário"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Tamanho"
#: constants.py:26
msgid "is greater than"
msgstr "é maior que"
#: constants.py:27
msgid "is less than"
msgstr "é menor que"
#: constants.py:31
msgid "Move message to"
msgstr "Mover mensagem para"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Manter cópia local"
#: constants.py:40
msgid "Redirect message to"
msgstr "Redirecionar mensagem para"
#: constants.py:48
msgid "Reject message"
msgstr "Rejeitar mensagem"
#: constants.py:50
msgid "Stop processing"
msgstr "Parar processo"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Marque para ativar esse conjunto de filtros"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Nome"
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr "Todas as seguintes"
#: forms.py:91
msgid "Any of the following"
msgstr "Uma das seguintes"
#: forms.py:92
msgid "All messages"
msgstr "Todas as mensagens"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Nome errado do filtro"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Configurações do ManageSieve"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Endereço do servidor"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Endereço do servidor MANAGESIEVE"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Porta do servidor"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Porta de escuta do servidor MANAGESIEVE"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Conectar usando STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Usar a extensão STARTTLS "
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Mecanismo de autenticação"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Mecanismo de autenticação preferencial"
#: forms.py:381
msgid "IMAP settings"
msgstr "Configurações IMAP"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Endereço do seu servidor IMAP"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Usar uma conexão segura"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Usar uma conexão segura para acessar o servidor IMAP"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Porta de escuta do seu servidor IMAP"
#: forms.py:405
msgid "General"
msgstr "Geral"
#: forms.py:409
msgid "Editor mode"
msgstr "Modo editor"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Selecione o modo que você deseja que o editor trabalhe"
#: forms.py:415
msgid "Mailboxes"
msgstr "Caixas de correio"
#: forms.py:419
msgid "Trash folder"
msgstr "Pasta para Itens Deletados"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Pasta que irão as mensagens deletadas"
#: forms.py:425
msgid "Sent folder"
msgstr "Pasta para Itens Enviados"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Pasta onde irão as cópias de mensagens enviadas"
#: forms.py:431
msgid "Drafts folder"
msgstr "Pasta para Rascunhos"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Pasta onde irão os rascunhos"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Filtros de mensagem"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Falha ao recuperar o delimitador de hierarquia"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Falha ao conectar no servidor IMAP: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Conexão com o servidor MANAGESIEVE falhou, verifique sua configuração"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Espaço no servidor insuficiente"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Plugin para criar facilmente filtros 'server-side'"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Filtros do Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Condições"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Ações"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Ativo"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Sem filtros definidos"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Conjuntos de filtros"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "ativo"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Nenhum conjunto de filtros ainda definido. Você deve criar um para poder definir filtros."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Novo conjunto de filtros"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Novo filtro"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Salvar conjunto de filtros"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Ativar conjunto de filtros"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Remover conjunto de filtros"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Baixar"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Editar filtro"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Remover esse filtro"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Mover esse filtro para cima"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Mover esse filtro para baixo"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Falha ao recuperar conjunto de filtros"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Criar"
#: views.py:161
msgid "Filter created"
msgstr "Filtro criado"
#: views.py:179
msgid "Update"
msgstr "Atualizar"
#: views.py:186
msgid "Filter modified"
msgstr "Filtro modificado"
#: views.py:209
msgid "Filter removed"
msgstr "Filtro removido"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Falha ao remover filtro"
#: views.py:225
msgid "Filters set saved"
msgstr "Conjunto de filtros salvo"
#: views.py:241
msgid "Filters set created"
msgstr "Conjunto de filtros criado"
#: views.py:246
msgid "Create a new filters set"
msgstr "Criar um novo conjunto de filtros"
#: views.py:265
msgid "Filters set deleted"
msgstr "Conjunto de filtros apagados"
#: views.py:278
msgid "Filters set activated"
msgstr "Conjuntos de filtros ativados"
#: views.py:307
msgid "yes"
msgstr "sim"
#: views.py:311
msgid "no"
msgstr "não"
@@ -0,0 +1,40 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Paulino Michelazzo <paulino@michelazzo.com.br>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-23 21:21+0000\n"
"Last-Translator: Paulino Michelazzo <paulino@michelazzo.com.br>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/tonio/modoboa/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Remover este filtro?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Salvar alterações?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Remover conjunto de filtros?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Ativar este conjunto de filtros?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "ativo"
@@ -0,0 +1,381 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Damien Duponchelle <dduponchelle@gmail.com>, 2013
# lusitan <lusitan@hushmail.com>, 2013
# Mike C. <miguelmclara@gmail.com>, 2012
# Mike C. <miguelmclara@gmail.com>, 2013-2014
# Sandra Ribeiro <sandrarib@gmail.com>, 2012-2013
# Sandra Ribeiro <sandrarib@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2014-12-29 12:51+0000\n"
"Last-Translator: Mike C. <miguelmclara@gmail.com>\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/"
"modoboa/language/pt_PT/)\n"
"Language: pt_PT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "contém"
#: constants.py:7
msgid "does not contain"
msgstr "não contém"
#: constants.py:8
msgid "is"
msgstr "é"
#: constants.py:9
msgid "is not"
msgstr "não é"
#: constants.py:14
msgid "Subject"
msgstr "Assunto"
#: constants.py:17
msgid "Sender"
msgstr "Remetente"
#: constants.py:20
msgid "Recipient"
msgstr "Destinatário"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Tamanho"
#: constants.py:26
msgid "is greater than"
msgstr "é maior do que"
#: constants.py:27
msgid "is less than"
msgstr "é menor do que"
#: constants.py:31
msgid "Move message to"
msgstr "Mover mensagens para"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr ""
#: constants.py:40
msgid "Redirect message to"
msgstr "Reencaminhar mensagens para"
#: constants.py:48
#, fuzzy
#| msgid "Redirect message to"
msgid "Reject message"
msgstr "Reencaminhar mensagens para"
#: constants.py:50
msgid "Stop processing"
msgstr ""
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Marque para ativar estes filtros"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Nome"
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr "Todos estes"
#: forms.py:91
msgid "Any of the following"
msgstr "Algum destes"
#: forms.py:92
msgid "All messages"
msgstr "Todas as mensagens"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Filtro inválido"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Definições do MANAGESIEVE"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Endereço do Servidor"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Endereço do servidor MANAGESIEVE"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Porta do Servidor"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Porta atribuída ao servidor MANAGESIEVE"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Ligar via STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Usar a extensão STARTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Mecanismo de autenticação"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Método de autenticação preferencial"
#: forms.py:381
#, fuzzy
#| msgid "ManageSieve settings"
msgid "IMAP settings"
msgstr "Definições do MANAGESIEVE"
#: forms.py:386
#, fuzzy
#| msgid "Address of your MANAGESIEVE server"
msgid "Address of your IMAP server"
msgstr "Endereço do servidor MANAGESIEVE"
#: forms.py:390
msgid "Use a secured connection"
msgstr ""
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr ""
#: forms.py:398
#, fuzzy
#| msgid "Listening port of your MANAGESIEVE server"
msgid "Listening port of your IMAP server"
msgstr "Porta atribuída ao servidor MANAGESIEVE"
#: forms.py:405
msgid "General"
msgstr "Geral"
#: forms.py:409
msgid "Editor mode"
msgstr "Modo de edição"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Seleccione o modo de funcionamento do editor"
#: forms.py:415
msgid "Mailboxes"
msgstr ""
#: forms.py:419
msgid "Trash folder"
msgstr ""
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr ""
#: forms.py:425
msgid "Sent folder"
msgstr ""
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr ""
#: forms.py:431
msgid "Drafts folder"
msgstr ""
#: forms.py:432
msgid "Folder where drafts go"
msgstr ""
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Gerir filtros"
#: imaputils.py:99
#, fuzzy
#| msgid "Failed to retrieve filters set"
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Falha ao recuperar os filtros definidos"
#: imaputils.py:135
#, fuzzy, python-format
#| msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgid "Connection to IMAP server failed: %s"
msgstr "Ligação ao servidor MANAGESIEVE falhou, valide a configuração"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Ligação ao servidor MANAGESIEVE falhou, valide a configuração"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Espaço insuficiente no servidor"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Plugin para criar facilmente filtros 'server-side'"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Filtros Sieve"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Condições"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Ações"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Ativo"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Ainda não existe nenhum filtro definido"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Grupos de filtros"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "ativo"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr ""
"Não existem grupos de filtros. Terá de criar um para poder definir filtros."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Novo grupo de filtros"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Novo filtro"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Guardar grupo de filtros"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Ativar grupo de filtros"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Eliminar grupo de filtros"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Descarregar"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Editar filtro"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Eliminar este filtro"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Mover este filtro para cima"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Mover este filtro para baixo"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Falha ao recuperar os filtros definidos"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Criar"
#: views.py:161
msgid "Filter created"
msgstr "Filtro criado"
#: views.py:179
msgid "Update"
msgstr "Actualizar"
#: views.py:186
msgid "Filter modified"
msgstr "Filtro modificado"
#: views.py:209
msgid "Filter removed"
msgstr "Filtro removido"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Falha ao remover filtro"
#: views.py:225
msgid "Filters set saved"
msgstr "Grupo de filtros guardado"
#: views.py:241
msgid "Filters set created"
msgstr "Grupo de filtros criado"
#: views.py:246
msgid "Create a new filters set"
msgstr "Criar novo grupo de filtros"
#: views.py:265
msgid "Filters set deleted"
msgstr "Grupo de filtros eliminado"
#: views.py:278
msgid "Filters set activated"
msgstr "Grupo de filtros ativado"
#: views.py:307
msgid "yes"
msgstr "sim"
#: views.py:311
msgid "no"
msgstr "não"
@@ -0,0 +1,42 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# lusitan <lusitan@hushmail.com>, 2013
# Mike C. <miguelmclara@gmail.com>, 2012,2014
# Sandra Ribeiro <sandrarib@gmail.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/tonio/modoboa/language/pt_PT/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_PT\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Remover este filtro?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Guardar alterações?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Limpar filtros definidos?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Activar estes filtros?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "activo"
@@ -0,0 +1,364 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ravil Kadyrbayev, 2015
# Yuriy Matskanyuk <umackanuk@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2018-11-27 18:02+0000\n"
"Last-Translator: Yuriy Matskanyuk <umackanuk@gmail.com>\n"
"Language-Team: Russian (http://www.transifex.com/tonio/modoboa/language/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#: constants.py:6
msgid "contains"
msgstr "содержит"
#: constants.py:7
msgid "does not contain"
msgstr "не содержит"
#: constants.py:8
msgid "is"
msgstr "является"
#: constants.py:9
msgid "is not"
msgstr "не"
#: constants.py:14
msgid "Subject"
msgstr "Тема"
#: constants.py:17
msgid "Sender"
msgstr "Отправитель"
#: constants.py:20
msgid "Recipient"
msgstr "Получатель"
#: constants.py:23
msgid "Cc"
msgstr "Копия"
#: constants.py:25
msgid "Size"
msgstr "размер"
#: constants.py:26
msgid "is greater than"
msgstr "больше"
#: constants.py:27
msgid "is less than"
msgstr "меньше"
#: constants.py:31
msgid "Move message to"
msgstr "Переместить сообщение"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr ""
#: constants.py:40
msgid "Redirect message to"
msgstr "Перенаправление сообщение для"
#: constants.py:48
msgid "Reject message"
msgstr ""
#: constants.py:50
msgid "Stop processing"
msgstr "Остановить обработку"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Проверьте, чтобы активировать этот набор фильтров"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Логин"
#: forms.py:89
msgid "Match type"
msgstr ""
#: forms.py:90
msgid "All of the following"
msgstr "Любое из следующих"
#: forms.py:91
msgid "Any of the following"
msgstr "Любое из следующих"
#: forms.py:92
msgid "All messages"
msgstr "Все сообщения"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Неверное имя фильтра"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "Настройки фильтров содержания"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Адрес сервера"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Адрес вашего MANAGESIEVE сервера"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Порт сервера"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "Порт вашего MANAGESIEVE сервера"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Подключение с помощью STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Используйте расширение STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Механизм аутентификации"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Предпочтительный механизм аутентификации"
#: forms.py:381
msgid "IMAP settings"
msgstr ""
#: forms.py:386
msgid "Address of your IMAP server"
msgstr ""
#: forms.py:390
msgid "Use a secured connection"
msgstr "Использовать защищенное соединение"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr ""
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr ""
#: forms.py:405
msgid "General"
msgstr "Основные"
#: forms.py:409
msgid "Editor mode"
msgstr "Режим редактирования"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Выберите нужный режим для работы редактора"
#: forms.py:415
msgid "Mailboxes"
msgstr "Почтовые ящики"
#: forms.py:419
msgid "Trash folder"
msgstr "Корзина"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr ""
#: forms.py:425
msgid "Sent folder"
msgstr "Отправленные"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr ""
#: forms.py:431
msgid "Drafts folder"
msgstr "Черновики"
#: forms.py:432
msgid "Folder where drafts go"
msgstr ""
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Фильтры сообщений"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr ""
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr ""
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Соединение с сервером MANAGESIEVE не удалось, проверьте конфигурацию"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Нет свободного места на сервере"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Плагин позволяет создавать фильтры на сервере"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Фильтры содержания"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Соответствие"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Действие"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Активно"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Фильтры не определены"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Набор фильтров"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "Активно"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Фильтры не определены. Вы должны определить и установить фильтры "
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Новый набор фильтров"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Новый фильтр"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Сохранить набор фильтров"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Активировать набор фильтров"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Удалить набор фильтров"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Загрузить"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Редактировать фильтр"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Удалить этот фильтр"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Переместить этот фильтр выше"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Переместить этот фильтр ниже"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Не удалось получить набор фильтров"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Создать"
#: views.py:161
msgid "Filter created"
msgstr "Фильтр создан"
#: views.py:179
msgid "Update"
msgstr "Обновление"
#: views.py:186
msgid "Filter modified"
msgstr "Фильтр модифицирован"
#: views.py:209
msgid "Filter removed"
msgstr "Фильтр удален"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Ошибка удаления фильтра"
#: views.py:225
msgid "Filters set saved"
msgstr "Набор фильтров сохранен"
#: views.py:241
msgid "Filters set created"
msgstr "Набор фильтров создан"
#: views.py:246
msgid "Create a new filters set"
msgstr "Создание нового набора фильтров"
#: views.py:265
msgid "Filters set deleted"
msgstr "Набор фильтров удален"
#: views.py:278
msgid "Filters set activated"
msgstr "Набор фильтров активирован"
#: views.py:307
msgid "yes"
msgstr "Да"
#: views.py:311
msgid "no"
msgstr "Нет"
@@ -0,0 +1,39 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Russian (http://www.transifex.com/tonio/modoboa/language/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Удалить фильтр?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Сохранить изменения?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Удалить набор фильтров?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Активировать набор фильтров?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "активно"
@@ -0,0 +1,364 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Martin Persson <martin.jens.persson@gmail.com>, 2014
# Olle Gustafsson <olle@dalnix.se>, 2013,2015,2017-2018
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-01 18:04+0100\n"
"PO-Revision-Date: 2018-08-14 08:14+0000\n"
"Last-Translator: Olle Gustafsson <olle@dalnix.se>\n"
"Language-Team: Swedish (http://www.transifex.com/tonio/modoboa/language/sv/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: sv\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: constants.py:6
msgid "contains"
msgstr "innehåller"
#: constants.py:7
msgid "does not contain"
msgstr "innehåller inte"
#: constants.py:8
msgid "is"
msgstr "är"
#: constants.py:9
msgid "is not"
msgstr "är inte"
#: constants.py:14
msgid "Subject"
msgstr "Ämne"
#: constants.py:17
msgid "Sender"
msgstr "Avsändare"
#: constants.py:20
msgid "Recipient"
msgstr "Mottagare"
#: constants.py:23
msgid "Cc"
msgstr "Cc"
#: constants.py:25
msgid "Size"
msgstr "Storlek"
#: constants.py:26
msgid "is greater than"
msgstr "är större än"
#: constants.py:27
msgid "is less than"
msgstr "är mindre än"
#: constants.py:31
msgid "Move message to"
msgstr "Flytta meddelande till"
#: constants.py:36 constants.py:44
msgid "Keep local copy"
msgstr "Behåll lokal kopia"
#: constants.py:40
msgid "Redirect message to"
msgstr "Omdirigera meddelande till"
#: constants.py:48
msgid "Reject message"
msgstr "Avvisa meddelande"
#: constants.py:50
msgid "Stop processing"
msgstr "Sluta bearbeta"
#: forms.py:33
msgid "Check to activate this filters set"
msgstr "Markera för att aktivera denna filter regel"
#: forms.py:87 templates/modoboa_sievefilters/guieditor.html:6
msgid "Name"
msgstr "Namn"
#: forms.py:89
msgid "Match type"
msgstr "Matcha typ"
#: forms.py:90
msgid "All of the following"
msgstr "Alla av följande"
#: forms.py:91
msgid "Any of the following"
msgstr "Någon av följande"
#: forms.py:92
msgid "All messages"
msgstr "Alla meddelanden"
#: forms.py:107
msgid "Wrong filter name"
msgstr "Fel filter namn"
#: forms.py:351
msgid "ManageSieve settings"
msgstr "ManageSieve inställningar"
#: forms.py:354 forms.py:384
msgid "Server address"
msgstr "Server adress"
#: forms.py:356
msgid "Address of your MANAGESIEVE server"
msgstr "Adressen till din MANAGESIEVE server"
#: forms.py:361 forms.py:396
msgid "Server port"
msgstr "Server port"
#: forms.py:363
msgid "Listening port of your MANAGESIEVE server"
msgstr "MANAGESIEVE server lyssnar på port"
#: forms.py:368
msgid "Connect using STARTTLS"
msgstr "Anslut med STARTTLS"
#: forms.py:370
msgid "Use the STARTTLS extension"
msgstr "Använd STARTTLS"
#: forms.py:374
msgid "Authentication mechanism"
msgstr "Autentiseringsmekanism"
#: forms.py:377
msgid "Prefered authentication mechanism"
msgstr "Önskad autentiseringsmekanism"
#: forms.py:381
msgid "IMAP settings"
msgstr "IMAP inställningar"
#: forms.py:386
msgid "Address of your IMAP server"
msgstr "Adress för din IMAP server"
#: forms.py:390
msgid "Use a secured connection"
msgstr "Använd en säker anslutning"
#: forms.py:392
msgid "Use a secured connection to access IMAP server"
msgstr "Använd en säker anslutning för att ansluta till IMAP server"
#: forms.py:398
msgid "Listening port of your IMAP server"
msgstr "Lyssningsport för din IMAP server"
#: forms.py:405
msgid "General"
msgstr "Generellt"
#: forms.py:409
msgid "Editor mode"
msgstr "Redigeringsläge"
#: forms.py:411
msgid "Select the mode you want the editor to work in"
msgstr "Välj det läge du vill redigeraren att arbeta i"
#: forms.py:415
msgid "Mailboxes"
msgstr "Brevlådor"
#: forms.py:419
msgid "Trash folder"
msgstr "Skräpkatalog"
#: forms.py:420
msgid "Folder where deleted messages go"
msgstr "Katalog där raderade meddelanden hamnar"
#: forms.py:425
msgid "Sent folder"
msgstr "Skickat katalog"
#: forms.py:426
msgid "Folder where copies of sent messages go"
msgstr "Katalog där en kopia av skickade meddelanden hamnar"
#: forms.py:431
msgid "Drafts folder"
msgstr "Utkast katalog"
#: forms.py:432
msgid "Folder where drafts go"
msgstr "Katalog där utkast sparas"
#: handlers.py:19 modo_extension.py:26
#: templates/modoboa_sievefilters/index.html:4
msgid "Message filters"
msgstr "Meddelandefilter"
#: imaputils.py:99
msgid "Failed to retrieve hierarchy delimiter"
msgstr "Misslyckades med att hämta hierarkinavgränsaren"
#: imaputils.py:135
#, python-format
msgid "Connection to IMAP server failed: %s"
msgstr "Anslutningen till IMAP servern misslyckades: %s"
#: lib.py:54
msgid "Connection to MANAGESIEVE server failed, check your configuration"
msgstr "Anslutning till MANAGESIEVE servern misslyckades, kontrollera din konfiguration"
#: lib.py:98
msgid "Not enough space on server"
msgstr "Inte tillräckligt med utrymme på servern"
#: modo_extension.py:18
msgid "Plugin to easily create server-side filters"
msgstr "Plugin för att enkelt skapa filter på serversidan"
#: modo_extension.py:24
msgid "Sieve filters"
msgstr "Sieve filter"
#: templates/modoboa_sievefilters/filter.html:9
msgid "Conditions"
msgstr "Villkor"
#: templates/modoboa_sievefilters/filter.html:20
#: templates/modoboa_sievefilters/guieditor.html:5 views.py:104
msgid "Actions"
msgstr "Åtgärder"
#: templates/modoboa_sievefilters/guieditor.html:7
msgid "Active"
msgstr "Aktivera"
#: templates/modoboa_sievefilters/guieditor.html:24
msgid "No filter defined yet"
msgstr "Inga filter definierade än"
#: templates/modoboa_sievefilters/index.html:82
msgid "Filter sets"
msgstr "Filter uppsättning"
#: templates/modoboa_sievefilters/index.html:86
msgid "active"
msgstr "aktivera"
#: templates/modoboa_sievefilters/index.html:101
msgid ""
"No filters set defined yet. You must create one in order to define filters."
msgstr "Inga filter uppsättningar definierade. Du måste skapa en uppsättning för att kunna definiera filter."
#: templatetags/sfilters_tags.py:21
msgid "New filters set"
msgstr "Ny filter uppsättning"
#: templatetags/sfilters_tags.py:39 views.py:153
msgid "New filter"
msgstr "Nytt filter"
#: templatetags/sfilters_tags.py:50
msgid "Save filters set"
msgstr "Spara filter uppsättning"
#: templatetags/sfilters_tags.py:58
msgid "Activate filters set"
msgstr "Aktivera filter uppsättning"
#: templatetags/sfilters_tags.py:63
msgid "Remove filters set"
msgstr "Radera filter uppsättning"
#: templatetags/sfilters_tags.py:67
msgid "Download"
msgstr "Ladda ner"
#: templatetags/sfilters_tags.py:82 views.py:175
msgid "Edit filter"
msgstr "Redigera filter"
#: templatetags/sfilters_tags.py:90
msgid "Remove this filter"
msgstr "Radera detta filter"
#: templatetags/sfilters_tags.py:99
msgid "Move this filter up"
msgstr "Flytta upp detta filter"
#: templatetags/sfilters_tags.py:108
msgid "Move this filter down"
msgstr "Flytta ner detta filter"
#: views.py:87
msgid "Failed to retrieve filters set"
msgstr "Det gick inte att hämta filteruppsättning"
#: views.py:156 views.py:248
msgid "Create"
msgstr "Skapa"
#: views.py:161
msgid "Filter created"
msgstr "Filter krashade"
#: views.py:179
msgid "Update"
msgstr "Uppdatera"
#: views.py:186
msgid "Filter modified"
msgstr "Filter modifierat"
#: views.py:209
msgid "Filter removed"
msgstr "Filter raderat"
#: views.py:210
msgid "Failed to remove filter"
msgstr "Det gick inte att radera filtret"
#: views.py:225
msgid "Filters set saved"
msgstr "Filter uppsättning sparat"
#: views.py:241
msgid "Filters set created"
msgstr "Filter uppsättning skapat"
#: views.py:246
msgid "Create a new filters set"
msgstr "Skapa ny filteruppsättning"
#: views.py:265
msgid "Filters set deleted"
msgstr "Filter uppsättning raderad"
#: views.py:278
msgid "Filters set activated"
msgstr "Filter uppsättning aktiverad"
#: views.py:307
msgid "yes"
msgstr "ja"
#: views.py:311
msgid "no"
msgstr "nej"
@@ -0,0 +1,40 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Olle Gustafsson <olle@dalnix.se>, 2013,2015
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-11 13:54+0100\n"
"PO-Revision-Date: 2017-09-22 09:57+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Swedish (http://www.transifex.com/tonio/modoboa/language/sv/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: sv\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/modoboa_sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Radera detta filter?"
#: static/modoboa_sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Spara ändringar?"
#: static/modoboa_sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Radera filteruppsättning?"
#: static/modoboa_sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Aktivera filteruppsättning?"
#: static/modoboa_sievefilters/js/lib.js:154
#: static/modoboa_sievefilters/js/lib.js:203
msgid "active"
msgstr "aktivera"
+91
View File
@@ -0,0 +1,91 @@
# coding: utf-8
"""Mock objects."""
SAMPLE_SIEVE_SCRIPT = """require ["fileinto", "copy"];
# Filter: test1
if anyof (header :contains "To" "test1") {
fileinto :copy "Test1";
}
# Filter: test2
if anyof (header :contains "To" "test2") {
fileinto "Test2";
}
"""
SAMPLE_SIEVE_SCRIPT2 = """require ["fileinto"];
# Filter: Tést
if anyof (header :contains "Subject" "toto", not header :contains "To" "peé") {
fileinto "Administratif.Contrats.CIC";
stop;
}
"""
class ManagesieveClientMock(object):
"""Fake managesieve client."""
def __init__(self, *args, **kwargs):
self.scripts = {
"main_script": SAMPLE_SIEVE_SCRIPT,
"second_script": SAMPLE_SIEVE_SCRIPT,
"third_script": SAMPLE_SIEVE_SCRIPT2,
}
def connect(self, username, password, **kwargs):
return username == "user@test.com" and password == "toto"
def logout(self):
return True
def capability(self):
return """
"IMPLEMENTATION" "Example1 ManageSieved v001"
"VERSION" "1.0"
"SASL" "PLAIN SCRAM-SHA-1 GSSAPI"
"SIEVE" "fileinto vacation"
"STARTTLS"
"""
def havespace(self, name, size):
return True
def listscripts(self):
return ("main_script", ["second_script"])
def getscript(self, name):
return self.scripts.get(name)
def putscript(self, name, content):
self.scripts[name] = content
return True
def setactive(self, name):
return True
def deletescript(self, name):
return True
class IMAP4Mock(object):
"""Fake IMAP4 client."""
def __init__(self, *args, **kwargs):
self.untagged_responses = {}
def _quote(self, data):
return data
def _simple_command(self, name, *args, **kwargs):
if name == "CAPABILITY":
self.untagged_responses["CAPABILITY"] = [b""]
elif name == "LIST":
self.untagged_responses["LIST"] = [b'() "." "INBOX"']
return "OK", None
def list(self):
return "OK", [b'() "." "INBOX"']
+108
View File
@@ -0,0 +1,108 @@
"""
Copy of the original rfc6266 package, just the generation part.
Orginal code is available at https://github.com/g2p/rfc6266.
"""
from urllib.parse import quote
# RFC 2616
separator_chars = '()<>@,;:\\"/[]?={} \t'
# RFC 5987
attr_chars_nonalnum = "!#$&+-.^_`|~"
def percent_encode(string, safe, encoding):
return quote(string, safe, encoding, errors="strict")
def is_token_char(ch):
# Must be ascii, and neither a control char nor a separator char
asciicode = ord(ch)
# < 128 means ascii, exclude control chars at 0-31 and 127,
# exclude separator characters.
return 31 < asciicode < 127 and ch not in separator_chars
def is_token(candidate):
return all(is_token_char(ch) for ch in candidate)
def is_ascii(text):
return all(ord(ch) < 128 for ch in text)
def fits_inside_codec(text, codec):
try:
text.encode(codec)
except UnicodeEncodeError:
return False
else:
return True
def is_lws_safe(text):
return normalize_ws(text) == text
def normalize_ws(text):
return " ".join(text.split())
def qd_quote(text):
return text.replace("\\", "\\\\").replace('"', '\\"')
def build_header(filename, disposition="attachment", filename_compat=None):
"""Generate a Content-Disposition header for a given filename.
For legacy clients that don't understand the filename* parameter,
a filename_compat value may be given.
It should either be ascii-only (recommended) or iso-8859-1 only.
In the later case it should be a character string
(unicode in Python 2).
Options for generating filename_compat (only useful for legacy clients):
- ignore (will only send filename*);
- strip accents using unicode's decomposing normalisations,
which can be done from unicode data (stdlib), and keep only ascii;
- use the ascii transliteration tables from Unidecode (PyPI);
- use iso-8859-1
Ignore is the safest, and can be used to trigger a fallback
to the document location (which can be percent-encoded utf-8
if you control the URLs).
See https://tools.ietf.org/html/rfc6266#appendix-D
"""
# While this method exists, it could also sanitize the filename
# by rejecting slashes or other weirdness that might upset a receiver.
if disposition != "attachment":
assert is_token(disposition)
rv = disposition
if is_token(filename):
rv += "; filename=%s" % (filename,)
return rv
elif is_ascii(filename) and is_lws_safe(filename):
qd_filename = qd_quote(filename)
rv += '; filename="%s"' % (qd_filename,)
if qd_filename == filename:
# RFC 6266 claims some implementations are iffy on qdtext's
# backslash-escaping, we'll include filename* in that case.
return rv
elif filename_compat:
if is_token(filename_compat):
rv += "; filename=%s" % (filename_compat,)
else:
assert is_lws_safe(filename_compat)
rv += '; filename="%s"' % (qd_quote(filename_compat),)
# alnum are already considered always-safe, but the rest isn't.
# Python encodes ~ when it shouldn't, for example.
rv += "; filename*=utf-8''%s" % (
percent_encode(filename, safe=attr_chars_nonalnum, encoding="utf-8"),
)
# This will only encode filename_compat, if it used non-ascii iso-8859-1.
return rv.encode("iso-8859-1")
@@ -0,0 +1,134 @@
.fset_container {
padding: 0 15px;
}
#set_content textarea {
width: 100%;
height: 500px;
}
#set_content label {
margin: 5px;
}
#filters_list {
border-left: 1px solid #CCC;
position: absolute;
padding-left: 5px;
left: 190px;
right: 10px;
top: 0px;
bottom: 0px;
}
#id_content {
width: 95% !important;
height: 270px;
}
/* Filter form */
#filterform {
margin: 7px;
}
#filterform h2 {
margin: 0;
padding: 0;
font-size: 1em;
margin-top: 20px;
}
#submit_container {
width: 100%;
position: absolute;
bottom: 5px;
text-align: center;
}
#error {
white-space: pre-line;
}
#addfilterlist {
background-color: #FAFAFA;
border: 1px solid #CCCCCC;
border-radius: 26px 26px 26px 26px;
padding: 14px 5px 5px;
}
#matchtype {
font-size: 0.9em;
}
div.item {
margin-bottom: 10px;
margin-left: 5px;
overflow: auto;
}
ul.errors {
list-style: none;
display: inline;
margin: 0;
padding: 0;
}
.errors li {
color: red;
font-weight: bold;
font-size: 0.9em;
display: inline;
}
.leftbox {
width: 155px;
}
.leftbox h1 {
float: none !important;
padding: 0;
margin: 0;
font-size: 1.1em;
}
.leftbox ul {
list-style: none;
padding: 5px 0 0 0;
margin: 0;
}
.leftbox li {
margin: 5px 0;
}
.leftbox a, .leftbox a:visited {
color: black;
text-decoration: none;
}
.leftbox a:hover {
color: #6AB0C5;
text-decoration: none;
}
.leftbox li.selected {
background-color: #FFFF77;
}
.leftbox img {
float: left;
margin-right: 5px;
}
a.green {
text-decoration: none;
font-weight: bold;
color: green;
}
a.red {
text-decoration: none;
font-weight: bold;
color: red;
}
@@ -0,0 +1,362 @@
;(function ($) {
var DynamicCondition = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.dyncondition.defaults, options)
this.fields_cnt = 1
this.nextid = 1
var $addlink = $('<a />', {
href: '#',
id: this.$element.attr('id') + '_rmbtn',
html: '<span class="fa fa-plus"></span>',
})
$('select[name*=cond_target_]').change($.proxy(this.target_change, this))
$addlink.click($.proxy(this.addfields, this))
this.$element.append($addlink)
for (var cpt = 1; true; cpt++) {
var $div = $('#condition_' + cpt)
if (!$div.length) {
break
}
$div.append(this.newrmlink())
this.nextid++
this.fields_cnt++
}
}
DynamicCondition.prototype = {
constructor: DynamicCondition,
loadoperators: function ($container, tpl) {
$container.empty()
for (var ocpt = 0; ocpt < tpl.operators.length; ocpt++) {
var $opt = $('<option />', {
value: tpl['operators'][ocpt][0],
html: tpl['operators'][ocpt][1],
})
$container.append($opt)
}
},
removefields: function (e) {
var $link = $(e.target).parent()
var id = $link.attr('id')
e.preventDefault()
$('#' + id.replace('_rmbtn', '')).remove()
this.fields_cnt--
},
newrmlink: function () {
return $('<a />', {
href: '#',
id: 'condition_' + this.nextid + '_rmbtn',
html: '<span class="fa fa-trash"></span>',
}).click($.proxy(this.removefields, this))
},
addfields: function (e) {
e.preventDefault()
var $div = $('<div />', {
id: 'condition_' + this.nextid,
class: 'item',
})
var $condtarget = $('<select />', {
id: 'id_cond_target_' + this.nextid,
name: 'cond_target_' + this.nextid,
class: 'form-control element-inline-block',
}).change($.proxy(this.target_change, this))
var $condoperator = $('<select />', {
id: 'id_cond_operator_' + this.nextid,
name: 'cond_operator_' + this.nextid,
class: 'form-control element-inline-block',
})
var $condvalue = $('<input />', {
id: 'id_cond_value_' + this.nextid,
name: 'cond_value_' + this.nextid,
type: 'text',
class: 'form-control element-inline-block',
})
var $rmlink = this.newrmlink()
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt]
var $opt = $('<option />', {
value: tpl.name,
html: tpl.label,
})
$condtarget.append($opt)
if (tpl.name != this.options.defaultcond) {
continue
}
this.loadoperators($condoperator, tpl)
}
$div.append($condtarget, $condoperator, $condvalue, $rmlink)
$condtarget.wrap('<div class="col-sm-3"/>')
$condoperator.wrap('<div class="col-sm-3"/>')
$condvalue.wrap('<div class="col-sm-4"/>')
this.$element.after($div)
this.nextid++
this.fields_cnt++
},
target_change: function (e) {
var $target = $(e.target)
var parts = $target.attr('name').split('_')
var $container = $('#id_cond_operator_' + parts[2])
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt]
if (tpl.name != $target.val()) {
continue
}
this.loadoperators($container, tpl)
break
}
},
counter: function () {
return this.fields_cnt
},
}
$.fn.dyncondition = function (method) {
return this.each(function () {
var $this = $(this),
data = $this.data('dyncondition'),
options = typeof method === 'object' && method
if (!data) {
$this.data('dyncondition', new DynamicCondition(this, options))
}
if (typeof method === 'string') {
data[method]()
}
})
}
$.fn.dyncondition.defaults = {
defaultcond: 'Subject',
templates: [],
}
})(jQuery)
/*
* Dynamic actions class
*/
;(function ($) {
var DynamicAction = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.dynaction.defaults, options)
this.nextid = 1
this.fields_cnt = 1
var $addlink = $('<a />', {
href: '#',
id: this.$element.attr('id') + '_rmbtn',
html: '<span class="fa fa-plus"></span>',
})
$('select[name*=action_name_]').change($.proxy(this.name_change, this))
$addlink.click($.proxy(this.addfields, this))
this.$element.append($addlink)
for (var cpt = 1; true; cpt++) {
var $div = $('#action_' + cpt)
if (!$div.length) {
break
}
$div.append(this.newrmlink())
this.nextid++
this.fields_cnt++
}
$('#id_action_arg_0_0')
.find('option')
.each(function () {
var $this = $(this),
cpt = 1,
start = 0,
value = $this.val()
while (true) {
var id = value.indexOf(options.hdelimiter, start)
if (id == -1) {
break
}
$this.css('margin-left', cpt * 20 + 'px')
cpt++
start = id + 1
}
})
this.$fdoptions = $('#id_action_arg_0_0').find('option')
}
DynamicAction.prototype = {
constructor: DynamicAction,
loadargs: function (id, tpl) {
var res = []
for (var acpt = 0; acpt < tpl.args.length; acpt++) {
var argname = 'action_arg_' + id + '_' + acpt
var $child = null
if (tpl.args[acpt].type == 'list') {
$child = $('<select />', {
id: 'id_' + argname,
name: argname,
class: 'form-control',
})
if (tpl.args[acpt].vloader != undefined) {
this[tpl.args[acpt].vloader]($child)
}
} else if (tpl.args[acpt].type === 'boolean') {
$child = $('<div />', { class: 'checkbox' })
var $label = $('<label />', { html: tpl.args[acpt].label })
$label.appendTo($child)
$('<input />', {
type: 'checkbox',
id: 'id_' + argname,
name: argname,
}).prependTo($label)
} else if (tpl.args[acpt].type == 'string') {
$child = $('<input />', {
type: 'text',
id: 'id_' + argname,
name: argname,
class: 'form-control',
})
}
res.push($child)
}
return res
},
/**
* Remove current action argument(s) before a change.
*/
deletePrevArgs: function (id) {
for (var cpt = 0; true; cpt++) {
var $arg = $('#id_action_arg_' + id + '_' + cpt)
if (!$arg.length) {
break
}
$arg.parent().remove()
}
},
/**
* Method called when the action is changed.
*/
name_change: function (e) {
var $target = $(e.target)
var parts = $target.attr('name').split('_')
var $pos = $('#action_' + parts[2] + '_rmbtn')
e.preventDefault()
this.deletePrevArgs(parts[2])
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt]
var $div = $('<div class="col-sm-6" />')
if (tpl.name != $target.val()) {
continue
}
$.each(this.loadargs(parts[2], tpl), function (idx, arg) {
arg.appendTo($div)
})
$pos.before($div)
}
},
newrmlink: function () {
return $('<a />', {
href: '#',
id: 'action_' + this.nextid + '_rmbtn',
html: '<span class="fa fa-trash"></span>',
}).click($.proxy(this.removefields, this))
},
addfields: function (e) {
e.preventDefault()
var $div = $('<div />', { id: 'action_' + this.nextid, class: 'item' })
var $actioname = $('<select />', {
id: 'id_action_name_' + this.nextid,
name: 'action_name_' + this.nextid,
class: 'form-control element-inline-block',
})
var $rmlink = this.newrmlink()
var args = null
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt]
var $opt = $('<option />', {
value: tpl.name,
html: tpl.label,
})
$actioname.append($opt)
if (tpl.name != this.options.defaultaction) {
continue
}
args = this.loadargs(this.nextid, tpl)
}
$div.append($actioname)
$actioname.change($.proxy(this.name_change, this))
var $argsDiv = $('<div class="col-sm-6" />')
$.each(args, function (idx, element) {
$argsDiv.append(element)
})
$actioname.wrap('<div class="col-sm-4"/>')
$div.append($argsDiv)
$div.append($rmlink)
this.$element.after($div)
this.nextid++
this.fields_cnt++
},
removefields: function (e) {
var $link = $(e.target).parent()
var id = $link.attr('id')
e.preventDefault()
$('#' + id.replace('_rmbtn', '')).remove()
this.fields_cnt--
},
userfolders: function ($container) {
this.$fdoptions.each(function () {
$container.append($(this).clone())
})
},
}
$.fn.dynaction = function (method) {
return this.each(function () {
var $this = $(this),
data = $this.data('dynaction'),
options = typeof method === 'object' && method
if (!data) {
$this.data('dynaction', new DynamicAction(this, options))
}
if (typeof method === 'string') {
data[method]()
}
})
}
$.fn.dynaction.defaults = {
defaultaction: 'fileinto',
hdelimiter: '.',
templates: [],
}
})(jQuery)
@@ -0,0 +1,247 @@
/*
* Plugin : "Sieve filters"
*
* Javascript utilities
*/
var curscript = null
/*
* Remove a filter from a filters set.
*
*/
function removefilter(evt) {
evt.preventDefault()
if (!confirm(gettext('Remove this filter?'))) {
return
}
var $this = $(this)
$.ajax({
url: $this.attr('href'),
dataType: 'json',
}).done(function (response) {
histomanager.update(true)
$('body').notify('success', response, 2000)
})
}
/*
* Handler listening for the "change" event on the filters sets list.
*/
function changefs(evt) {
evt.preventDefault()
histomanager.baseurl($(this).val()).update()
}
/*
* Initializes buttons located inside the filters list
*/
function init_filters_list() {
$('a[name=togglestate]').click(toggle_filter_state)
$('a[name*=movefilter_]').click(move_filter)
$('a[name=removefilter]').click(removefilter)
}
/*
* Function called just after a new filters set has been loaded.
*
* It updates the middle DIV and initializes the user interface.
*/
function loadfs(response) {
curscript = histomanager.getbaseurl()
$('#curfset').val(curscript)
$('#set_content').html(response.content)
if ($('#filters_list') != undefined) {
init_filters_list()
}
if ($('#fsetmenu').length) {
$('#fsetmenu').remove()
}
$('#menu').after($(response.menu))
$('a[name=activatefs]').click(activatefs)
$('a[name=savefs]').click(savefs)
$('a[name=removefs]').click(removefs)
}
/*
* Common function to send a command that manipulates a filters set.
*
* (remove, activate)
*/
function send_command(cmd, extracb) {
$.ajax({
url: cmd,
dataType: 'json',
}).done(function (response) {
if (extracb != undefined) {
extracb(response)
}
if (!response.norefresh) {
histomanager.update(true)
}
$('body').notify('success', response.respmsg, 2000)
})
}
/*
* Common utility used by filters set manipulation functions to
* validate any new asked action. (activate, save, remove)
*/
function check_prereqs(evt, msg) {
evt.preventDefault()
if (!curscript) {
return false
}
if (msg != undefined && !confirm(msg)) {
return false
}
return true
}
/*
* Save the current filters set by applying the modifications to the
* server.
*/
function savefs(evt) {
if (!check_prereqs(evt, gettext('Save changes?'))) {
return
}
var $editor = $('#feditor')
$.ajax({
url: $editor.attr('action'),
type: $editor.attr('method'),
dataType: 'json',
data: $editor.serialize(),
}).done(function (response) {
$('body').notify('success', response.respmsg, 2000)
})
}
/*
* Remove the current filters set.
*
* On success, the user will be automatically redirected to the active
* filters set.
*/
function removefs(evt) {
if (!check_prereqs(evt, gettext('Remove filters set?'))) {
return
}
send_command($(this).attr('href'), function (response) {
var $curfset = $('#curfset')
$curfset.find("option[value='" + curscript + "']").remove()
histomanager.baseurl(response.newfs, 1)
})
}
/*
* Activate the current filters set.
*/
function activatefs(evt) {
var $this = $(this)
if (!check_prereqs(evt, gettext('Activate this filters set?'))) {
return
}
send_command($this.attr('href'), function (response) {
$('#curfset')
.find('option')
.each(function (index, element) {
var $element = $(element)
$element.val() == curscript
? $element.html(curscript + ' (' + gettext('active') + ')')
: $element.html($element.val())
})
})
}
/*
* Enable/Disable a specific filter
*/
function toggle_filter_state(event) {
var $this = $(this)
event.preventDefault()
$.ajax({
url: $this.attr('href'),
dataType: 'json',
}).done(function (response) {
$this.html(response.label)
$this.attr('class', '')
$this.addClass(response.color)
})
}
function move_filter(event) {
var $this = $(this)
event.preventDefault()
$.ajax({
url: $this.attr('href'),
dataType: 'json',
}).done(function (response) {
$('#set_content').html(response.content)
init_filters_list()
})
}
function filterset_created(data) {
var name = data.url
var option = $('<option value="' + name + '">' + name + '</option>')
var curfset = $(parent.document).find('#curfset')
curfset.append(option)
curfset.val(name)
if (data.active) {
curfset.find('option').each(function (index, element) {
var $element = $(element)
if ($element.val() == name) {
$element.html(name + ' (' + gettext('active') + ')')
} else {
$element.html($element.val())
}
})
}
histomanager.baseurl(data.url, 1).update(1)
}
function filtersetform_cb() {
$('#newfiltersset')
.find('input')
.keypress(function (e) {
if (e.which == 13) e.preventDefault()
})
$('.submit').on('click', function (e) {
simple_ajax_form_post(e, {
formid: 'newfiltersset',
reload_on_success: false,
success_cb: filterset_created,
})
})
}
/*
* From here: specific functions used by the user-friendly form to
* create/edit filters.
*/
function fetch_templates(url, container) {
if (container) {
return container
}
var result
$.ajax({
url: url,
async: false,
dataType: 'json',
}).done(function (data) {
result = data
})
return result
}
@@ -0,0 +1,26 @@
{% extends "common/generic_modal_form.html" %}
{% load i18n form_tags sfilters_tags %}
{% block modal_css_classes %}modal-lg{% endblock %}
{% block formbody %}
{% if oldname %}<input type="hidden" name="oldname" value="{{ oldname }}" />{% endif %}
{% render_field form.name %}
<h6>{% trans "Conditions" %}</h6>
<div id="addfilterlist">
<div id="matchtype">
{% render_field form.match_type %}
</div>
<div id="hide" style="display:{{ hidestyle }}">
{% for ec in conds_nb %}
{% display_condition form ec %}
{% endfor %}
</div>
</div>
<h6>{% trans "Actions" %}</h6>
<div id="addfilterlist">
{% for ea in actions_nb %}
{% display_action form ea %}
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,25 @@
{% load i18n sfilters_tags %}
{% if fs.filters %}
<table class="table table-striped table-bordered">
<tr>
<th width="10%">{% trans "Actions" %}</th>
<th>{% trans "Name" %}</th>
<th width="5%">{% trans "Active" %}</th>
</tr>
{% for filter in fs.filters %}
<tr>
<td>{% filter_actions fs.name filter forloop.counter forloop.last %}</td>
<td>{{ filter.name }}</td>
<td>
<a name="togglestate"
class={% if filter.enabled %}"green"{% else %}"red"{% endif %}
href="{% url 'sievefilters:filter_toggle_state' fs.name filter.name %}">
{{ filter.enabled|yesno }}
</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="alert alert-info">{% trans "No filter defined yet" %}</div>
{% endif %}
@@ -0,0 +1,104 @@
{% extends "twocols.html" %}
{% load i18n static sfilters_tags %}
{% block pagetitle %}{% trans "Message filters" %}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'sievefilters/css/style.css' %}" />
{% endblock %}
{% block extra_js %}
<script type="text/javascript" src="{% static 'js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'sievefilters/js/lib.js' %}"></script>
<script type="text/javascript" src="{% static 'js/dynamic_input.js' %}"></script>
<script type="text/javascript" src="{% static 'sievefilters/js/jquery.dynfields.js' %}"></script>
<script type="text/javascript">
var histomanager = new History({
deflocation: "{{ default_script }}",
defcallback: loadfs
});
var cond_templates = null;
var action_templates = null;
function display_filter_errors(data) {
$("ul.errors").remove();
$.each(data.form_errors, function(id, value) {
if (id.indexOf('cond') && id.indexOf('action')) {
return;
}
var fullid = "id_" + (data.prefix ? data.prefix + "-" : "") + id;
var $widget = $("#" + fullid);
var ulid = fullid + "-error";
var $ul = $("<ul />", {
"class": "errors",
"id": ulid
}).append($("<li />", {
html: value[0]
}));
$widget.after($ul);
});
}
function filterform_cb() {
cond_templates = fetch_templates("{% url 'sievefilters:templates_get' 'condition' %}", cond_templates);
action_templates = fetch_templates("{% url 'sievefilters:templates_get' 'action' %}", action_templates);
var $dc = $("#condition_0").dyncondition({
templates: cond_templates
}).data('dyncondition');
var $da = $("#action_0").dynaction({
templates: action_templates,
hdelimiter: '{{ hdelimiter }}'
}).data('dynaction');
$("input[name=match_type]").click(function(e) {
if ($(this).val() == "all") {
$("div[id*=condition]").hide();
} else {
$("div[id*=condition]").show();
}
});
$(".submit").on('click', function(e) {
var counters = "conds=" + $dc.fields_cnt + "&actions=" + $da.fields_cnt;
simple_ajax_form_post(e, {
formid: 'filterform',
reload_mode: 'history',
extradata: counters,
error_cb: display_filter_errors
});
});
}
$(document).ready(function() {
$("#curfset").change(changefs);
});
</script>
{% endblock %}
{% block leftcol %}
<ul id="menu" class="nav nav-sidebar">
<li class="nav-header">{% trans "Filter sets" %}</li>
<li class="fset_container">
<select id="curfset" class="form-control">
{% if active_script %}
<option value="{{ active_script }}">{{ active_script }} ({% trans "active" %})</option>
{% endif %}
{% for script in scripts %}
<option value="{{ script }}">{{ script }}</option>
{% endfor %}
</select>
</li>
{# It will add a li item : #}
{% sfilters_menu user %}
</ul>
{% endblock %}
{% block apparea %}
<div id="set_content">
{% if not active_script and not scripts %}
<div class="alert alert-info">{% trans "No filters set defined yet. You must create one in order to define filters." %}</div>
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,4 @@
<form action="{% url 'sievefilters:fs_save' name %}"
method="POST" id="feditor" name="feditor">{% csrf_token %}
<textarea id="scriptcontent" name="scriptcontent">{{ scriptcontent }}</textarea>
</form>
@@ -0,0 +1,213 @@
"""Custom template tags."""
from django import template
from django.urls import reverse
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
from modoboa.lib.web_utils import render_actions
from modoboa.lib.templatetags.form_tags import configure_field_classes
from .. import lib
register = template.Library()
@register.simple_tag
def sfilters_menu(user):
entries = [
{
"name": "newfilterset",
"img": "fa fa-plus",
"label": _("New filters set"),
"url": reverse("sievefilters:fs_add"),
"modal": True,
"modalcb": "filtersetform_cb",
}
]
return render_to_string("common/menulist.html", {"entries": entries, "user": user})
@register.simple_tag
def fset_menu(mode, setname):
entries = []
if mode == "gui":
entries += [
{
"name": "newfilter",
"img": "fa fa-plus",
"label": _("New filter"),
"url": reverse("sievefilters:filter_add", args=[setname]),
"modal": True,
"autowidth": True,
"modalcb": "filterform_cb",
}
]
if mode == "raw":
entries += [
{
"name": "savefs",
"img": "fa fa-download-alt",
"label": _("Save filters set"),
"url": reverse("sievefilters:fs_save", args=[setname]),
},
]
entries += [
{
"name": "activatefs",
"img": "fa fa-check",
"label": _("Activate filters set"),
"url": reverse("sievefilters:fs_activate", args=[setname]),
},
{
"name": "removefs",
"img": "fa fa-trash",
"label": _("Remove filters set"),
"url": reverse("sievefilters:fs_delete", args=[setname]),
},
{
"name": "downloadfs",
"img": "fa fa-download",
"label": _("Download"),
"url": reverse("sievefilters:fs_download", args=[setname]),
},
]
return render_to_string("common/menulist.html", {"entries": entries})
@register.simple_tag
def filter_actions(setname, f, position, islast):
actions = [
{
"name": "editfilter",
"url": reverse("sievefilters:filter_change", args=[setname, f["name"]]),
"img": "fa fa-edit",
"title": _("Edit filter"),
"modal": True,
"autowidth": True,
"modalcb": "filterform_cb",
},
{
"name": "removefilter",
"url": reverse("sievefilters:filter_delete", args=[setname, f["name"]]),
"img": "fa fa-trash",
"title": _("Remove this filter"),
},
]
if position != 1:
actions += [
{
"name": "movefilter_up",
"url": reverse(
"sievefilters:filter_move_up", args=[setname, f["name"]]
),
"img": "fa fa-chevron-up",
"title": _("Move this filter up"),
}
]
if not islast:
actions += [
{
"name": "movefilter_down",
"url": reverse(
"sievefilters:filter_move_down", args=[setname, f["name"]]
),
"img": "fa fa-chevron-down",
"title": _("Move this filter down"),
},
]
return render_actions(actions)
@register.simple_tag
def display_errors(errors):
if not len(errors):
return ""
t = template.Template(
"""
<ul class="errors">
{% for error in errors %}
<li>{{ error|safe }}</li>
{% endfor %}
</ul>
"""
)
return t.render(template.Context({"errors": errors}))
@register.simple_tag
def display_condition(form, cnt):
target = form["cond_target_%d" % cnt]
configure_field_classes(target)
operator = form["cond_operator_%d" % cnt]
configure_field_classes(operator)
value = form["cond_value_%d" % cnt]
configure_field_classes(value)
t = template.Template(
"""
<div id="condition_{{ idx }}" class="item">
<div class="col-lg-3 col-md-3 col-sm-3">{{ tfield }}</div>
<div class="col-lg-3 col-md-3 col-sm-3">{{ofield}}</div>
<div class="col-lg-4 col-md-4 col-sm-4">{{ vfield }}</div>
{{ verrors }}
</div>
"""
)
return t.render(
template.Context(
{
"idx": cnt,
"tfield": target,
"ofield": operator,
"vfield": value,
"verrors": display_errors(value.errors),
}
)
)
@register.simple_tag
def display_action(form, cnt):
action = form["action_name_%d" % cnt]
tpl = lib.find_action_template(action.initial)
configure_field_classes(action)
values = []
verrors = []
for pos, argtpl in enumerate(tpl.get("args", [])):
fieldname = "action_arg_{}_{}".format(cnt, pos)
if fieldname not in form.fields:
continue
field = form[fieldname]
configure_field_classes(field)
arg = {"field": field}
if field.errors:
verrors += field.errors
if argtpl["type"] == "boolean":
arg["checkbox"] = True
values += [arg]
t = template.Template(
"""
<div id="action_{{ idx }}" class="item">
<div class="col-sm-4">
{{ afield }}
</div>
<div class="col-sm-6">
{% for v in values %}{% if v.checkbox %}<div class="checkbox"><label>{{ v.field }} {{ v.field.label }}</label></div>{% else %}{{ v.field }}{% endif %}{% endfor %}
{{ verrors }}
</div>
</div>
"""
)
return t.render(
template.Context(
{
"idx": cnt,
"afield": action,
"values": values,
"verrors": display_errors(verrors),
}
)
)
+165
View File
@@ -0,0 +1,165 @@
# coding: utf-8
"""Sievefilters tests."""
from unittest import mock
from django.urls import reverse
from modoboa.admin import factories as admin_factories
from modoboa.core import models as core_models
from modoboa.lib.tests import ModoTestCase
from . import mocks
class SieveFiltersTestCase(ModoTestCase):
"""Check sieve filters."""
@classmethod
def setUpTestData(cls):
"""Create some users."""
super().setUpTestData()
admin_factories.populate_database()
cls.user = core_models.User.objects.get(username="user@test.com")
def setUp(self):
"""Connect with a simpler user."""
patcher = mock.patch("sievelib.managesieve.Client")
self.mock_client = patcher.start()
self.mock_client.return_value = mocks.ManagesieveClientMock()
self.addCleanup(patcher.stop)
patcher = mock.patch("imaplib.IMAP4")
self.mock_imap4 = patcher.start()
self.mock_imap4.return_value = mocks.IMAP4Mock()
self.addCleanup(patcher.stop)
url = reverse("core:login")
data = {"username": self.user.username, "password": "toto"}
self.client.post(url, data)
def test_index(self):
"""Test index view."""
response = self.client.get(reverse("sievefilters:index"))
self.assertContains(response, "main_script (active)")
response = self.client.post(reverse("core:logout"))
self.assertEqual(response.status_code, 302)
def test_getfs(self):
"""Test getfs view."""
response = self.ajax_get(reverse("sievefilters:fs_get", args=["main_script"]))
self.assertIn("/sfilters/main_script/editfilter/test1/", response["content"])
def test_toggle_filter_state(self):
"""Test toggle_filter_state view."""
url = reverse("sievefilters:filter_toggle_state", args=["main_script", "test1"])
response = self.ajax_get(url)
self.assertEqual(response["color"], "red")
response = self.ajax_get(url)
self.assertEqual(response["color"], "green")
def test_move_filter_up(self):
"""Test move_filter_up view."""
url = reverse("sievefilters:filter_move_up", args=["main_script", "test2"])
response = self.ajax_get(url)
pos1 = response["content"].find("/sfilters/main_script/editfilter/test2/")
pos2 = response["content"].find("/sfilters/main_script/editfilter/test1/")
self.assertTrue(pos1 < pos2)
def test_move_filter_down(self):
"""Test move_filter_down view."""
url = reverse("sievefilters:filter_move_down", args=["main_script", "test1"])
response = self.ajax_get(url)
pos1 = response["content"].find("/sfilters/main_script/editfilter/test2/")
pos2 = response["content"].find("/sfilters/main_script/editfilter/test1/")
self.assertTrue(pos1 < pos2)
def test_new_filter(self):
"""Test new_filter view."""
url = reverse("sievefilters:filter_add", args=["main_script"])
response = self.client.get(url)
self.assertContains(response, "New filter")
data = {
"name": "tést filter",
"match_type": "anyof",
"conds": 1,
"cond_target_0": "Subject",
"cond_operator_0": "contains",
"cond_value_0": "Test",
"actions": 1,
"action_name_0": "fileinto",
"action_arg_0_0": "Trash",
}
response = self.ajax_post(url, data)
self.assertEqual(response, "Filter created")
def test_edit_filter(self):
"""Test edit_filter view."""
url = reverse("sievefilters:filter_change", args=["main_script", "test1"])
response = self.client.get(url)
self.assertContains(response, "Edit filter")
data = {
"oldname": "test1",
"name": "test1",
"match_type": "all",
"conds": 1,
"cond_target_0": "To",
"cond_operator_0": "contains",
"cond_value_0": "test1",
"actions": 1,
"action_name_0": "fileinto",
"action_arg_0_0": "Trash",
"action_arg_0_1": True,
}
response = self.ajax_post(url, data)
self.assertEqual(response, "Filter modified")
def test_edit_filter_with_not(self):
"""Try to edit a filter containing a not operator."""
url = reverse("sievefilters:filter_change", args=["third_script", "Tést"])
response = self.client.get(url)
self.assertContains(response, "Edit filter")
def test_remove_filter(self):
"""Test removefilter view."""
url = reverse("sievefilters:filter_delete", args=["second_script", "test1"])
response = self.ajax_get(url)
self.assertEqual(response, "Filter removed")
def test_download_filters_set(self):
"""Test download_filters_set view."""
url = reverse("sievefilters:fs_download", args=["main_script"])
response = self.client.get(url)
self.assertEqual(response.content.decode(), mocks.SAMPLE_SIEVE_SCRIPT)
def test_activate_filters_set(self):
"""Test activate_filters_set view."""
url = reverse("sievefilters:fs_activate", args=["second_script"])
response = self.ajax_get(url)
self.assertEqual(response["respmsg"], "Filters set activated")
def test_remove_filters_set(self):
"""Test remove_filters_set view."""
url = reverse("sievefilters:fs_delete", args=["second_script"])
response = self.ajax_get(url)
self.assertEqual(response["respmsg"], "Filters set deleted")
def test_new_filters_set(self):
"""Test new_filters_set view."""
url = reverse("sievefilters:fs_add")
response = self.client.get(url)
self.assertContains(response, "Create a new filters set")
data = {"name": "new_script"}
response = self.ajax_post(url, data)
self.assertEqual(response["respmsg"], "Filters set created")
def test_savefs(self):
"""Test savefs view."""
url = reverse("sievefilters:fs_save", args=["new_script"])
data = {"scriptcontent": mocks.SAMPLE_SIEVE_SCRIPT}
response = self.ajax_post(url, data)
self.assertEqual(response["respmsg"], "Filters set saved")
+42
View File
@@ -0,0 +1,42 @@
"""Sieve urls."""
from django.urls import path
from . import views
app_name = "sievefilters"
urlpatterns = [
path("", views.index, name="index"),
path("savefs/<str:name>/", views.savefs, name="fs_save"),
path("newfs/", views.new_filters_set, name="fs_add"),
path("removefs/<str:name>/", views.remove_filters_set, name="fs_delete"),
path("activatefs/<str:name>/", views.activate_filters_set, name="fs_activate"),
path("downloadfs/<str:name>/", views.download_filters_set, name="fs_download"),
path("templates/<str:ftype>/", views.get_templates, name="templates_get"),
path("<str:setname>/newfilter/", views.newfilter, name="filter_add"),
path(
"<str:setname>/editfilter/<path:fname>/", views.editfilter, name="filter_change"
),
path(
"<str:setname>/removefilter/<path:fname>/",
views.removefilter,
name="filter_delete",
),
path(
"<str:setname>/togglestate/<path:fname>/",
views.toggle_filter_state,
name="filter_toggle_state",
),
path(
"<str:setname>/moveup/<path:fname>/",
views.move_filter_up,
name="filter_move_up",
),
path(
"<str:setname>/movedown/<path:fname>/",
views.move_filter_down,
name="filter_move_down",
),
path("<str:name>/", views.getfs, name="fs_get"),
]
+326
View File
@@ -0,0 +1,326 @@
"""Custom views."""
from sievelib.commands import BadArgument, BadValue
from sievelib.managesieve import Error
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.encoding import smart_bytes, smart_str
from django.utils.translation import gettext as _
from modoboa.admin.lib import needs_mailbox
from modoboa.lib.connections import ConnectionError
from modoboa.lib.exceptions import BadRequest
from modoboa.lib.web_utils import _render_error, ajax_response, render_to_json_response
from . import constants
from .forms import (
FilterForm,
build_filter_form_from_qdict,
build_filter_form_from_filter,
FiltersSetForm,
)
from .imaputils import get_imapconnector
from .lib import SieveClient, SieveClientError
from .rfc6266 import build_header
from .templatetags.sfilters_tags import fset_menu
@login_required
@needs_mailbox()
def index(request, tplname="sievefilters/index.html"):
try:
sc = SieveClient(
user=request.user.username, password=request.session["password"]
)
except ConnectionError as e:
return _render_error(request, user_context={"error": e})
try:
active_script, scripts = sc.listscripts()
except Error as e:
return _render_error(request, user_context={"error": e})
if active_script is None:
active_script = ""
default_script = "%s/" % scripts[0] if len(scripts) else ""
else:
default_script = "%s/" % active_script
return render(
request,
tplname,
{
"selection": "user",
"active_script": active_script,
"default_script": default_script,
"scripts": sorted(scripts),
"hdelimiter": get_imapconnector(request).hdelimiter,
},
)
@login_required
@needs_mailbox()
def get_templates(request, ftype):
if ftype == "condition":
data = constants.CONDITION_TEMPLATES
else:
data = constants.ACTION_TEMPLATES
return JsonResponse(data, safe=False)
@login_required
@needs_mailbox()
def getfs(request, name):
sc = SieveClient(user=request.user.username, password=request.session["password"])
editormode = request.user.parameters.get_value("editor_mode")
error = None
try:
content = sc.getscript(name, format=editormode)
except SieveClientError as e:
error = str(e)
else:
if content is None:
error = _("Failed to retrieve filters set")
if error is not None:
return ajax_response(request, "ko", respmsg=error)
if editormode == "raw":
htmlcontent = render_to_string(
"sievefilters/rawfilter.html", {"name": name, "scriptcontent": content}
)
else:
htmlcontent = render_to_string("sievefilters/guieditor.html", {"fs": content})
menu = (
"<ul id='fsetmenu' class='nav nav-sidebar'>"
"<li class='nav-header'>{}</li>{}</ul>".format(
_("Actions"), fset_menu(editormode, name)
)
)
resp = {"menu": menu, "content": htmlcontent}
return render_to_json_response(resp)
def build_filter_ctx(ctx, form):
ctx["form"] = form
ctx["conds_nb"] = range(form.conds_cnt)
ctx["actions_nb"] = range(form.actions_cnt)
return ctx
def submitfilter(request, setname, okmsg, tplname, tplctx, update=False, sc=None):
form = build_filter_form_from_qdict(request)
if form.is_valid():
if sc is None:
sc = SieveClient(
user=request.user.username, password=request.session["password"]
)
fset = sc.getscript(setname, format="fset")
conditions, actions = form.tofilter()
match_type = form.cleaned_data["match_type"]
if match_type == "all":
match_type = "anyof"
conditions = [("true",)]
fltname = form.cleaned_data["name"].encode("utf-8")
try:
if not update:
fset.addfilter(fltname, conditions, actions, match_type)
else:
oldname = request.POST["oldname"].encode("utf-8")
fset.updatefilter(oldname, fltname, conditions, actions, match_type)
except (BadArgument, BadValue) as inst:
raise BadRequest(str(inst))
sc.pushscript(fset.name, "{}".format(fset))
return render_to_json_response(okmsg)
return render_to_json_response({"form_errors": form.errors}, status=400)
@login_required
@needs_mailbox()
def newfilter(request, setname, tplname="sievefilters/filter.html"):
ctx = {
"title": _("New filter"),
"formid": "filterform",
"action": reverse("sievefilters:filter_add", args=[setname]),
"action_label": _("Create"),
"action_classes": "submit",
}
if request.method == "POST":
return submitfilter(request, setname, _("Filter created"), tplname, ctx)
conds = [("Subject", "contains", "")]
actions = [("fileinto", "")]
form = FilterForm(conds, actions, request)
ctx = build_filter_ctx(ctx, form)
return render(request, tplname, ctx)
@login_required
@needs_mailbox()
def editfilter(request, setname, fname, tplname="sievefilters/filter.html"):
ctx = {
"title": _("Edit filter"),
"formid": "filterform",
"action": reverse("sievefilters:filter_change", args=[setname, fname]),
"action_label": _("Update"),
"action_classes": "submit",
}
sc = SieveClient(user=request.user.username, password=request.session["password"])
if request.method == "POST":
return submitfilter(
request, setname, _("Filter modified"), tplname, ctx, update=True, sc=sc
)
fset = sc.getscript(setname, format="fset")
fname = smart_bytes(fname)
f = fset.getfilter(fname)
form = build_filter_form_from_filter(request, fname, f)
ctx = build_filter_ctx(ctx, form)
ctx["oldname"] = fname
ctx["hidestyle"] = "none" if form.fields["match_type"].initial == "all" else "block"
return render(request, tplname, ctx)
@login_required
@needs_mailbox()
def removefilter(request, setname, fname):
sc = SieveClient(user=request.user.username, password=request.session["password"])
fset = sc.getscript(setname, format="fset")
if fset.removefilter(fname.encode("utf-8")):
sc.pushscript(fset.name, "{}".format(fset))
return render_to_json_response(_("Filter removed"))
return render_to_json_response(_("Failed to remove filter"), status=500)
@login_required
@needs_mailbox()
def savefs(request, name):
if "scriptcontent" not in request.POST:
return
sc = SieveClient(user=request.user.username, password=request.session["password"])
try:
sc.pushscript(name, request.POST["scriptcontent"])
except SieveClientError as e:
error = str(e)
return ajax_response(request, "ko", respmsg=error)
return ajax_response(request, respmsg=_("Filters set saved"))
@login_required
@needs_mailbox()
def new_filters_set(request, tplname="common/generic_modal_form.html"):
if request.method == "POST":
form = FiltersSetForm(request.POST)
if form.is_valid():
sc = SieveClient(
user=request.user.username, password=request.session["password"]
)
sc.pushscript(
form.cleaned_data["name"], "# Empty script", form.cleaned_data["active"]
)
return render_to_json_response(
{
"url": form.cleaned_data["name"],
"active": form.cleaned_data["active"],
"respmsg": _("Filters set created"),
}
)
return render_to_json_response({"form_errors": form.errors}, status=400)
ctx = {
"title": _("Create a new filters set"),
"formid": "newfiltersset",
"action_label": _("Create"),
"action_classes": "submit",
"action": reverse("sievefilters:fs_add"),
"withmenu": False,
"withunseen": False,
"form": FiltersSetForm(),
}
return render(request, tplname, ctx)
@login_required
@needs_mailbox()
def remove_filters_set(request, name):
sc = SieveClient(user=request.user.username, password=request.session["password"])
sc.deletescript(name)
acs, scripts = sc.listscripts()
ctx = {
"respmsg": _("Filters set deleted"),
"newfs": acs,
}
return render_to_json_response(ctx)
@login_required
@needs_mailbox()
def activate_filters_set(request, name):
sc = SieveClient(user=request.user.username, password=request.session["password"])
try:
sc.activatescript(name)
except SieveClientError as e:
return ajax_response(request, "ko", respmsg=str(e))
return ajax_response(request, respmsg=_("Filters set activated"))
@login_required
@needs_mailbox()
def download_filters_set(request, name):
sc = SieveClient(user=request.user.username, password=request.session["password"])
try:
script = sc.getscript(name)
except SieveClientError as e:
return ajax_response(request, "ko", respmsg=str(e))
resp = HttpResponse(script)
resp["Content-Type"] = "text/plain; charset=utf-8"
resp["Content-Length"] = len(script)
resp["Content-Disposition"] = build_header("%s.txt" % name)
return resp
@login_required
@needs_mailbox()
def toggle_filter_state(request, setname, fname):
sc = SieveClient(user=request.user.username, password=request.session["password"])
fname = smart_str(fname)
fset = sc.getscript(setname, format="fset")
if fset.is_filter_disabled(fname):
ret = fset.enablefilter(fname)
newstate = _("yes")
color = "green"
else:
ret = fset.disablefilter(fname)
newstate = _("no")
color = "red"
if not ret:
pass
sc.pushscript(setname, "{}".format(fset))
return render_to_json_response({"label": newstate, "color": color})
def move_filter(request, setname, fname, direction):
sc = SieveClient(user=request.user.username, password=request.session["password"])
fset = sc.getscript(setname, format="fset")
fset.movefilter(fname.encode("utf-8"), direction)
sc.pushscript(setname, str(fset))
return ajax_response(request, template="sievefilters/guieditor.html", fs=fset)
@login_required
@needs_mailbox()
def move_filter_up(request, setname, fname):
return move_filter(request, setname, fname, "up")
@login_required
@needs_mailbox()
def move_filter_down(request, setname, fname):
return move_filter(request, setname, fname, "down")
+2
View File
@@ -29,6 +29,7 @@ urlpatterns = [
path("stats/", include("modoboa.maillog.urls")),
path("dmarc/", include("modoboa.dmarc.urls")),
path("imap_migration/", include("modoboa.imap_migration.urls")),
path("sfilters/", include("modoboa.sievefilters.urls")),
path("user/forward/", user_views.forward, name="user_forward"),
path(
"accounts/password_reset/",
@@ -100,6 +101,7 @@ urlpatterns += [
name="docs-index-v2",
),
path("api/schema-v2/redoc/", SpectacularRedocView.as_view(url_name="schema-v2")),
path("api/o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
path("api/v1/", include("modoboa.urls_api_v1", namespace="v1")),
path("api/v2/", include("modoboa.urls_api_v2", namespace="v2")),
]
+1
View File
@@ -19,6 +19,7 @@ urlpatterns = [
path("", include("modoboa.transport.api.v2.urls")),
path("", include("modoboa.pdfcredentials.api.v2.urls")),
path("", include("modoboa.postfix_autoreply.api.v2.urls")),
path("", include("modoboa.sievefilters.api.v2.urls")),
]
urlpatterns += exts_pool.get_urls(category="api")
+4
View File
@@ -13,6 +13,7 @@ djangorestframework>=3.14,<3.16
drf-spectacular
uritemplate
djangorestframework-simplejwt==5.3.1
django-oauth-toolkit
passlib~=1.7.4
bcrypt # Requires libffi-dev and python-dev
@@ -48,3 +49,6 @@ python-magic>=0.4.24
# RQ
rq>=1.13.0
django-rq
# Sieve
sievelib>=1.1.1
+14 -1
View File
@@ -66,6 +66,7 @@ INSTALLED_APPS = (
"reversion",
"ckeditor",
"ckeditor_uploader",
"oauth2_provider",
"rest_framework",
"rest_framework.authtoken",
"drf_spectacular",
@@ -95,6 +96,7 @@ MODOBOA_APPS = (
"modoboa.dmarc",
"modoboa.imap_migration",
"modoboa.postfix_autoreply",
"modoboa.sievefilters",
# Modoboa extensions here.
)
@@ -189,6 +191,17 @@ STATICFILES_DIRS = (os.path.join(BASE_DIR, "..", "modoboa", "bower_components"),
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# oAuth2 settings
OAUTH2_PROVIDER = {
"SCOPES": {
"read": "Read scope",
"write": "Write scope",
"introspection": "Introspect token scope",
},
"DEFAULT_SCOPES": ["read", "write"],
}
# Rest framework settings
REST_FRAMEWORK = {
@@ -202,7 +215,7 @@ REST_FRAMEWORK = {
"password_recovery_apply": "25/hour",
},
"DEFAULT_AUTHENTICATION_CLASSES": (
"modoboa.core.drf_authentication.JWTAuthenticationWith2FA",
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
),