mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
WIP: sieve filters
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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": "*",
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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")},
|
||||
]
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"']
|
||||
@@ -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),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -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")
|
||||
@@ -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"),
|
||||
]
|
||||
@@ -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")
|
||||
@@ -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")),
|
||||
]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user