summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Api/Swimlane.php12
-rw-r--r--app/Auth/Ldap.php422
-rw-r--r--app/Auth/ReverseProxy.php1
-rw-r--r--app/Controller/Board.php12
-rw-r--r--app/Controller/Budget.php135
-rw-r--r--app/Controller/Doc.php22
-rw-r--r--app/Controller/File.php38
-rw-r--r--app/Controller/Hourlyrate.php89
-rw-r--r--app/Controller/Swimlane.php6
-rw-r--r--app/Controller/Taskcreation.php36
-rw-r--r--app/Controller/Taskmodification.php28
-rw-r--r--app/Core/Base.php2
-rw-r--r--app/Core/ObjectStorage/FileStorage.php150
-rw-r--r--app/Core/ObjectStorage/ObjectStorageException.php9
-rw-r--r--app/Core/ObjectStorage/ObjectStorageInterface.php68
-rw-r--r--app/Core/PluginBase.php47
-rw-r--r--app/Core/PluginLoader.php137
-rw-r--r--app/Core/Response.php8
-rw-r--r--app/Core/Router.php46
-rw-r--r--app/Core/Template.php52
-rw-r--r--app/Core/Tool.php101
-rw-r--r--app/Core/Translator.php23
-rw-r--r--app/Helper/App.php2
-rw-r--r--app/Helper/Hook.php49
-rw-r--r--app/Integration/Mailgun.php4
-rw-r--r--app/Integration/Postmark.php5
-rw-r--r--app/Integration/Sendgrid.php4
-rw-r--r--app/Locale/cs_CZ/translations.php32
-rw-r--r--app/Locale/da_DK/translations.php32
-rw-r--r--app/Locale/de_DE/translations.php32
-rw-r--r--app/Locale/es_ES/translations.php86
-rw-r--r--app/Locale/fi_FI/translations.php32
-rw-r--r--app/Locale/fr_FR/translations.php174
-rw-r--r--app/Locale/hu_HU/translations.php32
-rw-r--r--app/Locale/id_ID/translations.php1072
-rw-r--r--app/Locale/it_IT/translations.php32
-rw-r--r--app/Locale/ja_JP/translations.php32
-rwxr-xr-x[-rw-r--r--]app/Locale/nb_NO/translations.php542
-rw-r--r--app/Locale/nl_NL/translations.php32
-rw-r--r--app/Locale/pl_PL/translations.php32
-rw-r--r--app/Locale/pt_BR/translations.php46
-rw-r--r--app/Locale/pt_PT/translations.php32
-rw-r--r--app/Locale/ru_RU/translations.php33
-rw-r--r--app/Locale/sr_Latn_RS/translations.php32
-rw-r--r--app/Locale/sv_SE/translations.php32
-rw-r--r--app/Locale/th_TH/translations.php32
-rw-r--r--app/Locale/tr_TR/translations.php32
-rw-r--r--app/Locale/zh_CN/translations.php32
-rw-r--r--app/Model/Acl.php13
-rw-r--r--app/Model/Budget.php214
-rw-r--r--app/Model/Config.php2
-rw-r--r--app/Model/File.php244
-rw-r--r--app/Model/HourlyRate.php121
-rw-r--r--app/Model/Subtask.php41
-rw-r--r--app/Model/SubtaskTimeTracking.php19
-rw-r--r--app/Model/Swimlane.php28
-rw-r--r--app/Schema/Mysql.php44
-rw-r--r--app/Schema/Postgres.php41
-rw-r--r--app/Schema/Sqlite.php41
-rw-r--r--app/ServiceProvider/ClassProvider.php25
-rw-r--r--app/Subscriber/SubtaskTimeTrackingSubscriber.php1
-rw-r--r--app/Template/app/sidebar.php1
-rw-r--r--app/Template/board/table_swimlane.php18
-rw-r--r--app/Template/budget/breakdown.php30
-rw-r--r--app/Template/budget/create.php47
-rw-r--r--app/Template/budget/index.php34
-rw-r--r--app/Template/budget/remove.php13
-rw-r--r--app/Template/budget/sidebar.php16
-rw-r--r--app/Template/config/sidebar.php1
-rw-r--r--app/Template/currency/index.php2
-rw-r--r--app/Template/export/sidebar.php1
-rw-r--r--app/Template/file/show.php2
-rw-r--r--app/Template/gantt/task_creation.php12
-rw-r--r--app/Template/header.php33
-rw-r--r--app/Template/hourlyrate/index.php46
-rw-r--r--app/Template/hourlyrate/remove.php13
-rw-r--r--app/Template/layout.php43
-rw-r--r--app/Template/project/dropdown.php30
-rw-r--r--app/Template/project/sidebar.php2
-rw-r--r--app/Template/project_user/sidebar.php2
-rw-r--r--app/Template/swimlane/edit.php21
-rw-r--r--app/Template/swimlane/index.php22
-rw-r--r--app/Template/swimlane/table.php2
-rw-r--r--app/Template/task/color_picker.php11
-rw-r--r--app/Template/task/sidebar.php4
-rw-r--r--app/Template/task_creation/form.php11
-rw-r--r--app/Template/task_modification/edit_task.php11
-rw-r--r--app/Template/user/sidebar.php7
-rw-r--r--app/common.php120
-rw-r--r--app/constants.php5
-rw-r--r--app/routes.php117
91 files changed, 3027 insertions, 2425 deletions
diff --git a/app/Api/Swimlane.php b/app/Api/Swimlane.php
index fb40841f..13838d77 100644
--- a/app/Api/Swimlane.php
+++ b/app/Api/Swimlane.php
@@ -40,14 +40,18 @@ class Swimlane extends \Core\Base
return $this->swimlane->getDefault($project_id);
}
- public function addSwimlane($project_id, $name)
+ public function addSwimlane($project_id, $name, $description = '')
{
- return $this->swimlane->create($project_id, $name);
+ return $this->swimlane->create(array('project_id' => $project_id, 'name' => $name, 'description' => $description));
}
- public function updateSwimlane($swimlane_id, $name)
+ public function updateSwimlane($swimlane_id, $name, $description = null)
{
- return $this->swimlane->rename($swimlane_id, $name);
+ $values = array('id' => $swimlane_id, 'name' => $name);
+ if (!is_null($description)) {
+ $values['description'] = $description;
+ }
+ return $this->swimlane->update($values);
}
public function removeSwimlane($project_id, $swimlane_id)
diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php
index c1459b4e..0ccd09a4 100644
--- a/app/Auth/Ldap.php
+++ b/app/Auth/Ldap.php
@@ -20,6 +20,178 @@ class Ldap extends Base
const AUTH_NAME = 'LDAP';
/**
+ * Get LDAP server name
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapServer()
+ {
+ return LDAP_SERVER;
+ }
+
+ /**
+ * Get LDAP server port
+ *
+ * @access public
+ * @return integer
+ */
+ public function getLdapPort()
+ {
+ return LDAP_PORT;
+ }
+
+ /**
+ * Get LDAP username (proxy auth)
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapUsername()
+ {
+ return LDAP_USERNAME;
+ }
+
+ /**
+ * Get LDAP password (proxy auth)
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapPassword()
+ {
+ return LDAP_PASSWORD;
+ }
+
+ /**
+ * Get LDAP Base DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapBaseDn()
+ {
+ return LDAP_ACCOUNT_BASE;
+ }
+
+ /**
+ * Get LDAP account id attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountId()
+ {
+ return LDAP_ACCOUNT_ID;
+ }
+
+ /**
+ * Get LDAP account email attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountEmail()
+ {
+ return LDAP_ACCOUNT_EMAIL;
+ }
+
+ /**
+ * Get LDAP account name attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountName()
+ {
+ return LDAP_ACCOUNT_FULLNAME;
+ }
+
+ /**
+ * Get LDAP account memberof attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountMemberOf()
+ {
+ return LDAP_ACCOUNT_MEMBEROF;
+ }
+
+ /**
+ * Get LDAP admin group DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapGroupAdmin()
+ {
+ return LDAP_GROUP_ADMIN_DN;
+ }
+
+ /**
+ * Get LDAP project admin group DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapGroupProjectAdmin()
+ {
+ return LDAP_GROUP_PROJECT_ADMIN_DN;
+ }
+
+ /**
+ * Get LDAP username pattern
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapUserPattern($username)
+ {
+ return sprintf(LDAP_USER_PATTERN, $username);
+ }
+
+ /**
+ * Return true if the LDAP username is case sensitive
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isLdapAccountCaseSensitive()
+ {
+ return LDAP_USERNAME_CASE_SENSITIVE;
+ }
+
+ /**
+ * Return true if the automatic account creation is enabled
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isLdapAccountCreationEnabled()
+ {
+ return LDAP_ACCOUNT_CREATION;
+ }
+
+ /**
+ * Ge the list of attributes to fetch when reading the LDAP user entry
+ *
+ * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
+ *
+ * @access public
+ * @return array
+ */
+ public function getProfileAttributes()
+ {
+ return array_values(array_filter(array(
+ $this->getLdapAccountId(),
+ $this->getLdapAccountName(),
+ $this->getLdapAccountEmail(),
+ $this->getLdapAccountMemberOf()
+ )));
+ }
+
+ /**
* Authenticate the user
*
* @access public
@@ -29,7 +201,7 @@ class Ldap extends Base
*/
public function authenticate($username, $password)
{
- $username = LDAP_USERNAME_CASE_SENSITIVE ? $username : strtolower($username);
+ $username = $this->isLdapAccountCaseSensitive() ? $username : strtolower($username);
$result = $this->findUser($username, $password);
if (is_array($result)) {
@@ -46,7 +218,7 @@ class Ldap extends Base
else {
// We create automatically a new user
- if (LDAP_ACCOUNT_CREATION && $this->createUser($username, $result['name'], $result['email'])) {
+ if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) {
$user = $this->user->getByUsername($username);
}
else {
@@ -65,28 +237,6 @@ class Ldap extends Base
}
/**
- * Create a new local user after the LDAP authentication
- *
- * @access public
- * @param string $username Username
- * @param string $name Name of the user
- * @param string $email Email address
- * @return bool
- */
- public function createUser($username, $name, $email)
- {
- $values = array(
- 'username' => $username,
- 'name' => $name,
- 'email' => $email,
- 'is_admin' => 0,
- 'is_ldap_user' => 1,
- );
-
- return $this->user->create($values);
- }
-
- /**
* Find the user from the LDAP server
*
* @access public
@@ -98,8 +248,8 @@ class Ldap extends Base
{
$ldap = $this->connect();
- if (is_resource($ldap) && $this->bind($ldap, $username, $password)) {
- return $this->search($ldap, $username, $password);
+ if ($ldap !== false && $this->bind($ldap, $username, $password)) {
+ return $this->getProfile($ldap, $username, $password);
}
return false;
@@ -108,13 +258,14 @@ class Ldap extends Base
/**
* LDAP connection
*
- * @access private
- * @return resource $ldap LDAP connection
+ * @access public
+ * @return resource|boolean
*/
- private function connect()
+ public function connect()
{
if (! function_exists('ldap_connect')) {
- die('The PHP LDAP extension is required');
+ $this->logger->error('The PHP LDAP extension is required');
+ return false;
}
// Skip SSL certificate verification
@@ -122,10 +273,11 @@ class Ldap extends Base
putenv('LDAPTLS_REQCERT=never');
}
- $ldap = ldap_connect(LDAP_SERVER, LDAP_PORT);
+ $ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
- if (! is_resource($ldap)) {
- die('Unable to connect to the LDAP server: "'.LDAP_SERVER.'"');
+ if ($ldap === false) {
+ $this->logger->error('Unable to connect to the LDAP server');
+ return false;
}
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
@@ -134,30 +286,32 @@ class Ldap extends Base
ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1);
if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) {
- die('Unable to use ldap_start_tls()');
+ $this->logger->error('Unable to use ldap_start_tls()');
+ return false;
}
return $ldap;
}
/**
- * LDAP bind
+ * LDAP authentication
*
- * @access private
- * @param resource $ldap LDAP connection
- * @param string $username Username
- * @param string $password Password
+ * @access public
+ * @param resource $ldap
+ * @param string $username
+ * @param string $password
+ * @param string $ldap_type
* @return boolean
*/
- private function bind($ldap, $username, $password)
+ public function bind($ldap, $username, $password, $ldap_type = LDAP_BIND_TYPE)
{
- if (LDAP_BIND_TYPE === 'user') {
- $ldap_username = sprintf(LDAP_USERNAME, $username);
+ if ($ldap_type === 'user') {
+ $ldap_username = $this->getLdapUserPattern($username);
$ldap_password = $password;
}
- else if (LDAP_BIND_TYPE === 'proxy') {
- $ldap_username = LDAP_USERNAME;
- $ldap_password = LDAP_PASSWORD;
+ else if ($ldap_type === 'proxy') {
+ $ldap_username = $this->getLdapUsername();
+ $ldap_password = $this->getLdapPassword();
}
else {
$ldap_username = null;
@@ -172,118 +326,182 @@ class Ldap extends Base
}
/**
- * LDAP user lookup
+ * Get LDAP user profile
*
- * @access private
- * @param resource $ldap LDAP connection
- * @param string $username Username
- * @param string $password Password
+ * @access public
+ * @param resource $ldap
+ * @param string $username
+ * @param string $password
* @return boolean|array
*/
- private function search($ldap, $username, $password)
+ public function getProfile($ldap, $username, $password)
{
- $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, sprintf(LDAP_USER_PATTERN, $username), array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL));
-
- if ($sr === false) {
+ $entries = $this->executeQuery($ldap, $this->getLdapUserPattern($username));
+ if ($entries === false) {
return false;
}
- $info = ldap_get_entries($ldap, $sr);
+ if (@ldap_bind($ldap, $entries[0]['dn'], $password)) {
+ return $this->prepareProfile($ldap, $entries, $username);
+ }
- // User not found
- if (count($info) == 0 || $info['count'] == 0) {
- return false;
+ return false;
+ }
+
+ /**
+ * Build user profile from LDAP information
+ *
+ * @access public
+ * @param resource $ldap
+ * @param array $entries
+ * @param string $username
+ * @return boolean|array
+ */
+ public function prepareProfile($ldap, array $entries, $username)
+ {
+ if ($this->getLdapAccountId() !== '') {
+ $username = $this->getEntry($entries, $this->getLdapAccountId(), $username);
}
- // We got our user
- if (@ldap_bind($ldap, $info[0]['dn'], $password)) {
+ return array(
+ 'username' => $username,
+ 'name' => $this->getEntry($entries, $this->getLdapAccountName()),
+ 'email' => $this->getEntry($entries, $this->getLdapAccountEmail()),
+ 'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupAdmin()),
+ 'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupProjectAdmin()),
+ 'is_ldap_user' => 1,
+ );
+ }
- return array(
- 'username' => $username,
- 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME),
- 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL),
- );
+ /**
+ * Check group membership
+ *
+ * @access public
+ * @param array $group_entries
+ * @param string $group_dn
+ * @return boolean
+ */
+ public function isMemberOf(array $group_entries, $group_dn)
+ {
+ if (! isset($group_entries['count']) || empty($group_dn)) {
+ return false;
+ }
+
+ for ($i = 0; $i < $group_entries['count']; $i++) {
+ if ($group_entries[$i] === $group_dn) {
+ return true;
+ }
}
return false;
}
/**
- * Retrieve info on LDAP user
+ * Retrieve info on LDAP user by username or email
*
- * @param string $username Username
- * @param string $email Email address
+ * @access public
+ * @param string $username
+ * @param string $email
+ * @return boolean|array
*/
public function lookup($username = null, $email = null)
{
- $query = $this->getQuery($username, $email);
- if ($query === false) {
+ $query = $this->getLookupQuery($username, $email);
+ if ($query === '') {
return false;
}
- // Connect and attempt anonymous bind
+ // Connect and attempt anonymous or proxy binding
$ldap = $this->connect();
- if (! is_resource($ldap) || ! $this->bind($ldap, null, null)) {
+ if ($ldap === false || ! $this->bind($ldap, null, null)) {
return false;
}
// Try to find user
- $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, $query, array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_ID));
- if ($sr === false) {
+ $entries = $this->executeQuery($ldap, $query);
+ if ($entries === false) {
+ return false;
+ }
+
+ // User id not retrieved: LDAP_ACCOUNT_ID not properly configured
+ if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][0])) {
return false;
}
- $info = ldap_get_entries($ldap, $sr);
+ return $this->prepareProfile($ldap, $entries, $username);
+ }
- // User not found
- if (count($info) == 0 || $info['count'] == 0) {
+ /**
+ * Execute LDAP query
+ *
+ * @access private
+ * @param resource $ldap
+ * @param string $query
+ * @return boolean|array
+ */
+ private function executeQuery($ldap, $query)
+ {
+ $sr = ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
+ if ($sr === false) {
return false;
}
- // User id not retrieved: LDAP_ACCOUNT_ID not properly configured
- if (empty($username) && ! isset($info[0][LDAP_ACCOUNT_ID][0])) {
+ $entries = ldap_get_entries($ldap, $sr);
+ if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
return false;
}
- return array(
- 'username' => $this->getFromInfo($info, LDAP_ACCOUNT_ID, $username),
- 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME),
- 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL, $email),
- );
+ return $entries;
}
/**
* Get the LDAP query to find a user
*
- * @param string $username Username
- * @param string $email Email address
+ * @access private
+ * @param string $username
+ * @param string $email
+ * @return string
*/
- private function getQuery($username, $email)
+ private function getLookupQuery($username, $email)
{
- if ($username && $email) {
- return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.LDAP_ACCOUNT_EMAIL.'='.$email.'))';
+ if (! empty($username) && ! empty($email)) {
+ return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))';
}
- else if ($username) {
- return sprintf(LDAP_USER_PATTERN, $username);
+ else if (! empty($username)) {
+ return $this->getLdapUserPattern($username);
}
- else if ($email) {
- return '('.LDAP_ACCOUNT_EMAIL.'='.$email.')';
- }
- else {
- return false;
+ else if (! empty($email)) {
+ return '('.$this->getLdapAccountEmail().'='.$email.')';
}
+
+ return '';
}
/**
- * Return a value from the LDAP info
+ * Return one entry from a list of entries
*
- * @param array $info LDAP info
- * @param string $key Key
- * @param string $default Default value if key not set in entry
+ * @access private
+ * @param array $entries LDAP entries
+ * @param string $key Key
+ * @param string $default Default value if key not set in entry
* @return string
*/
- private function getFromInfo($info, $key, $default = '')
+ private function getEntry(array $entries, $key, $default = '')
+ {
+ return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default;
+ }
+
+ /**
+ * Return subset of entries
+ *
+ * @access private
+ * @param array $entries
+ * @param string $key
+ * @param array $default
+ * @return array
+ */
+ private function getEntries(array $entries, $key, $default = array())
{
- return isset($info[0][$key][0]) ? $info[0][$key][0] : $default;
+ return isset($entries[0][$key]) ? $entries[0][$key] : $default;
}
}
diff --git a/app/Auth/ReverseProxy.php b/app/Auth/ReverseProxy.php
index c8fd5eec..7818254c 100644
--- a/app/Auth/ReverseProxy.php
+++ b/app/Auth/ReverseProxy.php
@@ -28,7 +28,6 @@ class ReverseProxy extends Base
public function authenticate()
{
if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) {
-
$login = $_SERVER[REVERSE_PROXY_USER_HEADER];
$user = $this->user->getByUsername($login);
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index 179c6b3c..a552b9cf 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -321,6 +321,18 @@ class Board extends Base
}
/**
+ * Display swimlane description in tooltip
+ *
+ * @access public
+ */
+ public function swimlane()
+ {
+ $this->getProject();
+ $swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
+ $this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
+ }
+
+ /**
* Enable collapsed mode
*
* @access public
diff --git a/app/Controller/Budget.php b/app/Controller/Budget.php
deleted file mode 100644
index a2f7e0db..00000000
--- a/app/Controller/Budget.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-namespace Controller;
-
-/**
- * Budget
- *
- * @package controller
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * Budget index page
- *
- * @access public
- */
- public function index()
- {
- $project = $this->getProject();
-
- $this->response->html($this->projectLayout('budget/index', array(
- 'daily_budget' => $this->budget->getDailyBudgetBreakdown($project['id']),
- 'project' => $project,
- 'title' => t('Budget')
- ), 'budget/sidebar'));
- }
-
- /**
- * Cost breakdown by users/subtasks/tasks
- *
- * @access public
- */
- public function breakdown()
- {
- $project = $this->getProject();
-
- $paginator = $this->paginator
- ->setUrl('budget', 'breakdown', array('project_id' => $project['id']))
- ->setMax(30)
- ->setOrder('start')
- ->setDirection('DESC')
- ->setQuery($this->budget->getSubtaskBreakdown($project['id']))
- ->calculate();
-
- $this->response->html($this->projectLayout('budget/breakdown', array(
- 'paginator' => $paginator,
- 'project' => $project,
- 'title' => t('Budget')
- ), 'budget/sidebar'));
- }
-
- /**
- * Create budget lines
- *
- * @access public
- */
- public function create(array $values = array(), array $errors = array())
- {
- $project = $this->getProject();
-
- if (empty($values)) {
- $values['date'] = date('Y-m-d');
- }
-
- $this->response->html($this->projectLayout('budget/create', array(
- 'lines' => $this->budget->getAll($project['id']),
- 'values' => $values + array('project_id' => $project['id']),
- 'errors' => $errors,
- 'project' => $project,
- 'title' => t('Budget lines')
- ), 'budget/sidebar'));
- }
-
- /**
- * Validate and save a new budget
- *
- * @access public
- */
- public function save()
- {
- $project = $this->getProject();
-
- $values = $this->request->getValues();
- list($valid, $errors) = $this->budget->validateCreation($values);
-
- if ($valid) {
-
- if ($this->budget->create($values['project_id'], $values['amount'], $values['comment'], $values['date'])) {
- $this->session->flash(t('The budget line have been created successfully.'));
- $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
- }
- else {
- $this->session->flashError(t('Unable to create the budget line.'));
- }
- }
-
- $this->create($values, $errors);
- }
-
- /**
- * Confirmation dialog before removing a budget
- *
- * @access public
- */
- public function confirm()
- {
- $project = $this->getProject();
-
- $this->response->html($this->projectLayout('budget/remove', array(
- 'project' => $project,
- 'budget_id' => $this->request->getIntegerParam('budget_id'),
- 'title' => t('Remove a budget line'),
- ), 'budget/sidebar'));
- }
-
- /**
- * Remove a budget
- *
- * @access public
- */
- public function remove()
- {
- $this->checkCSRFParam();
- $project = $this->getProject();
-
- if ($this->budget->remove($this->request->getIntegerParam('budget_id'))) {
- $this->session->flash(t('Budget line removed successfully.'));
- } else {
- $this->session->flashError(t('Unable to remove this budget line.'));
- }
-
- $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
- }
-}
diff --git a/app/Controller/Doc.php b/app/Controller/Doc.php
index 19644b84..f9f0a675 100644
--- a/app/Controller/Doc.php
+++ b/app/Controller/Doc.php
@@ -16,7 +16,7 @@ class Doc extends Base
{
$url = $this->helper->url;
$data = file_get_contents($filename);
- list($title,, $content) = explode("\n", $data, 3);
+ list($title,) = explode("\n", $data, 2);
$replaceUrl = function (array $matches) use ($url) {
return '('.$url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')';
@@ -32,16 +32,24 @@ class Doc extends Base
public function show()
{
- $filename = $this->request->getStringParam('file', 'index');
+ $page = $this->request->getStringParam('file', 'index');
- if (! preg_match('/^[a-z0-9\-]+/', $filename)) {
- $filename = 'index';
+ if (! preg_match('/^[a-z0-9\-]+/', $page)) {
+ $page = 'index';
}
- $filename = __DIR__.'/../../doc/'.$filename.'.markdown';
+ $filenames = array(__DIR__.'/../../doc/'.$page.'.markdown');
+ $filename = __DIR__.'/../../doc/index.markdown';
- if (! file_exists($filename)) {
- $filename = __DIR__.'/../../doc/index.markdown';
+ if ($this->config->getCurrentLanguage() === 'fr_FR') {
+ array_unshift($filenames, __DIR__.'/../../doc/fr/'.$page.'.markdown');
+ }
+
+ foreach ($filenames as $file) {
+ if (file_exists($file)) {
+ $filename = $file;
+ break;
+ }
}
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
diff --git a/app/Controller/File.php b/app/Controller/File.php
index f73a9de9..7b7c75ee 100644
--- a/app/Controller/File.php
+++ b/app/Controller/File.php
@@ -60,7 +60,7 @@ class File extends Base
{
$task = $this->getTask();
- if (! $this->file->upload($task['project_id'], $task['id'], 'files')) {
+ if (! $this->file->uploadFiles($task['project_id'], $task['id'], 'files')) {
$this->session->flashError(t('Unable to upload the file.'));
}
@@ -76,14 +76,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $this->response->forceDownload($file['name']);
- $this->response->binary(file_get_contents($filename));
+ if ($file['task_id'] != $task['id']) {
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
- $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
+ $this->response->forceDownload($file['name']);
+ $this->objectStorage->passthru($file['path']);
}
/**
@@ -113,16 +112,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
-
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $metadata = getimagesize($filename);
- if (isset($metadata['mime'])) {
- $this->response->contentType($metadata['mime']);
- readfile($filename);
- }
+ if ($file['task_id'] != $task['id']) {
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
+
+ $this->response->contentType($this->file->getImageMimeType($file['name']));
+ $this->objectStorage->passthru($file['path']);
}
/**
@@ -134,17 +130,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
-
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $this->response->contentType('image/jpeg');
- $this->file->generateThumbnail(
- $filename,
- $this->request->getIntegerParam('width'),
- $this->request->getIntegerParam('height')
- );
+ if ($file['task_id'] != $task['id']) {
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
+
+ $this->response->contentType('image/jpeg');
+ $this->objectStorage->passthru($this->file->getThumbnailPath($file['path']));
}
/**
diff --git a/app/Controller/Hourlyrate.php b/app/Controller/Hourlyrate.php
deleted file mode 100644
index 19650ede..00000000
--- a/app/Controller/Hourlyrate.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-namespace Controller;
-
-/**
- * Hourly Rate controller
- *
- * @package controller
- * @author Frederic Guillot
- */
-class Hourlyrate extends User
-{
- /**
- * Display rate and form
- *
- * @access public
- */
- public function index(array $values = array(), array $errors = array())
- {
- $user = $this->getUser();
-
- $this->response->html($this->layout('hourlyrate/index', array(
- 'rates' => $this->hourlyRate->getAllByUser($user['id']),
- 'currencies_list' => $this->config->getCurrencies(),
- 'values' => $values + array('user_id' => $user['id']),
- 'errors' => $errors,
- 'user' => $user,
- )));
- }
-
- /**
- * Validate and save a new rate
- *
- * @access public
- */
- public function save()
- {
- $values = $this->request->getValues();
- list($valid, $errors) = $this->hourlyRate->validateCreation($values);
-
- if ($valid) {
-
- if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) {
- $this->session->flash(t('Hourly rate created successfully.'));
- $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $values['user_id'])));
- }
- else {
- $this->session->flashError(t('Unable to save the hourly rate.'));
- }
- }
-
- $this->index($values, $errors);
- }
-
- /**
- * Confirmation dialag box to remove a row
- *
- * @access public
- */
- public function confirm()
- {
- $user = $this->getUser();
-
- $this->response->html($this->layout('hourlyrate/remove', array(
- 'rate_id' => $this->request->getIntegerParam('rate_id'),
- 'user' => $user,
- )));
- }
-
- /**
- * Remove a row
- *
- * @access public
- */
- public function remove()
- {
- $this->checkCSRFParam();
- $user = $this->getUser();
-
- if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) {
- $this->session->flash(t('Rate removed successfully.'));
- }
- else {
- $this->session->flash(t('Unable to remove this rate.'));
- }
-
- $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $user['id'])));
- }
-}
diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php
index 054fa4ba..f92e9fe3 100644
--- a/app/Controller/Swimlane.php
+++ b/app/Controller/Swimlane.php
@@ -59,13 +59,12 @@ class Swimlane extends Base
public function save()
{
$project = $this->getProject();
-
$values = $this->request->getValues();
list($valid, $errors) = $this->swimlane->validateCreation($values);
if ($valid) {
- if ($this->swimlane->create($project['id'], $values['name'])) {
+ if ($this->swimlane->create($values)) {
$this->session->flash(t('Your swimlane have been created successfully.'));
$this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
@@ -134,8 +133,7 @@ class Swimlane extends Base
list($valid, $errors) = $this->swimlane->validateModification($values);
if ($valid) {
-
- if ($this->swimlane->rename($values['id'], $values['name'])) {
+ if ($this->swimlane->update($values)) {
$this->session->flash(t('Swimlane updated successfully.'));
$this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
diff --git a/app/Controller/Taskcreation.php b/app/Controller/Taskcreation.php
index ff25c5da..b9e9a33c 100644
--- a/app/Controller/Taskcreation.php
+++ b/app/Controller/Taskcreation.php
@@ -59,25 +59,29 @@ class Taskcreation extends Base
list($valid, $errors) = $this->taskValidator->validateCreation($values);
- if ($valid) {
+ if ($valid && $this->taskCreation->create($values)) {
+ $this->session->flash(t('Task created successfully.'));
+ $this->afterSave($project, $values);
+ }
+ else {
+ $this->session->flashError(t('Unable to create your task.'));
+ }
- if ($this->taskCreation->create($values)) {
- $this->session->flash(t('Task created successfully.'));
+ $this->create($values, $errors);
+ }
- if (isset($values['another_task']) && $values['another_task'] == 1) {
- unset($values['title']);
- unset($values['description']);
- $this->response->redirect($this->helper->url->to('taskcreation', 'create', $values));
- }
- else {
- $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
- }
- }
- else {
- $this->session->flashError(t('Unable to create your task.'));
+ private function afterSave(array $project, array &$values)
+ {
+ if (isset($values['another_task']) && $values['another_task'] == 1) {
+ unset($values['title']);
+ unset($values['description']);
+
+ if (! $this->request->isAjax()) {
+ $this->response->redirect($this->helper->url->to('taskcreation', 'create', $values));
}
}
-
- $this->create($values, $errors);
+ else {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
+ }
}
}
diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php
index 56d2b9f9..638af594 100644
--- a/app/Controller/Taskmodification.php
+++ b/app/Controller/Taskmodification.php
@@ -126,11 +126,13 @@ class Taskmodification extends Base
);
if ($ajax) {
- $this->response->html($this->template->render('task_modification/edit_task', $params));
+ $html = $this->template->render('task_modification/edit_task', $params);
}
else {
- $this->response->html($this->taskLayout('task_modification/edit_task', $params));
+ $html = $this->taskLayout('task_modification/edit_task', $params);
}
+
+ $this->response->html($html);
}
/**
@@ -145,24 +147,20 @@ class Taskmodification extends Base
list($valid, $errors) = $this->taskValidator->validateModification($values);
- if ($valid) {
-
- if ($this->taskModification->update($values)) {
- $this->session->flash(t('Task updated successfully.'));
+ if ($valid && $this->taskModification->update($values)) {
+ $this->session->flash(t('Task updated successfully.'));
- if ($this->request->getIntegerParam('ajax')) {
- $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
- }
- else {
- $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
- }
+ if ($this->request->isAjax()) {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
else {
- $this->session->flashError(t('Unable to update your task.'));
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
-
- $this->edit($values, $errors);
+ else {
+ $this->session->flashError(t('Unable to update your task.'));
+ $this->edit($values, $errors);
+ }
}
/**
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 3db0cf74..5ed8f40a 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -35,7 +35,6 @@ use Pimple\Container;
* @property \Model\Action $action
* @property \Model\Authentication $authentication
* @property \Model\Board $board
- * @property \Model\Budget $budget
* @property \Model\Category $category
* @property \Model\Color $color
* @property \Model\Comment $comment
@@ -43,7 +42,6 @@ use Pimple\Container;
* @property \Model\Currency $currency
* @property \Model\DateParser $dateParser
* @property \Model\File $file
- * @property \Model\HourlyRate $hourlyRate
* @property \Model\LastLogin $lastLogin
* @property \Model\Link $link
* @property \Model\Notification $notification
diff --git a/app/Core/ObjectStorage/FileStorage.php b/app/Core/ObjectStorage/FileStorage.php
new file mode 100644
index 00000000..66c62334
--- /dev/null
+++ b/app/Core/ObjectStorage/FileStorage.php
@@ -0,0 +1,150 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Local File Storage
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+class FileStorage implements ObjectStorageInterface
+{
+ /**
+ * Base path
+ *
+ * @access private
+ * @var string
+ */
+ private $path = '';
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param string $path
+ */
+ public function __construct($path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob)
+ {
+ $this->createFolder($key);
+
+ if (file_put_contents($this->path.DIRECTORY_SEPARATOR.$key, $blob) === false) {
+ throw new ObjectStorageException('Unable to write the file: '.$this->path.DIRECTORY_SEPARATOR.$key);
+ }
+ }
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return readfile($filename);
+ }
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($src_filename, $key)
+ {
+ $this->createFolder($key);
+ $dst_filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! rename($src_filename, $dst_filename)) {
+ throw new ObjectStorageException('Unable to move the file: '.$src_filename.' to '.$dst_filename);
+ }
+
+ return true;
+ }
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key)
+ {
+ $this->createFolder($key);
+ return move_uploaded_file($filename, $this->path.DIRECTORY_SEPARATOR.$key);
+ }
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (file_exists($filename)) {
+ return unlink($filename);
+ }
+
+ return false;
+ }
+
+ /**
+ * Create object folder
+ *
+ * @access private
+ * @param string $key
+ */
+ private function createFolder($key)
+ {
+ $folder = $this->path.DIRECTORY_SEPARATOR.dirname($key);
+
+ if (! is_dir($folder) && ! mkdir($folder, 0755, true)) {
+ throw new ObjectStorageException('Unable to create folder: '.$folder);
+ }
+ }
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageException.php b/app/Core/ObjectStorage/ObjectStorageException.php
new file mode 100644
index 00000000..e89aeb25
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+use Exception;
+
+class ObjectStorageException extends Exception
+{
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageInterface.php b/app/Core/ObjectStorage/ObjectStorageInterface.php
new file mode 100644
index 00000000..5440cf2b
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Object Storage Interface
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+interface ObjectStorageInterface
+{
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key);
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob);
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key);
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($filename, $key);
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key);
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key);
+}
diff --git a/app/Core/PluginBase.php b/app/Core/PluginBase.php
new file mode 100644
index 00000000..457afa03
--- /dev/null
+++ b/app/Core/PluginBase.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Core;
+
+/**
+ * Plugin Base class
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+abstract class PluginBase extends Base
+{
+ /**
+ * Method called for each request
+ *
+ * @abstract
+ * @access public
+ */
+ abstract public function initialize();
+
+ /**
+ * Returns all classes that needs to be stored in the DI container
+ *
+ * @access public
+ * @return array
+ */
+ public function getClasses()
+ {
+ return array();
+ }
+
+ /**
+ * Listen on internal events
+ *
+ * @access public
+ * @param string $event
+ * @param callable $callback
+ */
+ public function on($event, $callback)
+ {
+ $container = $this->container;
+
+ $this->container['dispatcher']->addListener($event, function() use ($container, $callback) {
+ call_user_func($callback, $container);
+ });
+ }
+}
diff --git a/app/Core/PluginLoader.php b/app/Core/PluginLoader.php
new file mode 100644
index 00000000..c7c254f7
--- /dev/null
+++ b/app/Core/PluginLoader.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Core;
+
+use DirectoryIterator;
+use PDOException;
+
+/**
+ * Plugin Loader
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class PluginLoader extends Base
+{
+ /**
+ * Schema version table for plugins
+ *
+ * @var string
+ */
+ const TABLE_SCHEMA = 'plugin_schema_versions';
+
+ /**
+ * Scan plugin folder and load plugins
+ *
+ * @access public
+ */
+ public function scan()
+ {
+ if (file_exists(__DIR__.'/../../plugins')) {
+ $dir = new DirectoryIterator(__DIR__.'/../../plugins');
+
+ foreach ($dir as $fileinfo) {
+ if (! $fileinfo->isDot() && $fileinfo->isDir()) {
+ $plugin = $fileinfo->getFilename();
+ $this->loadSchema($plugin);
+ $this->load($plugin);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load plugin
+ *
+ * @access public
+ */
+ public function load($plugin)
+ {
+ $class = '\Plugin\\'.$plugin.'\\Plugin';
+ $instance = new $class($this->container);
+
+ Tool::buildDic($this->container, $instance->getClasses());
+
+ $instance->initialize();
+ }
+
+ /**
+ * Load plugin schema
+ *
+ * @access public
+ * @param string $plugin
+ */
+ public function loadSchema($plugin)
+ {
+ $filename = __DIR__.'/../../plugins/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php';
+
+ if (file_exists($filename)) {
+ require($filename);
+ $this->migrateSchema($plugin);
+ }
+ }
+
+ /**
+ * Execute plugin schema migrations
+ *
+ * @access public
+ * @param string $plugin
+ */
+ public function migrateSchema($plugin)
+ {
+ $last_version = constant('\Plugin\\'.$plugin.'\Schema\VERSION');
+ $current_version = $this->getSchemaVersion($plugin);
+
+ try {
+
+ $this->db->startTransaction();
+ $this->db->getDriver()->disableForeignKeys();
+
+ for ($i = $current_version + 1; $i <= $last_version; $i++) {
+ $function_name = '\Plugin\\'.$plugin.'\Schema\version_'.$i;
+
+ if (function_exists($function_name)) {
+ call_user_func($function_name, $this->db->getConnection());
+ }
+ }
+
+ $this->db->getDriver()->enableForeignKeys();
+ $this->db->closeTransaction();
+ $this->setSchemaVersion($plugin, $i - 1);
+ }
+ catch (PDOException $e) {
+ $this->db->cancelTransaction();
+ $this->db->getDriver()->enableForeignKeys();
+ die('Unable to migrate schema for the plugin: '.$plugin.' => '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Get current plugin schema version
+ *
+ * @access public
+ * @param string $plugin
+ * @return integer
+ */
+ public function getSchemaVersion($plugin)
+ {
+ return (int) $this->db->table(self::TABLE_SCHEMA)->eq('plugin', strtolower($plugin))->findOneColumn('version');
+ }
+
+ /**
+ * Save last plugin schema version
+ *
+ * @access public
+ * @param string $plugin
+ * @param integer $version
+ * @return boolean
+ */
+ public function setSchemaVersion($plugin, $version)
+ {
+ $dictionary = array(
+ strtolower($plugin) => $version
+ );
+
+ return $this->db->getDriver()->upsert(self::TABLE_SCHEMA, 'plugin', 'version', $dictionary);
+ }
+}
diff --git a/app/Core/Response.php b/app/Core/Response.php
index d42a8f1e..f8ca015c 100644
--- a/app/Core/Response.php
+++ b/app/Core/Response.php
@@ -66,7 +66,13 @@ class Response
*/
public function redirect($url)
{
- header('Location: '.$url);
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
+ header('X-Ajax-Redirect: '.$url);
+ }
+ else {
+ header('Location: '.$url);
+ }
+
exit;
}
diff --git a/app/Core/Router.php b/app/Core/Router.php
index 6e7576d6..36bbfd55 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -213,49 +213,17 @@ class Router extends Base
if (! empty($_GET['controller']) && ! empty($_GET['action'])) {
$controller = $this->sanitize($_GET['controller'], 'app');
$action = $this->sanitize($_GET['action'], 'index');
+ $plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : '';
}
else {
- list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string));
+ list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes
+ $plugin = '';
}
- return $this->load(
- __DIR__.'/../Controller/'.ucfirst($controller).'.php',
- $controller,
- '\Controller\\'.ucfirst($controller),
- $action
- );
- }
-
- /**
- * Load a controller and execute the action
- *
- * @access private
- * @param string $filename
- * @param string $controller
- * @param string $class
- * @param string $method
- * @return bool
- */
- private function load($filename, $controller, $class, $method)
- {
- if (file_exists($filename)) {
-
- require $filename;
-
- if (! method_exists($class, $method)) {
- return false;
- }
-
- $this->action = $method;
- $this->controller = $controller;
-
- $instance = new $class($this->container);
- $instance->beforeAction($controller, $method);
- $instance->$method();
-
- return true;
- }
+ $class = empty($plugin) ? '\Controller\\'.ucfirst($controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($controller);
- return false;
+ $instance = new $class($this->container);
+ $instance->beforeAction($controller, $action);
+ $instance->$action();
}
}
diff --git a/app/Core/Template.php b/app/Core/Template.php
index ba869ee6..b75f7da1 100644
--- a/app/Core/Template.php
+++ b/app/Core/Template.php
@@ -13,11 +13,12 @@ use LogicException;
class Template extends Helper
{
/**
- * Template path
+ * List of template overrides
*
- * @var string
+ * @access private
+ * @var array
*/
- const PATH = 'app/Template/';
+ private $overrides = array();
/**
* Render a template
@@ -33,16 +34,10 @@ class Template extends Helper
*/
public function render($__template_name, array $__template_args = array())
{
- $__template_file = self::PATH.$__template_name.'.php';
-
- if (! file_exists($__template_file)) {
- throw new LogicException('Unable to load the template: "'.$__template_name.'"');
- }
-
extract($__template_args);
ob_start();
- include $__template_file;
+ include $this->getTemplateFile($__template_name);
return ob_get_clean();
}
@@ -62,4 +57,41 @@ class Template extends Helper
$template_args + array('content_for_layout' => $this->render($template_name, $template_args))
);
}
+
+ /**
+ * Define a new template override
+ *
+ * @access public
+ * @param string $original_template
+ * @param string $new_template
+ */
+ public function setTemplateOverride($original_template, $new_template)
+ {
+ $this->overrides[$original_template] = $new_template;
+ }
+
+ /**
+ * Find template filename
+ *
+ * Core template name: 'task/show'
+ * Plugin template name: 'myplugin:task/show'
+ *
+ * @access public
+ * @param string $template_name
+ * @return string
+ */
+ public function getTemplateFile($template_name)
+ {
+ $template_name = isset($this->overrides[$template_name]) ? $this->overrides[$template_name] : $template_name;
+
+ if (strpos($template_name, ':') !== false) {
+ list($plugin, $template) = explode(':', $template_name);
+ $path = __DIR__.'/../../plugins/'.ucfirst($plugin).'/Template/'.$template.'.php';
+ }
+ else {
+ $path = __DIR__.'/../Template/'.$template_name.'.php';
+ }
+
+ return $path;
+ }
}
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index 84e42ba8..887c8fb3 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -2,6 +2,8 @@
namespace Core;
+use Pimple\Container;
+
/**
* Tool class
*
@@ -23,7 +25,6 @@ class Tool
$fp = fopen($filename, 'w');
if (is_resource($fp)) {
-
foreach ($rows as $fields) {
fputcsv($fp, $fields);
}
@@ -51,4 +52,102 @@ class Tool
return $identifier;
}
+
+ /**
+ * Build dependency injection container from an array
+ *
+ * @static
+ * @access public
+ * @param Container $container
+ * @param array $namespaces
+ */
+ public static function buildDIC(Container $container, array $namespaces)
+ {
+ foreach ($namespaces as $namespace => $classes) {
+ foreach ($classes as $name) {
+ $class = '\\'.$namespace.'\\'.$name;
+ $container[lcfirst($name)] = function ($c) use ($class) {
+ return new $class($c);
+ };
+ }
+ }
+ }
+
+ /**
+ * Generate a jpeg thumbnail from an image
+ *
+ * @static
+ * @access public
+ * @param string $src_file Source file image
+ * @param string $dst_file Destination file image
+ * @param integer $resize_width Desired image width
+ * @param integer $resize_height Desired image height
+ */
+ public static function generateThumbnail($src_file, $dst_file, $resize_width = 250, $resize_height = 100)
+ {
+ $metadata = getimagesize($src_file);
+ $src_width = $metadata[0];
+ $src_height = $metadata[1];
+ $dst_y = 0;
+ $dst_x = 0;
+
+ if (empty($metadata['mime'])) {
+ return;
+ }
+
+ if ($resize_width == 0 && $resize_height == 0) {
+ $resize_width = 100;
+ $resize_height = 100;
+ }
+
+ if ($resize_width > 0 && $resize_height == 0) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ elseif ($resize_width == 0 && $resize_height > 0) {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ else {
+
+ $src_ratio = $src_width / $src_height;
+ $resize_ratio = $resize_width / $resize_height;
+
+ if ($src_ratio <= $resize_ratio) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+
+ $dst_y = ($dst_height - $resize_height) / 2 * (-1);
+ }
+ else {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+
+ $dst_x = ($dst_width - $resize_width) / 2 * (-1);
+ }
+
+ $dst_image = imagecreatetruecolor($resize_width, $resize_height);
+ }
+
+ switch ($metadata['mime']) {
+ case 'image/jpeg':
+ case 'image/jpg':
+ $src_image = imagecreatefromjpeg($src_file);
+ break;
+ case 'image/png':
+ $src_image = imagecreatefrompng($src_file);
+ break;
+ case 'image/gif':
+ $src_image = imagecreatefromgif($src_file);
+ break;
+ default:
+ return;
+ }
+
+ imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
+ imagejpeg($dst_image, $dst_file);
+ imagedestroy($dst_image);
+ }
}
diff --git a/app/Core/Translator.php b/app/Core/Translator.php
index e3d19692..e9aa1f3f 100644
--- a/app/Core/Translator.php
+++ b/app/Core/Translator.php
@@ -15,7 +15,7 @@ class Translator
*
* @var string
*/
- const PATH = 'app/Locale/';
+ const PATH = 'app/Locale';
/**
* Locale
@@ -196,18 +196,27 @@ class Translator
* @static
* @access public
* @param string $language Locale code: fr_FR
+ * @param string $path Locale folder
*/
- public static function load($language)
+ public static function load($language, $path = self::PATH)
{
setlocale(LC_TIME, $language.'.UTF-8', $language);
- $filename = self::PATH.$language.DIRECTORY_SEPARATOR.'translations.php';
+ $filename = $path.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'translations.php';
if (file_exists($filename)) {
- self::$locales = require $filename;
- }
- else {
- self::$locales = array();
+ self::$locales = array_merge(self::$locales, require($filename));
}
}
+
+ /**
+ * Clear locales stored in memory
+ *
+ * @static
+ * @access public
+ */
+ public static function unload()
+ {
+ self::$locales = array();
+ }
}
diff --git a/app/Helper/App.php b/app/Helper/App.php
index e5ebefcb..5fb89afe 100644
--- a/app/Helper/App.php
+++ b/app/Helper/App.php
@@ -67,9 +67,11 @@ class App extends \Core\Base
if (isset($this->session['flash_message'])) {
$html = '<div class="alert alert-success alert-fade-out">'.$this->helper->e($this->session['flash_message']).'</div>';
unset($this->session['flash_message']);
+ unset($this->session['flash_error_message']);
}
else if (isset($this->session['flash_error_message'])) {
$html = '<div class="alert alert-error">'.$this->helper->e($this->session['flash_error_message']).'</div>';
+ unset($this->session['flash_message']);
unset($this->session['flash_error_message']);
}
diff --git a/app/Helper/Hook.php b/app/Helper/Hook.php
new file mode 100644
index 00000000..77756757
--- /dev/null
+++ b/app/Helper/Hook.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Helper;
+
+/**
+ * Template Hook helpers
+ *
+ * @package helper
+ * @author Frederic Guillot
+ */
+class Hook extends \Core\Base
+{
+ private $hooks = array();
+
+ /**
+ * Render all attached hooks
+ *
+ * @access public
+ * @param string $hook
+ * @param array $variables
+ * @return string
+ */
+ public function render($hook, array $variables = array())
+ {
+ $buffer = '';
+
+ foreach ($this->hooks as $name => $template) {
+ if ($hook === $name) {
+ $buffer .= $this->template->render($template, $variables);
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Attach a template to a hook
+ *
+ * @access public
+ * @param string $hook
+ * @param string $template
+ * @return \Helper\Hook
+ */
+ public function attach($hook, $template)
+ {
+ $this->hooks[$hook] = $template;
+ return $this;
+ }
+}
diff --git a/app/Integration/Mailgun.php b/app/Integration/Mailgun.php
index 1451b211..076c311a 100644
--- a/app/Integration/Mailgun.php
+++ b/app/Integration/Mailgun.php
@@ -2,7 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
use Core\Tool;
/**
@@ -76,8 +75,7 @@ class Mailgun extends \Core\Base
// Get the Markdown contents
if (! empty($payload['stripped-html'])) {
- $markdown = new HTML_To_Markdown($payload['stripped-html'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['stripped-html']);
}
else if (! empty($payload['stripped-text'])) {
$description = $payload['stripped-text'];
diff --git a/app/Integration/Postmark.php b/app/Integration/Postmark.php
index dbb70aee..05bdf58b 100644
--- a/app/Integration/Postmark.php
+++ b/app/Integration/Postmark.php
@@ -2,8 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
-
/**
* Postmark integration
*
@@ -76,8 +74,7 @@ class Postmark extends \Core\Base
// Get the Markdown contents
if (! empty($payload['HtmlBody'])) {
- $markdown = new HTML_To_Markdown($payload['HtmlBody'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['HtmlBody']);
}
else if (! empty($payload['TextBody'])) {
$description = $payload['TextBody'];
diff --git a/app/Integration/Sendgrid.php b/app/Integration/Sendgrid.php
index 902749f6..fd58342a 100644
--- a/app/Integration/Sendgrid.php
+++ b/app/Integration/Sendgrid.php
@@ -2,7 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
use Core\Tool;
/**
@@ -79,8 +78,7 @@ class Sendgrid extends \Core\Base
// Get the Markdown contents
if (! empty($payload['html'])) {
- $markdown = new HTML_To_Markdown($payload['html'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['html']);
}
else if (! empty($payload['text'])) {
$description = $payload['text'];
diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php
index 557a62cc..8cea7367 100644
--- a/app/Locale/cs_CZ/translations.php
+++ b/app/Locale/cs_CZ/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Vzdálený',
'Enabled' => 'Povoleno',
'Disabled' => 'Zakázáno',
- 'Google account linked' => 'Google úÄet byl propojen',
- 'Github account linked' => 'Mit Githubaccount verbunden',
'Username:' => 'Uživatelské jméno:',
'Name:' => 'Jméno:',
'Email:' => 'e-mail',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horizontální rolování',
'Compact/wide view' => 'Kompaktní/plné zobrazení',
'No results match:' => 'Žádná shoda:',
- 'Remove hourly rate' => 'Stundensatz entfernen',
- 'Do you really want to remove this hourly rate?' => 'Opravdu chcete odstranit tuto hodinovou sazbu?',
- 'Hourly rates' => 'Hodinové sazby',
- 'Hourly rate' => 'Hodinová sazba',
'Currency' => 'Měna',
- 'Effective date' => 'Datum úÄinnosti',
- 'Add new rate' => 'Přidat novou hodinovou sazbu',
- 'Rate removed successfully.' => 'Sazba byla úspěšně odstraněna',
- 'Unable to remove this rate.' => 'Sazbu nelze odstranit.',
- 'Unable to save the hourly rate.' => 'Hodinovou sazbu nelze uložit',
- 'Hourly rate created successfully.' => 'Hodinová sazba byla úspěšně vytvořena.',
'Start time' => 'PoÄáteÄní datum',
'End time' => 'KoneÄné datum',
'Comment' => 'Komentář',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Soubory',
'Images' => 'Obrázky',
'Private project' => 'Soukromý projekt',
- 'Amount' => 'Částka',
// 'AUD - Australian Dollar' => '',
- 'Budget' => 'RozpoÄet',
- 'Budget line' => 'Položka rozpoÄtu',
- 'Budget line removed successfully.' => 'Položka rozpoÄtu byla odstranÄ›na',
- 'Budget lines' => 'Položky rozpoÄtu',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'Cena',
- 'Cost breakdown' => 'Rozpis nákladů',
'Custom Stylesheet' => 'Vlastní šablony stylů',
'download' => 'Stáhnout',
- 'Do you really want to remove this budget line?' => 'Opravdu chcete odstranit tuto rozpoÄtovou řádku?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Náklady',
'GBP - British Pound' => 'GBP - Britská Libra',
'INR - Indian Rupee' => 'INR - Indische Rupien',
'JPY - Japanese Yen' => 'JPY - Japanischer Yen',
- 'New budget line' => 'Nová položka rozpoÄtu',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
- 'Remove a budget line' => 'Budgetlinie entfernen',
- 'Remove budget line' => 'Budgetlinie entfernen',
'RSD - Serbian dinar' => 'RSD - Serbische Dinar',
- 'The budget line have been created successfully.' => 'Položka rozpoÄtu byla úspěšnÄ› vytvoÅ™ena.',
- 'Unable to create the budget line.' => 'Nelze vytvoÅ™it rozpoÄtovou řádku.',
- 'Unable to remove this budget line.' => 'Nelze vyjmout rozpoÄtovou řádku.',
'USD - US Dollar' => 'USD - US Dollar',
- 'Remaining' => 'Zbývající',
'Destination column' => 'Cílový sloupec',
'Move the task to another column when assigned to a user' => 'Přesunout úkol do jiného sloupce, když je úkol přiřazen uživateli.',
'Move the task to another column when assignee is cleared' => 'Přesunout úkol do jiného sloupce, když je pověření uživatele vymazáno.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurz',
'Change reference currency' => 'ZmÄ›nit referenÄní mÄ›nu',
'Add a new currency rate' => 'Přidat nový směnný kurz',
- 'Currency rates are used to calculate project budget.' => 'MÄ›nové sazby se používají k výpoÄtu rozpoÄtu projektu.',
'Reference currency' => 'ReferenÄní mÄ›na',
'The currency rate have been added successfully.' => 'Směnný kurz byl úspěšně přidán.',
'Unable to add this currency rate.' => 'Nelze přidat tento směnný kurz',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget Übersicht',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genug Daten für die Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index 6a41f065..027b22c5 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'Aktiv',
'Disabled' => 'Deaktiveret',
- 'Google account linked' => 'Google-konto forbundet',
- 'Github account linked' => 'Github-konto forbundet',
'Username:' => 'Brugernavn',
'Name:' => 'Navn:',
'Email:' => 'Email:',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index 7b38e9fc..0b1df2e7 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'angeschaltet',
'Disabled' => 'abgeschaltet',
- 'Google account linked' => 'Mit Google-Account verbunden',
- 'Github account linked' => 'Mit Github-Account verbunden',
'Username:' => 'Benutzername',
'Name:' => 'Name',
'Email:' => 'E-Mail',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horizontales Scrollen',
'Compact/wide view' => 'Kompakt/Breite-Ansicht',
'No results match:' => 'Keine Ergebnisse:',
- 'Remove hourly rate' => 'Stundensatz entfernen',
- 'Do you really want to remove this hourly rate?' => 'Diesen Stundensatz wirklich entfernen?',
- 'Hourly rates' => 'Stundensätze',
- 'Hourly rate' => 'Stundensatz',
'Currency' => 'Währung',
- 'Effective date' => 'Inkraftsetzung',
- 'Add new rate' => 'Neue Rate hinzufügen',
- 'Rate removed successfully.' => 'Rate erfolgreich entfernt',
- 'Unable to remove this rate.' => 'Nicht in der Lage, diese Rate zu entfernen.',
- 'Unable to save the hourly rate.' => 'Nicht in der Lage, diese Rate zu speichern',
- 'Hourly rate created successfully.' => 'Stundensatz erfolgreich angelegt.',
'Start time' => 'Startzeit',
'End time' => 'Endzeit',
'Comment' => 'Kommentar',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Dateien',
'Images' => 'Bilder',
'Private project' => 'privates Projekt',
- 'Amount' => 'Betrag',
'AUD - Australian Dollar' => 'AUD - Australische Dollar',
- 'Budget' => 'Budget',
- 'Budget line' => 'Budgetlinie',
- 'Budget line removed successfully.' => 'Budgetlinie erfolgreich entfernt',
- 'Budget lines' => 'Budgetlinien',
'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar',
'CHF - Swiss Francs' => 'CHF - Schweizer Franken',
- 'Cost' => 'Kosten',
- 'Cost breakdown' => 'Kostenaufschlüsselung',
'Custom Stylesheet' => 'benutzerdefiniertes Stylesheet',
'download' => 'Download',
- 'Do you really want to remove this budget line?' => 'Soll diese Budgetlinie wirklich entfernt werden?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Kosten',
'GBP - British Pound' => 'GBP - Britische Pfund',
'INR - Indian Rupee' => 'INR - Indische Rupien',
'JPY - Japanese Yen' => 'JPY - Japanische Yen',
- 'New budget line' => 'Neue Budgetlinie',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
- 'Remove a budget line' => 'Budgetlinie entfernen',
- 'Remove budget line' => 'Budgetlinie entfernen',
'RSD - Serbian dinar' => 'RSD - Serbische Dinar',
- 'The budget line have been created successfully.' => 'Die Budgetlinie wurde erfolgreich angelegt.',
- 'Unable to create the budget line.' => 'Budgetlinie konnte nicht erstellt werden.',
- 'Unable to remove this budget line.' => 'Budgetlinie konnte nicht gelöscht werden.',
'USD - US Dollar' => 'USD - US-Dollar',
- 'Remaining' => 'Verbleibend',
'Destination column' => 'Zielspalte',
'Move the task to another column when assigned to a user' => 'Aufgabe in eine andere Spalte verschieben, wenn ein User zugeordnet wurde.',
'Move the task to another column when assignee is cleared' => 'Aufgabe in eine andere Spalte verschieben, wenn die Zuordnung gelöscht wurde.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurse',
'Change reference currency' => 'Referenzwährung ändern',
'Add a new currency rate' => 'Neuen Währungskurs hinzufügen',
- 'Currency rates are used to calculate project budget.' => 'Währungskurse werden verwendet, um das Projektbudget zu berechnen.',
'Reference currency' => 'Referenzwährung',
'The currency rate have been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.',
'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget-Übersicht',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genügend Daten für diese Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index ac7c0e4b..1e15d8c0 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remota',
'Enabled' => 'Activada',
'Disabled' => 'Desactivada',
- 'Google account linked' => 'Vinculada con Cuenta de Google',
- 'Github account linked' => 'Vinculada con Cuenta de Gitgub',
'Username:' => 'Nombre de Usuario:',
'Name:' => 'Nombre:',
'Email:' => 'Correo electrónico:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Desplazamiento horizontal',
'Compact/wide view' => 'Vista compacta/amplia',
'No results match:' => 'No hay resultados coincidentes:',
- 'Remove hourly rate' => 'Quitar cobro horario',
- 'Do you really want to remove this hourly rate?' => '¿Realmente quire quitar el cobro horario?',
- 'Hourly rates' => 'Cobros horarios',
- 'Hourly rate' => 'Cobro horario',
'Currency' => 'Moneda',
- 'Effective date' => 'Fecha efectiva',
- 'Add new rate' => 'Añadir nuevo cobro',
- 'Rate removed successfully.' => 'Cobro quitado con éxito.',
- 'Unable to remove this rate.' => 'No pude quitar este cobro.',
- 'Unable to save the hourly rate.' => 'No pude grabar el cobro horario.',
- 'Hourly rate created successfully.' => 'Cobro horario creado con éxito',
'Start time' => 'Tiempo de inicio',
'End time' => 'Tiempo de fin',
'Comment' => 'Comentario',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Ficheros',
'Images' => 'Imágenes',
'Private project' => 'Proyecto privado',
- 'Amount' => 'Cantidad',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Presupuesto',
- 'Budget line' => 'Línea de presupuesto',
- 'Budget line removed successfully.' => 'Línea de presupuesto quitada con éxito',
- 'Budget lines' => 'Líneas de presupuesto',
'CAD - Canadian Dollar' => 'CAD - Dólar canadiense',
'CHF - Swiss Francs' => 'CHF - Francos suizos',
- 'Cost' => 'Costo',
- 'Cost breakdown' => 'Desglose de costes',
'Custom Stylesheet' => 'Hoja de estilo Personalizada',
'download' => 'descargar',
- 'Do you really want to remove this budget line?' => '¿Realmente quiere quitar esta línea de presupuesto?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Gastos',
'GBP - British Pound' => 'GBP - Libra británica',
'INR - Indian Rupee' => 'INR - Rupias indúes',
'JPY - Japanese Yen' => 'JPY - Yen japonés',
- 'New budget line' => 'Nueva línea de presupuesto',
'NZD - New Zealand Dollar' => 'NZD - Dóloar neocelandés',
- 'Remove a budget line' => 'Quitar una línea de presupuesto',
- 'Remove budget line' => 'Quitar línea de presupuesto',
'RSD - Serbian dinar' => 'RSD - Dinar serbio',
- 'The budget line have been created successfully.' => 'Se ha creado la línea de presupuesto con éxito.',
- 'Unable to create the budget line.' => 'No pude crear la línea de presupuesto.',
- 'Unable to remove this budget line.' => 'No pude quitar esta línea de presupuesto.',
'USD - US Dollar' => 'USD - Dólar Estadounidense',
- 'Remaining' => 'Restante',
'Destination column' => 'Columna destino',
'Move the task to another column when assigned to a user' => 'Mover la tarea a otra columna al asignarse al usuario',
'Move the task to another column when assignee is cleared' => 'Mover la tarea a otra columna al quitar el concesionario',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Cambio',
'Change reference currency' => 'Cambiar moneda de referencia',
'Add a new currency rate' => 'Añadir nuevo cambio de moneda',
- 'Currency rates are used to calculate project budget.' => 'Se usan los cambios de moneda para calcular el presupuesto del proyecto.',
'Reference currency' => 'Moneda de referencia',
'The currency rate have been added successfully.' => 'Se ha añadido el cambio de moneda con éxito',
'Unable to add this currency rate.' => 'No pude añadir este cambio de moneda.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s movió la tarea #%d a la primera calle',
'%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d a la calle "%s"',
'Swimlane' => 'Calle',
- 'Budget overview' => 'Resumen del Presupuesto',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'No hay datos suficientes como para mostrar algo.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Desatendida',
@@ -1040,31 +1008,31 @@ return array(
'Shared project' => 'Proyecto compartido',
'Project managers' => 'Administradores de proyecto',
'Project members' => 'Miembros de proyecto',
- // 'Gantt chart for all projects' => '',
- // 'Projects list' => '',
- // 'Gantt chart for this project' => '',
- // 'Project board' => '',
- // 'End date:' => '',
- // 'There is no start date or end date for this project.' => '',
- // 'Projects Gantt chart' => '',
- // 'Start date: %s' => '',
- // 'End date: %s' => '',
- // 'Link type' => '',
- // 'Change task color when using a specific task link' => '',
- // 'Task link creation or modification' => '',
- // 'Login with my Gitlab Account' => '',
- // 'Milestone' => '',
- // 'Gitlab Authentication' => '',
- // 'Help on Gitlab authentication' => '',
- // 'Gitlab Id' => '',
- // 'Gitlab Account' => '',
- // 'Link my Gitlab Account' => '',
- // 'Unlink my Gitlab Account' => '',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Gantt chart for all projects' => 'Diagrama de Gantt para todos los proyectos',
+ 'Projects list' => 'Lista de proyectos',
+ 'Gantt chart for this project' => 'Diagrama de Gantt para este proyecto',
+ 'Project board' => 'Tablero del proyecto',
+ 'End date:' => 'Fecha final',
+ 'There is no start date or end date for this project.' => 'No existe fecha de inicio o de fin para este proyecto.',
+ 'Projects Gantt chart' => 'Diagramas de Gantt de los proyectos',
+ 'Start date: %s' => 'Fecha inicial: %s',
+ 'End date: %s' => 'Fecha final: %s',
+ 'Link type' => 'Tipo de enlace',
+ 'Change task color when using a specific task link' => 'Cambiar colo de la tarea al usar un enlace específico a tarea',
+ 'Task link creation or modification' => 'Creación o modificación de enlace a tarea',
+ 'Login with my Gitlab Account' => 'Ingresar usando mi Cuenta en Gitlab',
+ 'Milestone' => 'Hito',
+ 'Gitlab Authentication' => 'Autenticación Gitlab',
+ 'Help on Gitlab authentication' => 'Ayuda con autenticación Gitlab',
+ 'Gitlab Id' => 'Id de Gitlab',
+ 'Gitlab Account' => 'Cuenta de Gitlab',
+ 'Link my Gitlab Account' => 'Enlazar con mi Cuenta en Gitlab',
+ 'Unlink my Gitlab Account' => 'Desenlazar con mi Cuenta en Gitlab',
+ 'Documentation: %s' => 'Documentación: %s',
+ 'Switch to the Gantt chart view' => 'Conmutar a vista de diagrama de Gantt',
+ 'Reset the search/filter box' => 'Limpiar la caja del filtro de búsqueda',
+ 'Documentation' => 'Documentación',
+ 'Table of contents' => 'Tabla de contenido',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Ayuda con permisos del proyecto',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index d8c749a3..da462831 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Etä',
'Enabled' => 'Käytössä',
'Disabled' => 'Pois käytöstä',
- 'Google account linked' => 'Google-tili liitetty',
- 'Github account linked' => 'Github-tili liitetty',
'Username:' => 'Käyttäjänimi:',
'Name:' => 'Nimi:',
'Email:' => 'Sähköpostiosoite:',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index 0c95c7eb..848b7624 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -75,19 +75,19 @@ return array(
'Change columns' => 'Changer les colonnes',
'Add a new column' => 'Ajouter une nouvelle colonne',
'Title' => 'Titre',
- 'Nobody assigned' => 'Personne assigné',
+ 'Nobody assigned' => 'Personne assignée',
'Assigned to %s' => 'Assigné à %s',
'Remove a column' => 'Supprimer une colonne',
'Remove a column from a board' => 'Supprimer une colonne d\'un tableau',
'Unable to remove this column.' => 'Impossible de supprimer cette colonne.',
'Do you really want to remove this column: "%s"?' => 'Voulez vraiment supprimer cette colonne : « %s » ?',
- 'This action will REMOVE ALL TASKS associated to this column!' => 'Cette action va supprimer toutes les tâches associées à cette colonne !',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'Cette action va supprimer toutes les tâches associées à cette colonne !',
'Settings' => 'Préférences',
'Application settings' => 'Paramètres de l\'application',
'Language' => 'Langue',
'Webhook token:' => 'Jeton de securité pour les webhooks :',
'API token:' => 'Jeton de securité pour l\'API :',
- 'Database size:' => 'Taille de la base de données :',
+ 'Database size:' => 'Taille de la base de données :',
'Download the database' => 'Télécharger la base de données',
'Optimize the database' => 'Optimiser la base de données',
'(VACUUM command)' => '(Commande VACUUM)',
@@ -96,15 +96,15 @@ return array(
'Edit a task' => 'Modifier une tâche',
'Column' => 'Colonne',
'Color' => 'Couleur',
- 'Assignee' => 'Personne assignée',
+ 'Assignee' => 'Personne assigné',
'Create another task' => 'Créer une autre tâche',
'New task' => 'Nouvelle tâche',
'Open a task' => 'Ouvrir une tâche',
- 'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?',
+ 'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?',
'Back to the board' => 'Retour au tableau',
'Created on %B %e, %Y at %k:%M %p' => 'Créé le %d/%m/%Y à %H:%M',
'There is nobody assigned' => 'Il n\'y a personne d\'assigné à cette tâche',
- 'Column on the board:' => 'Colonne sur le tableau : ',
+ 'Column on the board:' => 'Colonne sur le tableau : ',
'Status is open' => 'État ouvert',
'Status is closed' => 'État fermé',
'Close this task' => 'Fermer cette tâche',
@@ -142,7 +142,7 @@ return array(
'Unable to open this task.' => 'Impossible d\'ouvrir cette tâche.',
'Task opened successfully.' => 'Tâche ouverte avec succès.',
'Unable to close this task.' => 'Impossible de fermer cette tâche.',
- 'Task closed successfully.' => 'Tâche fermé avec succès.',
+ 'Task closed successfully.' => 'Tâche fermée avec succès.',
'Unable to update your task.' => 'Impossible de modifier cette tâche.',
'Task updated successfully.' => 'Tâche mise à jour avec succès.',
'Unable to create your task.' => 'Impossible de créer cette tâche.',
@@ -167,11 +167,11 @@ return array(
'%d closed tasks' => '%d tâches terminées',
'No task for this project' => 'Aucune tâche pour ce projet',
'Public link' => 'Lien public',
- 'There is no column in your project!' => 'Il n\'y a aucune colonne dans votre projet !',
+ 'There is no column in your project!' => 'Il n\'y a aucune colonne dans votre projet !',
'Change assignee' => 'Changer la personne assignée',
'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »',
'Timezone' => 'Fuseau horaire',
- 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
+ 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
'Page not found' => 'Page introuvable',
'Complexity' => 'Complexité',
'Task limit' => 'Tâches Max.',
@@ -197,7 +197,7 @@ return array(
'%B %e, %Y' => '%d %B %Y',
'%b %e, %Y' => '%d/%m/%Y',
'Automatic actions' => 'Actions automatisées',
- 'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajouté avec succès.',
+ 'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajoutée avec succès.',
'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.',
'Remove an action' => 'Supprimer une action',
'Unable to remove this action.' => 'Impossible de supprimer cette action',
@@ -210,7 +210,7 @@ return array(
'Action parameters' => 'Paramètres de l\'action',
'Action' => 'Action',
'Event' => 'Événement',
- 'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, executer l\'action correspondante.',
+ 'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, exécuter l\'action correspondante.',
'Next step' => 'Étape suivante',
'Define action parameters' => 'Définition des paramètres de l\'action',
'Save this action' => 'Sauvegarder cette action',
@@ -237,10 +237,10 @@ return array(
'Comment removed successfully.' => 'Commentaire supprimé avec succès.',
'Unable to remove this comment.' => 'Impossible de supprimer ce commentaire.',
'Do you really want to remove this comment?' => 'Voulez-vous vraiment supprimer ce commentaire ?',
- 'Only administrators or the creator of the comment can access to this page.' => 'Uniquement les administrateurs ou le créateur du commentaire peuvent accéder à cette page.',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Seuls les administrateurs ou le créateur du commentaire peuvent accéder à cette page.',
'Current password for the user "%s"' => 'Mot de passe actuel pour l\'utilisateur « %s »',
'The current password is required' => 'Le mot de passe actuel est obligatoire',
- 'Wrong password' => 'Mauvais mot de passe',
+ 'Wrong password' => 'Mot de passe invalide',
'Unknown' => 'Inconnu',
'Last logins' => 'Dernières connexions',
'Login date' => 'Date de connexion',
@@ -263,10 +263,10 @@ return array(
'%d comments' => '%d commentaires',
'%d comment' => '%d commentaire',
'Email address invalid' => 'Adresse email invalide',
- 'Your external account is not linked anymore to your profile.' => 'Votre compte externe n\'est plus relié à votre profile.',
+ 'Your external account is not linked anymore to your profile.' => 'Votre compte externe n\'est plus relié à votre profil.',
'Unable to unlink your external account.' => 'Impossible de supprimer votre compte externe.',
- 'External authentication failed' => 'Authentification externe échouée',
- 'Your external account is linked to your profile successfully.' => 'Votre compte externe est désormais lié à votre profile.',
+ 'External authentication failed' => 'L’authentification externe a échoué',
+ 'Your external account is linked to your profile successfully.' => 'Votre compte externe est désormais lié à votre profil.',
'Email' => 'Email',
'Link my Google Account' => 'Lier mon compte Google',
'Unlink my Google Account' => 'Ne plus utiliser mon compte Google',
@@ -283,7 +283,7 @@ return array(
'Category:' => 'Catégorie :',
'Categories' => 'Catégories',
'Category not found.' => 'Catégorie introuvable',
- 'Your category have been created successfully.' => 'Votre catégorie a été créé avec succès.',
+ 'Your category have been created successfully.' => 'Votre catégorie a été créée avec succès.',
'Unable to create your category.' => 'Impossible de créer votre catégorie.',
'Your category have been updated successfully.' => 'Votre catégorie a été mise à jour avec succès.',
'Unable to update your category.' => 'Impossible de mettre à jour votre catégorie.',
@@ -311,13 +311,13 @@ return array(
'Summary' => 'Résumé',
'Time tracking' => 'Suivi du temps',
'Estimate:' => 'Estimation :',
- 'Spent:' => 'Passé :',
+ 'Spent:' => 'Passé :',
'Do you really want to remove this sub-task?' => 'Voulez-vous vraiment supprimer cette sous-tâche ?',
- 'Remaining:' => 'Restant :',
+ 'Remaining:' => 'Restant :',
'hours' => 'heures',
'spent' => 'passé',
'estimated' => 'estimé',
- 'Sub-Tasks' => 'Sous-Tâches',
+ 'Sub-Tasks' => 'Sous-tâches',
'Add a sub-task' => 'Ajouter une sous-tâche',
'Original estimate' => 'Estimation originale',
'Create another sub-task' => 'Créer une autre sous-tâche',
@@ -332,8 +332,8 @@ return array(
'Sub-task updated successfully.' => 'Sous-tâche mise à jour avec succès.',
'Unable to update your sub-task.' => 'Impossible de mettre à jour votre sous-tâche.',
'Unable to create your sub-task.' => 'Impossible de créer votre sous-tâche.',
- 'Sub-task added successfully.' => 'Sous-tâche ajouté avec succès.',
- 'Maximum size: ' => 'Taille maximum : ',
+ 'Sub-task added successfully.' => 'Sous-tâche ajoutée avec succès.',
+ 'Maximum size: ' => 'Taille maximum : ',
'Unable to upload the file.' => 'Impossible de transférer le fichier.',
'Display another project' => 'Afficher un autre projet',
'Login with my Github Account' => 'Se connecter avec mon compte Github',
@@ -397,8 +397,6 @@ return array(
'Remote' => 'Distant',
'Enabled' => 'Activé',
'Disabled' => 'Désactivé',
- 'Google account linked' => 'Compte Google attaché',
- 'Github account linked' => 'Compte Github attaché',
'Username:' => 'Nom d\'utilisateur :',
'Name:' => 'Nom :',
'Email:' => 'Email :',
@@ -447,10 +445,10 @@ return array(
'%s moved the task #%d to the position %d in the column "%s"' => '%s a déplacé la tâche n°%d à la position n°%d dans la colonne « %s »',
'Activity' => 'Activité',
'Default values are "%s"' => 'Les valeurs par défaut sont « %s »',
- 'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparé par des virgules)',
- 'Task assignee change' => 'Modification de la personne assignée sur une tâche',
- '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche n˚%d pour %s',
- '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée sur la tâche %s pour %s',
+ 'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparation par des virgules)',
+ 'Task assignee change' => 'Modification de la personne assignée à une tâche',
+ '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée à la tâche n˚%d pour %s',
+ '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée à la tâche %s pour %s',
'New password for the user "%s"' => 'Nouveau mot de passe pour l\'utilisateur « %s »',
'Choose an event' => 'Choisir un événement',
'Github commit received' => 'Commit reçu via Github',
@@ -466,7 +464,7 @@ return array(
'Reference: %s' => 'Référence : %s',
'Label' => 'Libellé',
'Database' => 'Base de données',
- 'About' => 'A propos',
+ 'About' => 'À propos',
'Database driver:' => 'Type de base de données :',
'Board settings' => 'Paramètres du tableau',
'URL and token' => 'URL et jeton de sécurité',
@@ -529,12 +527,12 @@ return array(
'Previous' => 'Précédent',
'The id must be an integer' => 'L\'id doit être un entier',
'The project id must be an integer' => 'L\'id du projet doit être un entier',
- 'The status must be an integer' => 'Le status doit être un entier',
+ 'The status must be an integer' => 'Le statut doit être un entier',
'The subtask id is required' => 'L\'id de la sous-tâche est obligatoire',
- 'The subtask id must be an integer' => 'L\'id de la sous-tâche doit être en entier',
+ 'The subtask id must be an integer' => 'L\'id de la sous-tâche doit être un entier',
'The task id is required' => 'L\'id de la tâche est obligatoire',
- 'The task id must be an integer' => 'L\'id de la tâche doit être en entier',
- 'The user id must be an integer' => 'L\'id de l\'utilisateur doit être en entier',
+ 'The task id must be an integer' => 'L\'id de la tâche doit être un entier',
+ 'The user id must be an integer' => 'L\'id de l\'utilisateur doit être un entier',
'This value is required' => 'Cette valeur est obligatoire',
'This value must be numeric' => 'Cette valeur doit être numérique',
'Unable to create this task.' => 'Impossible de créer cette tâche',
@@ -552,7 +550,7 @@ return array(
'Add a new swimlane' => 'Ajouter une nouvelle swimlane',
'Change default swimlane' => 'Modifier la swimlane par défaut',
'Default swimlane' => 'Swimlane par défaut',
- 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?',
'Inactive swimlanes' => 'Swimlanes inactives',
'Set project manager' => 'Mettre chef de projet',
'Set project member' => 'Mettre membre du projet',
@@ -570,7 +568,7 @@ return array(
'Unable to update this swimlane.' => 'Impossible de mettre à jour cette swimlane.',
'Your swimlane have been created successfully.' => 'Votre swimlane a été créée avec succès.',
'Example: "Bug, Feature Request, Improvement"' => 'Exemple: « Incident, Demande de fonctionnalité, Amélioration »',
- 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparé par des virgules)',
+ 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparation par des virgules)',
'Gitlab commit received' => 'Commit reçu via Gitlab',
'Gitlab issue opened' => 'Ouverture d\'un ticket sur Gitlab',
'Gitlab issue closed' => 'Fermeture d\'un ticket sur Gitlab',
@@ -604,25 +602,25 @@ return array(
'User dashboard' => 'Tableau de bord de l\'utilisateur',
'Allow only one subtask in progress at the same time for a user' => 'Autoriser une seule sous-tâche en progrès en même temps pour un utilisateur',
'Edit column "%s"' => 'Modifier la colonne « %s »',
- 'Select the new status of the subtask: "%s"' => 'Selectionnez le nouveau statut de la sous-tâche : « %s »',
+ 'Select the new status of the subtask: "%s"' => 'Selectionnez le nouveau statut de la sous-tâche : « %s »',
'Subtask timesheet' => 'Feuille de temps des sous-tâches',
'There is nothing to show.' => 'Il n\'y a rien à montrer.',
'Time Tracking' => 'Feuille de temps',
'You already have one subtask in progress' => 'Vous avez déjà une sous-tâche en progrès',
- 'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?',
+ 'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?',
'Disallow login form' => 'Interdir le formulaire d\'authentification',
'Bitbucket commit received' => 'Commit reçu via Bitbucket',
'Bitbucket webhooks' => 'Webhook Bitbucket',
'Help on Bitbucket webhooks' => 'Aide sur les webhooks Bitbucket',
'Start' => 'Début',
'End' => 'Fin',
- 'Task age in days' => 'Age de la tâche en jours',
+ 'Task age in days' => 'Âge de la tâche en jours',
'Days in this column' => 'Jours dans cette colonne',
'%dd' => '%dj',
'Add a link' => 'Ajouter un lien',
'Add a new link' => 'Ajouter un nouveau lien',
- 'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?',
- 'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?',
+ 'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?',
+ 'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?',
'Field required' => 'Champ obligatoire',
'Link added successfully.' => 'Lien créé avec succès.',
'Link updated successfully.' => 'Lien mis à jour avec succès.',
@@ -658,7 +656,7 @@ return array(
'Expand tasks' => 'Déplier les tâches',
'Collapse tasks' => 'Replier les tâches',
'Expand/collapse tasks' => 'Plier/déplier les tâches',
- 'Close dialog box' => 'Fermer une boite de dialogue',
+ 'Close dialog box' => 'Fermer une boîte de dialogue',
'Submit a form' => 'Enregistrer un formulaire',
'Board view' => 'Page du tableau',
'Keyboard shortcuts' => 'Raccourcis clavier',
@@ -669,17 +667,7 @@ return array(
'Horizontal scrolling' => 'Défilement horizontal',
'Compact/wide view' => 'Basculer entre la vue compacte et étendue',
'No results match:' => 'Aucun résultat :',
- 'Remove hourly rate' => 'Supprimer un taux horaire',
- 'Do you really want to remove this hourly rate?' => 'Voulez-vous vraiment supprimer ce taux horaire ?',
- 'Hourly rates' => 'Taux horaires',
- 'Hourly rate' => 'Taux horaire',
'Currency' => 'Devise',
- 'Effective date' => 'Date d\'effet',
- 'Add new rate' => 'Ajouter un nouveau taux horaire',
- 'Rate removed successfully.' => 'Taux horaire supprimé avec succès.',
- 'Unable to remove this rate.' => 'Impossible de supprimer ce taux horaire.',
- 'Unable to save the hourly rate.' => 'Impossible de sauvegarder ce taux horaire.',
- 'Hourly rate created successfully.' => 'Taux horaire créé avec succès.',
'Start time' => 'Date de début',
'End time' => 'Date de fin',
'Comment' => 'Commentaire',
@@ -701,38 +689,22 @@ return array(
'Do you really want to remove this time slot?' => 'Voulez-vous vraiment supprimer ce créneau horaire ?',
'Remove time slot' => 'Supprimer un créneau horaire',
'Add new time slot' => 'Ajouter un créneau horaire',
- 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.',
'Files' => 'Fichiers',
'Images' => 'Images',
'Private project' => 'Projet privé',
- 'Amount' => 'Montant',
'AUD - Australian Dollar' => 'AUD - Dollar australien',
- 'Budget' => 'Budget',
- 'Budget line' => 'Ligne budgétaire',
- 'Budget line removed successfully.' => 'Ligne budgétaire supprimée avec succès.',
- 'Budget lines' => 'Lignes budgétaire',
'CAD - Canadian Dollar' => 'CAD - Dollar canadien',
'CHF - Swiss Francs' => 'CHF - Franc suisse',
- 'Cost' => 'Coût',
- 'Cost breakdown' => 'Détail des coûts',
'Custom Stylesheet' => 'Feuille de style personalisée',
'download' => 'télécharger',
- 'Do you really want to remove this budget line?' => 'Voulez-vous vraiment supprimer cette ligne budgétaire ?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Dépenses',
'GBP - British Pound' => 'GBP - Livre sterling',
'INR - Indian Rupee' => 'INR - Roupie indienne',
'JPY - Japanese Yen' => 'JPY - Yen',
- 'New budget line' => 'Nouvelle ligne budgétaire',
'NZD - New Zealand Dollar' => 'NZD - Dollar néo-zélandais',
- 'Remove a budget line' => 'Supprimer une ligne budgétaire',
- 'Remove budget line' => 'Supprimer une ligne budgétaire',
'RSD - Serbian dinar' => 'RSD - Dinar serbe',
- 'The budget line have been created successfully.' => 'La ligne de budgétaire a été créée avec succès.',
- 'Unable to create the budget line.' => 'Impossible de créer cette ligne budgétaire.',
- 'Unable to remove this budget line.' => 'Impossible de supprimer cette ligne budgétaire.',
'USD - US Dollar' => 'USD - Dollar américain',
- 'Remaining' => 'Restant',
'Destination column' => 'Colonne de destination',
'Move the task to another column when assigned to a user' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci est assignée à quelqu\'un',
'Move the task to another column when assignee is cleared' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci n\'est plus assignée',
@@ -748,7 +720,6 @@ return array(
'Rate' => 'Taux',
'Change reference currency' => 'Changer la monnaie de référence',
'Add a new currency rate' => 'Ajouter un nouveau taux pour une devise',
- 'Currency rates are used to calculate project budget.' => 'Le cours des devises est utilisé pour calculer le budget des projets.',
'Reference currency' => 'Devise de référence',
'The currency rate have been added successfully.' => 'Le taux de change a été ajouté avec succès.',
'Unable to add this currency rate.' => 'Impossible d\'ajouter ce taux de change',
@@ -769,8 +740,8 @@ return array(
'Code' => 'Code',
'Two factor authentication' => 'Authentification à deux-facteurs',
'Enable/disable two factor authentication' => 'Activer/désactiver l\'authentification à deux-facteurs',
- 'This QR code contains the key URI: ' => 'Ce code QR contient l\'url de la clé : ',
- 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Sauvegardez cette clé secrete dans votre logiciel TOTP (par exemple Google Authenticator ou FreeOTP).',
+ 'This QR code contains the key URI: ' => 'Ce code QR contient l\'url de la clé : ',
+ 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Sauvegardez cette clé secrète dans votre logiciel TOTP (par exemple Google Authenticator ou FreeOTP).',
'Check my code' => 'Vérifier mon code',
'Secret key: ' => 'Clé secrète : ',
'Test your device' => 'Testez votre appareil',
@@ -796,9 +767,9 @@ return array(
'Sendgrid (incoming emails)' => 'Sendgrid (emails entrants)',
'Help on Sendgrid integration' => 'Aide sur l\'intégration avec Sendgrid',
'Disable two factor authentication' => 'Désactiver l\'authentification à deux facteurs',
- 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?',
+ 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?',
'Edit link' => 'Modifier un lien',
- 'Start to type task title...' => 'Entrez le titre de la tâche...',
+ 'Start to type task title...' => 'Entrez le titre de la tâche…',
'A task cannot be linked to itself' => 'Une tâche ne peut être liée à elle-même',
'The exact same link already exists' => 'Un lien identique existe déjà',
'Recurrent task is scheduled to be generated' => 'La tâche récurrente est programmée pour être créée',
@@ -814,17 +785,17 @@ return array(
'Timeframe to calculate new due date' => 'Échelle de temps pour calculer la nouvelle date d\'échéance',
'Base date to calculate new due date' => 'Date à utiliser pour calculer la nouvelle date d\'échéance',
'Action date' => 'Date de l\'action',
- 'Base date to calculate new due date: ' => 'Date utilisée pour calculer la nouvelle date d\'échéance : ',
- 'This task has created this child task: ' => 'Cette tâche a créée la tâche enfant : ',
+ 'Base date to calculate new due date: ' => 'Date utilisée pour calculer la nouvelle date d\'échéance : ',
+ 'This task has created this child task: ' => 'Cette tâche a créée la tâche enfant : ',
'Day(s)' => 'Jour(s)',
'Existing due date' => 'Date d\'échéance existante',
- 'Factor to calculate new due date: ' => 'Facteur pour calculer la nouvelle date d\'échéance : ',
+ 'Factor to calculate new due date: ' => 'Facteur pour calculer la nouvelle date d\'échéance : ',
'Month(s)' => 'Mois',
'Recurrence' => 'Récurrence',
- 'This task has been created by: ' => 'Cette tâche a été créée par :',
- 'Recurrent task has been generated:' => 'Une tâche récurrente a été générée :',
- 'Timeframe to calculate new due date: ' => 'Échelle de temps pour calculer la nouvelle date d\'échéance : ',
- 'Trigger to generate recurrent task: ' => 'Déclencheur pour générer la tâche récurrente : ',
+ 'This task has been created by: ' => 'Cette tâche a été créée par :',
+ 'Recurrent task has been generated:' => 'Une tâche récurrente a été générée :',
+ 'Timeframe to calculate new due date: ' => 'Échelle de temps pour calculer la nouvelle date d\'échéance : ',
+ 'Trigger to generate recurrent task: ' => 'Déclencheur pour générer la tâche récurrente : ',
'When task is closed' => 'Lorsque la tâche est fermée',
'When task is moved from first column' => 'Lorsque la tâche est déplacée en dehors de la première colonne',
'When task is moved to last column' => 'Lorsque la tâche est déplacée dans la dernière colonne',
@@ -836,7 +807,7 @@ return array(
'Jabber nickname' => 'Pseudonyme Jabber',
'Multi-user chat room' => 'Salon de discussion multi-utilisateurs',
'Help on Jabber integration' => 'Aide sur l\'intégration avec Jabber',
- 'The server address must use this format: "tcp://hostname:5222"' => 'L\'adresse du serveur doit utiliser le format suivant : « tcp://hostname:5222 »',
+ 'The server address must use this format: "tcp://hostname:5222"' => 'L\'adresse du serveur doit utiliser le format suivant : « tcp://hostname:5222 »',
'Calendar settings' => 'Paramètres du calendrier',
'Project calendar view' => 'Vue en mode projet du calendrier',
'Project settings' => 'Paramètres du projet',
@@ -849,7 +820,7 @@ return array(
'iCal feed' => 'Abonnement iCal',
'Preferences' => 'Préférences',
'Security' => 'Sécurité',
- 'Two factor authentication disabled' => 'Authentification à deux facteurs désactivé',
+ 'Two factor authentication disabled' => 'Authentification à deux facteurs désactivée',
'Two factor authentication enabled' => 'Authentification à deux facteurs activée',
'Unable to update this user.' => 'Impossible de mettre à jour cet utilisateur.',
'There is no user management for private projects.' => 'Il n\'y a pas de gestion d\'utilisateurs pour les projets privés.',
@@ -862,8 +833,8 @@ return array(
'Commit made by @%s on Github' => 'Commit fait par @%s sur Github',
'By @%s on Github' => 'Par @%s sur Github',
'Commit made by @%s on Gitlab' => 'Commit fait par @%s sur Gitlab',
- 'Add a comment log when moving the task between columns' => 'Ajouter un commentaire d\'information lorsque une tâche est déplacée dans une autre colonnes',
- 'Move the task to another column when the category is changed' => 'Déplacer une tâche vers une autre colonne lorsque la catégorie a changée',
+ 'Add a comment log when moving the task between columns' => 'Ajouter un commentaire d\'information lorsque une tâche est déplacée dans une autre colonne',
+ 'Move the task to another column when the category is changed' => 'Déplacer une tâche vers une autre colonne lorsque la catégorie a changé',
'Send a task by email to someone' => 'Envoyer une tâche par email à quelqu\'un',
'Reopen a task' => 'Rouvrir une tâche',
'Bitbucket issue opened' => 'Ticket Bitbucket ouvert',
@@ -880,16 +851,13 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s a déplacé la tâche n°%d dans la première swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s a déplacé la tâche n°%d dans la swimlane « %s »',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Vue d\'ensemble du budget',
- 'Type' => 'Type',
- 'There is not enough data to show something.' => 'Il n\'y a pas assez de données pour montrer quelque chose.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s a déplacé la tâche %s dans la première swimlane',
- '%s moved the task %s to the swimlane "%s"' => '%s a déplacé la tâche %s dans la swimlane « %s »',
- 'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période selectionnée.',
- 'This report contains all tasks information for the given date range.' => 'Ce rapport contient les informations de toutes les tâches pour la période selectionnée.',
+ '%s moved the task %s to the swimlane "%s"' => '%s a déplacé la tâche %s dans la swimlane « %s »',
+ 'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période sélectionnée.',
+ 'This report contains all tasks information for the given date range.' => 'Ce rapport contient les informations de toutes les tâches pour la période sélectionnée.',
'Project activities for %s' => 'Activité des projets pour « %s »',
'view the board on Kanboard' => 'voir le tableau sur Kanboard',
'The task have been moved to the first swimlane' => 'La tâche a été déplacée dans la première swimlane',
@@ -907,15 +875,15 @@ return array(
'Recurrence settings have been modified' => 'Les réglages de la récurrence ont été modifiés',
'Time spent changed: %sh' => 'Le temps passé a été changé : %sh',
'Time estimated changed: %sh' => 'Le temps estimé a été changé : %sh',
- 'The field "%s" have been updated' => 'Le champ « %s » a été mis à jour',
+ 'The field "%s" have been updated' => 'Le champ « %s » a été mis à jour',
'The description have been modified' => 'La description a été modifiée',
- 'Do you really want to close the task "%s" as well as all subtasks?' => 'Voulez-vous vraiment fermer la tâche « %s » ainsi que toutes ses sous-tâches ?',
+ 'Do you really want to close the task "%s" as well as all subtasks?' => 'Voulez-vous vraiment fermer la tâche « %s » ainsi que toutes ses sous-tâches ?',
'Swimlane: %s' => 'Swimlane : %s',
'I want to receive notifications for:' => 'Je veux reçevoir les notifications pour :',
'All tasks' => 'Toutes les Tâches',
'Only for tasks assigned to me' => 'Seulement les tâches qui me sont assignées',
'Only for tasks created by me' => 'Seulement les tâches que j\'ai créées',
- 'Only for tasks created by me and assigned to me' => 'Seulement les tâches créées par moi-même et celles qui me sont asignées',
+ 'Only for tasks created by me and assigned to me' => 'Seulement les tâches créées par moi-même et celles qui me sont assignées',
'%A' => '%A',
'%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M',
'New due date: %B %e, %Y' => 'Nouvelle date d\'échéance : %d/%m/%Y',
@@ -995,7 +963,7 @@ return array(
'Github Id' => 'Identifiant Github',
'Remote user' => 'Utilisateur distant',
'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Les utilisateurs distants ne stockent pas leur mot de passe dans la base de données de Kanboard, exemples : comptes LDAP, Github ou Google.',
- 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si vous cochez la case « Interdir le formulaire d\'authentification », les identifiants entrés dans le formulaire d\'authentification seront ignorés.',
+ 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si vous cochez la case « Interdire le formulaire d\'authentification », les identifiants entrés dans le formulaire d\'authentification seront ignorés.',
'By @%s on Gitlab' => 'Par @%s sur Gitlab',
'Gitlab issue comment created' => 'Commentaire créé sur un ticket Gitlab',
'New remote user' => 'Créer un utilisateur distant',
@@ -1023,14 +991,14 @@ return array(
'Sort by position' => 'Trier par position',
'Sort by date' => 'Trier par date',
'Add task' => 'Ajouter une tâche',
- 'Start date:' => 'Date de début :',
- 'Due date:' => 'Date d\'échéance :',
+ 'Start date:' => 'Date de début :',
+ 'Due date:' => 'Date d\'échéance :',
'There is no start date or due date for this task.' => 'Il n\'y a pas de date de début ou de date de fin pour cette tâche.',
'Moving or resizing a task will change the start and due date of the task.' => 'Déplacer ou redimensionner une tâche va changer la date de début et la date de fin de la tâche.',
- 'There is no task in your project.' => 'Il n\'y a aucun tâche dans votre projet.',
+ 'There is no task in your project.' => 'Il n\'y a aucune tâche dans votre projet.',
'Gantt chart' => 'Diagramme de Gantt',
- 'People who are project managers' => 'Personnes qui sont gestionnaire de projet',
- 'People who are project members' => 'Personnes qui sont membre de projet',
+ 'People who are project managers' => 'Personnes qui sont gestionnaires de projet',
+ 'People who are project members' => 'Personnes qui sont membres de projet',
'NOK - Norwegian Krone' => 'NOK - Couronne norvégienne',
'Show this column' => 'Montrer cette colonne',
'Hide this column' => 'Cacher cette colonne',
@@ -1043,14 +1011,14 @@ return array(
'Project managers' => 'Gestionnaires de projet',
'Project members' => 'Membres de projet',
'Gantt chart for all projects' => 'Diagramme de Gantt pour tous les projets',
- 'Projects list' => 'List des projets',
+ 'Projects list' => 'Liste des projets',
'Gantt chart for this project' => 'Diagramme de Gantt pour ce projet',
'Project board' => 'Tableau du projet',
- 'End date:' => 'Date de fin :',
+ 'End date:' => 'Date de fin :',
'There is no start date or end date for this project.' => 'Il n\'y a pas de date de début ou de date de fin pour ce projet.',
'Projects Gantt chart' => 'Diagramme de Gantt des projets',
- 'Start date: %s' => 'Date de début : %s',
- 'End date: %s' => 'Date de fin : %s',
+ 'Start date: %s' => 'Date de début : %s',
+ 'End date: %s' => 'Date de fin : %s',
'Link type' => 'Type de lien',
'Change task color when using a specific task link' => 'Changer la couleur de la tâche lorsqu\'un lien spécifique est utilisé',
'Task link creation or modification' => 'Création ou modification d\'un lien sur une tâche',
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index b346f1e3..a3bbd8f5 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Távoli',
'Enabled' => 'Engedélyezve',
'Disabled' => 'Letiltva',
- 'Google account linked' => 'Google fiók összekapcsolva',
- 'Github account linked' => 'Github fiók összekapcsolva',
'Username:' => 'Felhasználónév:',
'Name:' => 'Név:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Vízszintes görgetés',
'Compact/wide view' => 'Kompakt/széles nézet',
'No results match:' => 'Nincs találat:',
- 'Remove hourly rate' => 'Órabér törlése',
- 'Do you really want to remove this hourly rate?' => 'Valóban törölni kívánja az órabért?',
- 'Hourly rates' => 'Órabérek',
- 'Hourly rate' => 'Órabér',
'Currency' => 'Pénznem',
- 'Effective date' => 'Hatálybalépés ideje',
- 'Add new rate' => 'Új bér',
- 'Rate removed successfully.' => 'Bér sikeresen törölve.',
- 'Unable to remove this rate.' => 'Bér törlése sikertelen.',
- 'Unable to save the hourly rate.' => 'Órabér mentése sikertelen.',
- 'Hourly rate created successfully.' => 'Órabér sikeresen mentve.',
'Start time' => 'Kezdés ideje',
'End time' => 'Végzés ideje',
'Comment' => 'Megjegyzés',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Fájlok',
'Images' => 'Képek',
'Private project' => 'Privát projekt',
- 'Amount' => 'Összeg',
'AUD - Australian Dollar' => 'AUD - Ausztrál dollár',
- 'Budget' => 'Költségvetés',
- 'Budget line' => 'Költségvetési tétel',
- 'Budget line removed successfully.' => 'Költségvetési tétel sikeresen törölve.',
- 'Budget lines' => 'Költségvetési tételek',
'CAD - Canadian Dollar' => 'CAD - Kanadai dollár',
'CHF - Swiss Francs' => 'CHF - Svájci frank',
- 'Cost' => 'Költség',
- 'Cost breakdown' => 'Költség visszaszámlálás',
'Custom Stylesheet' => 'Egyéni sítluslap',
'download' => 'letöltés',
- 'Do you really want to remove this budget line?' => 'Biztos törölni akarja ezt a költségvetési tételt?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Kiadások',
'GBP - British Pound' => 'GBP - Angol font',
'INR - Indian Rupee' => 'INR - Indiai rúpia',
'JPY - Japanese Yen' => 'JPY - Japán Yen',
- 'New budget line' => 'Új költségvetési tétel',
'NZD - New Zealand Dollar' => 'NZD - Új-Zélandi dollár',
- 'Remove a budget line' => 'Költségvetési tétel törlése',
- 'Remove budget line' => 'Költségvetési tétel törlése',
'RSD - Serbian dinar' => 'RSD - Szerb dínár',
- 'The budget line have been created successfully.' => 'Költségvetési tétel sikeresen létrehozva.',
- 'Unable to create the budget line.' => 'Költségvetési tétel létrehozása sikertelen.',
- 'Unable to remove this budget line.' => 'Költségvetési tétel törlése sikertelen.',
'USD - US Dollar' => 'USD - Amerikai ollár',
- 'Remaining' => 'Maradék',
'Destination column' => 'Cél oszlop',
'Move the task to another column when assigned to a user' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés után',
'Move the task to another column when assignee is cleared' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés törlésekor',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php
new file mode 100644
index 00000000..a7b52c6f
--- /dev/null
+++ b/app/Locale/id_ID/translations.php
@@ -0,0 +1,1072 @@
+<?php
+
+return array(
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => ' ',
+ 'None' => 'Tidak satupun',
+ 'edit' => 'modifikasi',
+ 'Edit' => 'Modifikasi',
+ 'remove' => 'hapus',
+ 'Remove' => 'Hapus',
+ 'Update' => 'Perbaharui',
+ 'Yes' => 'Ya',
+ 'No' => 'Tidak',
+ 'cancel' => 'batal',
+ 'or' => 'atau',
+ 'Yellow' => 'Kuning',
+ 'Blue' => 'Biru',
+ 'Green' => 'Hijau',
+ 'Purple' => 'Ungu',
+ 'Red' => 'Merah',
+ 'Orange' => 'Jingga',
+ 'Grey' => 'Abu-abu',
+ 'Brown' => 'Coklat',
+ 'Deep Orange' => 'Oranye',
+ 'Dark Grey' => 'Abu-abu Gelap',
+ 'Pink' => 'Merah Muda',
+ 'Teal' => 'Teal',
+ 'Cyan'=> 'Sian',
+ 'Lime' => 'Lime',
+ 'Light Green' => 'Hijau Muda',
+ 'Amber' => 'Amber',
+ 'Save' => 'Simpan',
+ 'Login' => 'Masuk',
+ 'Official website:' => 'Situs resmi :',
+ 'Unassigned' => 'Belum ditugaskan',
+ 'View this task' => 'Lihat tugas ini',
+ 'Remove user' => 'Hapus pengguna',
+ 'Do you really want to remove this user: "%s"?' => 'Anda yakin akan menghapus pengguna ini : « %s » ?',
+ 'New user' => 'Pengguna baru',
+ 'All users' => 'Semua pengguna',
+ 'Username' => 'Nama pengguna',
+ 'Password' => 'Kata sandi',
+ 'Administrator' => 'Administrator',
+ 'Sign in' => 'Masuk',
+ 'Users' => 'Pengguna',
+ 'No user' => 'Tidak ada pengguna',
+ 'Forbidden' => 'Terlarang',
+ 'Access Forbidden' => 'Akses Dilarang',
+ 'Edit user' => 'Rubah Pengguna',
+ 'Logout' => 'Keluar',
+ 'Bad username or password' => 'Nama pengguna atau kata sandri buruk',
+ 'Edit project' => 'Rubah proyek',
+ 'Name' => 'Nama',
+ 'Projects' => 'Proyek',
+ 'No project' => 'Tidak ada proyek',
+ 'Project' => 'Proyek',
+ 'Status' => 'Status',
+ 'Tasks' => 'Tugas',
+ 'Board' => 'Papan',
+ 'Actions' => 'Tindakan',
+ 'Inactive' => 'Non Aktif',
+ 'Active' => 'Aktif',
+ 'Add this column' => 'Tambahkan kolom ini',
+ '%d tasks on the board' => '%d tugas di papan',
+ '%d tasks in total' => '%d tugas di total',
+ 'Unable to update this board.' => 'Tidak dapat memperbaharui papan ini',
+ 'Edit board' => 'Rubah papan',
+ 'Disable' => 'Nonaktifkan',
+ 'Enable' => 'Aktifkan',
+ 'New project' => 'Proyek Baru',
+ 'Do you really want to remove this project: "%s"?' => 'Apakah anda yakin akan menghapus proyek ini : « %s » ?',
+ 'Remove project' => 'Hapus proyek',
+ 'Edit the board for "%s"' => 'Rubah papan untuk « %s »',
+ 'All projects' => 'Semua proyek',
+ 'Change columns' => 'Rubah kolom',
+ 'Add a new column' => 'Tambah kolom baru',
+ 'Title' => 'Judul',
+ 'Nobody assigned' => 'Tidak ada yang ditugaskan',
+ 'Assigned to %s' => 'Ditugaskan ke %s',
+ 'Remove a column' => 'Hapus kolom',
+ 'Remove a column from a board' => 'Hapus kolom dari papan',
+ 'Unable to remove this column.' => 'Tidak dapat menghapus kolom ini.',
+ 'Do you really want to remove this column: "%s"?' => 'Apakah anda yakin akan menghapus kolom ini : « %s » ?',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'tindakan ini akan MENGHAPUS SEMUA TUGAS yang terkait dengan kolom ini!',
+ 'Settings' => 'Pengaturan',
+ 'Application settings' => 'Pengaturan aplikasi',
+ 'Language' => 'Bahasa',
+ 'Webhook token:' => 'Token webhook :',
+ 'API token:' => 'Token API :',
+ 'Database size:' => 'Ukuran basis data :',
+ 'Download the database' => 'Unduh basis data',
+ 'Optimize the database' => 'Optimasi basis data',
+ '(VACUUM command)' => '(perintah VACUUM)',
+ '(Gzip compressed Sqlite file)' => '(File Sqlite yang terkompress Gzip)',
+ 'Close a task' => 'Tutup tugas',
+ 'Edit a task' => 'Edit tugas',
+ 'Column' => 'Kolom',
+ 'Color' => 'Warna',
+ 'Assignee' => 'Orang yang ditugaskan',
+ 'Create another task' => 'Buat tugas lain',
+ 'New task' => 'Tugas baru',
+ 'Open a task' => 'Buka tugas',
+ 'Do you really want to open this task: "%s"?' => 'Apakah anda yakin akan membuka tugas ini : « %s » ?',
+ 'Back to the board' => 'Kembali ke papan',
+ 'Created on %B %e, %Y at %k:%M %p' => 'Dibuat pada tanggal %d/%m/%Y à %H:%M',
+ 'There is nobody assigned' => 'Tidak ada orang yand ditugaskan',
+ 'Column on the board:' => 'Kolom di dalam papan : ',
+ 'Status is open' => 'Status terbuka',
+ 'Status is closed' => 'Status ditutup',
+ 'Close this task' => 'Tutup tugas ini',
+ 'Open this task' => 'Buka tugas ini',
+ 'There is no description.' => 'Tidak ada deskripsi.',
+ 'Add a new task' => 'Tambah tugas baru',
+ 'The username is required' => 'nama pengguna diperlukan',
+ 'The maximum length is %d characters' => 'Panjang maksimum adalah %d karakter',
+ 'The minimum length is %d characters' => 'Panjang minimum adalah %d karakter',
+ 'The password is required' => 'Kata sandi diperlukan',
+ 'This value must be an integer' => 'Nilai ini harus integer',
+ 'The username must be unique' => 'Nama pengguna harus unik',
+ 'The user id is required' => 'Id Pengguna diperlukan',
+ 'Passwords don\'t match' => 'Kata sandi tidak cocok',
+ 'The confirmation is required' => 'Konfirmasi diperlukan',
+ 'The project is required' => 'Proyek diperlukan',
+ 'The id is required' => 'Id diperlukan',
+ 'The project id is required' => 'Id proyek diperlukan',
+ 'The project name is required' => 'Nama proyek diperlukan',
+ 'This project must be unique' => 'Proyek ini harus unik',
+ 'The title is required' => 'Judul diperlukan',
+ 'Settings saved successfully.' => 'Pengaturan berhasil disimpan.',
+ 'Unable to save your settings.' => 'Tidak dapat menyimpan pengaturan anda.',
+ 'Database optimization done.' => 'Optimasi basis data selesai.',
+ 'Your project have been created successfully.' => 'Proyek anda berhasil dibuat.',
+ 'Unable to create your project.' => 'Tidak dapat membuat proyek anda.',
+ 'Project updated successfully.' => 'Proyek berhasil diperbaharui.',
+ 'Unable to update this project.' => 'Tidak dapat memperbaharui proyek ini.',
+ 'Unable to remove this project.' => 'Tidak dapat menghapus proyek ini.',
+ 'Project removed successfully.' => 'Proyek berhasil dihapus.',
+ 'Project activated successfully.' => 'Proyek berhasil diaktivasi.',
+ 'Unable to activate this project.' => 'Tidak dapat mengaktifkan proyek ini.',
+ 'Project disabled successfully.' => 'Proyek berhasil dinonaktifkan.',
+ 'Unable to disable this project.' => 'Tidak dapat menonaktifkan proyek ini.',
+ 'Unable to open this task.' => 'Tidak dapat membuka tugas ini.',
+ 'Task opened successfully.' => 'Tugas berhasil dibuka.',
+ 'Unable to close this task.' => 'Tidak dapat menutup tugas ini.',
+ 'Task closed successfully.' => 'Tugas berhasil ditutup.',
+ 'Unable to update your task.' => 'Tidak dapat memperbaharui tugas ini.',
+ 'Task updated successfully.' => 'Tugas berhasil diperbaharui.',
+ 'Unable to create your task.' => 'Tidak dapat membuat tugas anda.',
+ 'Task created successfully.' => 'Tugas berhasil dibuat.',
+ 'User created successfully.' => 'Pengguna berhasil dibuat.',
+ 'Unable to create your user.' => 'Tidak dapat membuat pengguna anda.',
+ 'User updated successfully.' => 'Pengguna berhasil diperbaharui.',
+ 'Unable to update your user.' => 'Tidak dapat memperbaharui pengguna anda.',
+ 'User removed successfully.' => 'pengguna berhasil dihapus.',
+ 'Unable to remove this user.' => 'Tidak dapat menghapus pengguna ini.',
+ 'Board updated successfully.' => 'Papan berhasil diperbaharui.',
+ 'Ready' => 'Siap',
+ 'Backlog' => 'Tertunda',
+ 'Work in progress' => 'Sedang dalam pengerjaan',
+ 'Done' => 'Selesai',
+ 'Application version:' => 'Versi aplikasi :',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Diselesaikan pada tanggal %d/%m/%Y à %H:%M',
+ '%B %e, %Y at %k:%M %p' => '%d/%m/%Y à %H:%M',
+ 'Date created' => 'Tanggal dibuat',
+ 'Date completed' => 'Tanggal diselesaikan',
+ 'Id' => 'Id.',
+ '%d closed tasks' => '%d tugas yang ditutup',
+ 'No task for this project' => 'Tidak ada tugas dalam proyek ini',
+ 'Public link' => 'Tautan publik',
+ 'There is no column in your project!' => 'Tidak ada kolom didalam proyek anda!',
+ 'Change assignee' => 'Mengubah orang yand ditugaskan',
+ 'Change assignee for the task "%s"' => 'Mengubah orang yang ditugaskan untuk tugas « %s »',
+ 'Timezone' => 'Zona waktu',
+ 'Sorry, I didn\'t find this information in my database!' => 'Maaf, saya tidak menemukan informasi ini dalam basis data saya !',
+ 'Page not found' => 'Halaman tidak ditemukan',
+ 'Complexity' => 'Kompleksitas',
+ 'Task limit' => 'Batas tugas.',
+ 'Task count' => 'Jumlah tugas',
+ 'Edit project access list' => 'Modifikasi hak akses proyek',
+ 'Allow this user' => 'Memperbolehkan pengguna ini',
+ 'Don\'t forget that administrators have access to everything.' => 'Ingat bahwa administrator memiliki akses ke semua.',
+ 'Revoke' => 'Mencabut',
+ 'List of authorized users' => 'Daftar pengguna yang berwenang',
+ 'User' => 'Pengguna',
+ 'Nobody have access to this project.' => 'Tidak ada yang berwenang untuk mengakses proyek.',
+ 'Comments' => 'Komentar',
+ 'Write your text in Markdown' => 'Menulis teks anda didalam Markdown',
+ 'Leave a comment' => 'Tinggalkan komentar',
+ 'Comment is required' => 'Komentar diperlukan',
+ 'Leave a description' => 'Tinggalkan deskripsi',
+ 'Comment added successfully.' => 'Komentar berhasil ditambahkan.',
+ 'Unable to create your comment.' => 'Tidak dapat menambahkan komentar anda.',
+ 'Edit this task' => 'Modifikasi tugas ini',
+ 'Due Date' => 'Batas Tanggal Terakhir',
+ 'Invalid date' => 'Tanggal tidak valid',
+ 'Must be done before %B %e, %Y' => 'Harus diselesaikan sebelum tanggal %d/%m/%Y',
+ '%B %e, %Y' => '%d %B %Y',
+ '%b %e, %Y' => '%d/%m/%Y',
+ 'Automatic actions' => 'Tindakan otomatis',
+ 'Your automatic action have been created successfully.' => 'Tindakan otomatis anda berhasil dibuat.',
+ 'Unable to create your automatic action.' => 'Tidak dapat membuat tindakan otomatis anda.',
+ 'Remove an action' => 'Hapus tindakan',
+ 'Unable to remove this action.' => 'Tidak dapat menghapus tindakan ini',
+ 'Action removed successfully.' => 'Tindakan berhasil dihapus.',
+ 'Automatic actions for the project "%s"' => 'Tindakan otomatis untuk proyek ini « %s »',
+ 'Defined actions' => 'Tindakan didefinisikan',
+ 'Add an action' => 'Tambah tindakan',
+ 'Event name' => 'Nama acara',
+ 'Action name' => 'Nama tindakan',
+ 'Action parameters' => 'Parameter tindakan',
+ 'Action' => 'Tindakan',
+ 'Event' => 'Acara',
+ 'When the selected event occurs execute the corresponding action.' => 'Ketika acara yang dipilih terjadi, melakukan tindakan yang sesuai.',
+ 'Next step' => 'Langkah selanjutnya',
+ 'Define action parameters' => 'Definisi parameter tindakan',
+ 'Save this action' => 'Simpan tindakan ini',
+ 'Do you really want to remove this action: "%s"?' => 'Apakah anda yakin akan menghapus tindakan ini « %s » ?',
+ 'Remove an automatic action' => 'Hapus tindakan otomatis',
+ 'Assign the task to a specific user' => 'Menetapkan tugas untuk pengguna tertentu',
+ 'Assign the task to the person who does the action' => 'Memberikan tugas untuk orang yang melakukan tindakan',
+ 'Duplicate the task to another project' => 'Duplikasi tugas ke proyek lain',
+ 'Move a task to another column' => 'Pindahkan tugas ke kolom lain',
+ 'Task modification' => 'Modifikasi tugas',
+ 'Task creation' => 'Membuat tugas',
+ 'Closing a task' => 'Menutup tugas',
+ 'Assign a color to a specific user' => 'Menetapkan warna untuk pengguna tertentu',
+ 'Column title' => 'Judul kolom',
+ 'Position' => 'Posisi',
+ 'Move Up' => 'Pindah ke atas',
+ 'Move Down' => 'Pindah ke bawah',
+ 'Duplicate to another project' => 'Duplikasi ke proyek lain',
+ 'Duplicate' => 'Duplikasi',
+ 'link' => 'tautan',
+ 'Comment updated successfully.' => 'Komentar berhasil diperbaharui.',
+ 'Unable to update your comment.' => 'Tidak dapat memperbaharui komentar anda.',
+ 'Remove a comment' => 'Hapus komentar',
+ 'Comment removed successfully.' => 'Komentar berhasil dihapus.',
+ 'Unable to remove this comment.' => 'Tidak dapat menghapus komentar ini.',
+ 'Do you really want to remove this comment?' => 'Apakah anda yakin akan menghapus komentar ini ?',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Hanya administrator atau pembuat komentar yang dapat mengakses halaman ini.',
+ 'Current password for the user "%s"' => 'Kata sandi saat ini untuk pengguna « %s »',
+ 'The current password is required' => 'Kata sandi saat ini diperlukan',
+ 'Wrong password' => 'Kata sandi salah',
+ 'Unknown' => 'Tidak diketahui',
+ 'Last logins' => 'Masuk terakhir',
+ 'Login date' => 'Tanggal masuk',
+ 'Authentication method' => 'Metode otentifikasi',
+ 'IP address' => 'Alamat IP',
+ 'User agent' => 'Agen Pengguna',
+ 'Persistent connections' => 'Koneksi persisten',
+ 'No session.' => 'Tidak ada sesi.',
+ 'Expiration date' => 'Tanggal kadaluarsa',
+ 'Remember Me' => 'Ingat Saya',
+ 'Creation date' => 'Tanggal dibuat',
+ 'Everybody' => 'Semua orang',
+ 'Open' => 'Terbuka',
+ 'Closed' => 'Ditutup',
+ 'Search' => 'Cari',
+ 'Nothing found.' => 'Tidak ditemukan.',
+ 'Due date' => 'Batas tanggal terakhir',
+ 'Others formats accepted: %s and %s' => 'Format lain yang didukung : %s et %s',
+ 'Description' => 'Deskripsi',
+ '%d comments' => '%d komentar',
+ '%d comment' => '%d komentar',
+ 'Email address invalid' => 'Alamat email tidak valid',
+ 'Your external account is not linked anymore to your profile.' => 'Akun eksternal anda tidak lagi terhubung ke profil anda.',
+ 'Unable to unlink your external account.' => 'Tidak dapat memutuskan akun eksternal anda.',
+ 'External authentication failed' => 'Otentifikasi eksternal gagal',
+ 'Your external account is linked to your profile successfully.' => 'Akun eksternal anda berhasil dihubungkan ke profil anda.',
+ 'Email' => 'Email',
+ 'Link my Google Account' => 'Hubungkan akun Google saya',
+ 'Unlink my Google Account' => 'Putuskan akun Google saya',
+ 'Login with my Google Account' => 'Masuk menggunakan akun Google saya',
+ 'Project not found.' => 'Proyek tidak ditemukan.',
+ 'Task removed successfully.' => 'Tugas berhasil dihapus.',
+ 'Unable to remove this task.' => 'Tidak dapat menghapus tugas ini.',
+ 'Remove a task' => 'Hapus tugas',
+ 'Do you really want to remove this task: "%s"?' => 'Apakah anda yakin akan menghapus tugas ini « %s » ?',
+ 'Assign automatically a color based on a category' => 'Otomatis menetapkan warna berdasarkan kategori',
+ 'Assign automatically a category based on a color' => 'Otomatis menetapkan kategori berdasarkan warna',
+ 'Task creation or modification' => 'Tugas dibuat atau di mofifikasi',
+ 'Category' => 'Kategori',
+ 'Category:' => 'Kategori :',
+ 'Categories' => 'Kategori',
+ 'Category not found.' => 'Kategori tidak ditemukan',
+ 'Your category have been created successfully.' => 'Kategori anda berhasil dibuat.',
+ 'Unable to create your category.' => 'Tidak dapat membuat kategori anda.',
+ 'Your category have been updated successfully.' => 'Kategori anda berhasil diperbaharui.',
+ 'Unable to update your category.' => 'Tidak dapat memperbaharui kategori anda.',
+ 'Remove a category' => 'Hapus kategori',
+ 'Category removed successfully.' => 'Kategori berhasil dihapus.',
+ 'Unable to remove this category.' => 'Tidak dapat menghapus kategori ini.',
+ 'Category modification for the project "%s"' => 'Modifikasi kategori untuk proyek « %s »',
+ 'Category Name' => 'Nama Kategori',
+ 'Add a new category' => 'Tambah kategori baru',
+ 'Do you really want to remove this category: "%s"?' => 'Apakah anda yakin akan menghapus kategori ini « %s » ?',
+ 'All categories' => 'Semua kategori',
+ 'No category' => 'Tidak ada kategori',
+ 'The name is required' => 'Nama diperlukan',
+ 'Remove a file' => 'Hapus berkas',
+ 'Unable to remove this file.' => 'Tidak dapat menghapus berkas ini.',
+ 'File removed successfully.' => 'Berkas berhasil dihapus.',
+ 'Attach a document' => 'Lampirkan dokumen',
+ 'Do you really want to remove this file: "%s"?' => 'Apakah anda yakin akan menghapus berkas ini « %s » ?',
+ 'open' => 'buka',
+ 'Attachments' => 'Lampiran',
+ 'Edit the task' => 'Modifikasi tugas',
+ 'Edit the description' => 'Modifikasi deskripsi',
+ 'Add a comment' => 'Tambahkan komentar',
+ 'Edit a comment' => 'Modifikasi komentar',
+ 'Summary' => 'Ringkasan',
+ 'Time tracking' => 'Pelacakan waktu',
+ 'Estimate:' => 'Estimasi :',
+ 'Spent:' => 'Menghabiskan:',
+ 'Do you really want to remove this sub-task?' => 'Apakah anda yakin akan menghapus sub-tugas ini ?',
+ 'Remaining:' => 'Tersisa:',
+ 'hours' => 'jam',
+ 'spent' => 'menghabiskan',
+ 'estimated' => 'perkiraan',
+ 'Sub-Tasks' => 'Sub-tugas',
+ 'Add a sub-task' => 'Tambahkan sub-tugas',
+ 'Original estimate' => 'Perkiraan semula',
+ 'Create another sub-task' => 'Tambahkan sub-tugas lainnya',
+ 'Time spent' => 'Waktu yang dihabiskan',
+ 'Edit a sub-task' => 'Modifikasi sub-tugas',
+ 'Remove a sub-task' => 'Hapus sub-tugas',
+ 'The time must be a numeric value' => 'Waktu harus berisikan numerik',
+ 'Todo' => 'Yang harus dilakukan',
+ 'In progress' => 'Sedang proses',
+ 'Sub-task removed successfully.' => 'Sub-tugas berhasil dihapus.',
+ 'Unable to remove this sub-task.' => 'Tidak dapat menghapus sub-tugas.',
+ 'Sub-task updated successfully.' => 'Sub-tugas berhasil diperbaharui.',
+ 'Unable to update your sub-task.' => 'Tidak dapat memperbaharui sub-tugas anda.',
+ 'Unable to create your sub-task.' => 'Tidak dapat membuat sub-tugas anda.',
+ 'Sub-task added successfully.' => 'Sub-tugas berhasil dibuat.',
+ 'Maximum size: ' => 'Ukuran maksimum: ',
+ 'Unable to upload the file.' => 'Tidak dapat mengunggah berkas.',
+ 'Display another project' => 'Lihat proyek lain',
+ 'Login with my Github Account' => 'Masuk menggunakan akun Github saya',
+ 'Link my Github Account' => 'Hubungkan akun Github saya ',
+ 'Unlink my Github Account' => 'Putuskan akun Github saya',
+ 'Created by %s' => 'Dibuat oleh %s',
+ 'Last modified on %B %e, %Y at %k:%M %p' => 'Modifikasi terakhir pada tanggal %d/%m/%Y à %H:%M',
+ 'Tasks Export' => 'Ekspor Tugas',
+ 'Tasks exportation for "%s"' => 'Tugas di ekspor untuk « %s »',
+ 'Start Date' => 'Tanggal Mulai',
+ 'End Date' => 'Tanggal Berakhir',
+ 'Execute' => 'Eksekusi',
+ 'Task Id' => 'Id Tugas',
+ 'Creator' => 'Pembuat',
+ 'Modification date' => 'Tanggal modifikasi',
+ 'Completion date' => 'Tanggal penyelesaian',
+ 'Clone' => 'Klon',
+ 'Project cloned successfully.' => 'Kloning proyek berhasil.',
+ 'Unable to clone this project.' => 'Tidak dapat mengkloning proyek.',
+ 'Email notifications' => 'Pemberitahuan email',
+ 'Enable email notifications' => 'Aktifkan pemberitahuan dari email',
+ 'Task position:' => 'Posisi tugas :',
+ 'The task #%d have been opened.' => 'Tugas #%d telah dibuka.',
+ 'The task #%d have been closed.' => 'Tugas #%d telah ditutup.',
+ 'Sub-task updated' => 'Sub-tugas diperbaharui',
+ 'Title:' => 'Judul :',
+ 'Status:' => 'Status :',
+ 'Assignee:' => 'Ditugaskan ke :',
+ 'Time tracking:' => 'Pelacakan waktu :',
+ 'New sub-task' => 'Sub-tugas baru',
+ 'New attachment added "%s"' => 'Lampiran baru ditambahkan « %s »',
+ 'Comment updated' => 'Komentar ditambahkan',
+ 'New comment posted by %s' => 'Komentar baru ditambahkan oleh « %s »',
+ 'New attachment' => 'Lampirkan baru',
+ 'New comment' => 'Komentar baru',
+ 'Comment updated' => 'Komentar diperbaharui',
+ 'New subtask' => 'Sub-tugas baru',
+ 'Subtask updated' => 'Sub-tugas diperbaharui',
+ 'New task' => 'Tugas baru',
+ 'Task updated' => 'Tugas diperbaharui',
+ 'Task closed' => 'Tugas ditutup',
+ 'Task opened' => 'Tugas dibuka',
+ 'I want to receive notifications only for those projects:' => 'Saya ingin menerima pemberitahuan hanya untuk proyek-proyek yang dipilih :',
+ 'view the task on Kanboard' => 'lihat tugas di Kanboard',
+ 'Public access' => 'Akses publik',
+ 'User management' => 'Manajemen pengguna',
+ 'Active tasks' => 'Tugas aktif',
+ 'Disable public access' => 'Nonaktifkan akses publik',
+ 'Enable public access' => 'Aktifkan akses publik',
+ 'Public access disabled' => 'Akses publik dinonaktifkan',
+ 'Do you really want to disable this project: "%s"?' => 'Apakah anda yakin akan menonaktifkan proyek ini : « %s » ?',
+ 'Do you really want to enable this project: "%s"?' => 'Apakah anda yakin akan mengaktifkan proyek ini : « %s » ?',
+ 'Project activation' => 'Aktivasi proyek',
+ 'Move the task to another project' => 'Pindahkan tugas ke proyek lain',
+ 'Move to another project' => 'Pindahkan ke proyek lain',
+ 'Do you really want to duplicate this task?' => 'Apakah anda yakin akan menduplikasi tugas ini ?',
+ 'Duplicate a task' => 'Duplikasi tugas',
+ 'External accounts' => 'Akun eksternal',
+ 'Account type' => 'Tipe akun',
+ 'Local' => 'Lokal',
+ 'Remote' => 'Jauh',
+ 'Enabled' => 'Aktif',
+ 'Disabled' => 'Nonaktif',
+ 'Google account linked' => 'Akun Google yang terhubung',
+ 'Github account linked' => 'Akun Github yang terhubung',
+ 'Username:' => 'Nama pengguna :',
+ 'Name:' => 'Nama :',
+ 'Email:' => 'Email :',
+ 'Notifications:' => 'Pemberitahuan :',
+ 'Notifications' => 'Pemberitahuan',
+ 'Group:' => 'Grup :',
+ 'Regular user' => 'Pengguna normal',
+ 'Account type:' => 'Tipe akun :',
+ 'Edit profile' => 'Modifikasi profil',
+ 'Change password' => 'Rubah kata sandri',
+ 'Password modification' => 'Modifikasi kata sandi',
+ 'External authentications' => 'Otentifikasi eksternal',
+ 'Google Account' => 'Akun Google',
+ 'Github Account' => 'Akun Github',
+ 'Never connected.' => 'Tidak pernah terhubung.',
+ 'No account linked.' => 'Tidak ada akun terhubung.',
+ 'Account linked.' => 'Akun terhubung.',
+ 'No external authentication enabled.' => 'Tidak ada otentifikasi eksternal yang aktif.',
+ 'Password modified successfully.' => 'Kata sandi berhasil dimodifikasi.',
+ 'Unable to change the password.' => 'Tidak dapat merubah kata sandir.',
+ 'Change category for the task "%s"' => 'Rubah kategori untuk tugas « %s »',
+ 'Change category' => 'Rubah kategori',
+ '%s updated the task %s' => '%s memperbaharui tugas %s',
+ '%s opened the task %s' => '%s membuka tugas %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s memindahkan tugas %s ke posisi n°%d dalam kolom « %s »',
+ '%s moved the task %s to the column "%s"' => '%s memindahkan tugas %s ke kolom « %s »',
+ '%s created the task %s' => '%s membuat tugas %s',
+ '%s closed the task %s' => '%s menutup tugas %s',
+ '%s created a subtask for the task %s' => '%s membuat subtugas untuk tugas %s',
+ '%s updated a subtask for the task %s' => '%s memperbaharui subtugas untuk tugas %s',
+ 'Assigned to %s with an estimate of %s/%sh' => 'Ditugaskan untuk %s dengan perkiraan %s/%sh',
+ 'Not assigned, estimate of %sh' => 'Tidak ada yang ditugaskan, perkiraan %sh',
+ '%s updated a comment on the task %s' => '%s memperbaharui komentar pada tugas %s',
+ '%s commented the task %s' => '%s memberikan komentar pada tugas %s',
+ '%s\'s activity' => 'Aktifitas dari %s',
+ 'RSS feed' => 'RSS feed',
+ '%s updated a comment on the task #%d' => '%s memperbaharui komentar pada tugas n°%d',
+ '%s commented on the task #%d' => '%s memberikan komentar pada tugas n°%d',
+ '%s updated a subtask for the task #%d' => '%s memperbaharui subtugas untuk tugas n°%d',
+ '%s created a subtask for the task #%d' => '%s membuat subtugas untuk tugas n°%d',
+ '%s updated the task #%d' => '%s memperbaharui tugas n°%d',
+ '%s created the task #%d' => '%s membuat tugas n°%d',
+ '%s closed the task #%d' => '%s menutup tugas n°%d',
+ '%s open the task #%d' => '%s membuka tugas n°%d',
+ '%s moved the task #%d to the column "%s"' => '%s memindahkan tugas n°%d ke kolom « %s »',
+ '%s moved the task #%d to the position %d in the column "%s"' => '%s memindahkan tugas n°%d ke posisi n°%d dalam kolom « %s »',
+ 'Activity' => 'Aktifitas',
+ 'Default values are "%s"' => 'Standar nilai adalah« %s »',
+ 'Default columns for new projects (Comma-separated)' => 'Kolom default untuk proyek baru (dipisahkan dengan koma)',
+ 'Task assignee change' => 'Mengubah orang ditugaskan untuk tugas',
+ '%s change the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s',
+ '%s changed the assignee of the task %s to %s' => '%s mengubah orang yang ditugaskan dari tugas %s ke %s',
+ 'New password for the user "%s"' => 'Kata sandi baru untuk pengguna « %s »',
+ 'Choose an event' => 'Pilih acara',
+ 'Github commit received' => 'Menerima komit dari Github',
+ 'Github issue opened' => 'Tiket Github dibuka',
+ 'Github issue closed' => 'Tiket Github ditutup',
+ 'Github issue reopened' => 'Tiket Github dibuka kembali',
+ 'Github issue assignee change' => 'Rubah penugasan tiket Github',
+ 'Github issue label change' => 'Perubahan label pada tiket Github',
+ 'Create a task from an external provider' => 'Buat tugas dari pemasok eksternal',
+ 'Change the assignee based on an external username' => 'Rubah penugasan berdasarkan nama pengguna eksternal',
+ 'Change the category based on an external label' => 'Rubah kategori berdasarkan label eksternal',
+ 'Reference' => 'Referensi',
+ 'Reference: %s' => 'Referensi : %s',
+ 'Label' => 'Label',
+ 'Database' => 'Basis data',
+ 'About' => 'Tentang',
+ 'Database driver:' => 'Driver basis data :',
+ 'Board settings' => 'Pengaturan papan',
+ 'URL and token' => 'URL dan token',
+ 'Webhook settings' => 'Pengaturan webhook',
+ 'URL for task creation:' => 'URL untuk pembuatan tugas :',
+ 'Reset token' => 'Mereset token',
+ 'API endpoint:' => 'API endpoint :',
+ 'Refresh interval for private board' => 'Interval pembaruan untuk papan pribadi',
+ 'Refresh interval for public board' => 'Interval pembaruan untuk papan publik',
+ 'Task highlight period' => 'Periode puncak tugas',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (dalam detik) untuk mempertimbangkan tugas yang baru dimodifikasi (0 untuk menonaktifkan, standar 2 hari)',
+ 'Frequency in second (60 seconds by default)' => 'Frequensi dalam detik (standar 60 detik)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequensi dalam detik (0 untuk menonaktifkan fitur ini, standar 10 detik)',
+ 'Application URL' => 'URL Aplikasi',
+ 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Contoh : http://exemple.kanboard.net/ (digunakan untuk pemberitahuan email)',
+ 'Token regenerated.' => 'Token diregenerasi.',
+ 'Date format' => 'Format tanggal',
+ 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO selalu diterima, contoh : « %s » et « %s »',
+ 'New private project' => 'Proyek pribadi baru',
+ 'This project is private' => 'Proyek ini adalah pribadi',
+ 'Type here to create a new sub-task' => 'Ketik disini untuk membuat sub-tugas baru',
+ 'Add' => 'Tambah',
+ 'Estimated time: %s hours' => 'Perkiraan waktu: %s jam',
+ 'Time spent: %s hours' => 'Waktu dihabiskan : %s jam',
+ 'Started on %B %e, %Y' => 'Dimulai pada %d/%m/%Y',
+ 'Start date' => 'Tanggal mulai',
+ 'Time estimated' => 'Perkiraan waktu',
+ 'There is nothing assigned to you.' => 'Tidak ada yang diberikan kepada anda.',
+ 'My tasks' => 'Tugas saya',
+ 'Activity stream' => 'Arus aktifitas',
+ 'Dashboard' => 'Dasbor',
+ 'Confirmation' => 'Konfirmasi',
+ 'Allow everybody to access to this project' => 'Memungkinkan semua orang untuk mengakses proyek ini',
+ 'Everybody have access to this project.' => 'Semua orang mendapat akses untuk proyek ini.',
+ 'Webhooks' => 'Webhooks',
+ 'API' => 'API',
+ 'Github webhooks' => 'Webhook Github',
+ 'Help on Github webhooks' => 'Bantuan pada webhook Github',
+ 'Create a comment from an external provider' => 'Buat komentar dari pemasok eksternal',
+ 'Github issue comment created' => 'Komentar dibuat pada tiket Github',
+ 'Project management' => 'Manajemen proyek',
+ 'My projects' => 'Proyek saya',
+ 'Columns' => 'Kolom',
+ 'Task' => 'Tugas',
+ 'Your are not member of any project.' => 'Anda bukan anggota dari setiap proyek.',
+ 'Percentage' => 'Persentasi',
+ 'Number of tasks' => 'Jumlah dari tugas',
+ 'Task distribution' => 'Pembagian tugas',
+ 'Reportings' => 'Pelaporan',
+ 'Task repartition for "%s"' => 'Pembagian tugas untuk « %s »',
+ 'Analytics' => 'Analitis',
+ 'Subtask' => 'Subtugas',
+ 'My subtasks' => 'Subtugas saya',
+ 'User repartition' => 'Partisi ulang pengguna',
+ 'User repartition for "%s"' => 'Partisi ulang pengguna untuk « %s »',
+ 'Clone this project' => 'Gandakan proyek ini',
+ 'Column removed successfully.' => 'Kolom berhasil dihapus.',
+ 'Github Issue' => 'Tiket Github',
+ 'Not enough data to show the graph.' => 'Tidak cukup data untuk menampilkan grafik.',
+ 'Previous' => 'Sebelumnya',
+ 'The id must be an integer' => 'Id harus integer',
+ 'The project id must be an integer' => 'Id proyek harus integer',
+ 'The status must be an integer' => 'Status harus integer',
+ 'The subtask id is required' => 'Id subtugas diperlukan',
+ 'The subtask id must be an integer' => 'Id subtugas harus integer',
+ 'The task id is required' => 'Id tugas diperlukan',
+ 'The task id must be an integer' => 'Id tugas harus integer',
+ 'The user id must be an integer' => 'Id user harus integer',
+ 'This value is required' => 'Nilai ini diperlukan',
+ 'This value must be numeric' => 'Nilai ini harus angka',
+ 'Unable to create this task.' => 'Tidak dapat membuat tugas ini',
+ 'Cumulative flow diagram' => 'Diagram alir kumulatif',
+ 'Cumulative flow diagram for "%s"' => 'Diagram alir kumulatif untuk « %s »',
+ 'Daily project summary' => 'Ringkasan proyek harian',
+ 'Daily project summary export' => 'Ekspor ringkasan proyek harian',
+ 'Daily project summary export for "%s"' => 'Ekspor ringkasan proyek harian untuk « %s »',
+ 'Exports' => 'Ekspor',
+ 'This export contains the number of tasks per column grouped per day.' => 'Ekspor ini berisi jumlah dari tugas per kolom dikelompokan perhari.',
+ 'Nothing to preview...' => 'Tidak ada yang dapat dilihat...',
+ 'Preview' => 'Preview',
+ 'Write' => 'Tulis',
+ 'Active swimlanes' => 'Swimlanes aktif',
+ 'Add a new swimlane' => 'Tambah swimlane baru',
+ 'Change default swimlane' => 'Modifikasi standar swimlane',
+ 'Default swimlane' => 'Standar swimlane',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Apakah anda yakin akan menghapus swimlane ini : « %s » ?',
+ 'Inactive swimlanes' => 'Swimlanes tidak aktif',
+ 'Set project manager' => 'Masukan manajer proyek',
+ 'Set project member' => 'Masukan anggota proyek ',
+ 'Remove a swimlane' => 'Supprimer une swimlane',
+ 'Rename' => 'Ganti nama',
+ 'Show default swimlane' => 'Perlihatkan standar swimlane',
+ 'Swimlane modification for the project "%s"' => 'Modifikasi swimlane untuk proyek « %s »',
+ 'Swimlane not found.' => 'Swimlane tidak ditemukan.',
+ 'Swimlane removed successfully.' => 'Swimlane berhasil dihapus.',
+ 'Swimlanes' => 'Swimlanes',
+ 'Swimlane updated successfully.' => 'Swimlane berhasil diperbaharui.',
+ 'The default swimlane have been updated successfully.' => 'Standar swimlane berhasil diperbaharui.',
+ 'Unable to create your swimlane.' => 'Tidak dapat membuat swimlane anda.',
+ 'Unable to remove this swimlane.' => 'Tidak dapat menghapus swimlane ini.',
+ 'Unable to update this swimlane.' => 'Tidak dapat memperbaharui swimlane ini.',
+ 'Your swimlane have been created successfully.' => 'Swimlane anda berhasil dibuat.',
+ 'Example: "Bug, Feature Request, Improvement"' => 'Contoh: « Insiden, Permintaan Fitur, Perbaikan »',
+ 'Default categories for new projects (Comma-separated)' => 'Standar kategori untuk proyek baru (dipisahkan dengan koma)',
+ 'Gitlab commit received' => 'Menerima komit Gitlab',
+ 'Gitlab issue opened' => 'Tiket Gitlab dibuka',
+ 'Gitlab issue closed' => 'Tiket Gitlab ditutup',
+ 'Gitlab webhooks' => 'Webhook Gitlab',
+ 'Help on Gitlab webhooks' => 'Bantuan pada webhook Gitlab',
+ 'Integrations' => 'Integrasi',
+ 'Integration with third-party services' => 'Integrasi dengan layanan pihak ketiga',
+ 'Role for this project' => 'Peran untuk proyek ini',
+ 'Project manager' => 'Manajer proyek',
+ 'Project member' => 'Anggota proyek',
+ 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Seorang manajer proyek dapat mengubah pengaturan proyek dan memiliki lebih banyak keistimewaan dibandingkan dengan pengguna biasa.',
+ 'Gitlab Issue' => 'Tiket Gitlab',
+ 'Subtask Id' => 'Id Subtugas',
+ 'Subtasks' => 'Subtugas',
+ 'Subtasks Export' => 'Ekspor Subtugas',
+ 'Subtasks exportation for "%s"' => 'Ekspor subtugas untuk « %s »',
+ 'Task Title' => 'Judul Tugas',
+ 'Untitled' => 'Tanpa nama',
+ 'Application default' => 'Aplikasi standar',
+ 'Language:' => 'Bahasa :',
+ 'Timezone:' => 'Zona waktu :',
+ 'All columns' => 'Semua kolom',
+ 'Calendar' => 'Kalender',
+ 'Next' => 'Selanjutnya',
+ '#%d' => 'nËš%d',
+ 'All swimlanes' => 'Semua swimlane',
+ 'All colors' => 'Semua warna',
+ 'All status' => 'Semua status',
+ 'Moved to column %s' => 'Pindah ke kolom %s',
+ 'Change description' => 'Rubah deskripsi',
+ 'User dashboard' => 'Dasbor pengguna',
+ 'Allow only one subtask in progress at the same time for a user' => 'Izinkan hanya satu subtugas dalam proses secara bersamaan untuk satu pengguna',
+ 'Edit column "%s"' => 'Modifikasi kolom « %s »',
+ 'Select the new status of the subtask: "%s"' => 'Pilih status baru untuk subtugas : « %s »',
+ 'Subtask timesheet' => 'Subtugas absen',
+ 'There is nothing to show.' => 'Tidak ada yang dapat diperlihatkan.',
+ 'Time Tracking' => 'Pelacakan waktu',
+ 'You already have one subtask in progress' => 'Anda sudah ada satu subtugas dalam proses',
+ 'Which parts of the project do you want to duplicate?' => 'Bagian dalam proyek mana yang ingin anda duplikasi?',
+ 'Disallow login form' => 'Larang formulir masuk',
+ 'Bitbucket commit received' => 'Menerima komit Bitbucket',
+ 'Bitbucket webhooks' => 'Webhook Bitbucket',
+ 'Help on Bitbucket webhooks' => 'Bantuan pada webhook Bitbucket',
+ 'Start' => 'Mulai',
+ 'End' => 'Selesai',
+ 'Task age in days' => 'Usia tugas dalam hari',
+ 'Days in this column' => 'Hari dalam kolom ini',
+ '%dd' => '%dj',
+ 'Add a link' => 'Menambahkan tautan',
+ 'Add a new link' => 'Tambah tautan baru',
+ 'Do you really want to remove this link: "%s"?' => 'Apakah anda yakin akan menghapus tautan ini : « %s » ?',
+ 'Do you really want to remove this link with task #%d?' => 'Apakah anda yakin akan menghapus tautan ini dengan tugas n°%d ?',
+ 'Field required' => 'Field diperlukan',
+ 'Link added successfully.' => 'Tautan berhasil ditambahkan.',
+ 'Link updated successfully.' => 'Tautan berhasil diperbaharui.',
+ 'Link removed successfully.' => 'Tautan berhasil dihapus.',
+ 'Link labels' => 'Label tautan',
+ 'Link modification' => 'Modifikasi tautan',
+ 'Links' => 'Tautan',
+ 'Link settings' => 'Pengaturan tautan',
+ 'Opposite label' => 'Label berlawanan',
+ 'Remove a link' => 'Hapus tautan',
+ 'Task\'s links' => 'Tautan tugas',
+ 'The labels must be different' => 'Label harus berbeda',
+ 'There is no link.' => 'Tidak ada tautan.',
+ 'This label must be unique' => 'Label ini harus unik',
+ 'Unable to create your link.' => 'Tidak dapat membuat tautan anda.',
+ 'Unable to update your link.' => 'Tidak dapat memperbaharui tautan anda.',
+ 'Unable to remove this link.' => 'Tidak dapat menghapus tautan ini.',
+ 'relates to' => 'berhubungan dengan',
+ 'blocks' => 'blok',
+ 'is blocked by' => 'diblokir oleh',
+ 'duplicates' => 'duplikat',
+ 'is duplicated by' => 'diduplikasi oleh',
+ 'is a child of' => 'anak dari',
+ 'is a parent of' => 'orant tua dari',
+ 'targets milestone' => 'milestone target',
+ 'is a milestone of' => 'adalah milestone dari',
+ 'fixes' => 'perbaikan',
+ 'is fixed by' => 'diperbaiki oleh',
+ 'This task' => 'Tugas ini',
+ '<1h' => '<1h',
+ '%dh' => '%dh',
+ '%b %e' => '%e %b',
+ 'Expand tasks' => 'Perluas tugas',
+ 'Collapse tasks' => 'Lipat tugas',
+ 'Expand/collapse tasks' => 'Perluas/lipat tugas',
+ 'Close dialog box' => 'Tutup kotak dialog',
+ 'Submit a form' => 'Submit formulir',
+ 'Board view' => 'Table halaman',
+ 'Keyboard shortcuts' => 'pintas keyboard',
+ 'Open board switcher' => 'Buka table switcher',
+ 'Application' => 'Aplikasi',
+ 'since %B %e, %Y at %k:%M %p' => 'sejak %d/%m/%Y à %H:%M',
+ 'Compact view' => 'Tampilan kompak',
+ 'Horizontal scrolling' => 'Horisontal bergulir',
+ 'Compact/wide view' => 'Beralih antara tampilan kompak dan diperluas',
+ 'No results match:' => 'Tidak ada hasil :',
+ 'Remove hourly rate' => 'Hapus tarif per jam',
+ 'Do you really want to remove this hourly rate?' => 'Apakah anda yakin akan menghapus tarif per jam ini?',
+ 'Hourly rates' => 'Tarif per jam',
+ 'Hourly rate' => 'Tarif per jam',
+ 'Currency' => 'Mata uang',
+ 'Effective date' => 'Tanggal berlaku',
+ 'Add new rate' => 'Tambah tarif per jam baru',
+ 'Rate removed successfully.' => 'Tarif per jam berhasil dihapus.',
+ 'Unable to remove this rate.' => 'Tidak dapat menghapus tarif per jam ini.',
+ 'Unable to save the hourly rate.' => 'Tidak dapat menyimpan tarif per jam.',
+ 'Hourly rate created successfully.' => 'Tarif per jam berhasil dibuat.',
+ 'Start time' => 'Waktu mulai',
+ 'End time' => 'Waktu selesai',
+ 'Comment' => 'Komentar',
+ 'All day' => 'Semua hari',
+ 'Day' => 'Hari',
+ 'Manage timetable' => 'Mengatur jadwal',
+ 'Overtime timetable' => 'Jadwal lembur',
+ 'Time off timetable' => 'Jam absensi',
+ 'Timetable' => 'Jadwal',
+ 'Work timetable' => 'Jadwal kerja',
+ 'Week timetable' => 'Jadwal mingguan',
+ 'Day timetable' => 'Jadwal harian',
+ 'From' => 'Dari',
+ 'To' => 'Untuk',
+ 'Time slot created successfully.' => 'Slot waktu berhasil dibuat.',
+ 'Unable to save this time slot.' => 'Tidak dapat menyimpan slot waktu ini.',
+ 'Time slot removed successfully.' => 'Slot waktu berhasil dihapus.',
+ 'Unable to remove this time slot.' => 'Tidak dapat menghapus slot waktu ini.',
+ 'Do you really want to remove this time slot?' => 'Apakah anda yakin akan menghapus slot waktu ini?',
+ 'Remove time slot' => 'Hapus slot waktu',
+ 'Add new time slot' => 'Tambah slot waktu baru',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Jadwal ini digunakan ketika kotak centang "sepanjang hari" dicentang untuk dijadwalkan cuti dan lembur.',
+ 'Files' => 'Arsip',
+ 'Images' => 'Gambar',
+ 'Private project' => 'Proyek pribadi',
+ 'Amount' => 'Jumlah',
+ 'AUD - Australian Dollar' => 'AUD - Dollar Australia',
+ 'Budget' => 'Anggaran',
+ 'Budget line' => 'Garis anggaran',
+ 'Budget line removed successfully.' => 'Garis anggaran berhasil dihapus.',
+ 'Budget lines' => 'Garis anggaran',
+ 'CAD - Canadian Dollar' => 'CAD - Dollar Kanada',
+ 'CHF - Swiss Francs' => 'CHF - Swiss Prancis',
+ 'Cost' => 'Biaya',
+ 'Cost breakdown' => 'Rincian biaya',
+ 'Custom Stylesheet' => 'Kustomisasi Stylesheet',
+ 'download' => 'unduh',
+ 'Do you really want to remove this budget line?' => 'Apakah anda yakin akan menghapus garis anggaran ini?',
+ 'EUR - Euro' => 'EUR - Euro',
+ 'Expenses' => 'Beban',
+ 'GBP - British Pound' => 'GBP - Poundsterling inggris',
+ 'INR - Indian Rupee' => 'INR - Rupe India',
+ 'JPY - Japanese Yen' => 'JPY - Yen Jepang',
+ 'New budget line' => 'Garis anggaran baru',
+ 'NZD - New Zealand Dollar' => 'NZD - Dollar Selandia baru',
+ 'Remove a budget line' => 'Hapus garis anggaran',
+ 'Remove budget line' => 'Hapus garis anggaran',
+ 'RSD - Serbian dinar' => 'RSD - Dinar Serbia',
+ 'The budget line have been created successfully.' => 'Garis anggaran berhasil dibuat.',
+ 'Unable to create the budget line.' => 'Tidak dapat membuat garis anggaran.',
+ 'Unable to remove this budget line.' => 'Tidak dapat menghapus garis anggaran.',
+ 'USD - US Dollar' => 'USD - Dollar Amerika',
+ 'Remaining' => 'Sisa',
+ 'Destination column' => 'Kolom tujuan',
+ 'Move the task to another column when assigned to a user' => 'Pindahkan tugas ke kolom lain ketika ditugaskan ke pengguna',
+ 'Move the task to another column when assignee is cleared' => 'Pindahkan tugas ke kolom lain ketika orang yang ditugaskan dibersihkan',
+ 'Source column' => 'Sumber kolom',
+ 'Show subtask estimates (forecast of future work)' => 'Lihat perkiraan subtugas(perkiraan di masa depan)',
+ 'Transitions' => 'Transisi',
+ 'Executer' => 'Eksekusi',
+ 'Time spent in the column' => 'Waktu yang dihabiskan dalam kolom',
+ 'Task transitions' => 'Transisi tugas',
+ 'Task transitions export' => 'Ekspor transisi tugas',
+ 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Laporan ini berisi semua kolom yang pindah untuk setiap tugas dengan tanggal, pengguna dan waktu yang dihabiskan untuk setiap transisi.',
+ 'Currency rates' => 'Nilai tukar mata uang',
+ 'Rate' => 'Tarif',
+ 'Change reference currency' => 'Mengubah referensi mata uang',
+ 'Add a new currency rate' => 'Tambahkan nilai tukar mata uang baru',
+ 'Currency rates are used to calculate project budget.' => 'Nilai tukar mata uang digunakan untuk menghitung anggaran proyek.',
+ 'Reference currency' => 'Referensi mata uang',
+ 'The currency rate have been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.',
+ 'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang',
+ 'Send notifications to a Slack channel' => 'Kirim pemberitahuan ke saluran Slack',
+ 'Webhook URL' => 'URL webhook',
+ 'Help on Slack integration' => 'Bantuan pada integrasi Slack',
+ '%s remove the assignee of the task %s' => '%s menghapus penugasan dari tugas %s',
+ 'Send notifications to Hipchat' => 'Kirim pemberitahuan ke Hipchat',
+ 'API URL' => 'API URL',
+ 'Room API ID or name' => 'Id kamar API atau nama ',
+ 'Room notification token' => 'Token notifikasi kamar',
+ 'Help on Hipchat integration' => 'Bantuan pada integrasi Hipchat',
+ 'Enable Gravatar images' => 'Mengaktifkan gambar Gravatar',
+ 'Information' => 'Informasi',
+ 'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi',
+ 'The two factor authentication code is not valid.' => 'Kode dua faktor kode otentifikasi tidak valid.',
+ 'The two factor authentication code is valid.' => 'Kode dua faktor kode otentifikasi valid.',
+ 'Code' => 'Kode',
+ 'Two factor authentication' => 'Dua faktor otentifikasi',
+ 'Enable/disable two factor authentication' => 'Matikan/hidupkan dua faktor otentifikasi',
+ 'This QR code contains the key URI: ' => 'kode QR ini mengandung kunci URI : ',
+ 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Menyimpan kunci rahasia ini dalam perangkat lunak TOTP anda(misalnya Googel Authenticator atau FreeOTP).',
+ 'Check my code' => 'Memeriksa kode saya',
+ 'Secret key: ' => 'Kunci rahasia : ',
+ 'Test your device' => 'Menguji perangkat anda',
+ 'Assign a color when the task is moved to a specific column' => 'Menetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu',
+ '%s via Kanboard' => '%s via Kanboard',
+ 'uploaded by: %s' => 'diunggah oleh %s',
+ 'uploaded on: %s' => 'diunggah pada %s',
+ 'size: %s' => 'ukuran : %s',
+ 'Burndown chart for "%s"' => 'Grafik Burndown untku « %s »',
+ 'Burndown chart' => 'Grafik Burndown',
+ 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).',
+ 'Screenshot taken %s' => 'Screenshot diambil %s',
+ 'Add a screenshot' => 'Tambah screenshot',
+ 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Mengambil screenshot dan tekan CTRL + V atau ⌘ + V untuk paste di sini.',
+ 'Screenshot uploaded successfully.' => 'Screenshot berhasil diunggah.',
+ 'SEK - Swedish Krona' => 'SEK - Krona Swedia',
+ 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Identifier proyek adalah kode alfanumerik opsional digunakan untuk mengidentifikasi proyek Anda.',
+ 'Identifier' => 'Identifier',
+ 'Postmark (incoming emails)' => 'Postmark (email masuk)',
+ 'Help on Postmark integration' => 'Bantuan pada integrasi Postmark',
+ 'Mailgun (incoming emails)' => 'Mailgun (email masuk)',
+ 'Help on Mailgun integration' => 'Bantuan pada integrasi Mailgun',
+ 'Sendgrid (incoming emails)' => 'Sendgrid (email masuk)',
+ 'Help on Sendgrid integration' => 'Bantuan pada integrasi Sendgrid',
+ 'Disable two factor authentication' => 'Matikan dua faktor otentifikasi',
+ 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Apakah anda yakin akan mematikan dua faktor otentifikasi untuk pengguna ini : « %s » ?',
+ 'Edit link' => 'Modifikasi tautan',
+ 'Start to type task title...' => 'Mulai mengetik judul tugas...',
+ 'A task cannot be linked to itself' => 'Sebuah tugas tidak dapat dikaitkan dengan dirinya sendiri',
+ 'The exact same link already exists' => 'Tautan yang sama persis sudah ada',
+ 'Recurrent task is scheduled to be generated' => 'Tugas berulang dijadwalkan akan dihasilkan',
+ 'Recurring information' => 'Informasi berulang',
+ 'Score' => 'Skor',
+ 'The identifier must be unique' => 'Identifier harus unik',
+ 'This linked task id doesn\'t exists' => 'Id tugas terkait tidak ada',
+ 'This value must be alphanumeric' => 'Nilai harus alfanumerik',
+ 'Edit recurrence' => 'Modifikasi pengulangan',
+ 'Generate recurrent task' => 'Menghasilkan tugas berulang',
+ 'Trigger to generate recurrent task' => 'Memicu untuk menghasilkan tugas berulang',
+ 'Factor to calculate new due date' => 'Faktor untuk menghitung tanggal jatuh tempo baru',
+ 'Timeframe to calculate new due date' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru',
+ 'Base date to calculate new due date' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru',
+ 'Action date' => 'Tanggal aksi',
+ 'Base date to calculate new due date: ' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru: ',
+ 'This task has created this child task: ' => 'Tugas ini telah menciptakan tugas anak ini: ',
+ 'Day(s)' => 'Hari',
+ 'Existing due date' => 'Batas waktu yang ada',
+ 'Factor to calculate new due date: ' => 'Faktor untuk menghitung tanggal jatuh tempo baru: ',
+ 'Month(s)' => 'Bulan',
+ 'Recurrence' => 'Pengulangan',
+ 'This task has been created by: ' => 'Tugas ini telah dibuat oleh:',
+ 'Recurrent task has been generated:' => 'Tugas berulang telah dihasilkan:',
+ 'Timeframe to calculate new due date: ' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru: ',
+ 'Trigger to generate recurrent task: ' => 'Pemicu untuk menghasilkan tugas berulang: ',
+ 'When task is closed' => 'Ketika tugas ditutup',
+ 'When task is moved from first column' => 'Ketika tugas dipindahkan dari kolom pertama',
+ 'When task is moved to last column' => 'Ketika tugas dipindahkan ke kolom terakhir',
+ 'Year(s)' => 'Tahun',
+ 'Jabber (XMPP)' => 'Jabber (XMPP)',
+ 'Send notifications to Jabber' => 'Kirim pemberitahuan ke Jabber',
+ 'XMPP server address' => 'alamat server XMPP',
+ 'Jabber domain' => 'Domain Jabber',
+ 'Jabber nickname' => 'Nickname Jabber',
+ 'Multi-user chat room' => 'Multi-pengguna kamar obrolan',
+ 'Help on Jabber integration' => 'Bantuan pada integrasi Jabber',
+ 'The server address must use this format: "tcp://hostname:5222"' => 'Alamat server harus menggunakan format ini : « tcp://hostname:5222 »',
+ 'Calendar settings' => 'Pengaturan kalender',
+ 'Project calendar view' => 'Tampilan kalender proyek',
+ 'Project settings' => 'Pengaturan proyek',
+ 'Show subtasks based on the time tracking' => 'Tampilkan subtugas berdasarkan pelacakan waktu',
+ 'Show tasks based on the creation date' => 'Tampilkan tugas berdasarkan tanggal pembuatan',
+ 'Show tasks based on the start date' => 'Tampilkan tugas berdasarkan tanggal mulai',
+ 'Subtasks time tracking' => 'Pelacakan waktu subtgas',
+ 'User calendar view' => 'Pengguna tampilan kalender',
+ 'Automatically update the start date' => 'Memperbarui tanggal mulai otomatis',
+ 'iCal feed' => 'iCal feed',
+ 'Preferences' => 'Preferensi',
+ 'Security' => 'Keamanan',
+ 'Two factor authentication disabled' => 'Otentifikasi dua faktor dimatikan',
+ 'Two factor authentication enabled' => 'Otentifikasi dua faktor dihidupkan',
+ 'Unable to update this user.' => 'Tidak dapat memperbarui pengguna ini.',
+ 'There is no user management for private projects.' => 'Tidak ada manajemen pengguna untuk proyek-proyek pribadi.',
+ 'User that will receive the email' => 'Pengguna yang akan menerima email',
+ 'Email subject' => 'Subjek Email',
+ 'Date' => 'Tanggal',
+ 'By @%s on Bitbucket' => 'Oleh @%s pada Bitbucket',
+ 'Bitbucket Issue' => 'Tiket Bitbucket',
+ 'Commit made by @%s on Bitbucket' => 'Komit dibuat oleh @%s pada Bitbucket',
+ 'Commit made by @%s on Github' => 'Komit dibuat oleh @%s pada Github',
+ 'By @%s on Github' => 'Oleh @%s pada Github',
+ 'Commit made by @%s on Gitlab' => 'Komit dibuat oleh @%s pada Gitlab',
+ 'Add a comment log when moving the task between columns' => 'Menambahkan log komentar ketika memindahkan tugas antara kolom',
+ 'Move the task to another column when the category is changed' => 'Pindahkan tugas ke kolom lain ketika kategori berubah',
+ 'Send a task by email to someone' => 'Kirim tugas melalui email ke seseorang',
+ 'Reopen a task' => 'Membuka kembali tugas',
+ 'Bitbucket issue opened' => 'Tiket Bitbucket dibuka',
+ 'Bitbucket issue closed' => 'Tiket Bitbucket ditutup',
+ 'Bitbucket issue reopened' => 'Tiket Bitbucket dibuka kembali',
+ 'Bitbucket issue assignee change' => 'Perubahan penugasan tiket Bitbucket',
+ 'Bitbucket issue comment created' => 'Komentar dibuat tiket Bitbucket',
+ 'Column change' => 'Kolom berubah',
+ 'Position change' => 'Posisi berubah',
+ 'Swimlane change' => 'Swimlane berubah',
+ 'Assignee change' => 'Penerima berubah',
+ '[%s] Overdue tasks' => '[%s] Tugas terlambat',
+ 'Notification' => 'Pemberitahuan',
+ '%s moved the task #%d to the first swimlane' => '%s memindahkan tugas n°%d ke swimlane pertama',
+ '%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas n°%d ke swimlane « %s »',
+ 'Swimlane' => 'Swimlane',
+ 'Budget overview' => 'Gambaran anggaran',
+ 'Type' => 'Tipe',
+ 'There is not enough data to show something.' => 'Tidak ada data yang cukup untuk menunjukkan sesuatu.',
+ 'Gravatar' => 'Gravatar',
+ 'Hipchat' => 'Hipchat',
+ 'Slack' => 'Slack',
+ '%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama',
+ '%s moved the task %s to the swimlane "%s"' => '%s memindahkan tugas %s ke swimlane « %s »',
+ 'This report contains all subtasks information for the given date range.' => 'Laporan ini berisi semua informasi subtugas untuk rentang tanggal tertentu.',
+ 'This report contains all tasks information for the given date range.' => 'Laporan ini berisi semua informasi tugas untuk rentang tanggal tertentu.',
+ 'Project activities for %s' => 'Aktifitas proyek untuk « %s »',
+ 'view the board on Kanboard' => 'lihat papan di Kanboard',
+ 'The task have been moved to the first swimlane' => 'Tugas telah dipindahkan ke swimlane pertama',
+ 'The task have been moved to another swimlane:' => 'Tugas telah dipindahkan ke swimlane lain:',
+ 'Overdue tasks for the project "%s"' => 'Tugas terlambat untuk proyek « %s »',
+ 'New title: %s' => 'Judul baru : %s',
+ 'The task is not assigned anymore' => 'Tugas tidak ditugaskan lagi',
+ 'New assignee: %s' => 'Penerima baru : %s',
+ 'There is no category now' => 'Tidak ada kategori untuk sekarang',
+ 'New category: %s' => 'Kategori baru : %s',
+ 'New color: %s' => 'Warna baru : %s',
+ 'New complexity: %d' => 'Kompleksitas baru : %d',
+ 'The due date have been removed' => 'Tanggal jatuh tempo telah dihapus',
+ 'There is no description anymore' => 'Tidak ada deskripsi lagi',
+ 'Recurrence settings have been modified' => 'Pengaturan pengulangan telah dimodifikasi',
+ 'Time spent changed: %sh' => 'Waktu yang dihabiskan berubah : %sh',
+ 'Time estimated changed: %sh' => 'Perkiraan waktu berubah : %sh',
+ 'The field "%s" have been updated' => 'Field « %s » telah diperbaharui',
+ 'The description have been modified' => 'Deskripsi telah dimodifikasi',
+ 'Do you really want to close the task "%s" as well as all subtasks?' => 'Apakah anda yakin akan menutup tugas « %s » beserta semua sub-tugasnya ?',
+ 'Swimlane: %s' => 'Swimlane : %s',
+ 'I want to receive notifications for:' => 'Saya ingin menerima pemberitahuan untuk :',
+ 'All tasks' => 'Semua tugas',
+ 'Only for tasks assigned to me' => 'Hanya untuk tugas yang ditugaskan ke saya',
+ 'Only for tasks created by me' => 'Hanya untuk tugas yang dibuat oleh saya',
+ 'Only for tasks created by me and assigned to me' => 'Hanya untuk tugas yang dibuat oleh saya dan ditugaskan ke saya',
+ '%A' => '%A',
+ '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M',
+ 'New due date: %B %e, %Y' => 'Tanggal jatuh tempo baru : %d/%m/%Y',
+ 'Start date changed: %B %e, %Y' => 'Tanggal mulai berubah : %d/%m/%Y',
+ '%k:%M %p' => '%H:%M',
+ '%%Y-%%m-%%d' => '%%d/%%m/%%Y',
+ 'Total for all columns' => 'Total untuk semua kolom',
+ 'You need at least 2 days of data to show the chart.' => 'Anda memerlukan setidaknya 2 hari dari data yang menunjukkan grafik.',
+ '<15m' => '<15m',
+ '<30m' => '<30m',
+ 'Stop timer' => 'Hentikan timer',
+ 'Start timer' => 'Mulai timer',
+ 'Add project member' => 'Tambahkan anggota proyek',
+ 'Enable notifications' => 'Aktifkan pemberitahuan',
+ 'My activity stream' => 'Aliran kegiatan saya',
+ 'My calendar' => 'Kalender saya',
+ 'Search tasks' => 'Cari tugas',
+ 'Back to the calendar' => 'Kembali ke kalender',
+ 'Filters' => 'Filter',
+ 'Reset filters' => 'Reset ulang filter',
+ 'My tasks due tomorrow' => 'Tugas saya yang berakhir besok',
+ 'Tasks due today' => 'Tugas yang berakhir hari ini',
+ 'Tasks due tomorrow' => 'Tugas yang berakhir besok',
+ 'Tasks due yesterday' => 'Tugas yang berakhir kemarin',
+ 'Closed tasks' => 'Tugas yang ditutup',
+ 'Open tasks' => 'Buka Tugas',
+ 'Not assigned' => 'Tidak ditugaskan',
+ 'View advanced search syntax' => 'Lihat sintaks pencarian lanjutan',
+ 'Overview' => 'Ikhtisar',
+ '%b %e %Y' => '%b %e %Y',
+ 'Board/Calendar/List view' => 'Tampilan Papan/Kalender/Daftar',
+ 'Switch to the board view' => 'Beralih ke tampilan papan',
+ 'Switch to the calendar view' => 'Beralih ke tampilan kalender',
+ 'Switch to the list view' => 'Beralih ke tampilan daftar',
+ 'Go to the search/filter box' => 'Pergi ke kotak pencarian/filter',
+ 'There is no activity yet.' => 'Tidak ada aktifitas saat ini.',
+ 'No tasks found.' => 'Tidak ada tugas yang ditemukan.',
+ 'Keyboard shortcut: "%s"' => 'Keyboard shortcut : « %s »',
+ 'List' => 'Daftar',
+ 'Filter' => 'Filter',
+ 'Advanced search' => 'Pencarian lanjutan',
+ 'Example of query: ' => 'Contoh dari query : ',
+ 'Search by project: ' => 'Pencarian berdasarkan proyek : ',
+ 'Search by column: ' => 'Pencarian berdasarkan kolom : ',
+ 'Search by assignee: ' => 'Pencarian berdasarkan penerima : ',
+ 'Search by color: ' => 'Pencarian berdasarkan warna : ',
+ 'Search by category: ' => 'Pencarian berdasarkan kategori : ',
+ 'Search by description: ' => 'Pencarian berdasarkan deskripsi : ',
+ 'Search by due date: ' => 'Pencarian berdasarkan tanggal jatuh tempo : ',
+ 'Lead and Cycle time for "%s"' => 'Memimpin dan Siklus waktu untuk « %s »',
+ 'Average time spent into each column for "%s"' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom untuk « %s »',
+ 'Average time spent into each column' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom',
+ 'Average time spent' => 'Rata-rata waktu yang dihabiskan',
+ 'This chart show the average time spent into each column for the last %d tasks.' => 'Grafik ini menunjukkan rata-rata waktu yang dihabiskan dalam setiap kolom untuk %d tugas.',
+ 'Average Lead and Cycle time' => 'Rata-rata Memimpin dan Siklus waktu',
+ 'Average lead time: ' => 'Rata-rata waktu pimpinan : ',
+ 'Average cycle time: ' => 'Rata-rata siklus waktu : ',
+ 'Cycle Time' => 'Siklus Waktu',
+ 'Lead Time' => 'Lead Time',
+ 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Grafik ini menunjukkan memimpin rata-rata dan waktu siklus untuk %d tugas terakhir dari waktu ke waktu.',
+ 'Average time into each column' => 'Rata-rata waktu ke setiap kolom',
+ 'Lead and cycle time' => 'Lead dan siklus waktu',
+ 'Google Authentication' => 'Google Otentifikasi',
+ 'Help on Google authentication' => 'Bantuan pada otentifikasi Google',
+ 'Github Authentication' => 'Otentifikasi Github',
+ 'Help on Github authentication' => 'Bantuan pada otentifikasi Github',
+ 'Channel/Group/User (Optional)' => 'Kanal/Grup/Pengguna (pilihan)',
+ 'Lead time: ' => 'Lead time : ',
+ 'Cycle time: ' => 'Siklus waktu : ',
+ 'Time spent into each column' => 'Waktu yang dihabiskan di setiap kolom',
+ 'The lead time is the duration between the task creation and the completion.' => 'Lead time adalah durasi antara pembuatan tugas dan penyelesaian.',
+ 'The cycle time is the duration between the start date and the completion.' => 'Siklus waktu adalah durasi antara tanggal mulai dan tanggal penyelesaian.',
+ 'If the task is not closed the current time is used instead of the completion date.' => 'Jika tugas tidak ditutup waktu saat ini yang digunakan sebagai pengganti tanggal penyelesaian.',
+ 'Set automatically the start date' => 'Secara otomatis mengatur tanggal mulai',
+ 'Edit Authentication' => 'Modifikasi Otentifikasi',
+ 'Google Id' => 'Id Google',
+ 'Github Id' => 'Id Github',
+ 'Remote user' => 'Pengguna jauh',
+ 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Pengguna jauh tidak menyimpan kata sandi mereka dalam basis data Kanboard, contoh: akun LDAP, Google dan Github.',
+ 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Jika anda mencentang kotak "Larang formulir login", kredensial masuk ke formulis login akan diabaikan.',
+ 'By @%s on Gitlab' => 'Dengan @%s pada Gitlab',
+ 'Gitlab issue comment created' => 'Komentar dibuat pada tiket Gitlab',
+ 'New remote user' => 'Pengguna baru jauh',
+ 'New local user' => 'Pengguna baru lokal',
+ 'Default task color' => 'Standar warna tugas',
+ 'Hide sidebar' => 'Sembunyikan sidebar',
+ 'Expand sidebar' => 'Perluas sidebar',
+ 'This feature does not work with all browsers.' => 'Fitur ini tidak dapat digunakan di semua browsers',
+ 'There is no destination project available.' => 'Tidak ada destinasi proyek yang tersedia.',
+ 'Trigger automatically subtask time tracking' => 'Otomatis memicu pelacakan untuk subtugas',
+ 'Include closed tasks in the cumulative flow diagram' => 'Termasuk tugas yang ditutup pada diagram aliran kumulatif',
+ 'Current swimlane: %s' => 'Swimlane saat ini : %s',
+ 'Current column: %s' => 'Kolom saat ini : %s',
+ 'Current category: %s' => 'Kategori saat ini : %s',
+ 'no category' => 'tidak ada kategori',
+ 'Current assignee: %s' => 'Saat ini ditugaskan : %s',
+ 'not assigned' => 'Belum ditugaskan',
+ 'Author:' => 'Penulis :',
+ 'contributors' => 'kontributor',
+ 'License:' => 'Lisensi :',
+ 'License' => 'Lisensi',
+ 'Project Administrator' => 'Administrator proyek',
+ 'Enter the text below' => 'Masukkan teks di bawah',
+ 'Gantt chart for %s' => 'Grafik Gantt untuk %s',
+ 'Sort by position' => 'Urutkan berdasarkan posisi',
+ 'Sort by date' => 'Urutkan berdasarkan tanggal',
+ 'Add task' => 'Tambah tugas',
+ 'Start date:' => 'Tanggal mulai :',
+ 'Due date:' => 'Batas waktu :',
+ 'There is no start date or due date for this task.' => 'Tidak ada tanggal mulai dan batas waktu untuk tugas ini.',
+ 'Moving or resizing a task will change the start and due date of the task.' => 'Memindahkan atau mengubah ukuran tugas anda akan mengubah tanggal mulai dan batas waktu dari tugas ini.',
+ 'There is no task in your project.' => 'Tidak ada tugas didalam proyek anda.',
+ 'Gantt chart' => 'Grafik Gantt',
+ 'People who are project managers' => 'Orang-orang yang menjadi manajer proyek',
+ 'People who are project members' => 'Orang-orang yang menjadi anggota proyek',
+ 'NOK - Norwegian Krone' => 'NOK - Krone Norwegia',
+ 'Show this column' => 'Perlihatkan kolom ini',
+ 'Hide this column' => 'Sembunyikan kolom ini',
+ 'open file' => 'buka berkas',
+ 'End date' => 'Waktu berakhir',
+ 'Users overview' => 'Ikhtisar pengguna',
+ 'Managers' => 'Manajer',
+ 'Members' => 'Anggota',
+ 'Shared project' => 'Proyek bersama',
+ 'Project managers' => 'Manajer proyek',
+ 'Project members' => 'Anggota proyek',
+ 'Gantt chart for all projects' => 'Grafik Gantt untuk semua proyek',
+ 'Projects list' => 'Daftar proyek',
+ 'Gantt chart for this project' => 'Grafik Gantt untuk proyek ini',
+ 'Project board' => 'Papan proyek',
+ 'End date:' => 'Waktu berakhir :',
+ 'There is no start date or end date for this project.' => 'Tidak ada waktu mulai atau waktu berakhir untuk proyek ini',
+ 'Projects Gantt chart' => 'Proyek grafik Gantt',
+ 'Start date: %s' => 'Waktu mulai : %s',
+ 'End date: %s' => 'Waktu berakhir : %s',
+ 'Link type' => 'Tipe tautan',
+ 'Change task color when using a specific task link' => 'Rubah warna tugas ketika menggunakan tautan tugas yang spesifik',
+ 'Task link creation or modification' => 'Tautan pembuatan atau modifikasi tugas ',
+ 'Login with my Gitlab Account' => 'Masuk menggunakan akun Gitlab saya',
+ 'Milestone' => 'Milestone',
+ 'Gitlab Authentication' => 'Authentification Gitlab',
+ 'Help on Gitlab authentication' => 'Bantuan pada otentifikasi Gitlab',
+ 'Gitlab Id' => 'Id Gitlab',
+ 'Gitlab Account' => 'Akun Gitlab',
+ 'Link my Gitlab Account' => 'Hubungkan akun Gitlab saya',
+ 'Unlink my Gitlab Account' => 'Putuskan akun Gitlab saya',
+ 'Documentation: %s' => 'Dokumentasi : %s',
+ 'Switch to the Gantt chart view' => 'Beralih ke tampilan grafik Gantt',
+ 'Reset the search/filter box' => 'Atur ulang pencarian/kotak filter',
+ 'Documentation' => 'Dokumentasi',
+ 'Table of contents' => 'Daftar isi',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Bantuan dengan izin proyek',
+);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 06e2c5ca..e27245f9 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Abilitato',
'Disabled' => 'Disabilitato',
- 'Google account linked' => 'Account Google collegato',
- 'Github account linked' => 'Account Github collegato',
// 'Username:' => '',
'Name:' => 'Nome:',
// 'Email:' => '',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Scrolling orizzontale',
'Compact/wide view' => 'Vista compatta/estesa',
'No results match:' => 'Nessun risultato trovato:',
- 'Remove hourly rate' => 'Rimuovi tariffa oraria',
- 'Do you really want to remove this hourly rate?' => 'Vuoi davvero rimuovere questa tariffa oraria?',
- 'Hourly rates' => 'Tariffe orarie',
- 'Hourly rate' => 'Tariffa oraria',
'Currency' => 'Valuta',
- 'Effective date' => 'Data effettiva',
- 'Add new rate' => 'Aggiungi una nuova tariffa',
- 'Rate removed successfully.' => 'Tariffa rimossa con successo.',
- 'Unable to remove this rate.' => 'Impossibile rimuovere questa tariffa.',
- 'Unable to save the hourly rate.' => 'Impossibile salvare la tariffa oraria.',
- 'Hourly rate created successfully.' => 'Tariffa oraria creata con successo.',
'Start time' => 'Data di inizio',
'End time' => 'Data di completamento',
'Comment' => 'Commento',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
'Images' => 'Immagini',
'Private project' => 'Progetto privato',
- 'Amount' => 'Totale',
'AUD - Australian Dollar' => 'AUD - Dollari Australiani',
- 'Budget' => 'Bilancio',
- 'Budget line' => 'Limite di bilancio',
- 'Budget line removed successfully.' => 'Limite al bilancio rimosso con successo.',
- 'Budget lines' => 'Limiti al bilancio',
'CAD - Canadian Dollar' => 'CAD - Dollari Canadesi',
'CHF - Swiss Francs' => 'CHF - Franchi Svizzeri',
- 'Cost' => 'Costi',
- 'Cost breakdown' => 'Abbattimento dei costi',
'Custom Stylesheet' => 'CSS personalizzato',
// 'download' => '',
- 'Do you really want to remove this budget line?' => 'Vuoi davvero rimuovere questo limite al bilancio?',
// 'EUR - Euro' => '',
- 'Expenses' => 'Spese',
'GBP - British Pound' => 'GBP - Pound Inglesi',
'INR - Indian Rupee' => 'INR - Rupie Indiani',
'JPY - Japanese Yen' => 'JPY - Yen Giapponesi',
- 'New budget line' => 'Nuovo limite al bilancio',
'NZD - New Zealand Dollar' => 'NZD - Dollari della Nuova Zelanda',
- 'Remove a budget line' => 'Rimuovi un limite al bilancio',
- 'Remove budget line' => 'Rimuovi limite di bilancio',
'RSD - Serbian dinar' => 'RSD - Dinar Serbi',
- 'The budget line have been created successfully.' => 'Il limite al bilancio è stato creato correttamente',
- 'Unable to create the budget line.' => 'Impossibile creare il limite al bilancio',
- 'Unable to remove this budget line.' => 'Impossibile rimuovere questo limite al bilancio.',
'USD - US Dollar' => 'USD - Dollari Americani',
- 'Remaining' => 'Restanti',
'Destination column' => 'Colonna destinazione',
'Move the task to another column when assigned to a user' => 'Sposta il compito in un\'altra colonna quando viene assegnato ad un utente',
'Move the task to another column when assignee is cleared' => 'Sposta il compito in un\'altra colonna quando l\'assegnatario cancellato',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Cambio',
'Change reference currency' => 'Cambia la valuta di riferimento',
'Add a new currency rate' => 'Aggiungi un nuovo tasso di cambio',
- 'Currency rates are used to calculate project budget.' => 'I tassi di cambio sono utilizzati per calcolare i bilanci dei progetti',
'Reference currency' => 'Valuta di riferimento',
'The currency rate have been added successfully.' => 'Il tasso di cambio è stato aggiunto con successo.',
'Unable to add this currency rate.' => 'Impossibile aggiungere questo tasso di cambio.',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index cb8c550b..49f92f27 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'リモート',
'Enabled' => '有効',
'Disabled' => '無効',
- 'Google account linked' => 'Google アカウントãŒãƒªãƒ³ã‚¯',
- 'Github account linked' => 'Github ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒãƒªãƒ³ã‚¯',
'Username:' => 'ユーザå:',
'Name:' => 'åå‰:',
'Email:' => 'Email:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => '縦スクロール',
'Compact/wide view' => 'コンパクトï¼ãƒ¯ã‚¤ãƒ‰ãƒ“ュー',
'No results match:' => 'çµæžœãŒä¸€è‡´ã—ã¾ã›ã‚“ã§ã—ãŸ',
- 'Remove hourly rate' => '毎時レートを削除',
- 'Do you really want to remove this hourly rate?' => '毎時レートを削除ã—ã¾ã™ã‹ï¼Ÿ',
- 'Hourly rates' => '毎時レート',
- 'Hourly rate' => '毎時レート',
'Currency' => '通貨',
- 'Effective date' => '有効期é™',
- 'Add new rate' => 'æ–°ã—ã„レート',
- 'Rate removed successfully.' => 'レートã®å‰Šé™¤ã«æˆåŠŸã—ã¾ã—ãŸã€‚',
- 'Unable to remove this rate.' => 'レートを削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Unable to save the hourly rate.' => '時間毎ã®ãƒ¬ãƒ¼ãƒˆã‚’ä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Hourly rate created successfully.' => '時間毎ã®ãƒ¬ãƒ¼ãƒˆã‚’作æˆã—ã¾ã—ãŸã€‚',
'Start time' => '開始時間',
'End time' => '終了時間',
'Comment' => 'コメント',
@@ -703,34 +691,18 @@ return array(
'Files' => 'ファイル',
'Images' => 'ç”»åƒ',
'Private project' => 'プライベートプロジェクト',
- 'Amount' => 'æ•°é‡',
'AUD - Australian Dollar' => 'AUD - 豪ドル',
- 'Budget' => '予算',
- 'Budget line' => '予算ライン',
- 'Budget line removed successfully.' => '予算ラインを削除ã—ã¾ã—ãŸ.',
- 'Budget lines' => '予算ライン',
'CAD - Canadian Dollar' => 'CAD - 加ドル',
'CHF - Swiss Francs' => 'CHF - スイスフラン',
- 'Cost' => 'コスト',
- 'Cost breakdown' => 'コストブレークダウン',
'Custom Stylesheet' => 'カスタムスタイルシート',
'download' => 'ダウンロード',
- 'Do you really want to remove this budget line?' => 'ã“ã®äºˆç®—ラインを本当ã«å‰Šé™¤ã—ã¾ã™ã‹ï¼Ÿ',
'EUR - Euro' => 'EUR - ユーロ',
- 'Expenses' => '支出',
'GBP - British Pound' => 'GBP - 独ãƒãƒ³ãƒ‰',
'INR - Indian Rupee' => 'INR - 伊ルピー',
'JPY - Japanese Yen' => 'JPY - 日本円',
- 'New budget line' => 'æ–°ã—ã„予算ライン',
'NZD - New Zealand Dollar' => 'NZD - NZ ドル',
- 'Remove a budget line' => '予算ラインã®å‰Šé™¤',
- 'Remove budget line' => '予算ラインã®å‰Šé™¤',
'RSD - Serbian dinar' => 'RSD - セルビアデナール',
- 'The budget line have been created successfully.' => '予算ラインを作æˆã—ã¾ã—ãŸ',
- 'Unable to create the budget line.' => '予算ラインを作æˆã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Unable to remove this budget line.' => '予算ラインを削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
'USD - US Dollar' => 'USD - 米ドル',
- 'Remaining' => '残り',
'Destination column' => '移動先ã®ã‚«ãƒ©ãƒ ',
'Move the task to another column when assigned to a user' => 'ユーザã®å‰²ã‚Šå½“ã¦ã‚’ã—ãŸã‚‰ã‚¿ã‚¹ã‚¯ã‚’ä»–ã®ã‚«ãƒ©ãƒ ã«ç§»å‹•',
'Move the task to another column when assignee is cleared' => 'ユーザã®å‰²ã‚Šå½“ã¦ãŒãªããªã£ãŸã‚‰ã‚¿ã‚¹ã‚¯ã‚’ä»–ã®ã‚«ãƒ©ãƒ ã«ç§»å‹•',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'レート',
'Change reference currency' => 'ç¾åœ¨ã®åŸºè»¸é€šè²¨',
'Add a new currency rate' => 'æ–°ã—ã„通貨レートを追加',
- 'Currency rates are used to calculate project budget.' => '通貨レートã¯ãƒ—ロジェクト予算ã®ç®—出ã«åˆ©ç”¨ã•れã¾ã™ã€‚',
'Reference currency' => '基軸通貨',
// 'The currency rate have been added successfully.' => '',
'Unable to add this currency rate.' => 'ã“ã®é€šè²¨ãƒ¬ãƒ¼ãƒˆã‚’追加ã§ãã¾ã›ã‚“。',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php
index 155d49b4..9880b921 100644..100755
--- a/app/Locale/nb_NO/translations.php
+++ b/app/Locale/nb_NO/translations.php
@@ -1,8 +1,8 @@
<?php
return array(
- // 'number.decimals_separator' => '',
- // 'number.thousands_separator' => '',
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => '.',
'None' => 'Ingen',
'edit' => 'rediger',
'Edit' => 'Rediger',
@@ -14,12 +14,12 @@ return array(
'cancel' => 'avbryt',
'or' => 'eller',
'Yellow' => 'Gul',
- 'Blue' => 'Blå',
- 'Green' => 'Grønn',
+ 'Blue' => 'Blå',
+ 'Green' => 'Grønn',
'Purple' => 'Lilla',
- 'Red' => 'Rød',
+ 'Red' => 'Rød',
'Orange' => 'Orange',
- 'Grey' => 'Grå',
+ 'Grey' => 'Grå',
// 'Brown' => '',
// 'Deep Orange' => '',
// 'Dark Grey' => '',
@@ -61,7 +61,7 @@ return array(
'Inactive' => 'Inaktiv',
'Active' => 'Aktiv',
'Add this column' => 'Legg til denne kolonnen',
- '%d tasks on the board' => '%d Oppgaver på hovedsiden',
+ '%d tasks on the board' => '%d Oppgaver på hovedsiden',
'%d tasks in total' => '%d Oppgaver i alt',
'Unable to update this board.' => 'Ikke mulig at oppdatere hovedsiden',
'Edit board' => 'Endre prosjektsiden',
@@ -79,15 +79,15 @@ return array(
'Assigned to %s' => 'Tildelt: %s',
'Remove a column' => 'Fjern en kolonne',
'Remove a column from a board' => 'Fjern en kolonne fra et board',
- 'Unable to remove this column.' => 'Ikke mulig fjerne denne kolonnen',
+ 'Unable to remove this column.' => 'Ikke mulig ø fjerne denne kolonnen',
'Do you really want to remove this column: "%s"?' => 'Vil du fjerne denne kolonnen: "%s"?',
'This action will REMOVE ALL TASKS associated to this column!' => 'Denne handlingen vil SLETTE ALLE OPPGAVER tilknyttet denne kolonnen',
'Settings' => 'Innstillinger',
'Application settings' => 'Applikasjonsinnstillinger',
- 'Language' => 'Språk',
+ 'Language' => 'Språk',
'Webhook token:' => 'Webhook token:',
'API token:' => 'API Token:',
- 'Database size:' => 'Databasestørrelse:',
+ 'Database size:' => 'Databasestørrelse:',
'Download the database' => 'Last ned databasen',
'Optimize the database' => 'Optimaliser databasen',
'(VACUUM command)' => '(VACUUM kommando)',
@@ -99,36 +99,37 @@ return array(
'Assignee' => 'Tildelt',
'Create another task' => 'Opprett en annen oppgave',
'New task' => 'Ny oppgave',
- 'Open a task' => 'Ã…pne en oppgave',
- 'Do you really want to open this task: "%s"?' => 'Vil du åpe denne oppgaven: "%s"?',
+ 'Open a task' => 'Åpne en oppgave',
+ 'Do you really want to open this task: "%s"?' => 'Vil du åpne denne oppgaven: "%s"?',
'Back to the board' => 'Tilbake til prosjektsiden',
'Created on %B %e, %Y at %k:%M %p' => 'Opprettet %d.%m.%Y - %H:%M',
'There is nobody assigned' => 'Mangler tildeling',
'Column on the board:' => 'Kolonne:',
- 'Status is open' => 'Status: åpen',
+ 'Status is open' => 'Status: åpen',
'Status is closed' => 'Status: lukket',
'Close this task' => 'Lukk oppgaven',
- 'Open this task' => 'Ã…pne denne oppgaven',
+ 'Open this task' => 'Åpne denne oppgaven',
'There is no description.' => 'Det er ingen beskrivelse.',
'Add a new task' => 'Opprett ny oppgave',
- 'The username is required' => 'Brukernavn er påkrevd',
+ 'The username is required' => 'Brukernavn er påkrevd',
'The maximum length is %d characters' => 'Den maksimale lengden er %d tegn',
'The minimum length is %d characters' => 'Den minimale lengden er %d tegn',
- 'The password is required' => 'Passord er påkrevet',
- 'This value must be an integer' => 'Denne verdien skal være et tall',
- 'The username must be unique' => 'Brukernavnet skal være unikt',
- 'The user id is required' => 'Bruker-id er påkrevet',
+ 'The password is required' => 'Passord er påkrevet',
+ 'This value must be an integer' => 'Denne verdien skal være et tall',
+ 'The username must be unique' => 'Brukernavnet skal være unikt',
+ 'The user id is required' => 'Bruker-id er påkrevet',
'Passwords don\'t match' => 'Passordene stemmer ikke overens',
- 'The confirmation is required' => 'Bekreftelse er nødvendig',
- 'The project is required' => 'Prosjektet er påkrevet',
- 'The id is required' => 'Id\'en er påkrevd',
- 'The project id is required' => 'Prosjektet-id er påkrevet',
- 'The project name is required' => 'Prosjektnavn er påkrevet',
- 'This project must be unique' => 'Prosjektnavnet skal være unikt',
- 'The title is required' => 'Tittel er pårevet',
+ 'The confirmation is required' => 'Bekreftelse er nødvendig',
+ 'The project is required' => 'Prosjektet er påkrevet',
+ 'The id is required' => 'Id\'en er pøøkrevet',
+ 'The project id is required' => 'Prosjektet-id er påkrevet',
+ 'The project name is required' => 'Prosjektnavn er påkrevet',
+ 'This project must be unique' => 'Prosjektnavnet skal være unikt',
+ 'The title is required' => 'Tittel er pårevet',
+ 'There is no active project, the first step is to create a new project.' => 'Det er ingen aktive prosjekter. Førstesteg er åopprette et nytt prosjekt.',
'Settings saved successfully.' => 'Innstillinger lagret.',
'Unable to save your settings.' => 'Innstillinger kunne ikke lagres.',
- 'Database optimization done.' => 'Databaseoptimering er fullført.',
+ 'Database optimization done.' => 'Databaseoptimering er fullført.',
'Your project have been created successfully.' => 'Ditt prosjekt er opprettet.',
'Unable to create your project.' => 'Prosjektet kunne ikke opprettes',
'Project updated successfully.' => 'Prosjektet er oppdatert.',
@@ -139,9 +140,9 @@ return array(
'Unable to activate this project.' => 'Prosjektet kunne ikke aktiveres.',
'Project disabled successfully.' => 'Prosjektet er deaktiveret.',
'Unable to disable this project.' => 'Prosjektet kunne ikke deaktiveres.',
- 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.',
- 'Task opened successfully.' => 'Oppgaven er åpnet.',
- 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.',
+ 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.',
+ 'Task opened successfully.' => 'Oppgaven er åpnet.',
+ 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.',
'Task closed successfully.' => 'Oppgaven er lukket.',
'Unable to update your task.' => 'Oppgaven kunne ikke oppdateres.',
'Task updated successfully.' => 'Oppgaven er oppdatert.',
@@ -149,7 +150,7 @@ return array(
'Task created successfully.' => 'Oppgaven er opprettet.',
'User created successfully.' => 'Brukeren er opprettet.',
'Unable to create your user.' => 'Brukeren kunne ikke opprettes.',
- 'User updated successfully.' => 'Brukeren er opdateret',
+ 'User updated successfully.' => 'Brukeren er oppdatert',
'Unable to update your user.' => 'Din bruker kunne ikke oppdateres.',
'User removed successfully.' => 'Brukeren er fjernet.',
'Unable to remove this user.' => 'Brukeren kunne ikke slettes.',
@@ -157,13 +158,15 @@ return array(
'Ready' => 'Klar',
'Backlog' => 'Backlog',
'Work in progress' => 'Under arbeid',
- 'Done' => 'Utført',
+ 'Done' => 'Utført',
'Application version:' => 'Versjon:',
- 'Completed on %B %e, %Y at %k:%M %p' => 'Fullført %d.%m.%Y - %H:%M',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Fullført %d.%m.%Y - %H:%M',
'%B %e, %Y at %k:%M %p' => '%d.%m.%Y - %H:%M',
'Date created' => 'Dato for opprettelse',
- 'Date completed' => 'Dato for fullført',
+ 'Date completed' => 'Dato for fullført',
'Id' => 'ID',
+ 'Completed tasks' => 'Fullførte oppgaver',
+ 'Completed tasks for "%s"' => 'Fullførte oppgaver for "%s"',
'%d closed tasks' => '%d lukkede oppgaver',
'No task for this project' => 'Ingen oppgaver i dette prosjektet',
'Public link' => 'Offentligt lenke',
@@ -175,10 +178,10 @@ return array(
'Page not found' => 'Siden er ikke funnet',
'Complexity' => 'Kompleksitet',
'Task limit' => 'Oppgave begrensning',
- // 'Task count' => '',
+ 'Task count' => 'Antall oppgaver',
'Edit project access list' => 'Endre tillatelser for prosjektet',
'Allow this user' => 'Tillat denne brukeren',
- 'Don\'t forget that administrators have access to everything.' => 'Hust at administratorer har tilgang til alt.',
+ 'Don\'t forget that administrators have access to everything.' => 'Husk at administratorer har tilgang til alt.',
'Revoke' => 'Fjern',
'List of authorized users' => 'Liste over autoriserte brukere',
'User' => 'Bruker',
@@ -186,14 +189,14 @@ return array(
'Comments' => 'Kommentarer',
'Write your text in Markdown' => 'Skriv din tekst i markdown',
'Leave a comment' => 'Legg inn en kommentar',
- 'Comment is required' => 'Kommentar må legges inn',
+ 'Comment is required' => 'Kommentar må legges inn',
'Leave a description' => 'Legg inn en beskrivelse...',
'Comment added successfully.' => 'Kommentaren er lagt til.',
'Unable to create your comment.' => 'Din kommentar kunne ikke opprettes.',
'Edit this task' => 'Rediger oppgaven',
'Due Date' => 'Forfallsdato',
'Invalid date' => 'Ugyldig dato',
- 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y',
+ 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
// '%b %e, %Y' => '',
'Automatic actions' => 'Automatiske handlinger',
@@ -205,19 +208,19 @@ return array(
'Automatic actions for the project "%s"' => 'Automatiske handlinger for prosjektet "%s"',
'Defined actions' => 'Definerte handlinger',
'Add an action' => 'Legg til en handling',
- 'Event name' => 'Begivenhet',
+ 'Event name' => 'Hendelsehet',
'Action name' => 'Handling',
'Action parameters' => 'Handlingsparametre',
'Action' => 'Handling',
- 'Event' => 'Begivenhet',
- 'When the selected event occurs execute the corresponding action.' => 'Når den valgtebegivenheten oppstår, utføre tilsvarende handlin.',
+ 'Event' => 'Hendelse',
+ 'When the selected event occurs execute the corresponding action.' => 'Når den valgte hendelsen oppstår, utfør tilsvarende handling.',
'Next step' => 'Neste',
'Define action parameters' => 'Definer handlingsparametre',
'Save this action' => 'Lagre handlingen',
- 'Do you really want to remove this action: "%s"?' => 'Vil du virkelig slette denne handlingen: "%s"?',
+ 'Do you really want to remove this action: "%s"?' => 'Vil du slette denne handlingen: "%s"?',
'Remove an automatic action' => 'Fjern en automatisk handling',
'Assign the task to a specific user' => 'Tildel oppgaven til en bestemt bruker',
- 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen',
+ 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen',
'Duplicate the task to another project' => 'Kopier oppgaven til et annet prosjekt',
'Move a task to another column' => 'Flytt oppgaven til en annen kolonne',
'Task modification' => 'Oppgaveendring',
@@ -236,37 +239,41 @@ return array(
'Remove a comment' => 'Fjern en kommentar',
'Comment removed successfully.' => 'Kommentaren ble fjernet.',
'Unable to remove this comment.' => 'Kommentaren kunne ikke fjernes.',
- 'Do you really want to remove this comment?' => 'Vil du virkelig fjerne denne kommentaren?',
+ 'Do you really want to remove this comment?' => 'Vil du fjerne denne kommentaren?',
'Only administrators or the creator of the comment can access to this page.' => 'Kun administrator eller brukeren, som har oprettet kommentaren har adgang til denne siden.',
'Current password for the user "%s"' => 'Aktivt passord for brukeren "%s"',
- 'The current password is required' => 'Passord er påkrevet',
+ 'The current password is required' => 'Passord er påkrevet',
'Wrong password' => 'Feil passord',
'Unknown' => 'Ukjent',
- 'Last logins' => 'Siste login',
+ 'Last logins' => 'Siste innlogging',
'Login date' => 'Login dato',
'Authentication method' => 'Godkjenningsmetode',
'IP address' => 'IP Adresse',
'User agent' => 'User Agent',
'Persistent connections' => 'Varige forbindelser',
'No session.' => 'Ingen session.',
- 'Expiration date' => 'Utløpsdato',
+ 'Expiration date' => 'Utløpsdato',
'Remember Me' => 'Husk meg',
'Creation date' => 'Opprettelsesdato',
+ 'Filter by user' => 'Filtrer efter bruker',
+ 'Filter by due date' => 'Filtrer etter forfallsdato',
'Everybody' => 'Alle',
- 'Open' => 'Ã…pen',
+ 'Open' => 'Åpen',
'Closed' => 'Lukket',
- 'Search' => 'Søk',
+ 'Search' => 'Søk',
'Nothing found.' => 'Intet funnet.',
+ 'Search in the project "%s"' => 'Søk i prosjektet "%s"',
'Due date' => 'Forfallsdato',
'Others formats accepted: %s and %s' => 'Andre formater: %s og %s',
'Description' => 'Beskrivelse',
'%d comments' => '%d kommentarer',
'%d comment' => '%d kommentar',
'Email address invalid' => 'Ugyldig epost',
- // 'Your external account is not linked anymore to your profile.' => '',
- // 'Unable to unlink your external account.' => '',
- // 'External authentication failed' => '',
- // 'Your external account is linked to your profile successfully.' => '',
+ 'Your Google Account is not linked anymore to your profile.' => 'Din Google-konto er ikke lengre knyttet til din profil.',
+ 'Unable to unlink your Google Account.' => 'Det var ikke mulig ø fjerne din Google-konto.',
+ 'Google authentication failed' => 'Google godjenning mislyktes',
+ 'Unable to link your Google Account.' => 'Det var ikke mulig åknytte opp til din Google-konto.',
+ 'Your Google Account is linked to your profile successfully.' => 'Din Google-konto er knyttet til din profil.',
'Email' => 'Epost',
'Link my Google Account' => 'Knytt til min Google-konto',
'Unlink my Google Account' => 'Fjern knytningen til min Google-konto',
@@ -275,9 +282,9 @@ return array(
'Task removed successfully.' => 'Oppgaven er fjernet.',
'Unable to remove this task.' => 'Oppgaven kunne ikke fjernes.',
'Remove a task' => 'Fjern en oppgave',
- 'Do you really want to remove this task: "%s"?' => 'Vil du virkelig fjerne denne opgave: "%s"?',
+ 'Do you really want to remove this task: "%s"?' => 'Vil du fjerne denne oppgaven: "%s"?',
'Assign automatically a color based on a category' => 'Tildel automatisk en farge baseret for en kategori',
- 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve',
+ 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve',
'Task creation or modification' => 'Oppgaveopprettelse eller endring',
'Category' => 'Kategori',
'Category:' => 'Kategori:',
@@ -293,17 +300,18 @@ return array(
'Category modification for the project "%s"' => 'Endring av kategori for prosjektet "%s"',
'Category Name' => 'Kategorinavn',
'Add a new category' => 'Legg til ny kategori',
- 'Do you really want to remove this category: "%s"?' => 'Vil du virkelig fjerne kategorien: "%s"?',
+ 'Do you really want to remove this category: "%s"?' => 'Vil du fjerne kategorien: "%s"?',
+ 'Filter by category' => 'Filter etter kategori',
'All categories' => 'Alle kategorier',
'No category' => 'Ingen kategori',
- 'The name is required' => 'Navnet er påkrevet',
+ 'The name is required' => 'Navnet er påkrevet',
'Remove a file' => 'Fjern en fil',
'Unable to remove this file.' => 'Filen kunne ikke fjernes.',
'File removed successfully.' => 'Filen er fjernet.',
'Attach a document' => 'Legg til et dokument',
- 'Do you really want to remove this file: "%s"?' => 'Vil du virkelig fjerne filen: "%s"?',
- 'open' => 'Ã¥pen',
- 'Attachments' => 'Vedleggr',
+ 'Do you really want to remove this file: "%s"?' => 'Vil du fjerne filen: "%s"?',
+ 'open' => 'øpen',
+ 'Attachments' => 'Vedlegg',
'Edit the task' => 'Rediger oppgaven',
'Edit the description' => 'Rediger beskrivelsen',
'Add a comment' => 'Legg til en kommentar',
@@ -312,8 +320,8 @@ return array(
'Time tracking' => 'Tidsregistrering',
'Estimate:' => 'Estimat:',
'Spent:' => 'Brukt:',
- 'Do you really want to remove this sub-task?' => 'Vil du virkelig fjerne denne deloppgaven?',
- 'Remaining:' => 'Gjenværende:',
+ 'Do you really want to remove this sub-task?' => 'Vil du fjerne denne deloppgaven?',
+ 'Remaining:' => 'Gjenværende:',
'hours' => 'timer',
'spent' => 'brukt',
'estimated' => 'estimat',
@@ -324,8 +332,8 @@ return array(
'Time spent' => 'Tidsforbruk',
'Edit a sub-task' => 'Rediger en deloppgave',
'Remove a sub-task' => 'Fjern en deloppgave',
- 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi',
- 'Todo' => 'Gjøremål',
+ 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi',
+ 'Todo' => 'Gjøremål',
'In progress' => 'Under arbeid',
'Sub-task removed successfully.' => 'Deloppgaven er fjernet.',
'Unable to remove this sub-task.' => 'Deloppgaven kunne ikke fjernes.',
@@ -333,19 +341,24 @@ return array(
'Unable to update your sub-task.' => 'Deloppgaven kunne ikke opdateres.',
'Unable to create your sub-task.' => 'Deloppgaven kunne ikke oprettes.',
'Sub-task added successfully.' => 'Deloppgaven er lagt til.',
- 'Maximum size: ' => 'Maksimum størrelse: ',
+ 'Maximum size: ' => 'Maksimum størrelse: ',
'Unable to upload the file.' => 'Filen kunne ikke lastes opp.',
'Display another project' => 'Vis annet prosjekt...',
- // 'Login with my Github Account' => '',
- // 'Link my Github Account' => '',
- // 'Unlink my Github Account' => '',
+ 'Your GitHub account was successfully linked to your profile.' => 'Din GitHub-konto er knyttet til din profil.',
+ 'Unable to link your GitHub Account.' => 'Det var ikke mulig å knytte din GitHub-konto.',
+ 'GitHub authentication failed' => 'GitHub godkjenning mislyktes',
+ 'Your GitHub account is no longer linked to your profile.' => 'Din GitHub-konto er ikke lengere knyttet til din profil.',
+ 'Unable to unlink your GitHub Account.' => 'Det var ikke muligt at fjerne forbindelsen til din GitHub-konto.',
+ 'Login with my GitHub Account' => 'Login med min GitHub-konto',
+ 'Link my GitHub Account' => 'Knytt min GitHub-konto',
+ 'Unlink my GitHub Account' => 'Fjern knytningen til min GitHub-konto',
'Created by %s' => 'Opprettet av %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Sist endret %d.%m.%Y - %H:%M',
'Tasks Export' => 'Oppgave eksport',
'Tasks exportation for "%s"' => 'Oppgaveeksportering for "%s"',
'Start Date' => 'Start-dato',
'End Date' => 'Slutt-dato',
- 'Execute' => 'KKjør',
+ 'Execute' => 'KKjør',
'Task Id' => 'Oppgave ID',
'Creator' => 'Laget av',
'Modification date' => 'Endringsdato',
@@ -354,15 +367,15 @@ return array(
'Project cloned successfully.' => 'Prosjektet er kopiert.',
'Unable to clone this project.' => 'Prosjektet kunne ikke kopieres',
'Email notifications' => 'Epostvarslinger',
- 'Enable email notifications' => 'Aktiver eposvarslinger',
+ 'Enable email notifications' => 'Aktiver epostvarslinger',
'Task position:' => 'Oppgaveposisjon:',
- 'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
+ 'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
'The task #%d have been closed.' => 'Oppgaven #%d er lukket.',
'Sub-task updated' => 'Deloppgaven er oppdatert',
'Title:' => 'Tittel:',
'Status:' => 'Status:',
'Assignee:' => 'Ansvarlig:',
- 'Time tracking:' => 'Tidsmåling:',
+ 'Time tracking:' => 'Tidsmåling:',
'New sub-task' => 'Ny deloppgave',
'New attachment added "%s"' => 'Nytt vedlegg er lagt tilet "%s"',
'Comment updated' => 'Kommentar oppdatert',
@@ -373,21 +386,21 @@ return array(
'Subtask updated' => 'Deloppgave oppdatert',
'Task updated' => 'Oppgave oppdatert',
'Task closed' => 'Oppgave lukket',
- 'Task opened' => 'Oppgave åpnet',
+ 'Task opened' => 'Oppgave åpnet',
'I want to receive notifications only for those projects:' => 'Jeg vil kun ha varslinger for disse prosjekter:',
- 'view the task on Kanboard' => 'se oppgaven påhovedsiden',
+ 'view the task on Kanboard' => 'se oppgaven påhovedsiden',
'Public access' => 'Offentlig tilgang',
'User management' => 'Brukere',
'Active tasks' => 'Aktive oppgaver',
'Disable public access' => 'Deaktiver offentlig tilgang',
'Enable public access' => 'Aktiver offentlig tilgang',
'Public access disabled' => 'Offentlig tilgang er deaktivert',
- 'Do you really want to disable this project: "%s"?' => 'Vil du virkelig deaktivere prosjektet: "%s"?',
- 'Do you really want to enable this project: "%s"?' => 'Vil du virkelig aktivere prosjektet: "%s"?',
+ 'Do you really want to disable this project: "%s"?' => 'Vil du deaktivere prosjektet: "%s"?',
+ 'Do you really want to enable this project: "%s"?' => 'Vil du aktivere prosjektet: "%s"?',
'Project activation' => 'Prosjekt aktivering',
'Move the task to another project' => 'Flytt oppgaven til et annet prosjekt',
'Move to another project' => 'Flytt til et annet prosjekt',
- 'Do you really want to duplicate this task?' => 'Vil du virkelig kopiere denne oppgaven?',
+ 'Do you really want to duplicate this task?' => 'Vil du kopiere denne oppgaven?',
'Duplicate a task' => 'Kopier en oppgave',
'External accounts' => 'Eksterne kontoer',
'Account type' => 'Kontotype',
@@ -411,7 +424,7 @@ return array(
'External authentications' => 'Ekstern godkjenning',
'Google Account' => 'Google-konto',
'Github Account' => 'GitHub-konto',
- 'Never connected.' => 'Aldri knyttet.',
+ 'Never connected.' => 'Aldri innlogget.',
'No account linked.' => 'Ingen kontoer knyttet.',
'Account linked.' => 'Konto knyttet.',
'No external authentication enabled.' => 'Ingen eksterne godkjenninger aktiveret.',
@@ -420,18 +433,19 @@ return array(
'Change category for the task "%s"' => 'Endre kategori for oppgaven "%s"',
'Change category' => 'Endre kategori',
'%s updated the task %s' => '%s oppdaterte oppgaven %s',
- '%s opened the task %s' => '%s åpnet oppgaven %s',
+ '%s opened the task %s' => '%s åpnet oppgaven %s',
'%s moved the task %s to the position #%d in the column "%s"' => '%s flyttet oppgaven %s til posisjonen #%d i kolonnen "%s"',
'%s moved the task %s to the column "%s"' => '%s flyttet oppgaven %s til kolonnen "%s"',
'%s created the task %s' => '%s opprettet oppgaven %s',
'%s closed the task %s' => '%s lukket oppgaven %s',
'%s created a subtask for the task %s' => '%s opprettet en deloppgave for oppgaven %s',
'%s updated a subtask for the task %s' => '%s oppdaterte en deloppgave for oppgaven %s',
- 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh',
+ 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh',
'Not assigned, estimate of %sh' => 'Ikke tildelt, estimert til %sh',
'%s updated a comment on the task %s' => '%s oppdaterte en kommentar til oppgaven %s',
'%s commented the task %s' => '%s har kommentert oppgaven %s',
'%s\'s activity' => '%s\'s aktvitet',
+ 'No activity.' => 'Ingen aktivitet',
'RSS feed' => 'RSS feed',
'%s updated a comment on the task #%d' => '%s oppdaterte en kommentar til oppgaven #%d',
'%s commented on the task #%d' => '%s kommenterte oppgaven #%d',
@@ -440,7 +454,7 @@ return array(
'%s updated the task #%d' => '%s oppdaterte oppgaven #%d',
'%s created the task #%d' => '%s opprettet oppgaven #%d',
'%s closed the task #%d' => '%s lukket oppgaven #%d',
- '%s open the task #%d' => '%s åpnet oppgaven #%d',
+ '%s open the task #%d' => '%s åpnet oppgaven #%d',
'%s moved the task #%d to the column "%s"' => '%s flyttet oppgaven #%d til kolonnen "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s flyttet oppgaven #%d til posisjonen %d i kolonnen "%s"',
'Activity' => 'Aktivitetslogg',
@@ -452,21 +466,21 @@ return array(
'New password for the user "%s"' => 'Nytt passord for brukeren "%s"',
'Choose an event' => 'Velg en hendelse',
'Github commit received' => 'Github forpliktelse mottatt',
- 'Github issue opened' => 'Github problem åpnet',
+ 'Github issue opened' => 'Github problem åpnet',
'Github issue closed' => 'Github problem lukket',
- 'Github issue reopened' => 'Github problem gjenåpnet',
+ 'Github issue reopened' => 'Github problem gjenåpnet',
'Github issue assignee change' => 'Endre ansvarlig for Github problem',
'Github issue label change' => 'Endre etikett for Github problem',
'Create a task from an external provider' => 'Oppret en oppgave fra en ekstern tilbyder',
- 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn',
- 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett',
+ 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn',
+ 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett',
'Reference' => 'Referanse',
'Reference: %s' => 'Referanse: %s',
'Label' => 'Etikett',
'Database' => 'Database',
'About' => 'Om',
'Database driver:' => 'Database driver:',
- 'Board settings' => 'Innstillinger for ptosjektside',
+ 'Board settings' => 'Innstillinger for prosjektside',
'URL and token' => 'URL og token',
'Webhook settings' => 'Webhook innstillinger',
'URL for task creation:' => 'URL for oppgaveopprettelse:',
@@ -475,9 +489,9 @@ return array(
'Refresh interval for private board' => 'Oppdateringsintervall for privat hovedside',
'Refresh interval for public board' => 'Oppdateringsintervall for offentlig hovedside',
'Task highlight period' => 'Fremhevingsperiode for oppgave',
- 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for å anta at en oppgave nylig ble endretg (0 for å deaktivere, 2 dager som standard)',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for ø anta at en oppgave nylig ble endretg (0 for å deaktivere, 2 dager som standard)',
'Frequency in second (60 seconds by default)' => 'Frekevens i sekunder (60 sekunder som standard)',
- 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for å deaktivere denne funksjonen, 10 sekunder som standard)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for øt deaktivere denne funksjonen, 10 sekunder som standard)',
'Application URL' => 'Applikasjons URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Eksempel: http://example.kanboard.net/ (bruges til email notifikationer)',
'Token regenerated.' => 'Token regenerert.',
@@ -485,7 +499,7 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format er alltid akseptert, eksempelvis: "%s" og "%s"',
'New private project' => 'Nytt privat prosjekt',
'This project is private' => 'Dette projektet er privat',
- 'Type here to create a new sub-task' => 'Skriv her for å opprette en ny deloppgave',
+ 'Type here to create a new sub-task' => 'Skriv her for ø opprette en ny deloppgave',
'Add' => 'Legg til',
'Estimated time: %s hours' => 'Estimert tid: %s timer',
'Time spent: %s hours' => 'Tid brukt: %s timer',
@@ -510,21 +524,21 @@ return array(
'Columns' => 'Kolonner',
'Task' => 'Oppgave',
// 'Your are not member of any project.' => '',
- // 'Percentage' => '',
- // 'Number of tasks' => '',
- // 'Task distribution' => '',
- // 'Reportings' => '',
+ 'Percentage' => 'Prosent',
+ 'Number of tasks' => 'Antall oppgaver',
+ 'Task distribution' => 'Kolonnefordeling',
+ 'Reportings' => 'Rapportering',
// 'Task repartition for "%s"' => '',
'Analytics' => 'Analyser',
- // 'Subtask' => '',
+ 'Subtask' => 'Deloppgave',
'My subtasks' => 'Mine deloppgaver',
- // 'User repartition' => '',
+ 'User repartition' => 'Brukerfordeling',
// 'User repartition for "%s"' => '',
'Clone this project' => 'Kopier dette prosjektet',
- // 'Column removed successfully.' => '',
+ 'Column removed successfully.' => 'Kolonne flyttet',
// 'Github Issue' => '',
// 'Not enough data to show the graph.' => '',
- // 'Previous' => '',
+ 'Previous' => 'Forrige',
// 'The id must be an integer' => '',
// 'The project id must be an integer' => '',
// 'The status must be an integer' => '',
@@ -536,32 +550,32 @@ return array(
// 'This value is required' => '',
// 'This value must be numeric' => '',
// 'Unable to create this task.' => '',
- // 'Cumulative flow diagram' => '',
+ 'Cumulative flow diagram' => 'Kumulativt flytdiagram',
// 'Cumulative flow diagram for "%s"' => '',
- // 'Daily project summary' => '',
+ 'Daily project summary' => 'Daglig prosjektsammendrag',
// 'Daily project summary export' => '',
// 'Daily project summary export for "%s"' => '',
'Exports' => 'Eksporter',
// 'This export contains the number of tasks per column grouped per day.' => '',
- 'Nothing to preview...' => 'Ingenting å forhåndsvise',
- 'Preview' => 'Forhåndsvisning',
+ 'Nothing to preview...' => 'Ingenting å forhåndsvise',
+ 'Preview' => 'Forhåndsvisning',
'Write' => 'Skriv',
- 'Active swimlanes' => 'Aktive svæmmebaner',
- 'Add a new swimlane' => 'Legg til en ny svømmebane',
- 'Change default swimlane' => 'Endre standard svømmebane',
- 'Default swimlane' => 'Standard svømmebane',
+ 'Active swimlanes' => 'Aktive svømmebaner',
+ 'Add a new swimlane' => 'Legg til en ny svømmebane',
+ 'Change default swimlane' => 'Endre standard svømmebane',
+ 'Default swimlane' => 'Standard svømmebane',
// 'Do you really want to remove this swimlane: "%s"?' => '',
// 'Inactive swimlanes' => '',
'Set project manager' => 'Velg prosjektleder',
'Set project member' => 'Velg prosjektmedlem',
- 'Remove a swimlane' => 'Fjern en svømmebane',
+ 'Remove a swimlane' => 'Fjern en svømmebane',
'Rename' => 'Endre navn',
- 'Show default swimlane' => 'Vis standard svømmebane',
+ 'Show default swimlane' => 'Vis standard svømmebane',
// 'Swimlane modification for the project "%s"' => '',
- // 'Swimlane not found.' => '',
- // 'Swimlane removed successfully.' => '',
- 'Swimlanes' => 'Svømmebaner',
- 'Swimlane updated successfully.' => 'Svæmmebane oppdatert',
+ 'Swimlane not found.' => 'Svømmebane ikke funnet',
+ 'Swimlane removed successfully.' => 'Svømmebane fjernet',
+ 'Swimlanes' => 'Svømmebaner',
+ 'Swimlane updated successfully.' => 'Svømmebane oppdatert',
// 'The default swimlane have been updated successfully.' => '',
// 'Unable to create your swimlane.' => '',
// 'Unable to remove this swimlane.' => '',
@@ -576,27 +590,32 @@ return array(
// 'Help on Gitlab webhooks' => '',
'Integrations' => 'Integrasjoner',
'Integration with third-party services' => 'Integrasjoner med tredje-parts tjenester',
- // 'Role for this project' => '',
+ 'Role for this project' => 'Rolle for dette prosjektet',
'Project manager' => 'Prosjektleder',
'Project member' => 'Prosjektmedlem',
'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Prosjektlederen kan endre flere innstillinger for prosjektet enn den en vanlig bruker kan.',
// 'Gitlab Issue' => '',
- // 'Subtask Id' => '',
- // 'Subtasks' => '',
- // 'Subtasks Export' => '',
+ 'Subtask Id' => 'Deloppgave ID',
+ 'Subtasks' => 'Deloppgaver',
+ 'Subtasks Export' => 'Eksporter deloppgaver',
// 'Subtasks exportation for "%s"' => '',
- // 'Task Title' => '',
+ 'Task Title' => 'Oppgavetittel',
// 'Untitled' => '',
'Application default' => 'Standardinstilling',
- 'Language:' => 'Språk',
+ 'Language:' => 'Språk',
'Timezone:' => 'Tidssone',
- // 'All columns' => '',
+ 'All columns' => 'Alle kolonner',
+ 'Calendar for "%s"' => 'Kalender for "%s"',
+ 'Filter by column' => 'Filtrer etter kolonne',
+ 'Filter by status' => 'Filtrer etter status',
'Calendar' => 'Kalender',
- // 'Next' => '',
+ 'Next' => 'Neste',
// '#%d' => '',
- // 'All swimlanes' => '',
- // 'All colors' => '',
- // 'All status' => '',
+ 'Filter by color' => 'Filtrer etter farge',
+ 'Filter by swimlane' => 'Filtrer etter svømmebane',
+ 'All swimlanes' => 'Alle svømmebaner',
+ 'All colors' => 'Alle farger',
+ 'All status' => 'Alle statuser',
// 'Moved to column %s' => '',
'Change description' => 'Endre beskrivelse',
'User dashboard' => 'Brukerens hovedside',
@@ -604,16 +623,23 @@ return array(
// 'Edit column "%s"' => '',
// 'Select the new status of the subtask: "%s"' => '',
'Subtask timesheet' => 'Tidsskjema for deloppgaver',
- 'There is nothing to show.' => 'Ingen data å vise',
+ 'There is nothing to show.' => 'Ingen data å vise',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
- 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?',
- // 'Disallow login form' => '',
+ 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?',
+ 'Change dashboard view' => 'Endre visning',
+ 'Show/hide activities' => 'Vis/skjul aktiviteter',
+ 'Show/hide projects' => 'Vis/skjul prosjekter',
+ 'Show/hide subtasks' => 'Vis/skjul deloppgaver',
+ 'Show/hide tasks' => 'Vis/skjul oppgaver',
+ 'Disable login form' => 'Deaktiver innlogging',
+ 'Show/hide calendar' => 'Vis/skjul kalender',
+ 'User calendar' => 'Brukerens kalender',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
- // 'Start' => '',
- // 'End' => '',
+ 'Start' => 'Start',
+ 'End' => 'Slutt',
'Task age in days' => 'Dager siden oppgaven ble opprettet',
'Days in this column' => 'Dager siden oppgaven ble lagt i denne kolonnen',
// '%dd' => '',
@@ -621,7 +647,7 @@ return array(
'Add a new link' => 'Legg til en ny relasjon',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
- // 'Field required' => '',
+ 'Field required' => 'Feltet må fylles ut',
'Link added successfully.' => 'Ny relasjon er lagt til',
'Link updated successfully.' => 'Relasjon er oppdatert',
'Link removed successfully.' => 'Relasjon er fjernet',
@@ -647,8 +673,8 @@ return array(
'is a parent of' => 'er en overordnet oppgave av',
'targets milestone' => 'milepel',
'is a milestone of' => 'er en milepel av',
- 'fixes' => 'løser',
- 'is fixed by' => 'løses av',
+ 'fixes' => 'løser',
+ 'is fixed by' => 'løses av',
'This task' => 'Denne oppgaven',
// '<1h' => '',
// '%dh' => '',
@@ -662,7 +688,9 @@ return array(
'Keyboard shortcuts' => 'Hurtigtaster',
// 'Open board switcher' => '',
// 'Application' => '',
+ 'Filter recently updated' => 'Filter nylig oppdatert',
'since %B %e, %Y at %k:%M %p' => 'siden %B %e, %Y at %k:%M %p',
+ 'More filters' => 'Flere filtre',
'Compact view' => 'Kompakt visning',
'Horizontal scrolling' => 'Bla horisontalt',
'Compact/wide view' => 'Kompakt/bred visning',
@@ -680,9 +708,9 @@ return array(
// 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
- // 'Comment' => '',
- // 'All day' => '',
- // 'Day' => '',
+ 'Comment' => 'Kommentar',
+ 'All day' => 'Alle dager',
+ 'Day' => 'Dag',
'Manage timetable' => 'Tidstabell',
'Overtime timetable' => 'Overtidstabell',
'Time off timetable' => 'Fritidstabell',
@@ -703,18 +731,18 @@ return array(
'Files' => 'Filer',
'Images' => 'Bilder',
'Private project' => 'Privat prosjekt',
- // 'Amount' => '',
+ 'Amount' => 'Beløp',
// 'AUD - Australian Dollar' => '',
'Budget' => 'Budsjett',
// 'Budget line' => '',
// 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
+ 'Budget lines' => 'Budsjettlinjer',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
+ 'Cost' => 'Kostnad',
+ 'Cost breakdown' => 'Kostnadsnedbryting',
// 'Custom Stylesheet' => '',
- // 'download' => '',
+ 'download' => 'last ned',
// 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
// 'Expenses' => '',
@@ -731,10 +759,10 @@ return array(
// 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
// 'Remaining' => '',
- // 'Destination column' => '',
- 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker',
- 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ',
- // 'Source column' => '',
+ 'Destination column' => 'Ny kolonne',
+ 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker',
+ 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ',
+ 'Source column' => 'Opprinnelig kolonne',
// 'Show subtask estimates (forecast of future work)' => '',
'Transitions' => 'Statusendringer',
// 'Executer' => '',
@@ -782,10 +810,10 @@ return array(
// 'This chart show the task complexity over the time (Work Remaining).' => '',
// 'Screenshot taken %s' => '',
'Add a screenshot' => 'Legg til et skjermbilde',
- 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
+ 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
'Screenshot uploaded successfully.' => 'Skjermbilde opplastet',
// 'SEK - Swedish Krona' => '',
- 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
+ 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
'Identifier' => 'Prosjektkode',
// 'Postmark (incoming emails)' => '',
// 'Help on Postmark integration' => '',
@@ -807,26 +835,26 @@ return array(
// 'This value must be alphanumeric' => '',
'Edit recurrence' => 'Endre gjentakelser',
'Generate recurrent task' => 'Opprett gjentagende oppgave',
- 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave',
- 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist',
- 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist',
- 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist',
+ 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave',
+ 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist',
+ 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist',
+ 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist',
'Action date' => 'Hendelsesdato',
- 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist',
+ 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist',
'This task has created this child task: ' => 'Denne oppgaven har opprettet denne relaterte oppgaven',
'Day(s)' => 'Dager',
'Existing due date' => 'Eksisterende forfallsdato',
- 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist',
- 'Month(s)' => 'MÃ¥neder',
+ 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist',
+ 'Month(s)' => 'Måneder',
'Recurrence' => 'Gjentakelse',
'This task has been created by: ' => 'Denne oppgaven er opprettet av:',
// 'Recurrent task has been generated:' => '',
// 'Timeframe to calculate new due date: ' => '',
// 'Trigger to generate recurrent task: ' => '',
- 'When task is closed' => 'NÃ¥r oppgaven er lukket',
- 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
- 'When task is moved to last column' => 'NÃ¥r oppgaven er flyttet til siste kolonne',
- 'Year(s)' => 'Ã¥r',
+ 'When task is closed' => 'Når oppgaven er lukket',
+ 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
+ 'When task is moved to last column' => 'Når oppgaven er flyttet til siste kolonne',
+ 'Year(s)' => 'år',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
@@ -853,16 +881,16 @@ return array(
// 'There is no user management for private projects.' => '',
// 'User that will receive the email' => '',
// 'Email subject' => '',
- // 'Date' => '',
+ 'Date' => 'Dato',
// 'By @%s on Bitbucket' => '',
// 'Bitbucket Issue' => '',
// 'Commit made by @%s on Bitbucket' => '',
// 'Commit made by @%s on Github' => '',
// 'By @%s on Github' => '',
// 'Commit made by @%s on Gitlab' => '',
- 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene',
- 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres',
- 'Send a task by email to someone' => 'Send en oppgave på epost til noen',
+ 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene',
+ 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres',
+ 'Send a task by email to someone' => 'Send en oppgave på epost til noen',
// 'Reopen a task' => '',
// 'Bitbucket issue opened' => '',
// 'Bitbucket issue closed' => '',
@@ -871,14 +899,14 @@ return array(
// 'Bitbucket issue comment created' => '',
'Column change' => 'Endret kolonne',
'Position change' => 'Posisjonsendring',
- 'Swimlane change' => 'Endret svømmebane',
+ 'Swimlane change' => 'Endret svømmebane',
'Assignee change' => 'Endret eier',
// '[%s] Overdue tasks' => '',
'Notification' => 'Varsel',
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
- // 'Swimlane' => '',
- // 'Budget overview' => '',
+ 'Swimlane' => 'Svømmebane',
+ 'Budget overview' => 'Budsjettoversikt',
// 'Type' => '',
// 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
@@ -893,6 +921,7 @@ return array(
// 'The task have been moved to the first swimlane' => '',
// 'The task have been moved to another swimlane:' => '',
// 'Overdue tasks for the project "%s"' => '',
+ 'There is no completed tasks at the moment.' => 'Ingen fullførte oppgaver funnet.',
// 'New title: %s' => '',
// 'The task is not assigned anymore' => '',
// 'New assignee: %s' => '',
@@ -908,61 +937,62 @@ return array(
// 'The field "%s" have been updated' => '',
// 'The description have been modified' => '',
// 'Do you really want to close the task "%s" as well as all subtasks?' => '',
- // 'Swimlane: %s' => '',
- // 'I want to receive notifications for:' => '',
+ 'Swimlane: %s' => 'Svømmebane: %s',
+ 'Project calendar' => 'Prosjektkalender',
+ 'I want to receive notifications for:' => 'Jeg vil motta varslinger om:',
'All tasks' => 'Alle oppgaver',
- // 'Only for tasks assigned to me' => '',
- // 'Only for tasks created by me' => '',
- // 'Only for tasks created by me and assigned to me' => '',
+ 'Only for tasks assigned to me' => 'Kun oppgaver som er tildelt meg',
+ 'Only for tasks created by me' => 'Kun oppgaver som er opprettet av meg',
+ 'Only for tasks created by me and assigned to me' => 'Kun oppgaver som er opprettet av meg og tildelt meg',
// '%A' => '',
// '%b %e, %Y, %k:%M %p' => '',
// 'New due date: %B %e, %Y' => '',
// 'Start date changed: %B %e, %Y' => '',
// '%k:%M %p' => '',
// '%%Y-%%m-%%d' => '',
- // 'Total for all columns' => '',
- // 'You need at least 2 days of data to show the chart.' => '',
+ 'Total for all columns' => 'Totalt for alle kolonner',
+ //'You need at least 2 days of data to show the chart.' => '',
// '<15m' => '',
// '<30m' => '',
'Stop timer' => 'Stopp timer',
'Start timer' => 'Start timer',
'Add project member' => 'Legg til prosjektmedlem',
'Enable notifications' => 'Aktiver varslinger',
- // 'My activity stream' => '',
- // 'My calendar' => '',
- // 'Search tasks' => '',
- // 'Back to the calendar' => '',
- // 'Filters' => '',
- // 'Reset filters' => '',
- // 'My tasks due tomorrow' => '',
- // 'Tasks due today' => '',
- // 'Tasks due tomorrow' => '',
- // 'Tasks due yesterday' => '',
- // 'Closed tasks' => '',
- // 'Open tasks' => '',
- // 'Not assigned' => '',
- // 'View advanced search syntax' => '',
- // 'Overview' => '',
+ 'My activity stream' => 'Aktivitetslogg',
+ 'My calendar' => 'Min kalender',
+ 'Search tasks' => 'Søk oppgave',
+ 'Back to the calendar' => 'Tilbake til kalender',
+ 'Filters' => 'Filtere',
+ 'Reset filters' => 'Nullstill filter',
+ 'My tasks due tomorrow' => 'Mine oppgaver med frist i morgen',
+ 'Tasks due today' => 'Oppgaver med frist i dag',
+ 'Tasks due tomorrow' => 'Oppgaver med frist i morgen',
+ 'Tasks due yesterday' => 'Oppgaver med frist i går',
+ 'Closed tasks' => 'Fullførte oppgaver',
+ 'Open tasks' => 'Åpne oppgaver',
+ 'Not assigned' => 'Ikke tildelt',
+ 'View advanced search syntax' => 'Vis hjelp for avansert søk ',
+ 'Overview' => 'Oversikt',
// '%b %e %Y' => '',
- // 'Board/Calendar/List view' => '',
- // 'Switch to the board view' => '',
- // 'Switch to the calendar view' => '',
- // 'Switch to the list view' => '',
- // 'Go to the search/filter box' => '',
- // 'There is no activity yet.' => '',
- // 'No tasks found.' => '',
- // 'Keyboard shortcut: "%s"' => '',
- // 'List' => '',
- // 'Filter' => '',
- // 'Advanced search' => '',
- // 'Example of query: ' => '',
- // 'Search by project: ' => '',
- // 'Search by column: ' => '',
- // 'Search by assignee: ' => '',
- // 'Search by color: ' => '',
- // 'Search by category: ' => '',
- // 'Search by description: ' => '',
- // 'Search by due date: ' => '',
+ 'Board/Calendar/List view' => 'Oversikt/kalender/listevisning',
+ 'Switch to the board view' => 'Oversiktsvisning',
+ 'Switch to the calendar view' => 'Kalendevisning',
+ 'Switch to the list view' => 'Listevisning',
+ 'Go to the search/filter box' => 'Gå til søk/filter',
+ 'There is no activity yet.' => 'Ingen aktiviteter ennå.',
+ 'No tasks found.' => 'Ingen oppgaver funnet',
+ 'Keyboard shortcut: "%s"' => 'Hurtigtaster: "%s"',
+ 'List' => 'Liste',
+ 'Filter' => 'Filter',
+ 'Advanced search' => 'Avansert søk',
+ 'Example of query: ' => 'Eksempel på spørring',
+ 'Search by project: ' => 'Søk etter prosjekt',
+ 'Search by column: ' => 'Søk etter kolonne',
+ 'Search by assignee: ' => 'Søk etter tildelt',
+ 'Search by color: ' => 'Søk etter farge',
+ 'Search by category: ' => 'Søk etter kategori',
+ 'Search by description: ' => 'Søk etter beskrivelse',
+ 'Search by due date: ' => 'Søk etter frist',
// 'Lead and Cycle time for "%s"' => '',
// 'Average time spent into each column for "%s"' => '',
// 'Average time spent into each column' => '',
@@ -996,75 +1026,75 @@ return array(
// 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
// 'By @%s on Gitlab' => '',
// 'Gitlab issue comment created' => '',
- // 'New remote user' => '',
- // 'New local user' => '',
- // 'Default task color' => '',
- // 'Hide sidebar' => '',
- // 'Expand sidebar' => '',
+ 'New remote user' => 'Ny eksternbruker',
+ 'New local user' => 'Ny internbruker',
+ 'Default task color' => 'Standard oppgavefarge',
+ 'Hide sidebar' => 'Skjul sidemeny',
+ 'Expand sidebar' => 'Vis sidemeny',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
// 'Trigger automatically subtask time tracking' => '',
// 'Include closed tasks in the cumulative flow diagram' => '',
- // 'Current swimlane: %s' => '',
- // 'Current column: %s' => '',
- // 'Current category: %s' => '',
- // 'no category' => '',
- // 'Current assignee: %s' => '',
- // 'not assigned' => '',
- // 'Author:' => '',
- // 'contributors' => '',
- // 'License:' => '',
- // 'License' => '',
- // 'Project Administrator' => '',
- // 'Enter the text below' => '',
- // 'Gantt chart for %s' => '',
+ 'Current swimlane: %s' => 'Nåværende svømmebane: %s',
+ 'Current column: %s' => 'Nåværende kolonne: %s',
+ 'Current category: %s' => ': %s',
+ 'no category' => 'ingen kategori',
+ 'Current assignee: %s' => 'Tildelt til %s',
+ 'not assigned' => 'ikke tildelt',
+ 'Author:' => 'Opprettet av',
+ 'contributors' => 'bidragsytere',
+ 'License:' => 'Lisens:',
+ 'License' => 'Lisens',
+ 'Project Administrator' => 'Prosjektadministrator',
+ 'Enter the text below' => 'Legg inn teksten nedenfor',
+ 'Gantt chart for %s' => 'Gantt skjema for %s',
// 'Sort by position' => '',
- // 'Sort by date' => '',
- // 'Add task' => '',
- // 'Start date:' => '',
- // 'Due date:' => '',
- // 'There is no start date or due date for this task.' => '',
+ 'Sort by date' => 'Sorter etter dato',
+ 'Add task' => 'Legg til oppgave',
+ 'Start date:' => 'Startdato:',
+ 'Due date:' => 'Frist:',
+ 'There is no start date or due date for this task.' => 'Det er ingen startdato eller frist for denne oppgaven',
// 'Moving or resizing a task will change the start and due date of the task.' => '',
- // 'There is no task in your project.' => '',
- // 'Gantt chart' => '',
- // 'People who are project managers' => '',
- // 'People who are project members' => '',
+ 'There is no task in your project.' => 'Det er ingen oppgaver i dette prosjektet',
+ 'Gantt chart' => 'Gantt skjema',
+ 'People who are project managers' => 'Prosjektledere',
+ 'People who are project members' => 'Prosjektmedlemmer',
// 'NOK - Norwegian Krone' => '',
- // 'Show this column' => '',
- // 'Hide this column' => '',
- // 'open file' => '',
- // 'End date' => '',
- // 'Users overview' => '',
- // 'Managers' => '',
- // 'Members' => '',
- // 'Shared project' => '',
- // 'Project managers' => '',
- // 'Project members' => '',
+ 'Show this column' => 'Vis denne kolonnen',
+ 'Hide this column' => 'Skjul denne kolonnen',
+ 'open file' => 'Åpne fil',
+ 'End date' => 'Sluttdato',
+ 'Users overview' => 'Brukeroversikt',
+ 'Managers' => 'Ledere',
+ 'Members' => 'Medlemmer',
+ 'Shared project' => 'Delt prosjekt',
+ 'Project managers' => 'Prosjektledere',
+ 'Project members' => 'Prosjektmedlemmer',
// 'Gantt chart for all projects' => '',
- // 'Projects list' => '',
- // 'Gantt chart for this project' => '',
- // 'Project board' => '',
- // 'End date:' => '',
+ 'Projects list' => 'Prosjektliste',
+ 'Gantt chart for this project' => 'Gantt skjema for dette prosjektet',
+ 'Project board' => 'Prosjektsiden',
+ 'End date:' => 'Sluttdato:',
// 'There is no start date or end date for this project.' => '',
- // 'Projects Gantt chart' => '',
- // 'Start date: %s' => '',
- // 'End date: %s' => '',
- // 'Link type' => '',
+ 'Projects Gantt chart' => 'Gantt skjema for prosjekter',
+ 'Start date: %s' => 'Startdato: %s',
+ 'End date: %s' => 'Sluttdato: %s',
+ 'Link type' => 'Relasjonstype',
// 'Change task color when using a specific task link' => '',
// 'Task link creation or modification' => '',
// 'Login with my Gitlab Account' => '',
- // 'Milestone' => '',
+ 'Milestone' => 'Milepæl',
// 'Gitlab Authentication' => '',
// 'Help on Gitlab authentication' => '',
// 'Gitlab Id' => '',
// 'Gitlab Account' => '',
// 'Link my Gitlab Account' => '',
// 'Unlink my Gitlab Account' => '',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Documentation: %s' => 'Dokumentasjon: %s',
+ 'Switch to the Gantt chart view' => 'Gantt skjema visning',
+ 'Reset the search/filter box' => 'Nullstill søk/filter',
+ 'Documentation' => 'Dokumentasjon',
+ 'Table of contents' => 'Innholdsfortegnelse',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Hjelp med prosjekttilganger',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 23d64163..8be0c61d 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'Actief',
'Disabled' => 'Inactief',
- 'Google account linked' => 'Gelinkt Google Account',
- 'Github account linked' => 'Gelinkt Github Account',
'Username:' => 'Gebruikersnaam :',
'Name:' => 'Naam :',
'Email:' => 'Email :',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 9947cf31..d9cfcdbc 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Zdalne',
'Enabled' => 'Odblokowane',
'Disabled' => 'Zablokowane',
- 'Google account linked' => 'Połączone konto Google',
- 'Github account linked' => 'Połączone konto Github',
'Username:' => 'Nazwa Użytkownika:',
'Name:' => 'ImiÄ™ i Nazwisko',
'Email:' => 'Email: ',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Przewijanie poziome',
'Compact/wide view' => 'Pełny/Kompaktowy widok',
'No results match:' => 'Brak wyników:',
- 'Remove hourly rate' => 'Usuń stawkę godzinową',
- 'Do you really want to remove this hourly rate?' => 'Czy na pewno chcesz usunąć stawkę godzinową?',
- 'Hourly rates' => 'Stawki godzinowe',
- 'Hourly rate' => 'Stawka godzinowa',
'Currency' => 'Waluta',
- 'Effective date' => 'Data efektywna',
- 'Add new rate' => 'Dodaj nowÄ… stawkÄ™',
- 'Rate removed successfully.' => 'Stawka usunięta.',
- 'Unable to remove this rate.' => 'Nie można usunąć tej stawki.',
- 'Unable to save the hourly rate.' => 'Nie można zapisać tej stawki godzinowej.',
- 'Hourly rate created successfully.' => 'Stawka godzinowa utworzona pomyślnie.',
'Start time' => 'Rozpoczęto',
'End time' => 'Zakończono',
'Comment' => 'Komentarz',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Pliki',
'Images' => 'Obrazy',
'Private project' => 'Projekt prywatny',
- 'Amount' => 'Ilość',
'AUD - Australian Dollar' => 'AUD - Dolar australijski',
- 'Budget' => 'Budżet',
- 'Budget line' => 'Linia budżetowa',
- 'Budget line removed successfully.' => 'Linia budżetowa usunięta.',
- 'Budget lines' => 'Linie budżetowe',
'CAD - Canadian Dollar' => 'CAD - Dolar kanadyjski',
'CHF - Swiss Francs' => 'CHF - Frank szwajcarski',
- 'Cost' => 'Koszt',
- 'Cost breakdown' => 'Analiza kosztów',
'Custom Stylesheet' => 'Niestandardowy arkusz stylów',
'download' => 'pobierz',
- 'Do you really want to remove this budget line?' => 'Czy chcesz usunąć tą linię budżetową?',
// 'EUR - Euro' => '',
- 'Expenses' => 'Wydatki',
'GBP - British Pound' => 'GBP - Funt brytyjski',
'INR - Indian Rupee' => 'INR - Rupia indyjska',
'JPY - Japanese Yen' => 'JPY - Jen japoński',
- 'New budget line' => 'Nowa linia budżetowa',
'NZD - New Zealand Dollar' => 'NZD - Dolar nowozelandzki',
- 'Remove a budget line' => 'Usuń linię budżetową',
- 'Remove budget line' => 'Usuń linię budżetową',
'RSD - Serbian dinar' => 'RSD - Dinar serbski',
- // 'The budget line have been created successfully.' => '',
- 'Unable to create the budget line.' => 'Nie można utworzyć linii budżetowej',
- 'Unable to remove this budget line.' => 'Nie można usunąć tej linii budżetowej',
'USD - US Dollar' => 'USD - Dolar amerykański',
- 'Remaining' => 'Pozostało',
'Destination column' => 'Kolumna docelowa',
'Move the task to another column when assigned to a user' => 'PrzenieÅ› zadanie do innej kolumny gdy zostanie przypisane do osoby',
'Move the task to another column when assignee is cleared' => 'Przenieś zadanie do innej kolumny gdy osoba odpowiedzialna zostanie usunięta',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurs',
'Change reference currency' => 'Zmień walutę referencyjną',
'Add a new currency rate' => 'Dodaj nowy kurs waluty',
- 'Currency rates are used to calculate project budget.' => 'Kursy walut są używane do obliczeń budżetu projektu.',
'Reference currency' => 'Waluta referencyjna',
'The currency rate have been added successfully.' => 'Dodano kurs waluty',
'Unable to add this currency rate.' => 'Nie można dodać kursu waluty',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 6aa2f4dd..5f849457 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Habilitado',
'Disabled' => 'Desabilitado',
- 'Google account linked' => 'Conta do Google associada',
- 'Github account linked' => 'Conta do Github associada',
'Username:' => 'Usuário:',
'Name:' => 'Nome:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Rolagem horizontal',
'Compact/wide view' => 'Alternar entre a vista compacta e ampliada',
'No results match:' => 'Nenhum resultado:',
- 'Remove hourly rate' => 'Retirar taxa horária',
- 'Do you really want to remove this hourly rate?' => 'Você deseja realmente remover esta taxa horária?',
- 'Hourly rates' => 'Taxas horárias',
- 'Hourly rate' => 'Taxa horária',
'Currency' => 'Moeda',
- 'Effective date' => 'Data efetiva',
- 'Add new rate' => 'Adicionar nova taxa',
- 'Rate removed successfully.' => 'Taxa removido com sucesso.',
- 'Unable to remove this rate.' => 'Impossível de remover esta taxa.',
- 'Unable to save the hourly rate.' => 'Impossível salvar a taxa horária.',
- 'Hourly rate created successfully.' => 'Taxa horária criada com sucesso.',
'Start time' => 'Horário de início',
'End time' => 'Horário de término',
'Comment' => 'comentário',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Arquivos',
'Images' => 'Imagens',
'Private project' => 'Projeto privado',
- 'Amount' => 'Quantia',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Orçamento',
- 'Budget line' => 'Rubrica orçamental',
- 'Budget line removed successfully.' => 'Rubrica orçamental removida com sucesso',
- 'Budget lines' => 'Rubricas orçamentais',
'CAD - Canadian Dollar' => 'CAD - Dólar canadense',
'CHF - Swiss Francs' => 'CHF - Francos Suíços',
- 'Cost' => 'Custo',
- 'Cost breakdown' => 'Repartição dos custos',
'Custom Stylesheet' => 'Folha de estilo personalizado',
'download' => 'baixar',
- 'Do you really want to remove this budget line?' => 'Você deseja realmente remover esta rubrica orçamental?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Despesas',
'GBP - British Pound' => 'GBP - Libra Esterlina',
'INR - Indian Rupee' => 'INR - Rúpia indiana',
'JPY - Japanese Yen' => 'JPY - Iene japonês',
- 'New budget line' => 'Nova rubrica orçamental',
'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês',
- 'Remove a budget line' => 'Remover uma rubrica orçamental',
- 'Remove budget line' => 'Remover uma rubrica orçamental',
'RSD - Serbian dinar' => 'RSD - Dinar sérvio',
- 'The budget line have been created successfully.' => 'A rubrica orçamental foi criada com sucesso.',
- 'Unable to create the budget line.' => 'Impossível de adicionar esta rubrica orçamental.',
- 'Unable to remove this budget line.' => 'Impossível de remover esta rubrica orçamental.',
'USD - US Dollar' => 'USD - Dólar norte-americano',
- 'Remaining' => 'Restante',
'Destination column' => 'Coluna de destino',
'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um usuário',
'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Taxa',
'Change reference currency' => 'Mudar a moeda de referência',
'Add a new currency rate' => 'Adicionar uma nova taxa para uma moeda',
- 'Currency rates are used to calculate project budget.' => 'As taxas de câmbio são utilizadas para calcular o orçamento do projeto.',
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível de adicionar essa taxa de câmbio.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Visão geral do orçamento',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
@@ -1060,11 +1028,11 @@ return array(
'Gitlab Account' => 'Conta Gitlab',
'Link my Gitlab Account' => 'Vincular minha conta Gitlab',
'Unlink my Gitlab Account' => 'Desvincular minha conta Gitlab',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Documentation: %s' => 'Documentação: %s',
+ 'Switch to the Gantt chart view' => 'Mudar para a vista gráfico de Gantt',
+ 'Reset the search/filter box' => 'Reiniciar o campo de pesquisa',
+ 'Documentation' => 'Documentação',
+ 'Table of contents' => 'Ãndice',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Ajuda com as permissões dos projetos',
);
diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php
index ae73af1e..9e20b112 100644
--- a/app/Locale/pt_PT/translations.php
+++ b/app/Locale/pt_PT/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Activado',
'Disabled' => 'Desactivado',
- 'Google account linked' => 'Conta do Google associada',
- 'Github account linked' => 'Conta do Github associada',
'Username:' => 'Utilizador:',
'Name:' => 'Nome:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Deslocamento horizontal',
'Compact/wide view' => 'Alternar entre a vista compacta e ampliada',
'No results match:' => 'Nenhum resultado:',
- 'Remove hourly rate' => 'Retirar taxa horária',
- 'Do you really want to remove this hourly rate?' => 'Tem a certeza que quer remover esta taxa horária?',
- 'Hourly rates' => 'Taxas horárias',
- 'Hourly rate' => 'Taxa horária',
'Currency' => 'Moeda',
- 'Effective date' => 'Data efectiva',
- 'Add new rate' => 'Adicionar nova taxa',
- 'Rate removed successfully.' => 'Taxa removido com sucesso.',
- 'Unable to remove this rate.' => 'Impossível de remover esta taxa.',
- 'Unable to save the hourly rate.' => 'Impossível salvar a taxa horária.',
- 'Hourly rate created successfully.' => 'Taxa horária criada com sucesso.',
'Start time' => 'Horário de início',
'End time' => 'Horário de término',
'Comment' => 'comentário',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Arquivos',
'Images' => 'Imagens',
'Private project' => 'Projecto privado',
- 'Amount' => 'Quantia',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Orçamento',
- 'Budget line' => 'Rubrica orçamental',
- 'Budget line removed successfully.' => 'Rubrica orçamental removida com sucesso',
- 'Budget lines' => 'Rubricas orçamentais',
'CAD - Canadian Dollar' => 'CAD - Dólar canadense',
'CHF - Swiss Francs' => 'CHF - Francos Suíços',
- 'Cost' => 'Custo',
- 'Cost breakdown' => 'Repartição dos custos',
'Custom Stylesheet' => 'Folha de estilos personalizada',
'download' => 'transferir',
- 'Do you really want to remove this budget line?' => 'Tem a certeza que quer remover esta rubrica orçamental?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Despesas',
'GBP - British Pound' => 'GBP - Libra Esterlina',
'INR - Indian Rupee' => 'INR - Rúpia indiana',
'JPY - Japanese Yen' => 'JPY - Iene japonês',
- 'New budget line' => 'Nova rubrica orçamental',
'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês',
- 'Remove a budget line' => 'Remover uma rubrica orçamental',
- 'Remove budget line' => 'Remover uma rubrica orçamental',
'RSD - Serbian dinar' => 'RSD - Dinar sérvio',
- 'The budget line have been created successfully.' => 'A rubrica orçamental foi criada com sucesso.',
- 'Unable to create the budget line.' => 'Impossível adicionar esta rubrica orçamental.',
- 'Unable to remove this budget line.' => 'Impossível remover esta rubrica orçamental.',
'USD - US Dollar' => 'USD - Dólar norte-americano',
- 'Remaining' => 'Restante',
'Destination column' => 'Coluna de destino',
'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um utilizador',
'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Taxa',
'Change reference currency' => 'Mudar a moeda de referência',
'Add a new currency rate' => 'Adicionar uma nova taxa para uma moeda',
- 'Currency rates are used to calculate project budget.' => 'As taxas de câmbio são utilizadas para calcular o orçamento do projecto.',
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível adicionar essa taxa de câmbio.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Visão geral do orçamento',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index 0ad983ff..054e2ac8 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Удаленный',
'Enabled' => 'Включен',
'Disabled' => 'Выключены',
- 'Google account linked' => 'Профиль Google ÑвÑзан',
- 'Github account linked' => 'Профиль Github ÑвÑзан',
'Username:' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ:',
'Name:' => 'ИмÑ:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Широкий вид',
'Compact/wide view' => 'Компактный/широкий вид',
'No results match:' => 'ОтÑутÑтвуют результаты:',
- 'Remove hourly rate' => 'Удалить почаÑовую Ñтавку',
- 'Do you really want to remove this hourly rate?' => 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñту почаÑовую Ñтавку?',
- 'Hourly rates' => 'ПочаÑовые Ñтавки',
- 'Hourly rate' => 'ПочаÑÐ¾Ð²Ð°Ñ Ñтавка',
'Currency' => 'Валюта',
- 'Effective date' => 'Дата вÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð² Ñилу',
- 'Add new rate' => 'Добавить новый показатель',
- 'Rate removed successfully.' => 'Показатель уÑпешно удален.',
- 'Unable to remove this rate.' => 'Ðе удаетÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ Ñтот показатель.',
- 'Unable to save the hourly rate.' => 'Ðе удаетÑÑ Ñохранить почаÑовую Ñтавку.',
- 'Hourly rate created successfully.' => 'ПочаÑÐ¾Ð²Ð°Ñ Ñтавка уÑпешно Ñоздана.',
'Start time' => 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ‡Ð°Ð»Ð°',
'End time' => 'Ð’Ñ€ÐµÐ¼Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ',
'Comment' => 'Комментарий',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Файлы',
'Images' => 'ИзображениÑ',
'Private project' => 'Приватный проект',
- 'Amount' => 'КоличеÑтво',
'AUD - Australian Dollar' => 'AUD - ÐвÑтралийÑкий доллар',
- 'Budget' => 'Бюджет',
- 'Budget line' => 'Ð¡Ñ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð°',
- 'Budget line removed successfully.' => 'Ð‘ÑŽÐ´Ð¶ÐµÑ‚Ð½Ð°Ñ ÑÑ‚Ð°Ñ‚ÑŒÑ ÑƒÑпешно удалена.',
- 'Budget lines' => 'Статьи бюджета',
'CAD - Canadian Dollar' => 'CAD - КанадÑкий доллар',
'CHF - Swiss Francs' => 'CHF - ШвейцарÑкий Франк',
- 'Cost' => 'СтоимоÑть',
- 'Cost breakdown' => 'Ð”ÐµÑ‚Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð·Ð°Ñ‚Ñ€Ð°Ñ‚',
'Custom Stylesheet' => 'ПользовательÑкий Ñтиль',
'download' => 'загрузить',
- 'Do you really want to remove this budget line?' => 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñту Ñтатью бюджета?',
'EUR - Euro' => 'EUR - Евро',
- 'Expenses' => 'РаÑходы',
'GBP - British Pound' => 'GBP - БританÑкий фунт',
'INR - Indian Rupee' => 'INR - ИндийÑкий рупий',
'JPY - Japanese Yen' => 'JPY - ЯпонÑкай йена',
- 'New budget line' => 'ÐÐ¾Ð²Ð°Ñ ÑÑ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð°',
'NZD - New Zealand Dollar' => 'NZD - ÐовозеландÑкий доллар',
- 'Remove a budget line' => 'Удалить Ñтроку в бюджете',
- 'Remove budget line' => 'Удалить Ñтатью бюджета',
'RSD - Serbian dinar' => 'RSD - СербÑкий динар',
- 'The budget line have been created successfully.' => 'Ð¡Ñ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð° уÑпешно Ñоздана.',
- 'Unable to create the budget line.' => 'Ðе удаетÑÑ Ñоздать Ñту Ñтатью бюджета.',
- 'Unable to remove this budget line.' => 'Ðе удаетÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ Ñту Ñтатью бюджета.',
'USD - US Dollar' => 'USD - доллар СШÐ',
- 'Remaining' => 'Прочее',
'Destination column' => 'Колонка назначениÑ',
'Move the task to another column when assigned to a user' => 'ПеремеÑтить задачу в другую колонку, когда она назначена пользователю',
'Move the task to another column when assignee is cleared' => 'ПеремеÑтить задачу в другую колонку, когда назначение ÑнÑто ',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'КурÑ',
'Change reference currency' => 'Изменить Ñправочник валют',
'Add a new currency rate' => 'Добавить новый валютный курÑ',
- 'Currency rates are used to calculate project budget.' => 'КурÑÑ‹ валют иÑпользуютÑÑ Ð´Ð»Ñ Ñ€Ð°Ñчета бюджета проекта.',
'Reference currency' => 'Справочник валют',
'The currency rate have been added successfully.' => 'ÐšÑƒÑ€Ñ Ð²Ð°Ð»ÑŽÑ‚Ñ‹ был уÑпешно добавлен.',
'Unable to add this currency rate.' => 'Ðевозможно добавить Ñтот ÐºÑƒÑ€Ñ Ð²Ð°Ð»ÑŽÑ‚Ñ‹.',
@@ -895,7 +866,7 @@ return array(
'Overdue tasks for the project "%s"' => 'ПроÑроченные задачи Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° "%s"',
'New title: %s' => 'Ðовый заголовок: %s',
'The task is not assigned anymore' => 'Задача больше не назначена',
- 'New assignee: %s' => '',
+ // 'New assignee: %s' => '',
'There is no category now' => 'Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð·Ð´ÐµÑÑŒ нет категорий',
'New category: %s' => 'ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ: %s',
'New color: %s' => 'Ðовый цвет: %s',
@@ -919,7 +890,7 @@ return array(
'New due date: %B %e, %Y' => 'ÐÐ¾Ð²Ð°Ñ Ð´Ð°Ñ‚Ð° завершениÑ: %B %e, %Y',
'Start date changed: %B %e, %Y' => 'Изменить дату начала: %B %e, %Y',
'%k:%M %p' => '%k:%M %p',
- '%%Y-%%m-%%d' => '%%Г-%%м-%%д',
+ '%%Y-%%m-%%d' => '%%Y-%%m-%%d',
'Total for all columns' => 'Суммарно Ð´Ð»Ñ Ð²Ñех колонок',
'You need at least 2 days of data to show the chart.' => 'Ð”Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ñ‹ нужно по крайней мере 2 днÑ.',
'<15m' => '<15м',
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 0bc5c248..a05d67d3 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Udaljno',
'Enabled' => 'Omogući',
'Disabled' => 'Onemogući',
- 'Google account linked' => 'Połączone konto Google',
- 'Github account linked' => 'Połączone konto Github',
'Username:' => 'KorisniÄko ime:',
'Name:' => 'Ime i Prezime',
'Email:' => 'Email: ',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index 9c769724..e8bdb9ce 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Fjärr',
'Enabled' => 'Aktiverad',
'Disabled' => 'Inaktiverad',
- 'Google account linked' => 'Googlekonto länkat',
- 'Github account linked' => 'Githubkonto länkat',
'Username:' => 'Användarnam:',
'Name:' => 'Namn:',
'Email:' => 'E-post:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horisontell scroll',
'Compact/wide view' => 'Kompakt/bred vy',
'No results match:' => 'Inga matchande resultat',
- 'Remove hourly rate' => 'Ta bort timtaxa',
- 'Do you really want to remove this hourly rate?' => 'Vill du verkligen ta bort denna timtaxa?',
- 'Hourly rates' => 'Timtaxor',
- 'Hourly rate' => 'Timtaxa',
'Currency' => 'Valuta',
- 'Effective date' => 'Giltighetsdatum',
- 'Add new rate' => 'Lägg till ny taxa',
- 'Rate removed successfully.' => 'Taxan togs bort.',
- 'Unable to remove this rate.' => 'Kunde inte ta bort taxan.',
- 'Unable to save the hourly rate.' => 'Kunde inte spara timtaxan.',
- 'Hourly rate created successfully.' => 'Timtaxan skapades.',
'Start time' => 'Starttid',
'End time' => 'Sluttid',
'Comment' => 'Kommentar',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Filer',
'Images' => 'Bilder',
'Private project' => 'Privat projekt',
- 'Amount' => 'Belopp',
'AUD - Australian Dollar' => 'AUD - Australiska dollar',
- 'Budget' => 'Budget',
- 'Budget line' => 'Budgetlinje',
- 'Budget line removed successfully.' => 'Budgetlinjen togs bort.',
- 'Budget lines' => 'Budgetlinjer',
'CAD - Canadian Dollar' => 'CAD - Kanadensiska dollar',
'CHF - Swiss Francs' => 'CHF - Schweiziska Franc',
- 'Cost' => 'Kostnad',
- 'Cost breakdown' => 'Kostnadssammanställning',
'Custom Stylesheet' => 'Anpassad stilmall',
'download' => 'ladda ned',
- 'Do you really want to remove this budget line?' => 'Vill du verkligen ta bort budgetlinjen?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Utgifter',
'GBP - British Pound' => 'GBP - Brittiska Pund',
'INR - Indian Rupee' => 'INR - Indiska Rupier',
'JPY - Japanese Yen' => 'JPY - Japanska Yen',
- 'New budget line' => 'Ny budgetlinje',
'NZD - New Zealand Dollar' => 'NZD - Nya Zeeländska Dollar',
- 'Remove a budget line' => 'Ta bort en budgetlinje',
- 'Remove budget line' => 'Ta bort budgetlinje',
'RSD - Serbian dinar' => 'RSD - Serbiska Dinarer',
- 'The budget line have been created successfully.' => 'Budgetlinjen har skapats.',
- 'Unable to create the budget line.' => 'Kunde inte skapa budgetlinjen.',
- 'Unable to remove this budget line.' => 'Kunde inte ta bort budgetlinjen.',
'USD - US Dollar' => 'USD - Amerikanska Dollar',
- 'Remaining' => 'Återstående',
'Destination column' => 'MÃ¥lkolumn',
'Move the task to another column when assigned to a user' => 'Flytta uppgiften till en annan kolumn när den tilldelats en användare',
'Move the task to another column when assignee is cleared' => 'Flytta uppgiften till en annan kolumn när tilldelningen tas bort.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurs',
'Change reference currency' => 'Ändra referenskurs',
'Add a new currency rate' => 'Lägg till ny valutakurs',
- 'Currency rates are used to calculate project budget.' => 'Valutakurser används för att beräkna projektbudget.',
'Reference currency' => 'Referensvaluta',
'The currency rate have been added successfully.' => 'Valutakursen har lagts till.',
'Unable to add this currency rate.' => 'Kunde inte lägga till valutakursen.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s flyttade uppgiften #%d till första swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s flyttade uppgiften #%d till swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Budgetöversikt',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Det finns inte tillräckligt mycket data för att visa något.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index a5ed2474..d94107ad 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'รีโมท',
'Enabled' => 'เปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰',
'Disabled' => 'ปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰',
- 'Google account linked' => 'เชื่อมà¸à¸±à¸šà¸à¸¹à¹€à¸à¸´à¸¥à¹à¸­à¸„เคาท์',
- 'Github account linked' => 'เชื่อมà¸à¸±à¸šà¸à¸´à¸—ฮับà¹à¸­à¸„เคาท์',
'Username:' => 'ชื่อผู้ใช้:',
'Name:' => 'ชื่อ:',
'Email:' => 'อีเมล:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'เลื่อนตามà¹à¸™à¸§à¸™à¸­à¸™',
'Compact/wide view' => 'พอดี/à¸à¸§à¹‰à¸²à¸‡ มุมมอง',
'No results match:' => 'ไม่มีผลลัพท์ที่ตรง',
- 'Remove hourly rate' => 'ลบอัตรารายชั่วโมง',
- 'Do you really want to remove this hourly rate?' => 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¸­à¸±à¸•รารายชั่วโมง?',
- 'Hourly rates' => 'อัตรารายชั่วโมง',
- 'Hourly rate' => 'อัตรารายชั่วโมง',
'Currency' => 'สà¸à¸¸à¸¥à¹€à¸‡à¸´à¸™',
- 'Effective date' => 'วันที่จ่าย',
- 'Add new rate' => 'เพิ่มอัตราใหม่',
- 'Rate removed successfully.' => 'ลบอัตราเรียบร้อยà¹à¸¥à¹‰à¸§',
- 'Unable to remove this rate.' => 'ไม่สามารถลบอัตรานี้ได้',
- 'Unable to save the hourly rate.' => 'ไม่สามารถบันทึà¸à¸­à¸±à¸•รารายชั่วโมง',
- 'Hourly rate created successfully.' => 'อัตรารายชั่วโมงสร้างเรียบร้อยà¹à¸¥à¹‰à¸§',
'Start time' => 'เวลาเริ่มต้น',
'End time' => 'เวลาจบ',
'Comment' => 'ความคิดเห็น',
@@ -703,34 +691,18 @@ return array(
'Files' => 'ไฟล์',
'Images' => 'รูปภาพ',
'Private project' => 'โปรเจคส่วนตัว',
- 'Amount' => 'จำนวนเงิน',
// 'AUD - Australian Dollar' => '',
- 'Budget' => 'งบประมาณ',
- 'Budget line' => 'วงเงินงบประมาณ',
- 'Budget line removed successfully.' => 'ลบวงเงินประมาณเรียบร้อยà¹à¸¥à¹‰à¸§',
- 'Budget lines' => 'วงเงินงบประมาณ',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'มูลค่า',
- 'Cost breakdown' => 'รายละเอียดค่าใช้จ่าย',
// 'Custom Stylesheet' => '',
'download' => 'ดาวน์โหลด',
- 'Do you really want to remove this budget line?' => 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¸§à¸‡à¹€à¸‡à¸´à¸™à¸‡à¸šà¸›à¸£à¸°à¸¡à¸²à¸“นี้?',
// 'EUR - Euro' => '',
- 'Expenses' => 'รายจ่าย',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- 'New budget line' => 'วงเงินงบประมาณใหม่',
// 'NZD - New Zealand Dollar' => '',
- 'Remove a budget line' => 'ลบวงเงินประมาณ',
- 'Remove budget line' => 'ลบวงเงินประมาณ',
// 'RSD - Serbian dinar' => '',
- 'The budget line have been created successfully.' => 'สร้างวงเงินงบประมาณเรียบร้อยà¹à¸¥à¹‰à¸§',
- 'Unable to create the budget line.' => 'ไม่สามารถสร้างวงเงินงบประมาณได้',
- 'Unable to remove this budget line.' => 'ไม่สามารถลบวงเงินงบประมาณนี้',
// 'USD - US Dollar' => '',
- 'Remaining' => 'เหลืออยู่',
'Destination column' => 'คอลัมน์เป้าหมาย',
'Move the task to another column when assigned to a user' => 'ย้ายงานไปคอลัมน์อื่นเมื่อà¸à¸³à¸«à¸™à¸”บุคคลรับผิดชอบ',
'Move the task to another column when assignee is cleared' => 'ย้ายงานไปคอลัมน์อื่นเมื่อไม่à¸à¸³à¸«à¸™à¸”บุคคลรับผิดชอบ',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'อัตรา',
// 'Change reference currency' => '',
'Add a new currency rate' => 'เพิ่มอัตราà¹à¸¥à¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸‡à¸´à¸™à¸•ราใหม่',
- 'Currency rates are used to calculate project budget.' => 'อัตราà¹à¸¥à¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸‡à¸´à¸™à¸•ราถูà¸à¹ƒà¸Šà¹‰à¹ƒà¸™à¸à¸²à¸£à¸„ำนวณงบประมาณของโปรเจค',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index 9eb5c41e..87cccac7 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Uzak',
'Enabled' => 'EtkinleÅŸtirildi',
'Disabled' => 'Devre dışı bırakıldı',
- 'Google account linked' => 'Google hesabıyla bağlı',
- 'Github account linked' => 'Github hesabıyla bağlı',
'Username:' => 'Kullanıcı adı',
'Name:' => 'Ad',
'Email:' => 'E-Posta',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Geniş görünüm',
'Compact/wide view' => 'Ekrana sığdır / Geniş görünüm',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index 910bc0b4..102252e5 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => '远程',
'Enabled' => 'å¯ç”¨',
'Disabled' => 'åœç”¨',
- 'Google account linked' => 'å·²ç»é“¾æŽ¥è°·æ­Œè´¦å·',
- 'Github account linked' => 'å·²ç»é“¾æŽ¥Githubè´¦å·',
'Username:' => '用户å:',
'Name:' => 'å§“å:',
'Email:' => '电å­é‚®ä»¶ï¼š',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => '水平滚动',
'Compact/wide view' => '紧凑/宽视图',
'No results match:' => '无匹é…结果:',
- 'Remove hourly rate' => 'åˆ é™¤å°æ—¶å·¥èµ„',
- 'Do you really want to remove this hourly rate?' => '确定è¦åˆ é™¤æ­¤è®¡æ—¶å·¥èµ„å—?',
- 'Hourly rates' => 'å°æ—¶å·¥èµ„',
- 'Hourly rate' => 'å°æ—¶å·¥èµ„',
'Currency' => 'è´§å¸',
- 'Effective date' => '开始时间',
- 'Add new rate' => 'æ·»åŠ å°æ—¶å·¥èµ„',
- 'Rate removed successfully.' => 'æˆåŠŸåˆ é™¤å·¥èµ„ã€‚',
- 'Unable to remove this rate.' => 'æ— æ³•åˆ é™¤æ­¤å°æ—¶å·¥èµ„。',
- 'Unable to save the hourly rate.' => 'æ— æ³•åˆ é™¤å°æ—¶å·¥èµ„。',
- 'Hourly rate created successfully.' => 'æˆåŠŸåˆ›å»ºå°æ—¶å·¥èµ„。',
'Start time' => '开始时间',
'End time' => 'ç»“æŸæ—¶1é—´',
'Comment' => '注释',
@@ -703,34 +691,18 @@ return array(
'Files' => '文件',
'Images' => '图片',
'Private project' => 'ç§äººé¡¹ç›®',
- 'Amount' => 'æ•°é‡',
// 'AUD - Australian Dollar' => '',
- 'Budget' => '预算',
- 'Budget line' => '预算线',
- 'Budget line removed successfully.' => 'æˆåŠŸåˆ é™¤é¢„ç®—çº¿',
- 'Budget lines' => '预算线',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'æˆæœ¬',
- 'Cost breakdown' => 'æˆæœ¬åˆ†è§£',
'Custom Stylesheet' => '自定义样å¼è¡¨',
'download' => '下载',
- 'Do you really want to remove this budget line?' => '确定è¦åˆ é™¤æ­¤é¢„算线å—?',
// 'EUR - Euro' => '',
- 'Expenses' => '花费',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- 'New budget line' => '新预算线',
// 'NZD - New Zealand Dollar' => '',
- 'Remove a budget line' => '删除预算线',
- 'Remove budget line' => '删除预算线',
// 'RSD - Serbian dinar' => '',
- 'The budget line have been created successfully.' => 'æˆåŠŸåˆ›å»ºé¢„ç®—çº¿ã€‚',
- 'Unable to create the budget line.' => '无法创建预算线。',
- 'Unable to remove this budget line.' => '无法删除此预算线。',
// 'USD - US Dollar' => '',
- 'Remaining' => '剩余',
'Destination column' => '目标æ ç›®',
'Move the task to another column when assigned to a user' => '指定负责人时移动到其它æ ç›®',
'Move the task to another column when assignee is cleared' => '移除负责人时移动到其它æ ç›®',
@@ -746,7 +718,6 @@ return array(
'Rate' => '汇率',
'Change reference currency' => '修改å‚考货å¸',
'Add a new currency rate' => '添加新汇率',
- 'Currency rates are used to calculate project budget.' => '汇率会用æ¥è®¡ç®—项目预算。',
'Reference currency' => 'å‚考货å¸',
'The currency rate have been added successfully.' => 'æˆåŠŸæ·»åŠ æ±‡çŽ‡ã€‚',
'Unable to add this currency rate.' => '无法添加此汇率',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- 'Budget overview' => '预算概览',
- 'Type' => '类型',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 8c28cb1a..9a227cf5 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -64,7 +64,6 @@ class Acl extends Base
'export' => '*',
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
- 'budget' => '*',
'gantt' => array('project', 'savetaskdate', 'task', 'savetask'),
);
@@ -95,6 +94,18 @@ class Acl extends Base
);
/**
+ * Extend ACL rules
+ *
+ * @access public
+ * @param string $acl_name
+ * @param aray $rules
+ */
+ public function extend($acl_name, array $rules)
+ {
+ $this->$acl_name = array_merge($this->$acl_name, $rules);
+ }
+
+ /**
* Return true if the specified controller/action match the given acl
*
* @access public
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
deleted file mode 100644
index 76c42ca9..00000000
--- a/app/Model/Budget.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-
-namespace Model;
-
-use DateInterval;
-use DateTime;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Budget
- *
- * @package model
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'budget_lines';
-
- /**
- * Get all budget lines for a project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAll($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('date')->findAll();
- }
-
- /**
- * Get the current total of the budget
- *
- * @access public
- * @param integer $project_id
- * @return float
- */
- public function getTotal($project_id)
- {
- $result = $this->db->table(self::TABLE)->columns('SUM(amount) as total')->eq('project_id', $project_id)->findOne();
- return isset($result['total']) ? (float) $result['total'] : 0;
- }
-
- /**
- * Get breakdown by tasks/subtasks/users
- *
- * @access public
- * @param integer $project_id
- * @return \PicoDb\Table
- */
- public function getSubtaskBreakdown($project_id)
- {
- return $this->db
- ->table(SubtaskTimeTracking::TABLE)
- ->columns(
- SubtaskTimeTracking::TABLE.'.id',
- SubtaskTimeTracking::TABLE.'.user_id',
- SubtaskTimeTracking::TABLE.'.subtask_id',
- SubtaskTimeTracking::TABLE.'.start',
- SubtaskTimeTracking::TABLE.'.time_spent',
- Subtask::TABLE.'.task_id',
- Subtask::TABLE.'.title AS subtask_title',
- Task::TABLE.'.title AS task_title',
- Task::TABLE.'.project_id',
- User::TABLE.'.username',
- User::TABLE.'.name'
- )
- ->join(Subtask::TABLE, 'id', 'subtask_id')
- ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
- ->join(User::TABLE, 'id', 'user_id')
- ->eq(Task::TABLE.'.project_id', $project_id)
- ->callback(array($this, 'applyUserRate'));
- }
-
- /**
- * Gather necessary information to display the budget graph
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getDailyBudgetBreakdown($project_id)
- {
- $out = array();
- $in = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->gt('amount', 0)->asc('date')->getAll('date', 'amount');
- $time_slots = $this->getSubtaskBreakdown($project_id)->findAll();
-
- foreach ($time_slots as $slot) {
- $date = date('Y-m-d', $slot['start']);
-
- if (! isset($out[$date])) {
- $out[$date] = 0;
- }
-
- $out[$date] += $slot['cost'];
- }
-
- $start = key($in) ?: key($out);
- $end = new DateTime;
- $left = 0;
- $serie = array();
-
- for ($today = new DateTime($start); $today <= $end; $today->add(new DateInterval('P1D'))) {
-
- $date = $today->format('Y-m-d');
- $today_in = isset($in[$date]) ? (int) $in[$date] : 0;
- $today_out = isset($out[$date]) ? (int) $out[$date] : 0;
-
- if ($today_in > 0 || $today_out > 0) {
-
- $left += $today_in;
- $left -= $today_out;
-
- $serie[] = array(
- 'date' => $date,
- 'in' => $today_in,
- 'out' => -$today_out,
- 'left' => $left,
- );
- }
- }
-
- return $serie;
- }
-
- /**
- * Filter callback to apply the rate according to the effective date
- *
- * @access public
- * @param array $records
- * @return array
- */
- public function applyUserRate(array $records)
- {
- $rates = $this->hourlyRate->getAllByProject($records[0]['project_id']);
-
- foreach ($records as &$record) {
-
- $hourly_price = 0;
-
- foreach ($rates as $rate) {
-
- if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) {
- $hourly_price = $this->currency->getPrice($rate['currency'], $rate['rate']);
- break;
- }
- }
-
- $record['cost'] = $hourly_price * $record['time_spent'];
- }
-
- return $records;
- }
-
- /**
- * Add a new budget line in the database
- *
- * @access public
- * @param integer $project_id
- * @param float $amount
- * @param string $comment
- * @param string $date
- * @return boolean|integer
- */
- public function create($project_id, $amount, $comment, $date = '')
- {
- $values = array(
- 'project_id' => $project_id,
- 'amount' => $amount,
- 'comment' => $comment,
- 'date' => $date ?: date('Y-m-d'),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific budget line
- *
- * @access public
- * @param integer $budget_id
- * @return boolean
- */
- public function remove($budget_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $budget_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('project_id', t('Field required')),
- new Validators\Required('amount', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-} \ No newline at end of file
diff --git a/app/Model/Config.php b/app/Model/Config.php
index 6fa98f93..8e51da24 100644
--- a/app/Model/Config.php
+++ b/app/Model/Config.php
@@ -75,6 +75,7 @@ class Config extends Base
{
// Sorted by value
$languages = array(
+ 'id_ID' => 'Bahasa Indonesia',
'cs_CZ' => 'Čeština',
'da_DK' => 'Dansk',
'de_DE' => 'Deutsch',
@@ -135,6 +136,7 @@ class Config extends Base
'zh_CN' => 'zh-cn',
'ja_JP' => 'ja',
'th_TH' => 'th',
+ 'id_ID' => 'id'
);
$lang = $this->getCurrentLanguage();
diff --git a/app/Model/File.php b/app/Model/File.php
index f884e460..7adab42b 100644
--- a/app/Model/File.php
+++ b/app/Model/File.php
@@ -3,6 +3,8 @@
namespace Model;
use Event\FileEvent;
+use Core\Tool;
+use Core\ObjectStorage\ObjectStorageException;
/**
* File model
@@ -47,14 +49,17 @@ class File extends Base
*/
public function remove($file_id)
{
- $file = $this->getbyId($file_id);
+ try {
- if (! empty($file)) {
- @unlink(FILES_DIR.$file['path']);
- return $this->db->table(self::TABLE)->eq('id', $file_id)->remove();
- }
+ $file = $this->getbyId($file_id);
+ $this->objectStorage->remove($file['path']);
- return false;
+ return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove();
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -66,11 +71,11 @@ class File extends Base
*/
public function removeAll($task_id)
{
- $files = $this->getAll($task_id);
+ $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id');
$results = array();
- foreach ($files as $file) {
- $results[] = $this->remove($file['id']);
+ foreach ($file_ids as $file_id) {
+ $results[] = $this->remove($file_id);
}
return ! in_array(false, $results, true);
@@ -196,6 +201,30 @@ class File extends Base
}
/**
+ * Return the image mimetype based on the file extension
+ *
+ * @access public
+ * @param $filename
+ * @return string
+ */
+ public function getImageMimeType($filename)
+ {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ switch ($extension) {
+ case 'jpeg':
+ case 'jpg':
+ return 'image/jpeg';
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ default:
+ return 'image/jpeg';
+ }
+ }
+
+ /**
* Generate the path for a new filename
*
* @access public
@@ -210,6 +239,18 @@ class File extends Base
}
/**
+ * Generate the path for a thumbnails
+ *
+ * @access public
+ * @param string $key Storage key
+ * @return string
+ */
+ public function getThumbnailPath($key)
+ {
+ return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
+ }
+
+ /**
* Handle file upload
*
* @access public
@@ -218,11 +259,13 @@ class File extends Base
* @param string $form_name File form name
* @return bool
*/
- public function upload($project_id, $task_id, $form_name)
+ public function uploadFiles($project_id, $task_id, $form_name)
{
- $results = array();
+ try {
- if (! empty($_FILES[$form_name])) {
+ if (empty($_FILES[$form_name])) {
+ return false;
+ }
foreach ($_FILES[$form_name]['error'] as $key => $error) {
@@ -232,22 +275,27 @@ class File extends Base
$uploaded_filename = $_FILES[$form_name]['tmp_name'][$key];
$destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromFile($uploaded_filename, $destination_filename);
+ }
- if (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) {
+ $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename);
- $results[] = $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- $_FILES[$form_name]['size'][$key]
- );
- }
+ $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ $_FILES[$form_name]['size'][$key]
+ );
}
}
- }
- return ! in_array(false, $results, true);
+ return true;
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -261,129 +309,77 @@ class File extends Base
*/
public function uploadScreenshot($project_id, $task_id, $blob)
{
- $data = base64_decode($blob);
-
- if (empty($data)) {
- return false;
- }
-
$original_filename = e('Screenshot taken %s', dt('%B %e, %Y at %k:%M %p', time())).'.png';
- $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
-
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
-
- return $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->uploadContent($project_id, $task_id, $original_filename, $blob);
}
/**
* Handle file upload (base64 encoded content)
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $filename Filename
- * @param string $blob Base64 encoded image
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param string $original_filename Filename
+ * @param string $blob Base64 encoded file
* @return bool|integer
*/
- public function uploadContent($project_id, $task_id, $filename, $blob)
+ public function uploadContent($project_id, $task_id, $original_filename, $blob)
{
- $data = base64_decode($blob);
+ try {
- if (empty($data)) {
- return false;
- }
+ $data = base64_decode($blob);
- $destination_filename = $this->generatePath($project_id, $task_id, $filename);
+ if (empty($data)) {
+ return false;
+ }
+
+ $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
+ $this->objectStorage->put($destination_filename, $data);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromData($destination_filename, $data);
+ }
- return $this->create(
- $task_id,
- $filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ strlen($data)
+ );
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
- * Generate a jpeg thumbnail from an image (output directly the image)
+ * Generate thumbnail from a blob
*
* @access public
- * @param string $filename Source image
- * @param integer $resize_width Desired image width
- * @param integer $resize_height Desired image height
+ * @param string $destination_filename
+ * @param string $data
*/
- public function generateThumbnail($filename, $resize_width, $resize_height)
+ public function generateThumbnailFromData($destination_filename, &$data)
{
- $metadata = getimagesize($filename);
- $src_width = $metadata[0];
- $src_height = $metadata[1];
- $dst_y = 0;
- $dst_x = 0;
-
- if (empty($metadata['mime'])) {
- return;
- }
-
- if ($resize_width == 0 && $resize_height == 0) {
- $resize_width = 100;
- $resize_height = 100;
- }
-
- if ($resize_width > 0 && $resize_height == 0) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- elseif ($resize_width == 0 && $resize_height > 0) {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- else {
-
- $src_ratio = $src_width / $src_height;
- $resize_ratio = $resize_width / $resize_height;
-
- if ($src_ratio <= $resize_ratio) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
-
- $dst_y = ($dst_height - $resize_height) / 2 * (-1);
- }
- else {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
-
- $dst_x = ($dst_width - $resize_width) / 2 * (-1);
- }
+ $temp_filename = tempnam(sys_get_temp_dir(), 'datafile');
- $dst_image = imagecreatetruecolor($resize_width, $resize_height);
- }
-
- switch ($metadata['mime']) {
- case 'image/jpeg':
- case 'image/jpg':
- $src_image = imagecreatefromjpeg($filename);
- break;
- case 'image/png':
- $src_image = imagecreatefrompng($filename);
- break;
- case 'image/gif':
- $src_image = imagecreatefromgif($filename);
- break;
- default:
- return;
- }
+ file_put_contents($temp_filename, $data);
+ $this->generateThumbnailFromFile($temp_filename, $destination_filename);
+ unlink($temp_filename);
+ }
- imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
- imagejpeg($dst_image);
+ /**
+ * Generate thumbnail from a blob
+ *
+ * @access public
+ * @param string $uploaded_filename
+ * @param string $destination_filename
+ */
+ public function generateThumbnailFromFile($uploaded_filename, $destination_filename)
+ {
+ $thumbnail_filename = tempnam(sys_get_temp_dir(), 'thumbnail');
+ Tool::generateThumbnail($uploaded_filename, $thumbnail_filename);
+ $this->objectStorage->moveFile($thumbnail_filename, $this->getThumbnailPath($destination_filename));
}
}
diff --git a/app/Model/HourlyRate.php b/app/Model/HourlyRate.php
deleted file mode 100644
index 1550bdae..00000000
--- a/app/Model/HourlyRate.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
-namespace Model;
-
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Hourly Rate
- *
- * @package model
- * @author Frederic Guillot
- */
-class HourlyRate extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'hourly_rates';
-
- /**
- * Get all user rates for a given project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAllByProject($project_id)
- {
- $members = $this->projectPermission->getMembers($project_id);
-
- if (empty($members)) {
- return array();
- }
-
- return $this->db->table(self::TABLE)->in('user_id', array_keys($members))->desc('date_effective')->findAll();
- }
-
- /**
- * Get all rates for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return array
- */
- public function getAllByUser($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll();
- }
-
- /**
- * Get current rate for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return float
- */
- public function getCurrentRate($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0;
- }
-
- /**
- * Add a new rate in the database
- *
- * @access public
- * @param integer $user_id User id
- * @param float $rate Hourly rate
- * @param string $currency Currency code
- * @param string $date ISO8601 date format
- * @return boolean|integer
- */
- public function create($user_id, $rate, $currency, $date)
- {
- $values = array(
- 'user_id' => $user_id,
- 'rate' => $rate,
- 'currency' => $currency,
- 'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific rate
- *
- * @access public
- * @param integer $rate_id
- * @return boolean
- */
- public function remove($rate_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('user_id', t('Field required')),
- new Validators\Required('rate', t('Field required')),
- new Validators\Numeric('rate', t('This value must be numeric')),
- new Validators\Required('date_effective', t('Field required')),
- new Validators\Required('currency', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-}
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index d8a44aff..24508c91 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -49,6 +49,7 @@ class Subtask extends Base
*/
const EVENT_UPDATE = 'subtask.update';
const EVENT_CREATE = 'subtask.create';
+ const EVENT_DELETE = 'subtask.delete';
/**
* Get available status
@@ -174,6 +175,23 @@ class Subtask extends Base
}
/**
+ * Prepare data before insert
+ *
+ * @access public
+ * @param array $values Form values
+ */
+ public function prepareCreation(array &$values)
+ {
+ $this->prepare($values);
+
+ $values['position'] = $this->getLastPosition($values['task_id']) + 1;
+ $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO;
+ $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0;
+ $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0;
+ $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0;
+ }
+
+ /**
* Get the position of the last column for a given project
*
* @access public
@@ -198,9 +216,7 @@ class Subtask extends Base
*/
public function create(array $values)
{
- $this->prepare($values);
- $values['position'] = $this->getLastPosition($values['task_id']) + 1;
-
+ $this->prepareCreation($values);
$subtask_id = $this->persist(self::TABLE, $values);
if ($subtask_id) {
@@ -223,14 +239,13 @@ class Subtask extends Base
public function update(array $values)
{
$this->prepare($values);
+ $subtask = $this->getById($values['id']);
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
-
- $this->container['dispatcher']->dispatch(
- self::EVENT_UPDATE,
- new SubtaskEvent($values)
- );
+ $event = $subtask;
+ $event['changes'] = array_diff_assoc($values, $subtask);
+ $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new SubtaskEvent($event));
}
return $result;
@@ -302,7 +317,6 @@ class Subtask extends Base
$positions = array_flip($subtasks);
if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) {
-
$position = ++$subtasks[$subtask_id];
$subtasks[$positions[$position]]--;
@@ -402,7 +416,14 @@ class Subtask extends Base
*/
public function remove($subtask_id)
{
- return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+ $subtask = $this->getById($subtask_id);
+ $result = $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+
+ if ($result) {
+ $this->container['dispatcher']->dispatch(self::EVENT_DELETE, new SubtaskEvent($subtask));
+ }
+
+ return $result;
}
/**
diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php
index 997031e8..56998769 100644
--- a/app/Model/SubtaskTimeTracking.php
+++ b/app/Model/SubtaskTimeTracking.php
@@ -339,20 +339,7 @@ class SubtaskTimeTracking extends Base
*/
public function updateTaskTimeTracking($task_id)
{
- $result = $this->calculateSubtaskTime($task_id);
- $values = array();
-
- if ($result['total_spent'] > 0) {
- $values['time_spent'] = $result['total_spent'];
- }
-
- if ($result['total_estimated'] > 0) {
- $values['time_estimated'] = $result['total_estimated'];
- }
-
- if (empty($values)) {
- return true;
- }
+ $values = $this->calculateSubtaskTime($task_id);
return $this->db
->table(Task::TABLE)
@@ -373,8 +360,8 @@ class SubtaskTimeTracking extends Base
->table(Subtask::TABLE)
->eq('task_id', $task_id)
->columns(
- 'SUM(time_spent) AS total_spent',
- 'SUM(time_estimated) AS total_estimated'
+ 'SUM(time_spent) AS time_spent',
+ 'SUM(time_estimated) AS time_estimated'
)
->findOne();
}
diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php
index 3b78a406..06e879a4 100644
--- a/app/Model/Swimlane.php
+++ b/app/Model/Swimlane.php
@@ -160,7 +160,7 @@ class Swimlane extends Base
public function getSwimlanes($project_id)
{
$swimlanes = $this->db->table(self::TABLE)
- ->columns('id', 'name')
+ ->columns('id', 'name', 'description')
->eq('project_id', $project_id)
->eq('is_active', self::ACTIVE)
->orderBy('position', 'asc')
@@ -216,32 +216,30 @@ class Swimlane extends Base
* Add a new swimlane
*
* @access public
- * @param integer $project_id
- * @param string $name
+ * @param array $values Form values
* @return integer|boolean
*/
- public function create($project_id, $name)
+ public function create($values)
{
- return $this->persist(self::TABLE, array(
- 'project_id' => $project_id,
- 'name' => $name,
- 'position' => $this->getLastPosition($project_id),
- ));
+ if (! $this->project->exists($values['project_id'])) {
+ return 0;
+ }
+ $values['position'] = $this->getLastPosition($values['project_id']);
+ return $this->persist(self::TABLE, $values);
}
/**
- * Rename a swimlane
+ * Update a swimlane
*
* @access public
- * @param integer $swimlane_id Swimlane id
- * @param string $name Swimlane name
+ * @param array $values Form values
* @return bool
*/
- public function rename($swimlane_id, $name)
+ public function update(array $values)
{
return $this->db->table(self::TABLE)
- ->eq('id', $swimlane_id)
- ->update(array('name' => $name));
+ ->eq('id', $values['id'])
+ ->update($values);
}
/**
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index b1ac0ab9..e5dff0d5 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,23 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 85;
+const VERSION = 87;
+
+function version_87($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin VARCHAR(80) NOT NULL,
+ version INT NOT NULL DEFAULT 0,
+ PRIMARY KEY(plugin)
+ ) ENGINE=InnoDB CHARSET=utf8
+ ");
+}
+
+function version_86($pdo)
+{
+ $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT");
+}
function version_85($pdo)
{
@@ -306,19 +322,6 @@ function version_53($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent FLOAT DEFAULT 0");
}
-function version_52($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- `id` INT NOT NULL AUTO_INCREMENT,
- `project_id` INT NOT NULL,
- `amount` FLOAT NOT NULL,
- `date` VARCHAR(10) NOT NULL,
- `comment` TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
- PRIMARY KEY(id)
- ) ENGINE=InnoDB CHARSET=utf8');
-}
-
function version_51($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -365,19 +368,6 @@ function version_51($pdo)
) ENGINE=InnoDB CHARSET=utf8');
}
-function version_50($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id INT NOT NULL AUTO_INCREMENT,
- user_id INT NOT NULL,
- rate FLOAT DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency CHAR(3) NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
- PRIMARY KEY(id)
- ) ENGINE=InnoDB CHARSET=utf8");
-}
-
function version_49($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index 9477b416..e7422e8c 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,22 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 65;
+const VERSION = 67;
+
+function version_67($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin VARCHAR(80) NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL DEFAULT 0
+ )
+ ");
+}
+
+function version_66($pdo)
+{
+ $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT");
+}
function version_65($pdo)
{
@@ -300,18 +315,6 @@ function version_34($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
-function version_33($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- "id" SERIAL PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "amount" REAL NOT NULL,
- "date" VARCHAR(10) NOT NULL,
- "comment" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-
function version_32($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -354,18 +357,6 @@ function version_32($pdo)
)');
}
-function version_31($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id SERIAL PRIMARY KEY,
- user_id INTEGER NOT NULL,
- rate REAL DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency CHAR(3) NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
- )");
-}
-
function version_30($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index b4e4b948..b76e902a 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,22 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 81;
+const VERSION = 83;
+
+function version_83($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin TEXT NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL DEFAULT 0
+ )
+ ");
+}
+
+function version_82($pdo)
+{
+ $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT");
+}
function version_81($pdo)
{
@@ -277,18 +292,6 @@ function version_52($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
-function version_51($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- "id" INTEGER PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "amount" REAL NOT NULL,
- "date" TEXT NOT NULL,
- "comment" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-
function version_50($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -331,18 +334,6 @@ function version_50($pdo)
)');
}
-function version_49($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- rate REAL DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency TEXT NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
- )");
-}
-
function version_48($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index ef7aa575..a5677948 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -2,13 +2,16 @@
namespace ServiceProvider;
+use Core\ObjectStorage\FileStorage;
use Core\Paginator;
use Core\OAuth2;
+use Core\Tool;
use Model\Config;
use Model\Project;
use Model\Webhook;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
+use League\HTMLToMarkdown\HtmlConverter;
class ClassProvider implements ServiceProviderInterface
{
@@ -18,7 +21,6 @@ class ClassProvider implements ServiceProviderInterface
'Action',
'Authentication',
'Board',
- 'Budget',
'Category',
'Color',
'Comment',
@@ -26,7 +28,6 @@ class ClassProvider implements ServiceProviderInterface
'Currency',
'DateParser',
'File',
- 'HourlyRate',
'LastLogin',
'Link',
'Notification',
@@ -93,17 +94,7 @@ class ClassProvider implements ServiceProviderInterface
public function register(Container $container)
{
- foreach ($this->classes as $namespace => $classes) {
-
- foreach ($classes as $name) {
-
- $class = '\\'.$namespace.'\\'.$name;
-
- $container[lcfirst($name)] = function ($c) use ($class) {
- return new $class($c);
- };
- }
- }
+ Tool::buildDIC($container, $this->classes);
$container['paginator'] = $container->factory(function ($c) {
return new Paginator($c);
@@ -112,5 +103,13 @@ class ClassProvider implements ServiceProviderInterface
$container['oauth'] = $container->factory(function ($c) {
return new OAuth2($c);
});
+
+ $container['htmlConverter'] = function($c) {
+ return new HtmlConverter(array('strip_tags' => true));
+ };
+
+ $container['objectStorage'] = function($c) {
+ return new FileStorage(FILES_DIR);
+ };
}
}
diff --git a/app/Subscriber/SubtaskTimeTrackingSubscriber.php b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
index e45b2c93..2d3b5f99 100644
--- a/app/Subscriber/SubtaskTimeTrackingSubscriber.php
+++ b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
@@ -12,6 +12,7 @@ class SubtaskTimeTrackingSubscriber extends \Core\Base implements EventSubscribe
{
return array(
Subtask::EVENT_CREATE => array('updateTaskTime', 0),
+ Subtask::EVENT_DELETE => array('updateTaskTime', 0),
Subtask::EVENT_UPDATE => array(
array('logStartEnd', 10),
array('updateTaskTime', 0),
diff --git a/app/Template/app/sidebar.php b/app/Template/app/sidebar.php
index 2d966009..f4a455f8 100644
--- a/app/Template/app/sidebar.php
+++ b/app/Template/app/sidebar.php
@@ -19,6 +19,7 @@
<li <?= $this->app->getRouterAction() === 'activity' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?>
</li>
+ <?= $this->hook->render('dashboard:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/board/table_swimlane.php b/app/Template/board/table_swimlane.php
index be401633..1caa920d 100644
--- a/app/Template/board/table_swimlane.php
+++ b/app/Template/board/table_swimlane.php
@@ -7,6 +7,20 @@
<i class="fa fa-minus-circle hide-icon-swimlane-<?= $swimlane['id'] ?>"></i>
<i class="fa fa-plus-circle show-icon-swimlane-<?= $swimlane['id'] ?>" style="display: none"></i>
</a>
+
+ <?php if (! empty($swimlane['description'])): ?>
+ <span
+ title="<?= t('Description') ?>"
+ class="tooltip"
+ data-href="<?= $this->url->href('board', 'swimlane', array('swimlane_id' => $swimlane['id'], 'project_id' => $project['id'])) ?>">
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
+
+ <span title="<?= t('Task count') ?>" class="board-column-header-task-count swimlane-task-count-<?= $swimlane['id'] ?>">
+ (<?= $swimlane['nb_tasks'] ?>)
+ </span>
+
<span class="board-swimlane-toggle-title show-icon-swimlane-<?= $swimlane['id'] ?>"><?= $this->e($swimlane['name']) ?></span>
<?php endif ?>
</th>
@@ -62,10 +76,6 @@
<?php if (! $hide_swimlane): ?>
<th class="board-swimlane-title">
<?= $this->e($swimlane['name']) ?>
-
- <div title="<?= t('Task count') ?>" class="board-column-header-task-count">
- (<span><?= $swimlane['nb_tasks'] ?></span>)
- </div>
</th>
<?php endif ?>
diff --git a/app/Template/budget/breakdown.php b/app/Template/budget/breakdown.php
deleted file mode 100644
index 92561188..00000000
--- a/app/Template/budget/breakdown.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<div class="page-header">
- <h2><?= t('Cost breakdown') ?></h2>
-</div>
-
-<?php if ($paginator->isEmpty()): ?>
- <p class="alert"><?= t('There is nothing to show.') ?></p>
-<?php else: ?>
- <table class="table-fixed">
- <tr>
- <th class="column-20"><?= $paginator->order(t('Task'), 'task_title') ?></th>
- <th class="column-25"><?= $paginator->order(t('Subtask'), 'subtask_title') ?></th>
- <th class="column-20"><?= $paginator->order(t('User'), 'username') ?></th>
- <th class="column-10"><?= t('Cost') ?></th>
- <th class="column-10"><?= $paginator->order(t('Time spent'), \Model\SubtaskTimeTracking::TABLE.'.time_spent') ?></th>
- <th class="column-15"><?= $paginator->order(t('Date'), 'start') ?></th>
- </tr>
- <?php foreach ($paginator->getCollection() as $record): ?>
- <tr>
- <td><?= $this->url->link($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->url->link($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->url->link($this->e($record['name'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?></td>
- <td><?= n($record['cost']) ?></td>
- <td><?= n($record['time_spent']).' '.t('hours') ?></td>
- <td><?= dt('%B %e, %Y', $record['start']) ?></td>
- </tr>
- <?php endforeach ?>
- </table>
-
- <?= $paginator ?>
-<?php endif ?> \ No newline at end of file
diff --git a/app/Template/budget/create.php b/app/Template/budget/create.php
deleted file mode 100644
index a563796d..00000000
--- a/app/Template/budget/create.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<div class="page-header">
- <h2><?= t('Budget lines') ?></h2>
-</div>
-
-<?php if (! empty($lines)): ?>
-<table class="table-fixed table-stripped">
- <tr>
- <th class="column-20"><?= t('Budget line') ?></th>
- <th class="column-20"><?= t('Date') ?></th>
- <th><?= t('Comment') ?></th>
- <th><?= t('Action') ?></th>
- </tr>
- <?php foreach ($lines as $line): ?>
- <tr>
- <td><?= n($line['amount']) ?></td>
- <td><?= dt('%B %e, %Y', strtotime($line['date'])) ?></td>
- <td><?= $this->e($line['comment']) ?></td>
- <td>
- <?= $this->url->link(t('Remove'), 'budget', 'confirm', array('project_id' => $project['id'], 'budget_id' => $line['id'])) ?>
- </td>
- </tr>
- <?php endforeach ?>
-</table>
-
-<h3><?= t('New budget line') ?></h3>
-<?php endif ?>
-
-<form method="post" action="<?= $this->url->href('budget', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off">
-
- <?= $this->form->csrf() ?>
-
- <?= $this->form->hidden('id', $values) ?>
- <?= $this->form->hidden('project_id', $values) ?>
-
- <?= $this->form->label(t('Amount'), 'amount') ?>
- <?= $this->form->text('amount', $values, $errors, array('required'), 'form-numeric') ?>
-
- <?= $this->form->label(t('Date'), 'date') ?>
- <?= $this->form->text('date', $values, $errors, array('required'), 'form-date') ?>
-
- <?= $this->form->label(t('Comment'), 'comment') ?>
- <?= $this->form->text('comment', $values, $errors) ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- </div>
-</form> \ No newline at end of file
diff --git a/app/Template/budget/index.php b/app/Template/budget/index.php
deleted file mode 100644
index 51ef3d87..00000000
--- a/app/Template/budget/index.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<div class="page-header">
- <h2><?= t('Budget overview') ?></h2>
-</div>
-
-<?php if (! empty($daily_budget)): ?>
-<div id="budget-chart">
- <div id="chart"
- data-date-format="<?= e('%%Y-%%m-%%d') ?>"
- data-metrics='<?= json_encode($daily_budget, JSON_HEX_APOS) ?>'
- data-labels='<?= json_encode(array('in' => t('Budget line'), 'out' => t('Expenses'), 'left' => t('Remaining'), 'value' => t('Amount'), 'date' => t('Date'), 'type' => t('Type')), JSON_HEX_APOS) ?>'></div>
-</div>
-<hr/>
-<table class="table-fixed table-stripped">
- <tr>
- <th><?= t('Date') ?></td>
- <th><?= t('Budget line') ?></td>
- <th><?= t('Expenses') ?></td>
- <th><?= t('Remaining') ?></td>
- </tr>
- <?php foreach ($daily_budget as $line): ?>
- <tr>
- <td><?= dt('%B %e, %Y', strtotime($line['date'])) ?></td>
- <td><?= n($line['in']) ?></td>
- <td><?= n($line['out']) ?></td>
- <td><?= n($line['left']) ?></td>
- </tr>
- <?php endforeach ?>
-</table>
-<?php else: ?>
- <p class="alert"><?= t('There is not enough data to show something.') ?></p>
-<?php endif ?>
-
-<?= $this->asset->js('assets/js/vendor/d3.v3.min.js') ?>
-<?= $this->asset->js('assets/js/vendor/c3.min.js') ?> \ No newline at end of file
diff --git a/app/Template/budget/remove.php b/app/Template/budget/remove.php
deleted file mode 100644
index a5b906a1..00000000
--- a/app/Template/budget/remove.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="page-header">
- <h2><?= t('Remove budget line') ?></h2>
-</div>
-
-<div class="confirm">
- <p class="alert alert-info"><?= t('Do you really want to remove this budget line?') ?></p>
-
- <div class="form-actions">
- <?= $this->url->link(t('Yes'), 'budget', 'remove', array('project_id' => $project['id'], 'budget_id' => $budget_id), true, 'btn btn-red') ?>
- <?= t('or') ?>
- <?= $this->url->link(t('cancel'), 'budget', 'create', array('project_id' => $project['id'])) ?>
- </div>
-</div> \ No newline at end of file
diff --git a/app/Template/budget/sidebar.php b/app/Template/budget/sidebar.php
deleted file mode 100644
index 8477c052..00000000
--- a/app/Template/budget/sidebar.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<div class="sidebar">
- <h2><?= t('Budget') ?></h2>
- <ul>
- <li <?= $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Budget overview'), 'budget', 'index', array('project_id' => $project['id'])) ?>
- </li>
- <li <?= $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?>
- </li>
- <li <?= $this->app->getRouterAction() === 'breakdown' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Cost breakdown'), 'budget', 'breakdown', array('project_id' => $project['id'])) ?>
- </li>
- </ul>
- <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
- <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
-</div> \ No newline at end of file
diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php
index 3617979a..083da283 100644
--- a/app/Template/config/sidebar.php
+++ b/app/Template/config/sidebar.php
@@ -34,6 +34,7 @@
<li>
<?= $this->url->link(t('Documentation'), 'doc', 'show') ?>
</li>
+ <?= $this->hook->render('config:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/currency/index.php b/app/Template/currency/index.php
index f72c5700..1c78c47a 100644
--- a/app/Template/currency/index.php
+++ b/app/Template/currency/index.php
@@ -52,5 +52,3 @@
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>
-
-<p class="alert alert-info"><?= t('Currency rates are used to calculate project budget.') ?></p>
diff --git a/app/Template/export/sidebar.php b/app/Template/export/sidebar.php
index f204d29d..7e39a5af 100644
--- a/app/Template/export/sidebar.php
+++ b/app/Template/export/sidebar.php
@@ -13,6 +13,7 @@
<li <?= $this->app->getRouterAction() === 'summary' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Daily project summary'), 'export', 'summary', array('project_id' => $project['id'])) ?>
</li>
+ <?= $this->hook->render('export:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/file/show.php b/app/Template/file/show.php
index b1a0a813..a390c9fb 100644
--- a/app/Template/file/show.php
+++ b/app/Template/file/show.php
@@ -11,7 +11,7 @@
<li>
<?php if (function_exists('imagecreatetruecolor')): ?>
<div class="img_container">
- <img src="<?= $this->url->href('file', 'thumbnail', array('width' => 250, 'height' => 100, 'file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/>
+ <img src="<?= $this->url->href('file', 'thumbnail', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/>
</div>
<?php endif ?>
<p>
diff --git a/app/Template/gantt/task_creation.php b/app/Template/gantt/task_creation.php
index d0d14c1e..7997e231 100644
--- a/app/Template/gantt/task_creation.php
+++ b/app/Template/gantt/task_creation.php
@@ -12,7 +12,6 @@
<?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?>
<?= $this->form->label(t('Description'), 'description') ?>
-
<div class="form-tabs">
<div class="write-area">
<?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?>
@@ -29,6 +28,8 @@
</li>
</ul>
</div>
+
+ <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
</div>
<div class="form-column">
@@ -43,17 +44,14 @@
<?= $this->form->select('swimlane_id', $swimlanes_list, $values, $errors, array('tabindex="5"')) ?><br/>
<?php endif ?>
- <?= $this->form->label(t('Color'), 'color_id') ?>
- <?= $this->form->select('color_id', $colors_list, $values, $errors, array('tabindex="7"')) ?><br/>
-
<?= $this->form->label(t('Complexity'), 'score') ?>
- <?= $this->form->number('score', $values, $errors, array('tabindex="8"')) ?><br/>
+ <?= $this->form->number('score', $values, $errors, array('tabindex="6"')) ?><br/>
<?= $this->form->label(t('Start Date'), 'date_started') ?>
- <?= $this->form->text('date_started', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="9"'), 'form-date') ?>
+ <?= $this->form->text('date_started', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="7"'), 'form-date') ?>
<?= $this->form->label(t('Due Date'), 'date_due') ?>
- <?= $this->form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="10"'), 'form-date') ?><br/>
+ <?= $this->form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="8"'), 'form-date') ?><br/>
<div class="form-help"><?= t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')) ?></div>
</div>
diff --git a/app/Template/header.php b/app/Template/header.php
new file mode 100644
index 00000000..0bcfdbbc
--- /dev/null
+++ b/app/Template/header.php
@@ -0,0 +1,33 @@
+<header>
+ <nav>
+ <h1><?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->e($title) ?>
+ <?php if (! empty($description)): ?>
+ <span class="tooltip" title='<?= $this->e($this->text->markdown($description)) ?>'>
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
+ </h1>
+ <ul>
+ <?php if (isset($board_selector) && ! empty($board_selector)): ?>
+ <li>
+ <select id="board-selector"
+ class="chosen-select select-auto-redirect"
+ tabindex="-1"
+ data-notfound="<?= t('No results match:') ?>"
+ data-placeholder="<?= t('Display another project') ?>"
+ data-redirect-regex="PROJECT_ID"
+ data-redirect-url="<?= $this->url->href('board', 'show', array('project_id' => 'PROJECT_ID')) ?>">
+ <option value=""></option>
+ <?php foreach($board_selector as $board_id => $board_name): ?>
+ <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option>
+ <?php endforeach ?>
+ </select>
+ </li>
+ <?php endif ?>
+ <li>
+ <?= $this->url->link(t('Logout'), 'auth', 'logout') ?>
+ <span class="username hide-tablet">(<?= $this->user->getProfileLink() ?>)</span>
+ </li>
+ </ul>
+ </nav>
+</header> \ No newline at end of file
diff --git a/app/Template/hourlyrate/index.php b/app/Template/hourlyrate/index.php
deleted file mode 100644
index af305d07..00000000
--- a/app/Template/hourlyrate/index.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<div class="page-header">
- <h2><?= t('Hourly rates') ?></h2>
-</div>
-
-<?php if (! empty($rates)): ?>
-
-<table>
- <tr>
- <th><?= t('Hourly rate') ?></th>
- <th><?= t('Currency') ?></th>
- <th><?= t('Effective date') ?></th>
- <th><?= t('Action') ?></th>
- </tr>
- <?php foreach ($rates as $rate): ?>
- <tr>
- <td><?= n($rate['rate']) ?></td>
- <td><?= $rate['currency'] ?></td>
- <td><?= dt('%b %e, %Y', $rate['date_effective']) ?></td>
- <td>
- <?= $this->url->link(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?>
- </td>
- </tr>
- <?php endforeach ?>
-</table>
-
-<h3><?= t('Add new rate') ?></h3>
-<?php endif ?>
-
-<form method="post" action="<?= $this->url->href('hourlyrate', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
-
- <?= $this->form->hidden('user_id', $values) ?>
- <?= $this->form->csrf() ?>
-
- <?= $this->form->label(t('Hourly rate'), 'rate') ?>
- <?= $this->form->text('rate', $values, $errors, array('required'), 'form-numeric') ?>
-
- <?= $this->form->label(t('Currency'), 'currency') ?>
- <?= $this->form->select('currency', $currencies_list, $values, $errors, array('required')) ?>
-
- <?= $this->form->label(t('Effective date'), 'date_effective') ?>
- <?= $this->form->text('date_effective', $values, $errors, array('required'), 'form-date') ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- </div>
-</form>
diff --git a/app/Template/hourlyrate/remove.php b/app/Template/hourlyrate/remove.php
deleted file mode 100644
index 121436e4..00000000
--- a/app/Template/hourlyrate/remove.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="page-header">
- <h2><?= t('Remove hourly rate') ?></h2>
-</div>
-
-<div class="confirm">
- <p class="alert alert-info"><?= t('Do you really want to remove this hourly rate?') ?></p>
-
- <div class="form-actions">
- <?= $this->url->link(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?>
- <?= t('or') ?>
- <?= $this->url->link(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
- </div>
-</div> \ No newline at end of file
diff --git a/app/Template/layout.php b/app/Template/layout.php
index 3e883fbf..934fb62c 100644
--- a/app/Template/layout.php
+++ b/app/Template/layout.php
@@ -28,52 +28,29 @@
<link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png">
<title><?= isset($title) ? $this->e($title) : 'Kanboard' ?></title>
+
+ <?= $this->hook->render('layout:head') ?>
</head>
<body data-status-url="<?= $this->url->href('app', 'status') ?>"
data-login-url="<?= $this->url->href('auth', 'login') ?>"
+ data-markdown-preview-url="<?= $this->url->href('app', 'preview') ?>"
data-timezone="<?= $this->app->getTimezone() ?>"
data-js-lang="<?= $this->app->jsLang() ?>">
<?php if (isset($no_layout) && $no_layout): ?>
<?= $content_for_layout ?>
<?php else: ?>
- <header>
- <nav>
- <h1><?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->e($title) ?>
- <?php if (! empty($description)): ?>
- <span class="tooltip" title='<?= $this->e($this->text->markdown($description)) ?>'>
- <i class="fa fa-info-circle"></i>
- </span>
- <?php endif ?>
- </h1>
- <ul>
- <?php if (isset($board_selector) && ! empty($board_selector)): ?>
- <li>
- <select id="board-selector"
- class="chosen-select select-auto-redirect"
- tabindex="-1"
- data-notfound="<?= t('No results match:') ?>"
- data-placeholder="<?= t('Display another project') ?>"
- data-redirect-regex="PROJECT_ID"
- data-redirect-url="<?= $this->url->href('board', 'show', array('project_id' => 'PROJECT_ID')) ?>">
- <option value=""></option>
- <?php foreach($board_selector as $board_id => $board_name): ?>
- <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option>
- <?php endforeach ?>
- </select>
- </li>
- <?php endif ?>
- <li>
- <?= $this->url->link(t('Logout'), 'auth', 'logout') ?>
- <span class="username hide-tablet">(<?= $this->user->getProfileLink() ?>)</span>
- </li>
- </ul>
- </nav>
- </header>
+ <?= $this->hook->render('layout:top') ?>
+ <?= $this->render('header', array(
+ 'title' => $title,
+ 'description' => isset($description) ? $description : '',
+ 'board_selector' => $board_selector,
+ )) ?>
<section class="page">
<?= $this->app->flashMessage() ?>
<?= $content_for_layout ?>
</section>
+ <?= $this->hook->render('layout:bottom') ?>
<?php endif ?>
</body>
</html>
diff --git a/app/Template/project/dropdown.php b/app/Template/project/dropdown.php
index 0a53cc05..0f1e1f6b 100644
--- a/app/Template/project/dropdown.php
+++ b/app/Template/project/dropdown.php
@@ -9,21 +9,19 @@
</li>
<?php endif ?>
+<?= $this->hook->render('project:dropdown', array('project' => $project)) ?>
+
<?php if ($this->user->isProjectManagementAllowed($project['id'])): ?>
-<li>
- <i class="fa fa-line-chart fa-fw"></i>
- <?= $this->url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-pie-chart fa-fw"></i>
- <?= $this->url->link(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-download fa-fw"></i>
- <?= $this->url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-cog fa-fw"></i>
- <?= $this->url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?>
-</li>
+ <li>
+ <i class="fa fa-line-chart fa-fw"></i>
+ <?= $this->url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-download fa-fw"></i>
+ <?= $this->url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-cog fa-fw"></i>
+ <?= $this->url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?>
+ </li>
<?php endif ?>
diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php
index 7b5d976f..84bbb6b1 100644
--- a/app/Template/project/sidebar.php
+++ b/app/Template/project/sidebar.php
@@ -48,6 +48,8 @@
</li>
<?php endif ?>
<?php endif ?>
+
+ <?= $this->hook->render('project:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/project_user/sidebar.php b/app/Template/project_user/sidebar.php
index 8cc3f41b..98219a87 100644
--- a/app/Template/project_user/sidebar.php
+++ b/app/Template/project_user/sidebar.php
@@ -24,5 +24,7 @@
<li <?= $this->app->getRouterAction() === 'closed' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Closed tasks'), 'projectuser', 'closed', $filter) ?>
</li>
+
+ <?= $this->hook->render('project-user:sidebar') ?>
</ul>
</div> \ No newline at end of file
diff --git a/app/Template/swimlane/edit.php b/app/Template/swimlane/edit.php
index 1788fed2..dfc5cf0b 100644
--- a/app/Template/swimlane/edit.php
+++ b/app/Template/swimlane/edit.php
@@ -12,6 +12,27 @@
<?= $this->form->label(t('Name'), 'name') ?>
<?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
+ <?= $this->form->label(t('Description'), 'description') ?>
+
+ <div class="form-tabs">
+
+ <div class="write-area">
+ <?= $this->form->textarea('description', $values, $errors) ?>
+ </div>
+ <div class="preview-area">
+ <div class="markdown"></div>
+ </div>
+ <ul class="form-tabs-nav">
+ <li class="form-tab form-tab-selected">
+ <i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
+ </li>
+ <li class="form-tab">
+ <a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
+ </li>
+ </ul>
+ </div>
+ <div class="form-help"><?= $this->url->doc(t('Write your text in Markdown'), 'syntax-guide') ?></div>
+
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
diff --git a/app/Template/swimlane/index.php b/app/Template/swimlane/index.php
index daee6af5..797d2ca2 100644
--- a/app/Template/swimlane/index.php
+++ b/app/Template/swimlane/index.php
@@ -14,7 +14,27 @@
<?= $this->form->hidden('project_id', $values) ?>
<?= $this->form->label(t('Name'), 'name') ?>
- <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
+ <?= $this->form->text('name', $values, $errors, array('required', 'maxlength="50"')) ?>
+
+ <?= $this->form->label(t('Description'), 'description') ?>
+
+ <div class="form-tabs">
+ <div class="write-area">
+ <?= $this->form->textarea('description', $values, $errors) ?>
+ </div>
+ <div class="preview-area">
+ <div class="markdown"></div>
+ </div>
+ <ul class="form-tabs-nav">
+ <li class="form-tab form-tab-selected">
+ <i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
+ </li>
+ <li class="form-tab">
+ <a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
+ </li>
+ </ul>
+ </div>
+ <div class="form-help"><?= $this->url->doc(t('Write your text in Markdown'), 'syntax-guide') ?></div>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
diff --git a/app/Template/swimlane/table.php b/app/Template/swimlane/table.php
index f38572a3..b708e633 100644
--- a/app/Template/swimlane/table.php
+++ b/app/Template/swimlane/table.php
@@ -25,7 +25,7 @@
</li>
<?php endif ?>
<li>
- <?= $this->url->link(t('Rename'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?>
+ <?= $this->url->link(t('Edit'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?>
</li>
<li>
<?php if ($swimlane['is_active']): ?>
diff --git a/app/Template/task/color_picker.php b/app/Template/task/color_picker.php
new file mode 100644
index 00000000..a849b9ce
--- /dev/null
+++ b/app/Template/task/color_picker.php
@@ -0,0 +1,11 @@
+<div class="color-picker">
+<?php foreach ($colors_list as $color_id => $color_name): ?>
+ <div
+ data-color-id="<?= $color_id ?>"
+ class="color-square color-<?= $color_id ?> <?= isset($values['color_id']) && $values['color_id'] === $color_id ? 'color-square-selected' : '' ?>"
+ title="<?= $this->e($color_name) ?>">
+ </div>
+<?php endforeach ?>
+</div>
+
+<?= $this->form->hidden('color_id', $values) ?> \ No newline at end of file
diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php
index 1f06ab8c..cf0e9f76 100644
--- a/app/Template/task/sidebar.php
+++ b/app/Template/task/sidebar.php
@@ -18,6 +18,8 @@
<?= $this->url->link(t('Time tracking'), 'task', 'timetracking', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('task:sidebar:information') ?>
</ul>
<h2><?= t('Actions') ?></h2>
<ul>
@@ -66,6 +68,8 @@
<?= $this->url->link(t('Remove'), 'task', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('task:sidebar:actions') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/task_creation/form.php b/app/Template/task_creation/form.php
index 8a29896e..325ca1c8 100644
--- a/app/Template/task_creation/form.php
+++ b/app/Template/task_creation/form.php
@@ -10,8 +10,7 @@
</div>
<?php endif ?>
-<section id="task-section">
-<form method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off">
+<form id="task-form" method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
@@ -38,7 +37,7 @@
</ul>
</div>
- <div class="form-help"><?= $this->url->doc(t('Write your text in Markdown'), 'syntax-guide') ?></div>
+ <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
<?php if (! isset($duplicate)): ?>
<?= $this->form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?>
@@ -62,9 +61,6 @@
<?= $this->form->label(t('Column'), 'column_id') ?>
<?= $this->form->select('column_id', $columns_list, $values, $errors, array('tabindex="6"')) ?><br/>
- <?= $this->form->label(t('Color'), 'color_id') ?>
- <?= $this->form->select('color_id', $colors_list, $values, $errors, array('tabindex="7"')) ?><br/>
-
<?= $this->form->label(t('Complexity'), 'score') ?>
<?= $this->form->number('score', $values, $errors, array('tabindex="8"')) ?><br/>
@@ -80,5 +76,4 @@
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue" tabindex="11"/>
<?= t('or') ?> <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $values['project_id']), false, 'close-popover') ?>
</div>
-</form>
-</section>
+</form> \ No newline at end of file
diff --git a/app/Template/task_modification/edit_task.php b/app/Template/task_modification/edit_task.php
index fe4696d6..f4d7449a 100644
--- a/app/Template/task_modification/edit_task.php
+++ b/app/Template/task_modification/edit_task.php
@@ -1,8 +1,7 @@
<div class="page-header">
<h2><?= t('Edit a task') ?></h2>
</div>
-<section id="task-section">
-<form method="post" action="<?= $this->url->href('taskmodification', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" autocomplete="off">
+<form id="task-form" method="post" action="<?= $this->url->href('taskmodification', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
@@ -12,7 +11,6 @@
<?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"')) ?><br/>
<?= $this->form->label(t('Description'), 'description') ?>
-
<div class="form-tabs">
<div class="write-area">
<?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?>
@@ -30,6 +28,7 @@
</ul>
</div>
+ <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
</div>
<div class="form-column">
@@ -42,9 +41,6 @@
<?= $this->form->label(t('Category'), 'category_id') ?>
<?= $this->form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?><br/>
- <?= $this->form->label(t('Color'), 'color_id') ?>
- <?= $this->form->select('color_id', $colors_list, $values, $errors, array('tabindex="5"')) ?><br/>
-
<?= $this->form->label(t('Complexity'), 'score') ?>
<?= $this->form->number('score', $values, $errors, array('tabindex="6"')) ?><br/>
@@ -62,5 +58,4 @@
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?php endif ?>
</div>
-</form>
-</section>
+</form> \ No newline at end of file
diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php
index cd1c85c1..80fe8684 100644
--- a/app/Template/user/sidebar.php
+++ b/app/Template/user/sidebar.php
@@ -20,6 +20,8 @@
<?= $this->url->link(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('user:sidebar:information') ?>
</ul>
<h2><?= t('Actions') ?></h2>
@@ -60,14 +62,13 @@
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'authentication' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Edit Authentication'), 'user', 'authentication', array('user_id' => $user['id'])) ?>
</li>
- <li <?= $this->app->getRouterController() === 'hourlyrate' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
- </li>
<li <?= $this->app->getRouterController() === 'timetable' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Manage timetable'), 'timetable', 'index', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
+ <?= $this->hook->render('user:sidebar:actions', array('user' => $user)) ?>
+
<?php if ($this->user->isAdmin() && ! $this->user->isCurrentUser($user['id'])): ?>
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Remove'), 'user', 'remove', array('user_id' => $user['id'])) ?>
diff --git a/app/common.php b/app/common.php
index 1f1c7273..ea38ab36 100644
--- a/app/common.php
+++ b/app/common.php
@@ -30,120 +30,8 @@ $container->register(new ServiceProvider\ClassProvider);
$container->register(new ServiceProvider\EventDispatcherProvider);
if (ENABLE_URL_REWRITE) {
-
- // Dashboard
- $container['router']->addRoute('dashboard', 'app', 'index');
- $container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
-
- // Search routes
- $container['router']->addRoute('search', 'search', 'index');
- $container['router']->addRoute('search/:search', 'search', 'index', array('search'));
-
- // Project routes
- $container['router']->addRoute('projects', 'project', 'index');
- $container['router']->addRoute('project/create', 'project', 'create');
- $container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
- $container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
- $container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
- $container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
- $container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
- $container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
- $container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
- $container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
- $container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
- $container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
- $container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
-
- // Action routes
- $container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
-
- // Column routes
- $container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
-
- // Swimlane routes
- $container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
-
- // Category routes
- $container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
- $container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
-
- // Task routes
- $container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
- $container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
- $container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
-
- // Board routes
- $container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
- $container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
- $container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
-
- // Calendar routes
- $container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
- $container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
-
- // Listing routes
- $container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
- $container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
-
- // Gantt routes
- $container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id'));
- $container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting'));
-
- // Subtask routes
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id'));
-
- // Feed routes
- $container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
- $container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
-
- // Ical routes
- $container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
- $container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
-
- // Auth routes
- $container['router']->addRoute('oauth/google', 'oauth', 'google');
- $container['router']->addRoute('oauth/github', 'oauth', 'github');
- $container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab');
- $container['router']->addRoute('login', 'auth', 'login');
- $container['router']->addRoute('logout', 'auth', 'logout');
+ require __DIR__.'/routes.php';
}
+
+$plugin = new Core\PluginLoader($container);
+$plugin->scan();
diff --git a/app/constants.php b/app/constants.php
index cf515932..f25bd903 100644
--- a/app/constants.php
+++ b/app/constants.php
@@ -34,8 +34,11 @@ defined('LDAP_USER_PATTERN') or define('LDAP_USER_PATTERN', '');
defined('LDAP_ACCOUNT_FULLNAME') or define('LDAP_ACCOUNT_FULLNAME', 'displayname');
defined('LDAP_ACCOUNT_EMAIL') or define('LDAP_ACCOUNT_EMAIL', 'mail');
defined('LDAP_ACCOUNT_ID') or define('LDAP_ACCOUNT_ID', '');
-defined('LDAP_USERNAME_CASE_SENSITIVE') or define('LDAP_USERNAME_CASE_SENSITIVE', false);
+defined('LDAP_ACCOUNT_MEMBEROF') or define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
defined('LDAP_ACCOUNT_CREATION') or define('LDAP_ACCOUNT_CREATION', true);
+defined('LDAP_GROUP_ADMIN_DN') or define('LDAP_GROUP_ADMIN_DN', '');
+defined('LDAP_GROUP_PROJECT_ADMIN_DN') or define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
+defined('LDAP_USERNAME_CASE_SENSITIVE') or define('LDAP_USERNAME_CASE_SENSITIVE', false);
// Google authentication
defined('GOOGLE_AUTH') or define('GOOGLE_AUTH', false);
diff --git a/app/routes.php b/app/routes.php
new file mode 100644
index 00000000..159e8f6e
--- /dev/null
+++ b/app/routes.php
@@ -0,0 +1,117 @@
+<?php
+
+// Dashboard
+$container['router']->addRoute('dashboard', 'app', 'index');
+$container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
+
+// Search routes
+$container['router']->addRoute('search', 'search', 'index');
+$container['router']->addRoute('search/:search', 'search', 'index', array('search'));
+
+// Project routes
+$container['router']->addRoute('projects', 'project', 'index');
+$container['router']->addRoute('project/create', 'project', 'create');
+$container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
+$container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
+$container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
+$container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
+$container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
+$container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
+$container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
+$container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
+$container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
+$container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
+$container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
+
+// Action routes
+$container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
+
+// Column routes
+$container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
+
+// Swimlane routes
+$container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
+
+// Category routes
+$container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
+$container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
+
+// Task routes
+$container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
+$container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
+$container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
+
+// Board routes
+$container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
+$container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
+$container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
+
+// Calendar routes
+$container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
+$container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
+
+// Listing routes
+$container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
+$container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
+
+// Gantt routes
+$container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id'));
+$container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting'));
+
+// Subtask routes
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id'));
+
+// Feed routes
+$container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
+$container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
+
+// Ical routes
+$container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
+$container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
+
+// Auth routes
+$container['router']->addRoute('oauth/google', 'oauth', 'google');
+$container['router']->addRoute('oauth/github', 'oauth', 'github');
+$container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab');
+$container['router']->addRoute('login', 'auth', 'login');
+$container['router']->addRoute('logout', 'auth', 'logout');