diff --git a/src/phpunit.xml b/src/phpunit.xml
index 7a316a86..6edec58c 100644
--- a/src/phpunit.xml
+++ b/src/phpunit.xml
@@ -1,64 +1,47 @@
tests/Unittests/Functionaltests/Featuretests/Browser
- tests/Browser/Reseller/DashboardTest.php
- tests/Browser/Reseller/DistlistTest.php
- tests/Browser/Reseller/DomainTest.php
- tests/Browser/Reseller/InvitationsTest.php
- tests/Browser/Reseller/LogonTest.php
- tests/Browser/Reseller/PaymentMollieTest.php
- tests/Browser/Reseller/ResourceTest.php
- tests/Browser/Reseller/SharedFolderTest.php
- tests/Browser/Reseller/StatsTest.php
- tests/Browser/Reseller/UserTest.php
- tests/Browser/Reseller/WalletTest.php
- tests/Browser/Reseller/UserFinancesTest.php
- tests/Browser/LogonTest.php
- tests/Browser/SignupTest.phptests/Browser/PaymentStripeTest.php
- tests/Browser/Meet/RoomSetupTest.php
- tests/Browser/Meet/RoomControlsTest.php
- tests/Browser/Meet/RoomModeratorTest.php./app
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
index 390d542f..4e857ba0 100644
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -1,455 +1,454 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap')
import AppComponent from '../vue/App'
import MenuComponent from '../vue/Widgets/Menu'
import SupportForm from '../vue/Widgets/SupportForm'
-import { Tab } from 'bootstrap'
import { loadLangAsync, i18n } from './locale'
import { clearFormValidation, pick, startLoading, stopLoading } from './utils'
const routerState = {
afterLogin: null,
isLoggedIn: !!localStorage.getItem('token')
}
let loadingRoute
// Note: This has to be before the app is created
// Note: You cannot use app inside of the function
window.router.beforeEach((to, from, next) => {
// check if the route requires authentication and user is not logged in
if (to.meta.requiresAuth && !routerState.isLoggedIn) {
// remember the original request, to use after login
routerState.afterLogin = to;
// redirect to login page
next({ name: 'login' })
return
}
if (to.meta.loading) {
startLoading()
loadingRoute = to.name
}
next()
})
window.router.afterEach((to, from) => {
if (to.name && loadingRoute === to.name) {
stopLoading()
loadingRoute = null
}
// When changing a page remove old:
// - error page
// - modal backdrop
$('#error-page,.modal-backdrop.show').remove()
$('body').css('padding', 0) // remove padding added by unclosed modal
// Close the mobile menu
if ($('#header-menu .navbar-collapse.show').length) {
$('#header-menu .navbar-toggler').click();
}
})
const app = new Vue({
components: {
AppComponent,
MenuComponent,
},
i18n,
router: window.router,
data() {
return {
authInfo: null,
isUser: !window.isAdmin && !window.isReseller,
appName: window.config['app.name'],
appUrl: window.config['app.url'],
themeDir: '/themes/' + window.config['app.theme']
}
},
methods: {
clearFormValidation,
hasPermission(type) {
const key = 'enable' + type.charAt(0).toUpperCase() + type.slice(1)
return !!(this.authInfo && this.authInfo.statusInfo[key])
},
hasRoute(name) {
return this.$router.resolve({ name: name }).resolved.matched.length > 0
},
hasSKU(name) {
return this.authInfo.statusInfo.skus && this.authInfo.statusInfo.skus.indexOf(name) != -1
},
isController(wallet_id) {
if (wallet_id && this.authInfo) {
let i
for (i = 0; i < this.authInfo.wallets.length; i++) {
if (wallet_id == this.authInfo.wallets[i].id) {
return true
}
}
for (i = 0; i < this.authInfo.accounts.length; i++) {
if (wallet_id == this.authInfo.accounts[i].id) {
return true
}
}
}
return false
},
isDegraded() {
return this.authInfo && this.authInfo.isAccountDegraded
},
// Set user state to "logged in"
loginUser(response, dashboard, update) {
if (!update) {
routerState.isLoggedIn = true
this.authInfo = null
}
localStorage.setItem('token', response.access_token)
localStorage.setItem('refreshToken', response.refresh_token)
axios.defaults.headers.common.Authorization = 'Bearer ' + response.access_token
if (response.email) {
this.authInfo = response
}
if (dashboard !== false) {
this.$router.push(routerState.afterLogin || { name: 'dashboard' })
}
routerState.afterLogin = null
// Refresh the token before it expires
let timeout = response.expires_in || 0
// We'll refresh 60 seconds before the token expires
if (timeout > 60) {
timeout -= 60
}
// TODO: We probably should try a few times in case of an error
// TODO: We probably should prevent axios from doing any requests
// while the token is being refreshed
this.refreshTimeout = setTimeout(() => {
axios.post('api/auth/refresh', { refresh_token: response.refresh_token }).then(response => {
this.loginUser(response.data, false, true)
})
}, timeout * 1000)
},
// Set user state to "not logged in"
logoutUser(redirect) {
routerState.isLoggedIn = true
this.authInfo = null
localStorage.setItem('token', '')
localStorage.setItem('refreshToken', '')
delete axios.defaults.headers.common.Authorization
if (redirect !== false) {
this.$router.push({ name: 'login' })
}
clearTimeout(this.refreshTimeout)
},
logo(mode) {
let src = this.appUrl + this.themeDir + '/images/logo_' + (mode || 'header') + '.png'
return ``
},
pick,
startLoading,
stopLoading,
- tab(e) {
- e.preventDefault()
- new Tab(e.target).show()
- },
errorPage(code, msg, hint) {
// Until https://github.com/vuejs/vue-router/issues/977 is implemented
// we can't really use router to display error page as it has two side
// effects: it changes the URL and adds the error page to browser history.
// For now we'll be replacing current view with error page "manually".
if (!msg) msg = this.$te('error.' + code) ? this.$t('error.' + code) : this.$t('error.unknown')
if (!hint) hint = ''
const error_page = '
'
+ `
${code}
${msg}
${hint}
`
+ '
'
$('#error-page').remove()
$('#app').append(error_page)
app.updateBodyClass('error')
},
errorHandler(error) {
stopLoading()
const status = error.response ? error.response.status : 500
const message = error.response ? error.response.statusText : ''
if (status == 401) {
// Remember requested route to come back to it after log in
if (this.$route.meta.requiresAuth) {
routerState.afterLogin = this.$route
this.logoutUser()
} else {
this.logoutUser(false)
}
} else {
+ if (!error.response) {
+ console.error(error)
+ }
+
this.errorPage(status, message)
}
},
price(price, currency) {
// TODO: Set locale argument according to the currently used locale
return ((price || 0) / 100).toLocaleString('de-DE', { style: 'currency', currency: currency || 'CHF' })
},
priceLabel(cost, discount, currency) {
let index = ''
if (discount) {
cost = Math.floor(cost * ((100 - discount) / 100))
index = '\u00B9'
}
return this.price(cost, currency) + '/' + this.$t('wallet.month') + index
},
clickRecord(event) {
if (!/^(a|button|svg|path)$/i.test(event.target.nodeName)) {
$(event.target).closest('tr').find('a').trigger('click')
}
},
pageName(path) {
let page = this.$route.path
// check if it is a "menu page", find the page name
// otherwise we'll use the real path as page name
window.config.menu.every(item => {
if (item.location == page && item.page) {
page = item.page
return false
}
})
page = page.replace(/^\//, '')
return page ? page : '404'
},
supportDialog(container) {
let dialog = $('#support-dialog')[0]
if (!dialog) {
// FIXME: Find a nicer way of doing this
SupportForm.i18n = i18n
let form = new Vue(SupportForm)
form.$mount($('
').appendTo(container)[0])
form.$root = this
form.$toast = this.$toast
dialog = form.$el
}
dialog.__vue__.show()
},
statusClass(obj) {
if (obj.isDeleted) {
return 'text-muted'
}
if (obj.isDegraded || obj.isAccountDegraded || obj.isSuspended) {
return 'text-warning'
}
if (obj.isImapReady === false || obj.isLdapReady === false || obj.isVerified === false || obj.isConfirmed === false) {
return 'text-danger'
}
return 'text-success'
},
statusText(obj) {
if (obj.isDeleted) {
return this.$t('status.deleted')
}
if (obj.isDegraded || obj.isAccountDegraded) {
return this.$t('status.degraded')
}
if (obj.isSuspended) {
return this.$t('status.suspended')
}
if (obj.isImapReady === false || obj.isLdapReady === false || obj.isVerified === false || obj.isConfirmed === false) {
return this.$t('status.notready')
}
return this.$t('status.active')
},
// Append some wallet properties to the object
userWalletProps(object) {
let wallet = this.authInfo.accounts[0]
if (!wallet) {
wallet = this.authInfo.wallets[0]
}
if (wallet) {
object.currency = wallet.currency
if (wallet.discount) {
object.discount = wallet.discount
object.discount_description = wallet.discount_description
}
}
},
updateBodyClass(name) {
// Add 'class' attribute to the body, different for each page
// so, we can apply page-specific styles
document.body.className = 'page-' + (name || this.pageName()).replace(/\/.*$/, '')
}
}
})
// Fetch the locale file and the start the app
loadLangAsync().then(() => app.$mount('#app'))
// Add a axios request interceptor
axios.interceptors.request.use(
config => {
// This is the only way I found to change configuration options
// on a running application. We need this for browser testing.
config.headers['X-Test-Payment-Provider'] = window.config.paymentProvider
let loader = config.loader
if (loader) {
startLoading(loader)
}
return config
},
error => {
// Do something with request error
return Promise.reject(error)
}
)
// Add a axios response interceptor for general/validation error handler
axios.interceptors.response.use(
response => {
if (response.config.onFinish) {
response.config.onFinish()
}
let loader = response.config.loader
if (loader) {
stopLoading(loader)
}
return response
},
error => {
let loader = error.config.loader
if (loader) {
stopLoading(loader)
}
// Do not display the error in a toast message, pass the error as-is
if (axios.isCancel(error) || error.config.ignoreErrors) {
return Promise.reject(error)
}
if (error.config.onFinish) {
error.config.onFinish()
}
let error_msg
const status = error.response ? error.response.status : 200
const data = error.response ? error.response.data : {}
if (status == 422 && data.errors) {
error_msg = app.$t('error.form')
const modal = $('div.modal.show')
$(modal.length ? modal : 'form').each((i, form) => {
form = $(form)
$.each(data.errors, (idx, msg) => {
const input_name = (form.data('validation-prefix') || form.find('form').first().data('validation-prefix') || '') + idx
let input = form.find('#' + input_name)
if (!input.length) {
input = form.find('[name="' + input_name + '"]');
}
if (input.length) {
// Create an error message
// API responses can use a string, array or object
let msg_text = ''
if (typeof(msg) !== 'string') {
$.each(msg, (index, str) => {
msg_text += str + ' '
})
}
else {
msg_text = msg
}
let feedback = $('
').text(msg_text)
if (input.is('.list-input')) {
// List input widget
let controls = input.children(':not(:first-child)')
if (!controls.length && typeof msg == 'string') {
// this is an empty list (the main input only)
// and the error message is not an array
input.find('.main-input').addClass('is-invalid')
} else {
controls.each((index, element) => {
if (msg[index]) {
$(element).find('input').addClass('is-invalid')
}
})
}
input.addClass('is-invalid').next('.invalid-feedback').remove()
input.after(feedback)
} else {
// a special case, e.g. the invitation policy widget
if (input.is('select') && input.parent().is('.input-group-select.selected')) {
input = input.next()
}
// Standard form element
input.addClass('is-invalid')
input.parent().find('.invalid-feedback').remove()
input.parent().append(feedback)
}
}
})
form.find('.is-invalid:not(.list-input)').first().focus()
})
}
else if (data.status == 'error') {
error_msg = data.message
}
else {
error_msg = error.request ? error.request.statusText : error.message
}
app.$toast.error(error_msg || app.$t('error.server'))
// Pass the error as-is
return Promise.reject(error)
}
)
diff --git a/src/resources/js/bootstrap.js b/src/resources/js/bootstrap.js
index a4436d72..0647aa15 100644
--- a/src/resources/js/bootstrap.js
+++ b/src/resources/js/bootstrap.js
@@ -1,105 +1,106 @@
/**
* Import Cash (jQuery replacement)
*/
import $ from 'cash-dom'
window.$ = $
$.fn.focus = function() {
if (this.length && this[0].focus) {
this[0].focus()
}
return this
}
$.fn.click = function() {
if (this.length && this[0].click) {
this[0].click()
}
return this
}
/**
* Load Vue, VueRouter and global components
*/
import Vue from 'vue'
import VueRouter from 'vue-router'
import Btn from '../vue/Widgets/Btn'
import BtnRouter from '../vue/Widgets/BtnRouter'
+import Tabs from '../vue/Widgets/Tabs'
import Toast from '../vue/Widgets/Toast'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { Tooltip } from 'bootstrap'
window.Vue = Vue
-Vue.component('SvgIcon', FontAwesomeIcon)
Vue.component('Btn', Btn)
Vue.component('BtnRouter', BtnRouter)
+Vue.component('SvgIcon', FontAwesomeIcon)
+Vue.component('Tabs', Tabs)
const vTooltip = (el, binding) => {
let t = []
if (binding.modifiers.focus) t.push('focus')
if (binding.modifiers.hover) t.push('hover')
if (binding.modifiers.click) t.push('click')
if (!t.length) t.push('click')
el.tooltip = new Tooltip(el, {
title: binding.value,
placement: binding.arg || 'top',
trigger: t.join(' '),
html: !!binding.modifiers.html
})
}
Vue.directive('tooltip', {
bind: vTooltip,
update: vTooltip,
unbind (el) {
el.tooltip.dispose()
}
})
Vue.use(Toast)
-
Vue.use(VueRouter)
let vueRouterBase = '/'
try {
let url = new URL(window.config['app.url'])
vueRouterBase = url.pathname
} catch(e) {
// ignore
}
window.router = new VueRouter({
base: vueRouterBase,
mode: 'history',
routes: window.routes,
scrollBehavior (to, from, savedPosition) {
// Scroll the page to top, but not on Back action
return savedPosition || { x: 0, y: 0 }
}
})
/**
* Load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios')
axios.defaults.baseURL = vueRouterBase
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
// Register a few most common icons
import { library } from '@fortawesome/fontawesome-svg-core'
library.add(
require('@fortawesome/free-solid-svg-icons/faCheck').definition,
require('@fortawesome/free-solid-svg-icons/faCircleInfo').definition,
require('@fortawesome/free-solid-svg-icons/faPlus').definition,
require('@fortawesome/free-solid-svg-icons/faMagnifyingGlass').definition,
require('@fortawesome/free-solid-svg-icons/faTrashCan').definition,
require('@fortawesome/free-solid-svg-icons/faUser').definition,
)
diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php
index 2784bf13..0cb827ad 100644
--- a/src/resources/lang/en/ui.php
+++ b/src/resources/lang/en/ui.php
@@ -1,534 +1,534 @@
[
'faq' => "FAQ",
],
'btn' => [
'add' => "Add",
'accept' => "Accept",
'back' => "Back",
'cancel' => "Cancel",
'close' => "Close",
'continue' => "Continue",
'copy' => "Copy",
'delete' => "Delete",
'deny' => "Deny",
'download' => "Download",
'edit' => "Edit",
'file' => "Choose file...",
'moreinfo' => "More information",
'refresh' => "Refresh",
'reset' => "Reset",
'resend' => "Resend",
'save' => "Save",
'search' => "Search",
'share' => "Share",
'signup' => "Sign Up",
'submit' => "Submit",
'suspend' => "Suspend",
'unsuspend' => "Unsuspend",
'verify' => "Verify",
],
'companion' => [
'title' => "Companion App",
'name' => "Name",
'description' => "Use the Companion App on your mobile phone for advanced two factor authentication.",
'pair-new' => "Pair new device",
'paired' => "Paired devices",
'pairing-instructions' => "Pair a new device using the following QR-Code:",
'deviceid' => "Device ID",
'list-empty' => "There are currently no devices",
'delete' => "Remove devices",
'remove-devices' => "Remove Devices",
'remove-devices-text' => "Do you really want to remove all devices permanently?"
. " Please note that this action cannot be undone, and you can only remove all devices together."
. " You may pair devices you would like to keep individually again.",
],
'dashboard' => [
'beta' => "beta",
'distlists' => "Distribution lists",
'chat' => "Video chat",
'companion' => "Companion app",
'domains' => "Domains",
'files' => "Files",
'invitations' => "Invitations",
'profile' => "Your profile",
'resources' => "Resources",
'settings' => "Settings",
'shared-folders' => "Shared folders",
'users' => "User accounts",
'wallet' => "Wallet",
'webmail' => "Webmail",
'stats' => "Stats",
],
'distlist' => [
'list-title' => "Distribution list | Distribution lists",
'create' => "Create list",
'delete' => "Delete list",
'email' => "Email",
'list-empty' => "There are no distribution lists in this account.",
'name' => "Name",
'new' => "New distribution list",
'recipients' => "Recipients",
'sender-policy' => "Sender Access List",
'sender-policy-text' => "With this list you can specify who can send mail to the distribution list."
. " You can put a complete email address (jane@kolab.org), domain (kolab.org) or suffix (.org) that the sender email address is compared to."
. " If the list is empty, mail from anyone is allowed.",
],
'domain' => [
'delete' => "Delete domain",
'delete-domain' => "Delete {domain}",
'delete-text' => "Do you really want to delete this domain permanently?"
. " This is only possible if there are no users, aliases or other objects in this domain."
. " Please note that this action cannot be undone.",
'dns-verify' => "Domain DNS verification sample:",
'dns-config' => "Domain DNS configuration sample:",
'list-empty' => "There are no domains in this account.",
'namespace' => "Namespace",
'spf-whitelist' => "SPF Whitelist",
'spf-whitelist-text' => "The Sender Policy Framework allows a sender domain to disclose, through DNS, "
. "which systems are allowed to send emails with an envelope sender address within said domain.",
'spf-whitelist-ex' => "Here you can specify a list of allowed servers, for example: .ess.barracuda.com.",
'verify' => "Domain verification",
'verify-intro' => "In order to confirm that you're the actual holder of the domain, we need to run a verification process before finally activating it for email delivery.",
'verify-dns' => "The domain must have one of the following entries in DNS:",
'verify-dns-txt' => "TXT entry with value:",
'verify-dns-cname' => "or CNAME entry:",
'verify-outro' => "When this is done press the button below to start the verification.",
'verify-sample' => "Here's a sample zone file for your domain:",
'config' => "Domain configuration",
'config-intro' => "In order to let {app} receive email traffic for your domain you need to adjust the DNS settings, more precisely the MX entries, accordingly.",
'config-sample' => "Edit your domain's zone file and replace existing MX entries with the following values:",
'config-hint' => "If you don't know how to set DNS entries for your domain, please contact the registration service where you registered the domain or your web hosting provider.",
'create' => "Create domain",
'new' => "New domain",
],
'error' => [
'400' => "Bad request",
'401' => "Unauthorized",
'403' => "Access denied",
'404' => "Not found",
'405' => "Method not allowed",
'500' => "Internal server error",
'unknown' => "Unknown Error",
'server' => "Server Error",
'form' => "Form validation error",
],
'file' => [
'create' => "Create file",
'delete' => "Delete file",
'list-empty' => "There are no files in this account.",
'mimetype' => "Mimetype",
'mtime' => "Modified",
'new' => "New file",
'search' => "File name",
'sharing' => "Sharing",
'sharing-links-text' => "You can share the file with other users by giving them read-only access "
. "to the file via a unique link.",
],
'form' => [
'acl' => "Access rights",
'acl-full' => "All",
'acl-read-only' => "Read-only",
'acl-read-write' => "Read-write",
'amount' => "Amount",
'anyone' => "Anyone",
'code' => "Confirmation Code",
'config' => "Configuration",
'date' => "Date",
'description' => "Description",
'details' => "Details",
'disabled' => "disabled",
'domain' => "Domain",
'email' => "Email Address",
'emails' => "Email Addresses",
'enabled' => "enabled",
'firstname' => "First Name",
'general' => "General",
'lastname' => "Last Name",
'name' => "Name",
'months' => "months",
'none' => "none",
'or' => "or",
'password' => "Password",
'password-confirm' => "Confirm Password",
'phone' => "Phone",
'settings' => "Settings",
'shared-folder' => "Shared Folder",
'size' => "Size",
'status' => "Status",
'surname' => "Surname",
'type' => "Type",
'user' => "User",
'primary-email' => "Primary Email",
'id' => "ID",
'created' => "Created",
'deleted' => "Deleted",
],
'invitation' => [
'create' => "Create invite(s)",
'create-title' => "Invite for a signup",
'create-email' => "Enter an email address of the person you want to invite.",
'create-csv' => "To send multiple invitations at once, provide a CSV (comma separated) file, or alternatively a plain-text file, containing one email address per line.",
'list-empty' => "There are no invitations in the database.",
'title' => "Signup invitations",
'search' => "Email address or domain",
'send' => "Send invite(s)",
'status-completed' => "User signed up",
'status-failed' => "Sending failed",
'status-sent' => "Sent",
'status-new' => "Not sent yet",
],
'lang' => [
'en' => "English",
'de' => "German",
'fr' => "French",
'it' => "Italian",
],
'login' => [
'2fa' => "Second factor code",
'2fa_desc' => "Second factor code is optional for users with no 2-Factor Authentication setup.",
'forgot_password' => "Forgot password?",
'header' => "Please sign in",
'sign_in' => "Sign in",
'webmail' => "Webmail"
],
'meet' => [
'title' => "Voice & Video Conferencing",
'welcome' => "Welcome to our beta program for Voice & Video Conferencing.",
'url' => "You have a room of your own at the URL below. This room is only open when you yourself are in attendance. Use this URL to invite people to join you.",
'notice' => "This is a work in progress and more features will be added over time. Current features include:",
'sharing' => "Screen Sharing",
'sharing-text' => "Share your screen for presentations or show-and-tell.",
'security' => "Room Security",
'security-text' => "Increase the room security by setting a password that attendees will need to know"
. " before they can enter, or lock the door so attendees will have to knock, and a moderator can accept or deny those requests.",
'qa-title' => "Raise Hand (Q&A)",
'qa-text' => "Silent audience members can raise their hand to facilitate a Question & Answer session with the panel members.",
'moderation' => "Moderator Delegation",
'moderation-text' => "Delegate moderator authority for the session, so that a speaker is not needlessly"
. " interrupted with attendees knocking and other moderator duties.",
'eject' => "Eject Attendees",
'eject-text' => "Eject attendees from the session in order to force them to reconnect, or address policy"
. " violations. Click the user icon for effective dismissal.",
'silent' => "Silent Audience Members",
'silent-text' => "For a webinar-style session, configure the room to force all new attendees to be silent audience members.",
'interpreters' => "Language Specific Audio Channels",
'interpreters-text' => "Designate a participant to interpret the original audio to a target language, for sessions"
. " with multi-lingual attendees. The interpreter is expected to be able to relay the original audio, and override it.",
'beta-notice' => "Keep in mind that this is still in beta and might come with some issues."
. " Should you encounter any on your way, let us know by contacting support.",
// Room options dialog
'options' => "Room options",
'password' => "Password",
'password-none' => "none",
'password-clear' => "Clear password",
'password-set' => "Set password",
'password-text' => "You can add a password to your meeting. Participants will have to provide the password before they are allowed to join the meeting.",
'lock' => "Locked room",
'lock-text' => "When the room is locked participants have to be approved by a moderator before they could join the meeting.",
'nomedia' => "Subscribers only",
'nomedia-text' => "Forces all participants to join as subscribers (with camera and microphone turned off)."
. " Moderators will be able to promote them to publishers throughout the session.",
// Room menu
'partcnt' => "Number of participants",
'menu-audio-mute' => "Mute audio",
'menu-audio-unmute' => "Unmute audio",
'menu-video-mute' => "Mute video",
'menu-video-unmute' => "Unmute video",
'menu-screen' => "Share screen",
'menu-hand-lower' => "Lower hand",
'menu-hand-raise' => "Raise hand",
'menu-channel' => "Interpreted language channel",
'menu-chat' => "Chat",
'menu-fullscreen' => "Full screen",
'menu-fullscreen-exit' => "Exit full screen",
'menu-leave' => "Leave session",
// Room setup screen
'setup-title' => "Set up your session",
'mic' => "Microphone",
'cam' => "Camera",
'nick' => "Nickname",
'nick-placeholder' => "Your name",
'join' => "JOIN",
'joinnow' => "JOIN NOW",
'imaowner' => "I'm the owner",
// Room
'qa' => "Q & A",
'leave-title' => "Room closed",
'leave-body' => "The session has been closed by the room owner.",
'media-title' => "Media setup",
'join-request' => "Join request",
'join-requested' => "{user} requested to join.",
// Status messages
'status-init' => "Checking the room...",
'status-323' => "The room is closed. Please, wait for the owner to start the session.",
'status-324' => "The room is closed. It will be open for others after you join.",
'status-325' => "The room is ready. Please, provide a valid password.",
'status-326' => "The room is locked. Please, enter your name and try again.",
'status-327' => "Waiting for permission to join the room.",
'status-404' => "The room does not exist.",
'status-429' => "Too many requests. Please, wait.",
'status-500' => "Failed to connect to the room. Server error.",
// Other menus
'media-setup' => "Media setup",
'perm' => "Permissions",
'perm-av' => "Audio & Video publishing",
'perm-mod' => "Moderation",
'lang-int' => "Language interpreter",
'menu-options' => "Options",
],
'menu' => [
'cockpit' => "Cockpit",
'login' => "Login",
'logout' => "Logout",
'signup' => "Signup",
'toggle' => "Toggle navigation",
],
'msg' => [
'initializing' => "Initializing...",
'loading' => "Loading...",
'loading-failed' => "Failed to load data.",
'notfound' => "Resource not found.",
'info' => "Information",
'error' => "Error",
'uploading' => "Uploading...",
'warning' => "Warning",
'success' => "Success",
],
'nav' => [
'more' => "Load more",
'step' => "Step {i}/{n}",
],
'password' => [
'link-invalid' => "The password reset code is expired or invalid.",
'reset' => "Password Reset",
'reset-step1' => "Enter your email address to reset your password.",
'reset-step1-hint' => "You may need to check your spam folder or unblock {email}.",
'reset-step2' => "We sent out a confirmation code to your external email address."
. " Enter the code we sent you, or click the link in the message.",
],
'resource' => [
'create' => "Create resource",
'delete' => "Delete resource",
'invitation-policy' => "Invitation policy",
'invitation-policy-text' => "Event invitations for a resource are normally accepted automatically"
. " if there is no conflicting event on the requested time slot. Invitation policy allows"
. " for rejecting such requests or to require a manual acceptance from a specified user.",
'ipolicy-manual' => "Manual (tentative)",
'ipolicy-accept' => "Accept",
'ipolicy-reject' => "Reject",
'list-title' => "Resource | Resources",
'list-empty' => "There are no resources in this account.",
'new' => "New resource",
],
'settings' => [
'password-policy' => "Password Policy",
'password-retention' => "Password Retention",
'password-max-age' => "Require a password change every",
],
'shf' => [
'aliases-none' => "This shared folder has no email aliases.",
'create' => "Create folder",
'delete' => "Delete folder",
'acl-text' => "Defines user permissions to access the shared folder.",
'list-title' => "Shared folder | Shared folders",
'list-empty' => "There are no shared folders in this account.",
'new' => "New shared folder",
'type-mail' => "Mail",
'type-event' => "Calendar",
'type-contact' => "Address Book",
'type-task' => "Tasks",
'type-note' => "Notes",
'type-file' => "Files",
],
'signup' => [
'email' => "Existing Email Address",
'login' => "Login",
'title' => "Sign Up",
'step1' => "Sign up to start your free month.",
'step2' => "We sent out a confirmation code to your email address. Enter the code we sent you, or click the link in the message.",
'step3' => "Create your Kolab identity (you can choose additional addresses later).",
'voucher' => "Voucher Code",
],
'status' => [
'prepare-account' => "We are preparing your account.",
'prepare-domain' => "We are preparing the domain.",
'prepare-distlist' => "We are preparing the distribution list.",
'prepare-resource' => "We are preparing the resource.",
'prepare-shared-folder' => "We are preparing the shared folder.",
'prepare-user' => "We are preparing the user account.",
'prepare-hint' => "Some features may be missing or readonly at the moment.",
'prepare-refresh' => "The process never ends? Press the \"Refresh\" button, please.",
'ready-account' => "Your account is almost ready.",
'ready-domain' => "The domain is almost ready.",
'ready-distlist' => "The distribution list is almost ready.",
'ready-resource' => "The resource is almost ready.",
'ready-shared-folder' => "The shared-folder is almost ready.",
'ready-user' => "The user account is almost ready.",
'verify' => "Verify your domain to finish the setup process.",
'verify-domain' => "Verify domain",
'degraded' => "Degraded",
'deleted' => "Deleted",
'suspended' => "Suspended",
'notready' => "Not Ready",
'active' => "Active",
],
'support' => [
'title' => "Contact Support",
'id' => "Customer number or email address you have with us",
'id-pl' => "e.g. 12345678 or john@kolab.org",
'id-hint' => "Leave blank if you are not a customer yet",
'name' => "Name",
'name-pl' => "how we should call you in our reply",
'email' => "Working email address",
'email-pl' => "make sure we can reach you at this address",
'summary' => "Issue Summary",
'summary-pl' => "one sentence that summarizes your issue",
'expl' => "Issue Explanation",
],
'user' => [
'2fa-hint1' => "This will remove 2-Factor Authentication entitlement as well as the user-configured factors.",
'2fa-hint2' => "Please, make sure to confirm the user identity properly.",
'add-beta' => "Enable beta program",
'address' => "Address",
'aliases' => "Aliases",
- 'aliases-email' => "Email Aliases",
'aliases-none' => "This user has no email aliases.",
'add-bonus' => "Add bonus",
'add-bonus-title' => "Add a bonus to the wallet",
'add-penalty' => "Add penalty",
'add-penalty-title' => "Add a penalty to the wallet",
'auto-payment' => "Auto-payment",
'auto-payment-text' => "Fill up by {amount} when under {balance} using {method}",
'country' => "Country",
'create' => "Create user",
'custno' => "Customer No.",
'degraded-warning' => "The account is degraded. Some features have been disabled.",
'degraded-hint' => "Please, make a payment.",
'delete' => "Delete user",
'delete-account' => "Delete this account?",
'delete-email' => "Delete {email}",
'delete-text' => "Do you really want to delete this user permanently?"
. " This will delete all account data and withdraw the permission to access the email account."
. " Please note that this action cannot be undone.",
'discount' => "Discount",
'discount-hint' => "applied discount",
'discount-title' => "Account discount",
'distlists' => "Distribution lists",
'domains' => "Domains",
'ext-email' => "External Email",
+ 'email-aliases' => "Email Aliases",
'finances' => "Finances",
'greylisting' => "Greylisting",
'greylisting-text' => "Greylisting is a method of defending users against spam. Any incoming mail from an unrecognized sender "
. "is temporarily rejected. The originating server should try again after a delay. "
. "This time the email will be accepted. Spammers usually do not reattempt mail delivery.",
'list-title' => "User accounts",
'list-empty' => "There are no users in this account.",
'managed-by' => "Managed by",
'new' => "New user account",
'org' => "Organization",
'package' => "Package",
'pass-input' => "Enter password",
'pass-link' => "Set via link",
'pass-link-label' => "Link:",
'pass-link-hint' => "Press Submit to activate the link",
'passwordpolicy' => "Password Policy",
'price' => "Price",
'profile-title' => "Your profile",
'profile-delete' => "Delete account",
'profile-delete-title' => "Delete this account?",
'profile-delete-text1' => "This will delete the account as well as all domains, users and aliases associated with this account.",
'profile-delete-warning' => "This operation is irreversible",
'profile-delete-text2' => "As you will not be able to recover anything after this point, please make sure that you have migrated all data before proceeding.",
'profile-delete-support' => "As we always strive to improve, we would like to ask for 2 minutes of your time. "
. "The best tool for improvement is feedback from users, and we would like to ask "
. "for a few words about your reasons for leaving our service. Please send your feedback to {email}.",
'profile-delete-contact' => "Also feel free to contact {app} Support with any questions or concerns that you may have in this context.",
'reset-2fa' => "Reset 2-Factor Auth",
'reset-2fa-title' => "2-Factor Authentication Reset",
'resources' => "Resources",
'title' => "User account",
'search' => "User email address or name",
'search-pl' => "User ID, email or domain",
'skureq' => "{sku} requires {list}.",
'subscription' => "Subscription",
'subscriptions' => "Subscriptions",
'subscriptions-none' => "This user has no subscriptions.",
'users' => "Users",
],
'wallet' => [
'add-credit' => "Add credit",
'auto-payment-cancel' => "Cancel auto-payment",
'auto-payment-change' => "Change auto-payment",
'auto-payment-failed' => "The setup of automatic payments failed. Restart the process to enable automatic top-ups.",
'auto-payment-hint' => "Here is how it works: Every time your account runs low, we will charge your preferred payment method for an amount you choose."
. " You can cancel or change the auto-payment option at any time.",
'auto-payment-setup' => "Set up auto-payment",
'auto-payment-disabled' => "The configured auto-payment has been disabled. Top up your wallet or raise the auto-payment amount.",
'auto-payment-info' => "Auto-payment is set to fill up your account by {amount} every time your account balance gets under {balance}.",
'auto-payment-inprogress' => "The setup of the automatic payment is still in progress.",
'auto-payment-next' => "Next, you will be redirected to the checkout page, where you can provide your credit card details.",
'auto-payment-disabled-next' => "The auto-payment is disabled. Immediately after you submit new settings we'll enable it and attempt to top up your wallet.",
'auto-payment-update' => "Update auto-payment",
'banktransfer-hint' => "Please note that a bank transfer can take several days to complete.",
'currency-conv' => "Here is how it works: You specify the amount by which you want to top up your wallet in {wc}."
. " We will then convert this to {pc}, and on the next page you will be provided with the bank-details to transfer the amount in {pc}.",
'fill-up' => "Fill up by",
'history' => "History",
'month' => "month",
'noperm' => "Only account owners can access a wallet.",
'payment-amount-hint' => "Choose the amount by which you want to top up your wallet.",
'payment-method' => "Method of payment: {method}",
'payment-warning' => "You will be charged for {price}.",
'pending-payments' => "Pending Payments",
'pending-payments-warning' => "You have payments that are still in progress. See the \"Pending Payments\" tab below.",
'pending-payments-none' => "There are no pending payments for this account.",
'receipts' => "Receipts",
'receipts-hint' => "Here you can download receipts (in PDF format) for payments in specified period. Select the period and press the Download button.",
'receipts-none' => "There are no receipts for payments in this account. Please, note that you can download receipts after the month ends.",
'title' => "Account balance",
'top-up' => "Top up your wallet",
'transactions' => "Transactions",
'transactions-none' => "There are no transactions for this account.",
'when-below' => "when account balance is below",
],
];
diff --git a/src/resources/lang/fr/ui.php b/src/resources/lang/fr/ui.php
index 74ba9a51..2a6db372 100644
--- a/src/resources/lang/fr/ui.php
+++ b/src/resources/lang/fr/ui.php
@@ -1,482 +1,482 @@
[
'faq' => "FAQ",
],
'btn' => [
'add' => "Ajouter",
'accept' => "Accepter",
'back' => "Back",
'cancel' => "Annuler",
'close' => "Fermer",
'continue' => "Continuer",
'delete' => "Supprimer",
'deny' => "Refuser",
'download' => "Télécharger",
'edit' => "Modifier",
'file' => "Choisir le ficher...",
'moreinfo' => "Plus d'information",
'refresh' => "Actualiser",
'reset' => "Réinitialiser",
'resend' => "Envoyer à nouveau",
'save' => "Sauvegarder",
'search' => "Chercher",
'signup' => "S'inscrire",
'submit' => "Soumettre",
'suspend' => "Suspendre",
'unsuspend' => "Débloquer",
'verify' => "Vérifier",
],
'dashboard' => [
'beta' => "bêta",
'distlists' => "Listes de distribution",
'chat' => "Chat Vidéo",
'domains' => "Domaines",
'invitations' => "Invitations",
'profile' => "Votre profil",
'resources' => "Ressources",
'users' => "D'utilisateurs",
'wallet' => "Portefeuille",
'webmail' => "Webmail",
'stats' => "Statistiques",
],
'distlist' => [
'list-title' => "Liste de distribution | Listes de Distribution",
'create' => "Créer une liste",
'delete' => "Suprimmer une list",
'email' => "Courriel",
'list-empty' => "il n'y a pas de listes de distribution dans ce compte.",
'name' => "Nom",
'new' => "Nouvelle liste de distribution",
'recipients' => "Destinataires",
'sender-policy' => "Liste d'Accès d'Expéditeur",
'sender-policy-text' => "Cette liste vous permet de spécifier qui peut envoyer du courrier à la liste de distribution."
. " Vous pouvez mettre une adresse e-mail complète (jane@kolab.org), un domaine (kolab.org) ou un suffixe (.org)"
. " auquel l'adresse électronique de l'expéditeur est assimilée."
. " Si la liste est vide, le courriels de quiconque est autorisé."
],
'domain' => [
'dns-verify' => "Exemple de vérification du DNS d'un domaine:",
'dns-config' => "Exemple de configuration du DNS d'un domaine:",
'list-empty' => "Il y a pas de domaines dans ce compte.",
'namespace' => "Espace de noms",
'verify' => "Vérification du domaine",
'verify-intro' => "Afin de confirmer que vous êtes bien le titulaire du domaine, nous devons exécuter un processus de vérification avant de l'activer définitivement pour la livraison d'e-mails.",
'verify-dns' => "Le domaine doit avoir l'une des entrées suivantes dans le DNS:",
'verify-dns-txt' => "Entrée TXT avec valeur:",
'verify-dns-cname' => "ou entrée CNAME:",
'verify-outro' => "Lorsque cela est fait, appuyez sur le bouton ci-dessous pour lancer la vérification.",
'verify-sample' => "Voici un fichier de zone simple pour votre domaine:",
'config' => "Configuration du domaine",
'config-intro' => "Afin de permettre à {app} de recevoir le trafic de messagerie pour votre domaine, vous devez ajuster les paramètres DNS, plus précisément les entrées MX, en conséquence.",
'config-sample' => "Modifiez le fichier de zone de votre domaine et remplacez les entrées MX existantes par les valeurs suivantes:",
'config-hint' => "Si vous ne savez pas comment définir les entrées DNS pour votre domaine, veuillez contacter le service d'enregistrement auprès duquel vous avez enregistré le domaine ou votre fournisseur d'hébergement Web.",
'spf-whitelist' => "SPF Whitelist",
'spf-whitelist-text' => "Le Sender Policy Framework permet à un domaine expéditeur de dévoiler, par le biais de DNS,"
. " quels systèmes sont autorisés à envoyer des e-mails avec une adresse d'expéditeur d'enveloppe dans le domaine en question.",
'spf-whitelist-ex' => "Vous pouvez ici spécifier une liste de serveurs autorisés, par exemple: .ess.barracuda.com.",
'create' => "Créer domaine",
'new' => "Nouveau domaine",
'delete' => "Supprimer domaine",
'delete-domain' => "Supprimer {domain}",
'delete-text' => "Voulez-vous vraiment supprimer ce domaine de façon permanente?"
. " Ceci n'est possible que s'il n'y a pas d'utilisateurs, d'alias ou d'autres objets dans ce domaine."
. " Veuillez noter que cette action ne peut pas être inversée.",
],
'error' => [
'400' => "Mauvaide demande",
'401' => "Non autorisé",
'403' => "Accès refusé",
'404' => "Pas trouvé",
'405' => "Méthode non autorisée",
'500' => "Erreur de serveur interne",
'unknown' => "Erreur inconnu",
'server' => "Erreur de serveur",
'form' => "Erreur de validation du formulaire",
],
'form' => [
'acl' => "Droits d'accès",
'acl-full' => "Tout",
'acl-read-only' => "Lecture seulement",
'acl-read-write' => "Lecture-écriture",
'amount' => "Montant",
'anyone' => "Chacun",
'code' => "Le code de confirmation",
'config' => "Configuration",
'date' => "Date",
'description' => "Description",
'details' => "Détails",
'domain' => "Domaine",
'email' => "Adresse e-mail",
'firstname' => "Prénom",
'lastname' => "Nom de famille",
'none' => "aucun",
'or' => "ou",
'password' => "Mot de passe",
'password-confirm' => "Confirmer le mot de passe",
'phone' => "Téléphone",
'shared-folder' => "Dossier partagé",
'status' => "État",
'surname' => "Nom de famille",
'type' => "Type",
'user' => "Utilisateur",
'primary-email' => "Email principal",
'id' => "ID",
'created' => "Créé",
'deleted' => "Supprimé",
'disabled' => "Désactivé",
'enabled' => "Activé",
'general' => "Général",
'settings' => "Paramètres",
],
'invitation' => [
'create' => "Créez des invitation(s)",
'create-title' => "Invitation à une inscription",
'create-email' => "Saisissez l'adresse électronique de la personne que vous souhaitez inviter.",
'create-csv' => "Pour envoyer plusieurs invitations à la fois, fournissez un fichier CSV (séparé par des virgules) ou un fichier en texte brut, contenant une adresse e-mail par ligne.",
'list-empty' => "Il y a aucune invitation dans la mémoire de données.",
'title' => "Invitation d'inscription",
'search' => "Adresse E-mail ou domaine",
'send' => "Envoyer invitation(s)",
'status-completed' => "Utilisateur s'est inscrit",
'status-failed' => "L'envoi a échoué",
'status-sent' => "Envoyé",
'status-new' => "Pas encore envoyé",
],
'lang' => [
'en' => "Anglais",
'de' => "Allemand",
'fr' => "Français",
'it' => "Italien",
],
'login' => [
'2fa' => "Code du 2ème facteur",
'2fa_desc' => "Le code du 2ème facteur est facultatif pour les utilisateurs qui n'ont pas configuré l'authentification à deux facteurs.",
'forgot_password' => "Mot de passe oublié?",
'header' => "Veuillez vous connecter",
'sign_in' => "Se connecter",
'webmail' => "Webmail"
],
'meet' => [
'title' => "Voix et vidéo-conférence",
'welcome' => "Bienvenue dans notre programme bêta pour les conférences vocales et vidéo.",
'url' => "Vous disposez d'une salle avec l'URL ci-dessous. Cette salle ouvre uniquement quand vous y êtes vous-même. Utilisez cette URL pour inviter des personnes à vous rejoindre.",
'notice' => "Il s'agit d'un travail en évolution et d'autres fonctions seront ajoutées au fil du temps. Les fonctions actuelles sont les suivantes:",
'sharing' => "Partage d'écran",
'sharing-text' => "Partagez votre écran pour des présentations ou des exposés.",
'security' => "sécurité de chambre",
'security-text' => "Renforcez la sécurité de la salle en définissant un mot de passe que les participants devront connaître."
. " avant de pouvoir entrer, ou verrouiller la porte afin que les participants doivent frapper, et un modérateur peut accepter ou refuser ces demandes.",
'qa-title' => "Lever la main (Q&A)",
'qa-text' => "Les membres du public silencieux peuvent lever la main pour animer une séance de questions-réponses avec les membres du panel.",
'moderation' => "Délégation des Modérateurs",
'moderation-text' => "Déléguer l'autorité du modérateur pour la séance, afin qu'un orateur ne soit pas inutilement"
. " interrompu par l'arrivée des participants et d'autres tâches du modérateur.",
'eject' => "Éjecter les participants",
'eject-text' => "Éjectez les participants de la session afin de les obliger à se reconnecter ou de remédier aux violations des règles."
. " Cliquez sur l'icône de l'utilisateur pour un renvoi effectif.",
'silent' => "Membres du Public en Silence",
'silent-text' => "Pour une séance de type webinaire, configurez la salle pour obliger tous les nouveaux participants à être des spectateurs silencieux.",
'interpreters' => "Canaux d'Audio Spécifiques de Langues",
'interpreters-text' => "Désignez un participant pour interpréter l'audio original dans une langue cible, pour les sessions avec des participants multilingues."
. " L'interprète doit être capable de relayer l'audio original et de le remplacer.",
'beta-notice' => "Rappelez-vous qu'il s'agit d'une version bêta et pourrait entraîner des problèmes."
. " Au cas où vous rencontreriez des problèmes, n'hésitez pas à nous en faire part en contactant le support.",
// Room options dialog
'options' => "Options de salle",
'password' => "Mot de passe",
'password-none' => "aucun",
'password-clear' => "Effacer mot de passe",
'password-set' => "Définir le mot de passe",
'password-text' => "Vous pouvez ajouter un mot de passe à votre session. Les participants devront fournir le mot de passe avant d'être autorisés à rejoindre la session.",
'lock' => "Salle verrouillée",
'lock-text' => "Lorsque la salle est verrouillée, les participants doivent être approuvés par un modérateur avant de pouvoir rejoindre la réunion.",
'nomedia' => "Réservé aux abonnés",
'nomedia-text' => "Force tous les participants à se joindre en tant qu'abonnés (avec caméra et microphone désactivés)"
. "Les modérateurs pourront les promouvoir en tant qu'éditeurs tout au long de la session.",
// Room menu
'partcnt' => "Nombres de participants",
'menu-audio-mute' => "Désactiver le son",
'menu-audio-unmute' => "Activer le son",
'menu-video-mute' => "Désactiver la vidéo",
'menu-video-unmute' => "Activer la vidéo",
'menu-screen' => "Partager l'écran",
'menu-hand-lower' => "Baisser la main",
'menu-hand-raise' => "Lever la main",
'menu-channel' => "Canal de langue interprétée",
'menu-chat' => "Le Chat",
'menu-fullscreen' => "Plein écran",
'menu-fullscreen-exit' => "Sortir en plein écran",
'menu-leave' => "Quitter la session",
// Room setup screen
'setup-title' => "Préparez votre session",
'mic' => "Microphone",
'cam' => "Caméra",
'nick' => "Surnom",
'nick-placeholder' => "Votre nom",
'join' => "JOINDRE",
'joinnow' => "JOINDRE MAINTENANT",
'imaowner' => "Je suis le propriétaire",
// Room
'qa' => "Q & A",
'leave-title' => "Salle fermée",
'leave-body' => "La session a été fermée par le propriétaire de la salle.",
'media-title' => "Configuration des médias",
'join-request' => "Demande de rejoindre",
'join-requested' => "{user} demandé à rejoindre.",
// Status messages
'status-init' => "Vérification de la salle...",
'status-323' => "La salle est fermée. Veuillez attendre le démarrage de la session par le propriétaire.",
'status-324' => "La salle est fermée. Elle sera ouverte aux autres participants après votre adhésion.",
'status-325' => "La salle est prête. Veuillez entrer un mot de passe valide.",
'status-326' => "La salle est fermée. Veuillez entrer votre nom et réessayer.",
'status-327' => "En attendant la permission de joindre la salle.",
'status-404' => "La salle n'existe pas.",
'status-429' => "Trop de demande. Veuillez, patienter.",
'status-500' => "La connexion à la salle a échoué. Erreur de serveur.",
// Other menus
'media-setup' => "configuration des médias",
'perm' => "Permissions",
'perm-av' => "Publication d'audio et vidéo",
'perm-mod' => "Modération",
'lang-int' => "Interprète de langue",
'menu-options' => "Options",
],
'menu' => [
'cockpit' => "Cockpit",
'login' => "Connecter",
'logout' => "Deconnecter",
'signup' => "S'inscrire",
'toggle' => "Basculer la navigation",
],
'msg' => [
'initializing' => "Initialisation...",
'loading' => "Chargement...",
'loading-failed' => "Échec du chargement des données.",
'notfound' => "Resource introuvable.",
'info' => "Information",
'error' => "Erreur",
'warning' => "Avertissement",
'success' => "Succès",
],
'nav' => [
'more' => "Charger plus",
'step' => "Étape {i}/{n}",
],
'password' => [
'reset' => "Réinitialiser le mot de passe",
'reset-step1' => "Entrez votre adresse e-mail pour réinitialiser votre mot de passe.",
'reset-step1-hint' => "Veuillez vérifier votre dossier de spam ou débloquer {email}.",
'reset-step2' => "Nous avons envoyé un code de confirmation à votre adresse e-mail externe."
. " Entrez le code que nous vous avons envoyé, ou cliquez sur le lien dans le message.",
],
'resource' => [
'create' => "Créer une ressource",
'delete' => "Supprimer une ressource",
'invitation-policy' => "Procédure d'invitation",
'invitation-policy-text' => "Les invitations à des événements pour une ressource sont généralement acceptées automatiquement"
. " si aucun événement n'est en conflit avec le temps demandé. La procédure d'invitation le permet"
. " de rejeter ces demandes ou d'exiger une acceptation manuelle d'un utilisateur spécifique.",
'ipolicy-manual' => "Manuel (provisoire)",
'ipolicy-accept' => "Accepter",
'ipolicy-reject' => "Rejecter",
'list-title' => "Ressource | Ressources",
'list-empty' => "Il y a aucune ressource sur ce compte.",
'new' => "Nouvelle ressource",
],
'shf' => [
'create' => "Créer un dossier",
'delete' => "Supprimer un dossier",
'acl-text' => "Permet de définir les droits d'accès des utilisateurs au dossier partagé..",
'list-title' => "Dossier partagé | Dossiers partagés",
'list-empty' => "Il y a aucun dossier partagé dans ce compte.",
'new' => "Nouvelle dossier",
'type-mail' => "Courriel",
'type-event' => "Calendrier",
'type-contact' => "Carnet d'Adresses",
'type-task' => "Tâches",
'type-note' => "Notes",
'type-file' => "Fichiers",
],
'signup' => [
'email' => "Adresse e-mail actuelle",
'login' => "connecter",
'title' => "S'inscrire",
'step1' => "Inscrivez-vous pour commencer votre mois gratuit.",
'step2' => "Nous avons envoyé un code de confirmation à votre adresse e-mail. Entrez le code que nous vous avons envoyé, ou cliquez sur le lien dans le message.",
'step3' => "Créez votre identité Kolab (vous pourrez choisir des adresses supplémentaires plus tard).",
'voucher' => "Coupon Code",
],
'status' => [
'prepare-account' => "Votre compte est en cours de préparation.",
'prepare-domain' => "Le domain est en cours de préparation.",
'prepare-distlist' => "La liste de distribution est en cours de préparation.",
'prepare-shared-folder' => "Le dossier portagé est en cours de préparation.",
'prepare-user' => "Le compte d'utilisateur est en cours de préparation.",
'prepare-hint' => "Certaines fonctionnalités peuvent être manquantes ou en lecture seule pour le moment.",
'prepare-refresh' => "Le processus ne se termine jamais? Appuyez sur le bouton \"Refresh\", s'il vous plaît.",
'prepare-resource' => "Nous préparons la ressource.",
'ready-account' => "Votre compte est presque prêt.",
'ready-domain' => "Le domaine est presque prêt.",
'ready-distlist' => "La liste de distribution est presque prête.",
'ready-resource' => "La ressource est presque prête.",
'ready-shared-folder' => "Le dossier partagé est presque prêt.",
'ready-user' => "Le compte d'utilisateur est presque prêt.",
'verify' => "Veuillez vérifier votre domaine pour terminer le processus de configuration.",
'verify-domain' => "Vérifier domaine",
'degraded' => "Dégradé",
'deleted' => "Supprimé",
'suspended' => "Suspendu",
'notready' => "Pas Prêt",
'active' => "Actif",
],
'support' => [
'title' => "Contacter Support",
'id' => "Numéro de client ou adresse é-mail que vous avez chez nous.",
'id-pl' => "e.g. 12345678 ou john@kolab.org",
'id-hint' => "Laissez vide si vous n'êtes pas encore client",
'name' => "Nom",
'name-pl' => "comment nous devons vous adresser dans notre réponse",
'email' => "adresse e-mail qui fonctionne",
'email-pl' => "assurez-vous que nous pouvons vous atteindre à cette adresse",
'summary' => "Résumé du problème",
'summary-pl' => "une phrase qui résume votre situation",
'expl' => "Analyse du problème",
],
'user' => [
'2fa-hint1' => "Cela éliminera le droit à l'authentification à 2-Facteurs ainsi que les éléments configurés par l'utilisateur.",
'2fa-hint2' => "Veuillez vous assurer que l'identité de l'utilisateur est correctement confirmée.",
'add-beta' => "Activer le programme bêta",
'address' => "Adresse",
'aliases' => "Alias",
- 'aliases-email' => "Alias E-mail",
'aliases-none' => "Cet utilisateur n'aucune alias e-mail.",
'add-bonus' => "Ajouter un bonus",
'add-bonus-title' => "Ajouter un bonus au portefeuille",
'add-penalty' => "Ajouter une pénalité",
'add-penalty-title' => "Ajouter une pénalité au portefeuille",
'auto-payment' => "Auto-paiement",
'auto-payment-text' => "Recharger par {amount} quand le montant est inférieur à {balance} utilisant {method}",
'country' => "Pays",
'create' => "Créer un utilisateur",
'custno' => "No. de Client.",
'degraded-warning' => "Le compte est dégradé. Certaines fonctionnalités ont été désactivées.",
'degraded-hint' => "Veuillez effectuer un paiement.",
'delete' => "Supprimer Utilisateur",
'delete-email' => "Supprimer {email}",
'delete-text' => "Voulez-vous vraiment supprimer cet utilisateur de façon permanente?"
. " Cela supprimera toutes les données du compte et retirera la permission d'accéder au compte d'e-email."
. " Veuillez noter que cette action ne peut pas être révoquée.",
'discount' => "Rabais",
'discount-hint' => "rabais appliqué",
'discount-title' => "Rabais de compte",
'distlists' => "Listes de Distribution",
'domains' => "Domaines",
+ 'email-aliases' => "Alias E-mail",
'ext-email' => "E-mail externe",
'finances' => "Finances",
'greylisting' => "Greylisting",
'greylisting-text' => "La greylisting est une méthode de défense des utilisateurs contre le spam."
. " Tout e-mail entrant provenant d'un expéditeur non reconnu est temporairement rejeté."
. " Le serveur d'origine doit réessayer après un délai cette fois-ci, le mail sera accepté."
. " Les spammeurs ne réessayent généralement pas de remettre le mail.",
'list-title' => "Comptes d'utilisateur",
'list-empty' => "Il n'y a aucun utilisateur dans ce compte.",
'managed-by' => "Géré par",
'new' => "Nouveau compte d'utilisateur",
'org' => "Organisation",
'package' => "Paquet",
'price' => "Prix",
'profile-title' => "Votre profile",
'profile-delete' => "Supprimer compte",
'profile-delete-title' => "Supprimer ce compte?",
'profile-delete-text1' => "Cela supprimera le compte ainsi que tous les domaines, utilisateurs et alias associés à ce compte.",
'profile-delete-warning' => "Cette opération est irrévocable",
'profile-delete-text2' => "Comme vous ne pourrez plus rien récupérer après ce point, assurez-vous d'avoir migré toutes les données avant de poursuivre.",
'profile-delete-support' => "Étant donné que nous nous attachons à toujours nous améliorer, nous aimerions vous demander 2 minutes de votre temps. "
. "Le meilleur moyen de nous améliorer est le feedback des utilisateurs, et nous voudrions vous demander"
. "quelques mots sur les raisons pour lesquelles vous avez quitté notre service. Veuillez envoyer vos commentaires au {email}.",
'profile-delete-contact' => "Par ailleurs, n'hésitez pas à contacter le support de {app} pour toute question ou souci que vous pourriez avoir dans ce contexte.",
'reset-2fa' => "Réinitialiser l'authentification à 2-Facteurs.",
'reset-2fa-title' => "Réinitialisation de l'Authentification à 2-Facteurs",
'resources' => "Ressources",
'title' => "Compte d'utilisateur",
'search' => "Adresse e-mail ou nom de l'utilisateur",
'search-pl' => "ID utilisateur, e-mail ou domamine",
'skureq' => "{sku} demande {list}.",
'subscription' => "Subscription",
'subscriptions' => "Subscriptions",
'subscriptions-none' => "Cet utilisateur n'a pas de subscriptions.",
'users' => "Utilisateurs",
],
'wallet' => [
'add-credit' => "Ajouter un crédit",
'auto-payment-cancel' => "Annuler l'auto-paiement",
'auto-payment-change' => "Changer l'auto-paiement",
'auto-payment-failed' => "La configuration des paiements automatiques a échoué. Redémarrer le processus pour activer les top-ups automatiques.",
'auto-payment-hint' => "Cela fonctionne de la manière suivante: Chaque fois que votre compte est épuisé, nous débiterons votre méthode de paiement préférée d'un montant que vous aurez défini."
. " Vous pouvez annuler ou modifier l'option de paiement automatique à tout moment.",
'auto-payment-setup' => "configurer l'auto-paiement",
'auto-payment-disabled' => "L'auto-paiement configuré a été désactivé. Rechargez votre porte-monnaie ou augmentez le montant d'auto-paiement.",
'auto-payment-info' => "L'auto-paiement est set pour recharger votre compte par {amount} lorsque le solde de votre compte devient inférieur à {balance}.",
'auto-payment-inprogress' => "La configuration d'auto-paiement est toujours en cours.",
'auto-payment-next' => "Ensuite, vous serez redirigé vers la page de paiement, où vous pourrez fournir les coordonnées de votre carte de crédit.",
'auto-payment-disabled-next' => "L'auto-paiement est désactivé. Dès que vous aurez soumis de nouveaux paramètres, nous l'activerons et essaierons de recharger votre portefeuille.",
'auto-payment-update' => "Mise à jour de l'auto-paiement.",
'banktransfer-hint' => "Veuillez noter qu'un virement bancaire peut nécessiter plusieurs jours avant d'être effectué.",
'currency-conv' => "Le principe est le suivant: Vous spécifiez le montant dont vous voulez recharger votre portefeuille en {wc}."
. " Nous convertirons ensuite ce montant en {pc}, et sur la page suivante, vous obtiendrez les coordonnées bancaires pour transférer le montant en {pc}.",
'fill-up' => "Recharger par",
'history' => "Histoire",
'month' => "mois",
'noperm' => "Seuls les propriétaires de compte peuvent accéder à un portefeuille.",
'payment-amount-hint' => "Choisissez le montant dont vous voulez recharger votre portefeuille.",
'payment-method' => "Mode de paiement: {method}",
'payment-warning' => "Vous serez facturé pour {price}.",
'pending-payments' => "Paiements en attente",
'pending-payments-warning' => "Vous avez des paiements qui sont encore en cours. Voir l'onglet \"Paiements en attente\" ci-dessous.",
'pending-payments-none' => "Il y a aucun paiement en attente pour ce compte.",
'receipts' => "Reçus",
'receipts-hint' => "Vous pouvez télécharger ici les reçus (au format PDF) pour les paiements de la période spécifiée. Sélectionnez la période et appuyez sur le bouton Télécharger.",
'receipts-none' => "Il y a aucun reçu pour les paiements de ce compte. Veuillez noter que vous pouvez télécharger les reçus après la fin du mois.",
'title' => "Solde du compte",
'top-up' => "Rechargez votre portefeuille",
'transactions' => "Transactions",
'transactions-none' => "Il y a aucun transaction pour ce compte.",
'when-below' => "lorsque le solde du compte est inférieur à",
],
];
diff --git a/src/resources/vue/Admin/Distlist.vue b/src/resources/vue/Admin/Distlist.vue
index 76477f43..f7721947 100644
--- a/src/resources/vue/Admin/Distlist.vue
+++ b/src/resources/vue/Admin/Distlist.vue
@@ -1,113 +1,107 @@