From 4ce57aec079fd8071b693717805ccdc3cf9ee553 Mon Sep 17 00:00:00 2001 From: mbugeia Date: Tue, 21 Apr 2015 17:56:16 +0200 Subject: [PATCH] Update kanboard v1.0.13 --- sources/app/Action/TaskAssignColorColumn.php | 83 ++ sources/app/Action/TaskMoveColumnAssigned.php | 90 ++ .../app/Action/TaskMoveColumnUnAssigned.php | 90 ++ sources/app/Auth/Base.php | 1 + sources/app/Auth/GitHub.php | 2 +- sources/app/Auth/Google.php | 2 +- sources/app/Auth/Ldap.php | 6 +- sources/app/Auth/RememberMe.php | 3 + sources/app/Auth/ReverseProxy.php | 2 +- sources/app/Console/Base.php | 1 + sources/app/Console/TransitionExport.php | 34 + sources/app/Controller/Action.php | 2 +- sources/app/Controller/App.php | 6 +- sources/app/Controller/Base.php | 34 +- sources/app/Controller/Board.php | 9 +- sources/app/Controller/Budget.php | 135 +++ sources/app/Controller/Calendar.php | 12 +- sources/app/Controller/Category.php | 2 +- sources/app/Controller/Comment.php | 2 +- sources/app/Controller/Config.php | 23 +- sources/app/Controller/Currency.php | 89 ++ sources/app/Controller/Export.php | 10 + sources/app/Controller/File.php | 64 ++ sources/app/Controller/Hourlyrate.php | 89 ++ sources/app/Controller/Link.php | 2 +- sources/app/Controller/Project.php | 7 +- sources/app/Controller/Subtask.php | 22 +- sources/app/Controller/Swimlane.php | 4 +- sources/app/Controller/Task.php | 44 +- sources/app/Controller/Tasklink.php | 18 +- sources/app/Controller/Timetable.php | 39 + sources/app/Controller/Timetableday.php | 88 ++ sources/app/Controller/Timetableextra.php | 16 + sources/app/Controller/Timetableoff.php | 107 +++ sources/app/Controller/Timetableweek.php | 99 ++ sources/app/Controller/Twofactor.php | 140 +++ sources/app/Controller/User.php | 10 +- sources/app/Core/Helper.php | 112 ++- sources/app/Core/HttpClient.php | 68 ++ sources/app/Integration/Base.php | 1 + sources/app/Integration/BitbucketWebhook.php | 2 +- sources/app/Integration/GithubWebhook.php | 16 +- sources/app/Integration/GitlabWebhook.php | 4 +- sources/app/Integration/Hipchat.php | 53 ++ sources/app/Integration/SlackWebhook.php | 43 + sources/app/Locale/da_DK/translations.php | 112 +++ sources/app/Locale/de_DE/translations.php | 138 ++- sources/app/Locale/es_ES/translations.php | 112 +++ sources/app/Locale/fi_FI/translations.php | 112 +++ sources/app/Locale/fr_FR/translations.php | 112 +++ sources/app/Locale/hu_HU/translations.php | 168 +++- sources/app/Locale/it_IT/translations.php | 112 +++ sources/app/Locale/ja_JP/translations.php | 112 +++ sources/app/Locale/nl_NL/translations.php | 849 ++++++++++++++++++ sources/app/Locale/pl_PL/translations.php | 112 +++ sources/app/Locale/pt_BR/translations.php | 128 ++- sources/app/Locale/ru_RU/translations.php | 308 +++++-- .../app/Locale/sr_Latn_RS/translations.php | 849 ++++++++++++++++++ sources/app/Locale/sv_SE/translations.php | 308 +++++-- sources/app/Locale/th_TH/translations.php | 112 +++ sources/app/Locale/tr_TR/translations.php | 849 ++++++++++++++++++ sources/app/Locale/zh_CN/translations.php | 338 ++++--- sources/app/Model/Acl.php | 3 + sources/app/Model/Action.php | 7 +- sources/app/Model/Base.php | 7 +- sources/app/Model/Budget.php | 214 +++++ sources/app/Model/Comment.php | 3 +- sources/app/Model/Config.php | 26 + sources/app/Model/Currency.php | 104 +++ sources/app/Model/DateParser.php | 41 + sources/app/Model/File.php | 48 +- sources/app/Model/HourlyRate.php | 121 +++ sources/app/Model/ProjectActivity.php | 78 +- sources/app/Model/ProjectPermission.php | 20 +- sources/app/Model/Subtask.php | 87 +- sources/app/Model/SubtaskForecast.php | 124 +++ sources/app/Model/SubtaskTimeTracking.php | 91 +- sources/app/Model/Swimlane.php | 50 +- sources/app/Model/TaskFinder.php | 10 +- sources/app/Model/TaskLink.php | 2 + sources/app/Model/TaskPosition.php | 12 +- sources/app/Model/Timetable.php | 356 ++++++++ sources/app/Model/TimetableDay.php | 87 ++ sources/app/Model/TimetableExtra.php | 22 + sources/app/Model/TimetableOff.php | 125 +++ sources/app/Model/TimetableWeek.php | 91 ++ sources/app/Model/Transition.php | 170 ++++ sources/app/Model/User.php | 3 +- sources/app/Model/UserSession.php | 27 + sources/app/Model/Webhook.php | 39 +- sources/app/Schema/Mysql.php | 185 +++- sources/app/Schema/Postgres.php | 178 +++- sources/app/Schema/Sqlite.php | 180 +++- sources/app/ServiceProvider/ClassProvider.php | 14 +- .../app/ServiceProvider/DatabaseProvider.php | 2 + .../EventDispatcherProvider.php | 2 + sources/app/Subscriber/Base.php | 2 + .../app/Subscriber/NotificationSubscriber.php | 13 +- .../Subscriber/ProjectActivitySubscriber.php | 27 + .../app/Subscriber/TransitionSubscriber.php | 26 + sources/app/Template/analytic/layout.php | 2 +- sources/app/Template/app/dashboard.php | 10 +- sources/app/Template/app/projects.php | 10 +- sources/app/Template/app/subtasks.php | 4 + sources/app/Template/board/edit.php | 8 +- sources/app/Template/board/files.php | 41 +- sources/app/Template/board/filters.php | 26 +- sources/app/Template/board/show.php | 3 +- sources/app/Template/board/swimlane.php | 2 +- sources/app/Template/board/task_footer.php | 14 +- sources/app/Template/board/task_menu.php | 9 +- sources/app/Template/board/task_private.php | 4 - sources/app/Template/board/task_public.php | 4 - sources/app/Template/budget/breakdown.php | 34 + sources/app/Template/budget/create.php | 51 ++ sources/app/Template/budget/index.php | 35 + sources/app/Template/budget/remove.php | 13 + sources/app/Template/comment/show.php | 4 +- sources/app/Template/config/about.php | 1 + sources/app/Template/config/application.php | 3 + sources/app/Template/config/board.php | 1 + sources/app/Template/config/integrations.php | 43 + sources/app/Template/config/sidebar.php | 6 + sources/app/Template/currency/index.php | 56 ++ sources/app/Template/event/comment_create.php | 2 + sources/app/Template/event/comment_update.php | 2 + .../Template/{project => event}/events.php | 0 sources/app/Template/event/subtask_create.php | 2 + sources/app/Template/event/subtask_update.php | 2 + .../Template/event/task_assignee_change.php | 18 +- sources/app/Template/event/task_close.php | 2 + sources/app/Template/event/task_create.php | 2 + .../app/Template/event/task_move_column.php | 2 + .../app/Template/event/task_move_position.php | 2 + sources/app/Template/event/task_open.php | 2 + sources/app/Template/event/task_update.php | 2 + sources/app/Template/export/transitions.php | 26 + sources/app/Template/file/show.php | 57 +- sources/app/Template/hourlyrate/index.php | 46 + sources/app/Template/hourlyrate/remove.php | 13 + sources/app/Template/layout.php | 14 +- sources/app/Template/project/activity.php | 2 +- sources/app/Template/project/edit.php | 29 +- sources/app/Template/project/index.php | 8 +- sources/app/Template/project/integrations.php | 2 +- sources/app/Template/project/show.php | 12 +- sources/app/Template/project/sidebar.php | 8 +- sources/app/Template/subtask/create.php | 2 +- sources/app/Template/subtask/edit.php | 2 +- sources/app/Template/subtask/show.php | 12 +- sources/app/Template/task/activity.php | 5 + sources/app/Template/task/new.php | 4 +- sources/app/Template/task/show.php | 6 +- sources/app/Template/task/sidebar.php | 11 +- sources/app/Template/task/time_tracking.php | 10 +- sources/app/Template/task/transitions.php | 26 + sources/app/Template/tasklink/create.php | 8 +- sources/app/Template/tasklink/show.php | 22 +- sources/app/Template/timetable/index.php | 44 + sources/app/Template/timetable_day/index.php | 45 + sources/app/Template/timetable_day/remove.php | 13 + .../app/Template/timetable_extra/index.php | 56 ++ .../app/Template/timetable_extra/remove.php | 13 + sources/app/Template/timetable_off/index.php | 56 ++ sources/app/Template/timetable_off/remove.php | 13 + sources/app/Template/timetable_week/index.php | 46 + .../app/Template/timetable_week/remove.php | 13 + sources/app/Template/twofactor/check.php | 10 + sources/app/Template/twofactor/index.php | 37 + sources/app/Template/user/index.php | 4 + sources/app/Template/user/sidebar.php | 45 +- sources/app/Template/user/timesheet.php | 10 +- sources/app/constants.php | 8 +- sources/assets/css/app.css | 86 +- sources/assets/css/src/base.css | 10 + sources/assets/css/src/board.css | 26 +- sources/assets/css/src/table.css | 4 + sources/assets/css/src/task.css | 41 +- sources/assets/css/src/tooltip.css | 4 + sources/assets/img/gitlab-icon.png | Bin 0 -> 661 bytes sources/assets/img/hipchat-icon.png | Bin 0 -> 1373 bytes sources/assets/js/app.js | 34 +- sources/assets/js/src/base.js | 42 +- sources/assets/js/src/board.js | 61 +- sources/assets/js/src/budget.js | 38 + sources/assets/js/src/task.js | 22 - sources/assets/js/vendor/dimple.v2.1.0.min.js | 3 - sources/assets/js/vendor/dimple.v2.1.2.min.js | 3 + sources/config.default.php | 11 + sources/jsonrpc.php | 99 +- sources/kanboard | 1 + sources/vendor/autoload.php | 2 +- .../christian-riesen/base32/.scrutinizer.yml | 14 + .../vendor/christian-riesen/base32/LICENSE | 19 + .../vendor/christian-riesen/base32/build.xml | 130 +++ .../christian-riesen/base32/phpunit.xml.dist | 32 + .../christian-riesen/base32/src/Base32.php | 224 +++++ sources/vendor/christian-riesen/otp/LICENSE | 20 + .../christian-riesen/otp/example/index.php | 100 +++ .../christian-riesen/otp/phpunit.xml.dist | 15 + .../otp/src/Otp/GoogleAuthenticator.php | 159 ++++ .../christian-riesen/otp/src/Otp/Otp.php | 310 +++++++ .../otp/src/Otp/OtpInterface.php | 65 ++ sources/vendor/composer/autoload_classmap.php | 31 + .../vendor/composer/autoload_namespaces.php | 1 + sources/vendor/composer/autoload_psr4.php | 1 + sources/vendor/composer/autoload_real.php | 10 +- sources/vendor/composer/installed.json | 154 +++- .../fguillot/picodb/lib/PicoDb/Database.php | 2 +- .../picodb/lib/PicoDb/Driver/Mysql.php | 4 + .../picodb/lib/PicoDb/Driver/Postgres.php | 4 + .../fguillot/picodb/lib/PicoDb/Table.php | 7 +- .../swiftmailer/swiftmailer/.gitattributes | 3 + .../vendor/swiftmailer/swiftmailer/CHANGES | 9 + .../vendor/swiftmailer/swiftmailer/VERSION | 2 +- .../lib/classes/Swift/Events/SendEvent.php | 3 + .../lib/classes/Swift/FileSpool.php | 2 +- .../ContentEncoder/Base64ContentEncoder.php | 41 +- .../lib/classes/Swift/Mime/SimpleMessage.php | 2 +- .../classes/Swift/Mime/SimpleMimeEntity.php | 2 + .../lib/classes/Swift/Signers/SMimeSigner.php | 14 +- .../Swift/Transport/SpoolTransport.php | 2 +- .../lib/swiftmailer_generate_mimes_config.php | 1 - .../Component/Console/Command/Command.php | 4 + .../Component/Console/Command/HelpCommand.php | 2 +- .../Console/Descriptor/TextDescriptor.php | 2 +- .../Console/Formatter/OutputFormatter.php | 7 +- .../Formatter/OutputFormatterStyle.php | 2 +- .../Console/Helper/FormatterHelper.php | 12 +- .../Component/Console/Helper/Helper.php | 2 +- .../Console/Helper/ProgressHelper.php | 4 +- .../Component/Console/Helper/Table.php | 4 +- .../Component/Console/Input/ArrayInput.php | 6 +- .../Console/Tests/ApplicationTest.php | 6 + .../Console/Tests/Command/CommandTest.php | 6 + .../Console/Tests/Fixtures/application_1.txt | 2 +- .../Console/Tests/Fixtures/application_2.txt | 2 +- .../Tests/Fixtures/application_astext1.txt | 2 +- .../Tests/Fixtures/application_astext2.txt | 2 +- .../Tests/Fixtures/application_run1.txt | 2 +- .../Tests/Formatter/OutputFormatterTest.php | 5 + .../Tests/Helper/LegacyDialogHelperTest.php | 7 +- .../Tests/Helper/LegacyProgressHelperTest.php | 5 +- .../Tests/Helper/LegacyTableHelperTest.php | 32 +- .../Tests/Helper/QuestionHelperTest.php | 2 +- .../Console/Tests/Input/ArgvInputTest.php | 2 +- .../Console/Tests/Input/ArrayInputTest.php | 9 + .../Tests/Input/InputDefinitionTest.php | 10 +- .../Console/Tests/Input/StringInputTest.php | 3 + .../Component/Console/phpunit.xml.dist | 3 +- .../EventDispatcher/Tests/EventTest.php | 12 + .../EventDispatcher/phpunit.xml.dist | 3 +- 252 files changed, 11509 insertions(+), 795 deletions(-) create mode 100644 sources/app/Action/TaskAssignColorColumn.php create mode 100644 sources/app/Action/TaskMoveColumnAssigned.php create mode 100644 sources/app/Action/TaskMoveColumnUnAssigned.php create mode 100644 sources/app/Console/TransitionExport.php create mode 100644 sources/app/Controller/Budget.php create mode 100644 sources/app/Controller/Currency.php create mode 100644 sources/app/Controller/Hourlyrate.php create mode 100644 sources/app/Controller/Timetable.php create mode 100644 sources/app/Controller/Timetableday.php create mode 100644 sources/app/Controller/Timetableextra.php create mode 100644 sources/app/Controller/Timetableoff.php create mode 100644 sources/app/Controller/Timetableweek.php create mode 100644 sources/app/Controller/Twofactor.php create mode 100644 sources/app/Core/HttpClient.php create mode 100644 sources/app/Integration/Hipchat.php create mode 100644 sources/app/Integration/SlackWebhook.php create mode 100644 sources/app/Locale/nl_NL/translations.php create mode 100644 sources/app/Locale/sr_Latn_RS/translations.php create mode 100644 sources/app/Locale/tr_TR/translations.php create mode 100644 sources/app/Model/Budget.php create mode 100644 sources/app/Model/Currency.php create mode 100644 sources/app/Model/HourlyRate.php create mode 100644 sources/app/Model/SubtaskForecast.php create mode 100644 sources/app/Model/Timetable.php create mode 100644 sources/app/Model/TimetableDay.php create mode 100644 sources/app/Model/TimetableExtra.php create mode 100644 sources/app/Model/TimetableOff.php create mode 100644 sources/app/Model/TimetableWeek.php create mode 100644 sources/app/Model/Transition.php create mode 100644 sources/app/Subscriber/TransitionSubscriber.php create mode 100644 sources/app/Template/budget/breakdown.php create mode 100644 sources/app/Template/budget/create.php create mode 100644 sources/app/Template/budget/index.php create mode 100644 sources/app/Template/budget/remove.php create mode 100644 sources/app/Template/config/integrations.php create mode 100644 sources/app/Template/currency/index.php rename sources/app/Template/{project => event}/events.php (100%) create mode 100644 sources/app/Template/export/transitions.php create mode 100644 sources/app/Template/hourlyrate/index.php create mode 100644 sources/app/Template/hourlyrate/remove.php create mode 100644 sources/app/Template/task/activity.php create mode 100644 sources/app/Template/task/transitions.php create mode 100644 sources/app/Template/timetable/index.php create mode 100644 sources/app/Template/timetable_day/index.php create mode 100644 sources/app/Template/timetable_day/remove.php create mode 100644 sources/app/Template/timetable_extra/index.php create mode 100644 sources/app/Template/timetable_extra/remove.php create mode 100644 sources/app/Template/timetable_off/index.php create mode 100644 sources/app/Template/timetable_off/remove.php create mode 100644 sources/app/Template/timetable_week/index.php create mode 100644 sources/app/Template/timetable_week/remove.php create mode 100644 sources/app/Template/twofactor/check.php create mode 100644 sources/app/Template/twofactor/index.php create mode 100644 sources/assets/img/gitlab-icon.png create mode 100644 sources/assets/img/hipchat-icon.png create mode 100644 sources/assets/js/src/budget.js delete mode 100644 sources/assets/js/src/task.js delete mode 100644 sources/assets/js/vendor/dimple.v2.1.0.min.js create mode 100644 sources/assets/js/vendor/dimple.v2.1.2.min.js create mode 100644 sources/vendor/christian-riesen/base32/.scrutinizer.yml create mode 100644 sources/vendor/christian-riesen/base32/LICENSE create mode 100644 sources/vendor/christian-riesen/base32/build.xml create mode 100644 sources/vendor/christian-riesen/base32/phpunit.xml.dist create mode 100644 sources/vendor/christian-riesen/base32/src/Base32.php create mode 100644 sources/vendor/christian-riesen/otp/LICENSE create mode 100644 sources/vendor/christian-riesen/otp/example/index.php create mode 100644 sources/vendor/christian-riesen/otp/phpunit.xml.dist create mode 100644 sources/vendor/christian-riesen/otp/src/Otp/GoogleAuthenticator.php create mode 100644 sources/vendor/christian-riesen/otp/src/Otp/Otp.php create mode 100644 sources/vendor/christian-riesen/otp/src/Otp/OtpInterface.php diff --git a/sources/app/Action/TaskAssignColorColumn.php b/sources/app/Action/TaskAssignColorColumn.php new file mode 100644 index 0000000..2d10b77 --- /dev/null +++ b/sources/app/Action/TaskAssignColorColumn.php @@ -0,0 +1,83 @@ + t('Column'), + 'color_id' => t('Color'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'column_id', + ); + } + + /** + * Execute the action (set the task color) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $values = array( + 'id' => $data['task_id'], + 'color_id' => $this->getParam('color_id'), + ); + + return $this->taskModification->update($values); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return $data['column_id'] == $this->getParam('column_id'); + } +} diff --git a/sources/app/Action/TaskMoveColumnAssigned.php b/sources/app/Action/TaskMoveColumnAssigned.php new file mode 100644 index 0000000..decf4b0 --- /dev/null +++ b/sources/app/Action/TaskMoveColumnAssigned.php @@ -0,0 +1,90 @@ + t('Source column'), + 'dest_column_id' => t('Destination column') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'column_id', + 'project_id', + 'owner_id' + ); + } + + /** + * Execute the action (move the task to another column) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $original_task = $this->taskFinder->getById($data['task_id']); + + return $this->taskPosition->movePosition( + $data['project_id'], + $data['task_id'], + $this->getParam('dest_column_id'), + $original_task['position'], + $original_task['swimlane_id'], + false + ); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return $data['column_id'] == $this->getParam('src_column_id') && $data['owner_id']; + } +} diff --git a/sources/app/Action/TaskMoveColumnUnAssigned.php b/sources/app/Action/TaskMoveColumnUnAssigned.php new file mode 100644 index 0000000..b773252 --- /dev/null +++ b/sources/app/Action/TaskMoveColumnUnAssigned.php @@ -0,0 +1,90 @@ + t('Source column'), + 'dest_column_id' => t('Destination column') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'column_id', + 'project_id', + 'owner_id' + ); + } + + /** + * Execute the action (move the task to another column) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $original_task = $this->taskFinder->getById($data['task_id']); + + return $this->taskPosition->movePosition( + $data['project_id'], + $data['task_id'], + $this->getParam('dest_column_id'), + $original_task['position'], + $original_task['swimlane_id'], + false + ); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return $data['column_id'] == $this->getParam('src_column_id') && ! $data['owner_id']; + } +} diff --git a/sources/app/Auth/Base.php b/sources/app/Auth/Base.php index e023e4f..e2209e1 100644 --- a/sources/app/Auth/Base.php +++ b/sources/app/Auth/Base.php @@ -10,6 +10,7 @@ use Pimple\Container; * @package auth * @author Frederic Guillot * + * @property \Core\Session $session * @property \Model\Acl $acl * @property \Model\LastLogin $lastLogin * @property \Model\User $user diff --git a/sources/app/Auth/GitHub.php b/sources/app/Auth/GitHub.php index 0e335fb..816cc9c 100644 --- a/sources/app/Auth/GitHub.php +++ b/sources/app/Auth/GitHub.php @@ -34,7 +34,7 @@ class GitHub extends Base { $user = $this->user->getByGitHubId($github_id); - if ($user) { + if (! empty($user)) { $this->userSession->refresh($user); $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id'])); return true; diff --git a/sources/app/Auth/Google.php b/sources/app/Auth/Google.php index e7abae0..9a97703 100644 --- a/sources/app/Auth/Google.php +++ b/sources/app/Auth/Google.php @@ -35,7 +35,7 @@ class Google extends Base { $user = $this->user->getByGoogleId($google_id); - if ($user) { + if (! empty($user)) { $this->userSession->refresh($user); $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id'])); return true; diff --git a/sources/app/Auth/Ldap.php b/sources/app/Auth/Ldap.php index 376d16f..3ee6ec9 100644 --- a/sources/app/Auth/Ldap.php +++ b/sources/app/Auth/Ldap.php @@ -36,7 +36,7 @@ class Ldap extends Base $user = $this->user->getByUsername($username); - if ($user) { + if (! empty($user)) { // There is already a local user with that name if ($user['is_ldap_user'] == 0) { @@ -241,7 +241,7 @@ class Ldap extends Base } // User id not retrieved: LDAP_ACCOUNT_ID not properly configured - if (! $username && ! isset($info[0][LDAP_ACCOUNT_ID][0])) { + if (empty($username) && ! isset($info[0][LDAP_ACCOUNT_ID][0])) { return false; } @@ -261,7 +261,7 @@ class Ldap extends Base private function getQuery($username, $email) { if ($username && $email) { - return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.sprintf(LDAP_ACCOUNT_EMAIL, $email).')'; + return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.LDAP_ACCOUNT_EMAIL.'='.$email.'))'; } else if ($username) { return sprintf(LDAP_USER_PATTERN, $username); diff --git a/sources/app/Auth/RememberMe.php b/sources/app/Auth/RememberMe.php index 4736442..e8b20f3 100644 --- a/sources/app/Auth/RememberMe.php +++ b/sources/app/Auth/RememberMe.php @@ -103,6 +103,9 @@ class RememberMe extends Base // Create the session $this->userSession->refresh($this->user->getById($record['user_id'])); + // Do not ask 2FA for remember me session + $this->session['2fa_validated'] = true; + $this->container['dispatcher']->dispatch( 'auth.success', new AuthEvent(self::AUTH_NAME, $this->userSession->getId()) diff --git a/sources/app/Auth/ReverseProxy.php b/sources/app/Auth/ReverseProxy.php index 6cd01b2..c8fd5ee 100644 --- a/sources/app/Auth/ReverseProxy.php +++ b/sources/app/Auth/ReverseProxy.php @@ -32,7 +32,7 @@ class ReverseProxy extends Base $login = $_SERVER[REVERSE_PROXY_USER_HEADER]; $user = $this->user->getByUsername($login); - if (! $user) { + if (empty($user)) { $this->createUser($login); $user = $this->user->getByUsername($login); } diff --git a/sources/app/Console/Base.php b/sources/app/Console/Base.php index aeafbef..0724308 100644 --- a/sources/app/Console/Base.php +++ b/sources/app/Console/Base.php @@ -20,6 +20,7 @@ use Symfony\Component\Console\Command\Command; * @property \Model\Task $task * @property \Model\TaskExport $taskExport * @property \Model\TaskFinder $taskFinder + * @property \Model\Transition $transition */ abstract class Base extends Command { diff --git a/sources/app/Console/TransitionExport.php b/sources/app/Console/TransitionExport.php new file mode 100644 index 0000000..ad988c5 --- /dev/null +++ b/sources/app/Console/TransitionExport.php @@ -0,0 +1,34 @@ +setName('export:transitions') + ->setDescription('Task transitions CSV export') + ->addArgument('project_id', InputArgument::REQUIRED, 'Project id') + ->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)') + ->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $data = $this->transition->export( + $input->getArgument('project_id'), + $input->getArgument('start_date'), + $input->getArgument('end_date') + ); + + if (is_array($data)) { + Tool::csv($data); + } + } +} diff --git a/sources/app/Controller/Action.php b/sources/app/Controller/Action.php index 2b58dca..cd24453 100644 --- a/sources/app/Controller/Action.php +++ b/sources/app/Controller/Action.php @@ -157,7 +157,7 @@ class Action extends Base $project = $this->getProject(); $action = $this->action->getById($this->request->getIntegerParam('action_id')); - if ($action && $this->action->remove($action['id'])) { + if (! empty($action) && $this->action->remove($action['id'])) { $this->session->flash(t('Action removed successfully.')); } else { $this->session->flashError(t('Unable to remove this action.')); diff --git a/sources/app/Controller/App.php b/sources/app/Controller/App.php index 46731e7..ebe3967 100644 --- a/sources/app/Controller/App.php +++ b/sources/app/Controller/App.php @@ -46,21 +46,21 @@ class App extends Base $project_ids = array_keys($projects); $task_paginator = $this->paginator - ->setUrl('app', $action, array('pagination' => 'tasks')) + ->setUrl('app', $action, array('pagination' => 'tasks', 'user_id' => $user_id)) ->setMax(10) ->setOrder('tasks.id') ->setQuery($this->taskFinder->getUserQuery($user_id)) ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks'); $subtask_paginator = $this->paginator - ->setUrl('app', $action, array('pagination' => 'subtasks')) + ->setUrl('app', $action, array('pagination' => 'subtasks', 'user_id' => $user_id)) ->setMax(10) ->setOrder('tasks.id') ->setQuery($this->subtask->getUserQuery($user_id, $status)) ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks'); $project_paginator = $this->paginator - ->setUrl('app', $action, array('pagination' => 'projects')) + ->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id)) ->setMax(10) ->setOrder('name') ->setQuery($this->project->getQueryColumnStats($project_ids)) diff --git a/sources/app/Controller/Base.php b/sources/app/Controller/Base.php index d949048..10bf962 100644 --- a/sources/app/Controller/Base.php +++ b/sources/app/Controller/Base.php @@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\Config $config * @property \Model\DateParser $dateParser * @property \Model\File $file + * @property \Model\HourlyRate $hourlyRate * @property \Model\LastLogin $lastLogin * @property \Model\Notification $notification * @property \Model\Project $project @@ -43,6 +44,7 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\ProjectActivity $projectActivity * @property \Model\ProjectDailySummary $projectDailySummary * @property \Model\Subtask $subtask + * @property \Model\SubtaskForecast $subtaskForecast * @property \Model\Swimlane $swimlane * @property \Model\Task $task * @property \Model\Link $link @@ -56,12 +58,16 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\TaskPosition $taskPosition * @property \Model\TaskPermission $taskPermission * @property \Model\TaskStatus $taskStatus + * @property \Model\Timetable $timetable + * @property \Model\TimetableDay $timetableDay + * @property \Model\TimetableWeek $timetableWeek + * @property \Model\TimetableExtra $timetableExtra + * @property \Model\TimetableOff $timetableOff * @property \Model\TaskValidator $taskValidator * @property \Model\TaskLink $taskLink * @property \Model\CommentHistory $commentHistory * @property \Model\SubtaskHistory $subtaskHistory * @property \Model\SubtaskTimeTracking $subtaskTimeTracking - * @property \Model\TimeTracking $timeTracking * @property \Model\User $user * @property \Model\UserSession $userSession * @property \Model\Webhook $webhook @@ -148,7 +154,7 @@ abstract class Base $this->response->xss(); // Allow the public board iframe inclusion - if ($action !== 'readonly') { + if (ENABLE_XFRAME && $action !== 'readonly') { $this->response->xframe(); } @@ -171,6 +177,7 @@ abstract class Base if (! $this->acl->isPublicAction($controller, $action)) { $this->handleAuthentication(); + $this->handle2FA($controller, $action); $this->handleAuthorization($controller, $action); $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId()); @@ -194,6 +201,25 @@ abstract class Base } } + /** + * Check 2FA + * + * @access public + */ + public function handle2FA($controller, $action) + { + $ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'user' && $action === 'logout'); + + if ($ignore === false && $this->userSession->has2FA() && ! $this->userSession->check2FA()) { + + if ($this->request->isAjax()) { + $this->response->text('Not Authorized', 401); + } + + $this->response->redirect($this->helper->url('twofactor', 'code')); + } + } + /** * Check page access and authorization * @@ -311,7 +337,7 @@ abstract class Base { $task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id')); - if (! $task) { + if (empty($task)) { $this->notfound(); } @@ -330,7 +356,7 @@ abstract class Base $project_id = $this->request->getIntegerParam('project_id', $project_id); $project = $this->project->getById($project_id); - if (! $project) { + if (empty($project)) { $this->session->flashError(t('Project not found.')); $this->response->redirect('?controller=project'); } diff --git a/sources/app/Controller/Board.php b/sources/app/Controller/Board.php index 90b7f35..fa22226 100644 --- a/sources/app/Controller/Board.php +++ b/sources/app/Controller/Board.php @@ -117,7 +117,7 @@ class Board extends Base $project = $this->project->getByToken($token); // Token verification - if (! $project) { + if (empty($project)) { $this->forbidden(true); } @@ -127,6 +127,7 @@ class Board extends Base 'swimlanes' => $this->board->getBoard($project['id']), 'categories' => $this->category->getList($project['id'], false), 'title' => $project['name'], + 'description' => $project['description'], 'no_layout' => true, 'not_editable' => true, 'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'), @@ -187,6 +188,7 @@ class Board extends Base 'swimlanes' => $this->board->getBoard($project['id']), 'categories' => $this->category->getList($project['id'], true, true), 'title' => $project['name'], + 'description' => $project['description'], 'board_selector' => $board_selector, 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), 'board_highlight_period' => $this->config->get('board_highlight_period'), @@ -309,7 +311,7 @@ class Board extends Base $this->checkCSRFParam(); $column = $this->board->getColumn($this->request->getIntegerParam('column_id')); - if ($column && $this->board->removeColumn($column['id'])) { + if (! empty($column) && $this->board->removeColumn($column['id'])) { $this->session->flash(t('Column removed successfully.')); } else { $this->session->flashError(t('Unable to remove this column.')); @@ -439,7 +441,8 @@ class Board extends Base $task = $this->getTask(); $this->response->html($this->template->render('board/files', array( - 'files' => $this->file->getAll($task['id']), + 'files' => $this->file->getAllDocuments($task['id']), + 'images' => $this->file->getAllImages($task['id']), 'task' => $task, ))); } diff --git a/sources/app/Controller/Budget.php b/sources/app/Controller/Budget.php new file mode 100644 index 0000000..2c56c79 --- /dev/null +++ b/sources/app/Controller/Budget.php @@ -0,0 +1,135 @@ +getProject(); + + $this->response->html($this->projectLayout('budget/index', array( + 'daily_budget' => $this->budget->getDailyBudgetBreakdown($project['id']), + 'project' => $project, + 'title' => t('Budget') + ))); + } + + /** + * 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') + ))); + } + + /** + * 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') + ))); + } + + /** + * 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('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'), + ))); + } + + /** + * 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('budget', 'create', array('project_id' => $project['id']))); + } +} diff --git a/sources/app/Controller/Calendar.php b/sources/app/Controller/Calendar.php index 1c7ac7c..49c7f56 100644 --- a/sources/app/Controller/Calendar.php +++ b/sources/app/Controller/Calendar.php @@ -14,7 +14,7 @@ use Model\Task as TaskModel; class Calendar extends Base { /** - * Show calendar view + * Show calendar view for projects * * @access public */ @@ -59,9 +59,7 @@ class Calendar extends Base ->filterByDueDateRange($start, $end) ->toCalendarEvents(); - $subtask_timeslots = $this->subtaskTimeTracking->getProjectCalendarEvents($project_id, $start, $end); - - $this->response->json(array_merge($due_tasks, $subtask_timeslots)); + $this->response->json($due_tasks); } /** @@ -84,7 +82,9 @@ class Calendar extends Base $subtask_timeslots = $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end); - $this->response->json(array_merge($due_tasks, $subtask_timeslots)); + $subtask_forcast = $this->config->get('subtask_forecast') == 1 ? $this->subtaskForecast->getCalendarEvents($user_id, $end) : array(); + + $this->response->json(array_merge($due_tasks, $subtask_timeslots, $subtask_forcast)); } /** @@ -100,7 +100,7 @@ class Calendar extends Base $this->taskModification->update(array( 'id' => $values['task_id'], - 'date_due' => $values['date_due'], + 'date_due' => substr($values['date_due'], 0, 10), )); } } diff --git a/sources/app/Controller/Category.php b/sources/app/Controller/Category.php index 68961a0..515cc9c 100644 --- a/sources/app/Controller/Category.php +++ b/sources/app/Controller/Category.php @@ -21,7 +21,7 @@ class Category extends Base { $category = $this->category->getById($this->request->getIntegerParam('category_id')); - if (! $category) { + if (empty($category)) { $this->session->flashError(t('Category not found.')); $this->response->redirect('?controller=category&action=index&project_id='.$project_id); } diff --git a/sources/app/Controller/Comment.php b/sources/app/Controller/Comment.php index 5003200..a5f6b1f 100644 --- a/sources/app/Controller/Comment.php +++ b/sources/app/Controller/Comment.php @@ -20,7 +20,7 @@ class Comment extends Base { $comment = $this->comment->getById($this->request->getIntegerParam('comment_id')); - if (! $comment) { + if (empty($comment)) { $this->notfound(); } diff --git a/sources/app/Controller/Config.php b/sources/app/Controller/Config.php index 01c7ad5..3c88419 100644 --- a/sources/app/Controller/Config.php +++ b/sources/app/Controller/Config.php @@ -38,7 +38,14 @@ class Config extends Base { if ($this->request->isPost()) { - $values = $this->request->getValues() + array('subtask_restriction' => 0, 'subtask_time_tracking' => 0); + $values = $this->request->getValues(); + + if ($redirect === 'board') { + $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'subtask_forecast' => 0); + } + else if ($redirect === 'integrations') { + $values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0); + } if ($this->config->save($values)) { $this->config->reload(); @@ -97,6 +104,20 @@ class Config extends Base ))); } + /** + * Display the integration settings page + * + * @access public + */ + public function integrations() + { + $this->common('integrations'); + + $this->response->html($this->layout('config/integrations', array( + 'title' => t('Settings').' > '.t('Integrations'), + ))); + } + /** * Display the webhook settings page * diff --git a/sources/app/Controller/Currency.php b/sources/app/Controller/Currency.php new file mode 100644 index 0000000..fac34a3 --- /dev/null +++ b/sources/app/Controller/Currency.php @@ -0,0 +1,89 @@ +projectPermission->getAllowedProjects($this->userSession->getId()); + $params['config_content_for_layout'] = $this->template->render($template, $params); + + return $this->template->layout('config/layout', $params); + } + + /** + * Display all currency rates and form + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $this->response->html($this->layout('currency/index', array( + 'config_values' => array('application_currency' => $this->config->get('application_currency')), + 'values' => $values, + 'errors' => $errors, + 'rates' => $this->currency->getAll(), + 'currencies' => $this->config->getCurrencies(), + 'title' => t('Settings').' > '.t('Currency rates'), + ))); + } + + /** + * Validate and save a new currency rate + * + * @access public + */ + public function create() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->currency->validate($values); + + if ($valid) { + + if ($this->currency->create($values['currency'], $values['rate'])) { + $this->session->flash(t('The currency rate have been added successfully.')); + $this->response->redirect($this->helper->url('currency', 'index')); + } + else { + $this->session->flashError(t('Unable to add this currency rate.')); + } + } + + $this->index($values, $errors); + } + + /** + * Save reference currency + * + * @access public + */ + public function reference() + { + $values = $this->request->getValues(); + + if ($this->config->save($values)) { + $this->config->reload(); + $this->session->flash(t('Settings saved successfully.')); + } + else { + $this->session->flashError(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url('currency', 'index')); + } +} diff --git a/sources/app/Controller/Export.php b/sources/app/Controller/Export.php index 1997a4e..b8f932c 100644 --- a/sources/app/Controller/Export.php +++ b/sources/app/Controller/Export.php @@ -72,4 +72,14 @@ class Export extends Base { $this->common('projectDailySummary', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export')); } + + /** + * Transition export + * + * @access public + */ + public function transitions() + { + $this->common('transition', 'export', t('Transitions'), 'transitions', t('Task transitions export')); + } } diff --git a/sources/app/Controller/File.php b/sources/app/Controller/File.php index 3255fe8..3963e2d 100644 --- a/sources/app/Controller/File.php +++ b/sources/app/Controller/File.php @@ -101,6 +101,70 @@ class File extends Base } } + /** + * Return image thumbnails + * + * @access public + */ + public function thumbnail() + { + $task = $this->getTask(); + $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $width_param = $this->request->getIntegerParam('width'); + $height_param = $this->request->getIntegerParam('height'); + $filename = FILES_DIR.$file['path']; + + if ($file['task_id'] == $task['id'] && file_exists($filename)) { + + // Get new sizes + list($width, $height) = getimagesize($filename); + + if ($width_param == 0 && $height_param == 0) { + $newwidth = 100; + $newheight = 100; + } elseif ($width_param > 0 && $height_param == 0) { + $newwidth = $width_param; + $newheight = floor($height * ($width_param / $width)); + } elseif ($width_param == 0 && $height_param > 0) { + $newwidth = floor($width * ($height_param / $height)); + $newheight = $height_param; + } else { + $newwidth = $width_param; + $newheight = $height_param; + } + + // Load + $thumb = imagecreatetruecolor($newwidth, $newheight); + $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + + switch ($extension) { + case 'jpeg': + case 'jpg': + $source = imagecreatefromjpeg($filename); + break; + case 'png': + $source = imagecreatefrompng($filename); + break; + case 'gif': + $source = imagecreatefromgif($filename); + break; + default: + die('File "' . $filename . '" is not valid jpg, png or gif image.'); + break; + } + + // Resize + imagecopyresampled($thumb, $source, 0, 0, 0, 0, $newwidth, $newheight, $width, $height); + + $metadata = getimagesize($filename); + + if (isset($metadata['mime'])) { + $this->response->contentType($metadata['mime']); + imagejpeg($thumb); + } + } + } + /** * Remove a file * diff --git a/sources/app/Controller/Hourlyrate.php b/sources/app/Controller/Hourlyrate.php new file mode 100644 index 0000000..8d96e5c --- /dev/null +++ b/sources/app/Controller/Hourlyrate.php @@ -0,0 +1,89 @@ +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('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('hourlyrate', 'index', array('user_id' => $user['id']))); + } +} diff --git a/sources/app/Controller/Link.php b/sources/app/Controller/Link.php index ec9c619..4a29a3e 100644 --- a/sources/app/Controller/Link.php +++ b/sources/app/Controller/Link.php @@ -37,7 +37,7 @@ class Link extends Base { $link = $this->link->getById($this->request->getIntegerParam('link_id')); - if (! $link) { + if (empty($link)) { $this->notfound(); } diff --git a/sources/app/Controller/Project.php b/sources/app/Controller/Project.php index fb0a8d0..4e01271 100644 --- a/sources/app/Controller/Project.php +++ b/sources/app/Controller/Project.php @@ -23,7 +23,7 @@ class Project extends Base else { $project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId()); } - + $nb_projects = count($project_ids); $paginator = $this->paginator @@ -128,6 +128,11 @@ class Project extends Base { $project = $this->getProject(); $values = $this->request->getValues(); + + if ($project['is_private'] == 1) { + $values += array('is_private' => 0); + } + list($valid, $errors) = $this->project->validateModification($values); if ($valid) { diff --git a/sources/app/Controller/Subtask.php b/sources/app/Controller/Subtask.php index c7ec00d..5eff157 100644 --- a/sources/app/Controller/Subtask.php +++ b/sources/app/Controller/Subtask.php @@ -22,7 +22,7 @@ class Subtask extends Base { $subtask = $this->subtask->getById($this->request->getIntegerParam('subtask_id')); - if (! $subtask) { + if (empty($subtask)) { $this->notfound(); } @@ -185,7 +185,7 @@ class Subtask extends Base if ($redirect === 'board') { $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId()); - + $this->response->html($this->template->render('board/subtasks', array( 'subtasks' => $this->subtask->getAll($task['id']), 'task' => $task, @@ -259,4 +259,22 @@ class Subtask extends Base $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); } } + + /** + * Move subtask position + * + * @access public + */ + public function movePosition() + { + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + $subtask_id = $this->request->getIntegerParam('subtask_id'); + $direction = $this->request->getStringParam('direction'); + $method = $direction === 'up' ? 'moveUp' : 'moveDown'; + + $this->subtask->$method($task_id, $subtask_id); + $this->response->redirect($this->helper->url('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks'); + } } diff --git a/sources/app/Controller/Swimlane.php b/sources/app/Controller/Swimlane.php index de2f1f1..c6862d4 100644 --- a/sources/app/Controller/Swimlane.php +++ b/sources/app/Controller/Swimlane.php @@ -23,7 +23,7 @@ class Swimlane extends Base { $swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id')); - if (! $swimlane) { + if (empty($swimlane)) { $this->session->flashError(t('Swimlane not found.')); $this->response->redirect('?controller=swimlane&action=index&project_id='.$project_id); } @@ -86,7 +86,7 @@ class Swimlane extends Base { $project = $this->getProject(); - $values = $this->request->getValues(); + $values = $this->request->getValues() + array('show_default_swimlane' => 0); list($valid,) = $this->swimlane->validateDefaultModification($values); if ($valid) { diff --git a/sources/app/Controller/Task.php b/sources/app/Controller/Task.php index 741db61..5bca451 100644 --- a/sources/app/Controller/Task.php +++ b/sources/app/Controller/Task.php @@ -22,13 +22,13 @@ class Task extends Base $project = $this->project->getByToken($this->request->getStringParam('token')); // Token verification - if (! $project) { + if (empty($project)) { $this->forbidden(true); } $task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id')); - if (! $task) { + if (empty($task)) { $this->notfound(true); } @@ -68,12 +68,14 @@ class Task extends Base $this->response->html($this->taskLayout('task/show', array( 'project' => $this->project->getById($task['project_id']), - 'files' => $this->file->getAll($task['id']), + 'files' => $this->file->getAllDocuments($task['id']), + 'images' => $this->file->getAllImages($task['id']), 'comments' => $this->comment->getAll($task['id']), 'subtasks' => $subtasks, 'links' => $this->taskLink->getLinks($task['id']), 'task' => $task, 'values' => $values, + 'link_label_list' => $this->link->getList(0, false), 'columns_list' => $this->board->getColumnsList($task['project_id']), 'colors_list' => $this->color->getList(), 'date_format' => $this->config->get('application_date_format'), @@ -82,6 +84,23 @@ class Task extends Base ))); } + /** + * Display task activities + * + * @access public + */ + public function activites() + { + $task = $this->getTask(); + + $this->response->html($this->taskLayout('task/activity', array( + 'title' => $task['title'], + 'task' => $task, + 'ajax' => $this->request->isAjax(), + 'events' => $this->projectActivity->getTask($task['id']), + ))); + } + /** * Display a form to create a new task * @@ -91,11 +110,12 @@ class Task extends Base { $project = $this->getProject(); $method = $this->request->isAjax() ? 'render' : 'layout'; + $swimlanes_list = $this->swimlane->getList($project['id']); if (empty($values)) { $values = array( - 'swimlane_id' => $this->request->getIntegerParam('swimlane_id'), + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanes_list)), 'column_id' => $this->request->getIntegerParam('column_id'), 'color_id' => $this->request->getStringParam('color_id'), 'owner_id' => $this->request->getIntegerParam('owner_id'), @@ -112,6 +132,7 @@ class Task extends Base 'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true), 'colors_list' => $this->color->getList(), 'categories_list' => $this->category->getList($project['id']), + 'swimlanes_list' => $swimlanes_list, 'date_format' => $this->config->get('application_date_format'), 'date_formats' => $this->dateParser->getAvailableFormats(), 'title' => $project['name'].' > '.t('New task') @@ -522,4 +543,19 @@ class Task extends Base 'subtask_paginator' => $subtask_paginator, ))); } + + /** + * Display the task transitions + * + * @access public + */ + public function transitions() + { + $task = $this->getTask(); + + $this->response->html($this->taskLayout('task/transitions', array( + 'task' => $task, + 'transitions' => $this->transition->getAllByTask($task['id']), + ))); + } } diff --git a/sources/app/Controller/Tasklink.php b/sources/app/Controller/Tasklink.php index 61b7fab..8376b75 100644 --- a/sources/app/Controller/Tasklink.php +++ b/sources/app/Controller/Tasklink.php @@ -21,7 +21,7 @@ class Tasklink extends Base { $link = $this->taskLink->getById($this->request->getIntegerParam('link_id')); - if (! $link) { + if (empty($link)) { $this->notfound(); } @@ -36,6 +36,7 @@ class Tasklink extends Base public function create(array $values = array(), array $errors = array()) { $task = $this->getTask(); + $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); if (empty($values)) { $values = array( @@ -43,6 +44,17 @@ class Tasklink extends Base ); } + if ($ajax) { + $this->response->html($this->template->render('tasklink/create', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'labels' => $this->link->getList(0, false), + 'title' => t('Add a new link'), + 'ajax' => $ajax, + ))); + } + $this->response->html($this->taskLayout('tasklink/create', array( 'values' => $values, 'errors' => $errors, @@ -61,6 +73,7 @@ class Tasklink extends Base { $task = $this->getTask(); $values = $this->request->getValues(); + $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); list($valid, $errors) = $this->taskLink->validateCreation($values); @@ -68,6 +81,9 @@ class Tasklink extends Base if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->session->flash(t('Link added successfully.')); + if ($ajax) { + $this->response->redirect($this->helper->url('board', 'show', array('project_id' => $task['project_id']))); + } $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } else { diff --git a/sources/app/Controller/Timetable.php b/sources/app/Controller/Timetable.php new file mode 100644 index 0000000..65edb44 --- /dev/null +++ b/sources/app/Controller/Timetable.php @@ -0,0 +1,39 @@ +getUser(); + $from = $this->request->getStringParam('from', date('Y-m-d')); + $to = $this->request->getStringParam('to', date('Y-m-d', strtotime('next week'))); + $timetable = $this->timetable->calculate($user['id'], new DateTime($from), new DateTime($to)); + + $this->response->html($this->layout('timetable/index', array( + 'user' => $user, + 'timetable' => $timetable, + 'values' => array( + 'from' => $from, + 'to' => $to, + 'controller' => 'timetable', + 'action' => 'index', + 'user_id' => $user['id'], + ), + ))); + } +} diff --git a/sources/app/Controller/Timetableday.php b/sources/app/Controller/Timetableday.php new file mode 100644 index 0000000..eea44ae --- /dev/null +++ b/sources/app/Controller/Timetableday.php @@ -0,0 +1,88 @@ +getUser(); + + $this->response->html($this->layout('timetable_day/index', array( + 'timetable' => $this->timetableDay->getByUser($user['id']), + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->timetableDay->validateCreation($values); + + if ($valid) { + + if ($this->timetableDay->create($values['user_id'], $values['start'], $values['end'])) { + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url('timetableday', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_day/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->timetableDay->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url('timetableday', 'index', array('user_id' => $user['id']))); + } +} diff --git a/sources/app/Controller/Timetableextra.php b/sources/app/Controller/Timetableextra.php new file mode 100644 index 0000000..7c6fe26 --- /dev/null +++ b/sources/app/Controller/Timetableextra.php @@ -0,0 +1,16 @@ +getUser(); + + $paginator = $this->paginator + ->setUrl($this->controller_url, 'index', array('user_id' => $user['id'])) + ->setMax(10) + ->setOrder('date') + ->setDirection('desc') + ->setQuery($this->{$this->model}->getUserQuery($user['id'])) + ->calculate(); + + $this->response->html($this->layout($this->template_dir.'/index', array( + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'paginator' => $paginator, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->{$this->model}->validateCreation($values); + + if ($valid) { + + if ($this->{$this->model}->create( + $values['user_id'], + $values['date'], + isset($values['all_day']) && $values['all_day'] == 1, + $values['start'], + $values['end'], + $values['comment'])) { + + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url($this->controller_url, 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout($this->template_dir.'/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->{$this->model}->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url($this->controller_url, 'index', array('user_id' => $user['id']))); + } +} diff --git a/sources/app/Controller/Timetableweek.php b/sources/app/Controller/Timetableweek.php new file mode 100644 index 0000000..829f440 --- /dev/null +++ b/sources/app/Controller/Timetableweek.php @@ -0,0 +1,99 @@ +getUser(); + + if (empty($values)) { + + $day = $this->timetableDay->getByUser($user['id']); + + $values = array( + 'user_id' => $user['id'], + 'start' => isset($day[0]['start']) ? $day[0]['start'] : null, + 'end' => isset($day[0]['end']) ? $day[0]['end'] : null, + ); + } + + $this->response->html($this->layout('timetable_week/index', array( + 'timetable' => $this->timetableWeek->getByUser($user['id']), + 'values' => $values, + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->timetableWeek->validateCreation($values); + + if ($valid) { + + if ($this->timetableWeek->create($values['user_id'], $values['day'], $values['start'], $values['end'])) { + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url('timetableweek', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_week/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->timetableWeek->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url('timetableweek', 'index', array('user_id' => $user['id']))); + } +} diff --git a/sources/app/Controller/Twofactor.php b/sources/app/Controller/Twofactor.php new file mode 100644 index 0000000..e3451d3 --- /dev/null +++ b/sources/app/Controller/Twofactor.php @@ -0,0 +1,140 @@ +userSession->getId()) { + $this->forbidden(); + } + } + + /** + * Index + * + * @access public + */ + public function index() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $label = $user['email'] ?: $user['username']; + + $this->response->html($this->layout('twofactor/index', array( + 'user' => $user, + 'qrcode_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getQrCodeUrl('totp', $label, $user['twofactor_secret']) : '', + 'key_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getKeyUri('totp', $label, $user['twofactor_secret']) : '', + ))); + } + + /** + * Enable/disable 2FA + * + * @access public + */ + public function save() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $values = $this->request->getValues(); + + if (isset($values['twofactor_activated']) && $values['twofactor_activated'] == 1) { + $this->user->update(array( + 'id' => $user['id'], + 'twofactor_activated' => 1, + 'twofactor_secret' => GoogleAuthenticator::generateRandom(), + )); + } + else { + $this->user->update(array( + 'id' => $user['id'], + 'twofactor_activated' => 0, + 'twofactor_secret' => '', + )); + } + + // Allow the user to test or disable the feature + $_SESSION['user']['twofactor_activated'] = false; + + $this->session->flash(t('User updated successfully.')); + $this->response->redirect($this->helper->url('twofactor', 'index', array('user_id' => $user['id']))); + } + + /** + * Test 2FA + * + * @access public + */ + public function test() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $otp = new Otp; + $values = $this->request->getValues(); + + if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) { + $this->session->flash(t('The two factor authentication code is valid.')); + } + else { + $this->session->flashError(t('The two factor authentication code is not valid.')); + } + + $this->response->redirect($this->helper->url('twofactor', 'index', array('user_id' => $user['id']))); + } + + /** + * Check 2FA + * + * @access public + */ + public function check() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $otp = new Otp; + $values = $this->request->getValues(); + + if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) { + $this->session['2fa_validated'] = true; + $this->session->flash(t('The two factor authentication code is valid.')); + $this->response->redirect($this->helper->url('app', 'index')); + } + else { + $this->session->flashError(t('The two factor authentication code is not valid.')); + $this->response->redirect($this->helper->url('twofactor', 'code')); + } + } + + /** + * Ask the 2FA code + * + * @access public + */ + public function code() + { + $this->response->html($this->template->layout('twofactor/check', array( + 'title' => t('Check two factor authentication code'), + ))); + } +} diff --git a/sources/app/Controller/User.php b/sources/app/Controller/User.php index decdb64..5dad4ef 100644 --- a/sources/app/Controller/User.php +++ b/sources/app/Controller/User.php @@ -69,12 +69,12 @@ class User extends Base /** * Common layout for user views * - * @access private + * @access protected * @param string $template Template name * @param array $params Template parameters * @return string */ - private function layout($template, array $params) + protected function layout($template, array $params) { $content = $this->template->render($template, $params); $params['user_content_for_layout'] = $content; @@ -90,14 +90,14 @@ class User extends Base /** * Common method to get the user * - * @access private + * @access protected * @return array */ - private function getUser() + protected function getUser() { $user = $this->user->getById($this->request->getIntegerParam('user_id')); - if (! $user) { + if (empty($user)) { $this->notfound(); } diff --git a/sources/app/Core/Helper.php b/sources/app/Core/Helper.php index 01ebb08..2900341 100644 --- a/sources/app/Core/Helper.php +++ b/sources/app/Core/Helper.php @@ -502,7 +502,7 @@ class Helper public function markdown($text, array $link = array()) { $parser = new Markdown($link, $this); - $parser->setMarkupEscaped(true); + $parser->setMarkupEscaped(MARKDOWN_ESCAPE_HTML); return $parser->text($text); } @@ -677,4 +677,114 @@ class Helper array('task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'redirect' => $redirect) ); } + + /** + * Get all hours for day + * + * @access public + * @return array + */ + public function getDayHours() + { + $values = array(); + + foreach (range(0, 23) as $hour) { + foreach (array(0, 30) as $minute) { + $time = sprintf('%02d:%02d', $hour, $minute); + $values[$time] = $time; + } + } + + return $values; + } + + /** + * Get all days of a week + * + * @access public + * @return array + */ + public function getWeekDays() + { + $values = array(); + + foreach (range(1, 7) as $day) { + $values[$day] = $this->getWeekDay($day); + } + + return $values; + } + + /** + * Get the localized day name from the day number + * + * @access public + * @param integer $day Day number + * @return string + */ + public function getWeekDay($day) + { + return dt('%A', strtotime('next Monday +'.($day - 1).' days')); + } + + /** + * Get file icon + * + * @access public + * @param string $filename Filename + * @return string Font-Awesome-Icon-Name + */ + public function getFileIcon($filename){ + + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + switch ($extension) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return 'fa-file-image-o'; + case 'xls': + case 'xlsx': + return 'fa-file-excel-o'; + case 'doc': + case 'docx': + return 'fa-file-word-o'; + case 'ppt': + case 'pptx': + return 'fa-file-powerpoint-o'; + case 'zip': + case 'rar': + return 'fa-archive-o'; + case 'mp3': + return 'fa-audio-o'; + case 'avi': + return 'fa-video-o'; + case 'php': + case 'html': + case 'css': + return 'fa-code-o'; + case 'pdf': + return 'fa-file-pdf-o'; + } + + return 'fa-file-o'; + } + + /** + * Display gravatar image + * + * @access public + * @param string $email + * @param string $alt + * @return string + */ + public function avatar($email, $alt = '') + { + if (! empty($email) && $this->config->get('integration_gravatar') == 1) { + return ''.$this->e($alt).''; + } + + return ''; + } } diff --git a/sources/app/Core/HttpClient.php b/sources/app/Core/HttpClient.php new file mode 100644 index 0000000..e1d9085 --- /dev/null +++ b/sources/app/Core/HttpClient.php @@ -0,0 +1,68 @@ + array( + 'method' => 'POST', + 'protocol_version' => 1.1, + 'timeout' => self::HTTP_TIMEOUT, + 'max_redirects' => self::HTTP_MAX_REDIRECTS, + 'header' => implode("\r\n", $headers), + 'content' => json_encode($data) + ) + )); + + return @file_get_contents(trim($url), false, $context); + } +} diff --git a/sources/app/Integration/Base.php b/sources/app/Integration/Base.php index babf8c8..c6387fe 100644 --- a/sources/app/Integration/Base.php +++ b/sources/app/Integration/Base.php @@ -10,6 +10,7 @@ use Pimple\Container; * @package integration * @author Frederic Guillot * + * @property \Model\ProjectActivity $projectActivity * @property \Model\Task $task * @property \Model\TaskFinder $taskFinder * @property \Model\User $user diff --git a/sources/app/Integration/BitbucketWebhook.php b/sources/app/Integration/BitbucketWebhook.php index ccb89e1..7ff8087 100644 --- a/sources/app/Integration/BitbucketWebhook.php +++ b/sources/app/Integration/BitbucketWebhook.php @@ -78,7 +78,7 @@ class BitbucketWebhook extends Base $task = $this->taskFinder->getById($task_id); - if (! $task) { + if (empty($task)) { return false; } diff --git a/sources/app/Integration/GithubWebhook.php b/sources/app/Integration/GithubWebhook.php index fd0b49f..121fdd1 100644 --- a/sources/app/Integration/GithubWebhook.php +++ b/sources/app/Integration/GithubWebhook.php @@ -86,7 +86,7 @@ class GithubWebhook extends Base $task = $this->taskFinder->getById($task_id); - if (! $task) { + if (empty($task)) { continue; } @@ -142,7 +142,7 @@ class GithubWebhook extends Base $task = $this->taskFinder->getByReference($payload['issue']['number']); $user = $this->user->getByUsername($payload['comment']['user']['login']); - if ($task && $user) { + if (! empty($task) && ! empty($user)) { $event = array( 'project_id' => $this->project_id, @@ -198,7 +198,7 @@ class GithubWebhook extends Base { $task = $this->taskFinder->getByReference($issue['number']); - if ($task) { + if (! empty($task)) { $event = array( 'project_id' => $this->project_id, 'task_id' => $task['id'], @@ -227,7 +227,7 @@ class GithubWebhook extends Base { $task = $this->taskFinder->getByReference($issue['number']); - if ($task) { + if (! empty($task)) { $event = array( 'project_id' => $this->project_id, 'task_id' => $task['id'], @@ -257,7 +257,7 @@ class GithubWebhook extends Base $user = $this->user->getByUsername($issue['assignee']['login']); $task = $this->taskFinder->getByReference($issue['number']); - if ($user && $task) { + if (! empty($user) && ! empty($task)) { $event = array( 'project_id' => $this->project_id, @@ -288,7 +288,7 @@ class GithubWebhook extends Base { $task = $this->taskFinder->getByReference($issue['number']); - if ($task) { + if (! empty($task)) { $event = array( 'project_id' => $this->project_id, @@ -320,7 +320,7 @@ class GithubWebhook extends Base { $task = $this->taskFinder->getByReference($issue['number']); - if ($task) { + if (! empty($task)) { $event = array( 'project_id' => $this->project_id, @@ -352,7 +352,7 @@ class GithubWebhook extends Base { $task = $this->taskFinder->getByReference($issue['number']); - if ($task) { + if (! empty($task)) { $event = array( 'project_id' => $this->project_id, diff --git a/sources/app/Integration/GitlabWebhook.php b/sources/app/Integration/GitlabWebhook.php index e920f33..dbba366 100644 --- a/sources/app/Integration/GitlabWebhook.php +++ b/sources/app/Integration/GitlabWebhook.php @@ -122,7 +122,7 @@ class GitlabWebhook extends Base $task = $this->taskFinder->getById($task_id); - if (! $task) { + if (empty($task)) { return false; } @@ -193,7 +193,7 @@ class GitlabWebhook extends Base { $task = $this->taskFinder->getByReference($issue['id']); - if ($task) { + if (! empty($task)) { $event = array( 'project_id' => $this->project_id, 'task_id' => $task['id'], diff --git a/sources/app/Integration/Hipchat.php b/sources/app/Integration/Hipchat.php new file mode 100644 index 0000000..1306af6 --- /dev/null +++ b/sources/app/Integration/Hipchat.php @@ -0,0 +1,53 @@ +project->getbyId($project_id); + + $event['event_name'] = $event_name; + $event['author'] = $this->user->getFullname($this->session['user']); + + $html = ''; + $html .= ''.$project['name'].'
'; + $html .= $this->projectActivity->getTitle($event); + + if ($this->config->get('application_url')) { + $html .= '
'; + $html .= t('view the task on Kanboard').''; + } + + $payload = array( + 'message' => $html, + 'color' => 'yellow', + ); + + $url = sprintf( + '%s/v2/room/%s/notification?auth_token=%s', + $this->config->get('integration_hipchat_api_url'), + $this->config->get('integration_hipchat_room_id'), + $this->config->get('integration_hipchat_room_token') + ); + + $this->httpClient->post($url, $payload); + } +} diff --git a/sources/app/Integration/SlackWebhook.php b/sources/app/Integration/SlackWebhook.php new file mode 100644 index 0000000..1c2ea78 --- /dev/null +++ b/sources/app/Integration/SlackWebhook.php @@ -0,0 +1,43 @@ +project->getbyId($project_id); + + $event['event_name'] = $event_name; + $event['author'] = $this->user->getFullname($this->session['user']); + + $payload = array( + 'text' => '*['.$project['name'].']* '.str_replace('"', '"', $this->projectActivity->getTitle($event)), + 'username' => 'Kanboard', + 'icon_url' => 'http://kanboard.net/assets/img/favicon.png', + ); + + if ($this->config->get('application_url')) { + $payload['text'] .= ' - <'.$this->config->get('application_url'); + $payload['text'] .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id)); + $payload['text'] .= '|'.t('view the task on Kanboard').'>'; + } + + $this->httpClient->post($this->config->get('integration_slack_webhook_url'), $payload); + } +} diff --git a/sources/app/Locale/da_DK/translations.php b/sources/app/Locale/da_DK/translations.php index 75f27a7..20b5759 100644 --- a/sources/app/Locale/da_DK/translations.php +++ b/sources/app/Locale/da_DK/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Ingen', 'edit' => 'rediger', 'Edit' => 'Rediger', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/de_DE/translations.php b/sources/app/Locale/de_DE/translations.php index 54503b6..db1c932 100644 --- a/sources/app/Locale/de_DE/translations.php +++ b/sources/app/Locale/de_DE/translations.php @@ -1,6 +1,8 @@ ',', + 'number.thousands_separator' => '.', 'None' => 'Keines', 'edit' => 'Bearbeiten', 'Edit' => 'Bearbeiten', @@ -408,13 +410,13 @@ return array( 'Comment updated' => 'Kommentar wurde aktualisiert', 'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s', 'List of due tasks for the project "%s"' => 'Liste der fälligen Aufgaben für das Projekt "%s"', - // 'New attachment' => '', - // 'New comment' => '', - // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', + 'New attachment' => 'Neuer Anhang', + 'New comment' => 'Neuer Kommentar', + 'New subtask' => 'Neue Teilaufgabe', + 'Subtask updated' => 'Teilaufgabe aktualisiert', + 'Task updated' => 'Aufgabe aktualisiert', + 'Task closed' => 'Aufgabe geschlossen', + 'Task opened' => 'Aufgabe geöffnet', '[%s][Due tasks]' => '[%s][Fällige Aufgaben]', '[Kanboard] Notification' => '[Kanboard] Benachrichtigung', 'I want to receive notifications only for those projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:', @@ -498,9 +500,9 @@ return array( 'Task assignee change' => 'Zuständigkeit geändert', '%s change the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s', '%s changed the assignee of the task %s to %s' => '%s hat die Zuständigkeit der Aufgabe %s geändert um %s', - // 'Column Change' => '', - // 'Position Change' => '', - // 'Assignee Change' => '', + 'Column Change' => 'Spalte ändern', + 'Position Change' => 'Position ändern', + 'Assignee Change' => 'Zuordnung ändern', 'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"', 'Choose an event' => 'Aktion wählen', 'Github commit received' => 'Github commit empfangen', @@ -625,8 +627,8 @@ return array( 'Example: "Bug, Feature Request, Improvement"' => 'Beispiel: "Bug, Funktionswünsche, Verbesserung"', 'Default categories for new projects (Comma-separated)' => 'Standard Kategorien für neue Projekte (Komma-getrennt)', 'Gitlab commit received' => 'Gitlab commit erhalten', - 'Gitlab issue opened' => 'Gitlab Thema eröffnet', - 'Gitlab issue closed' => 'Gitlab Thema geschlossen', + 'Gitlab issue opened' => 'Gitlab Fehler eröffnet', + 'Gitlab issue closed' => 'Gitlab Fehler geschlossen', 'Gitlab webhooks' => 'Gitlab Webhook', 'Help on Gitlab webhooks' => 'Hilfe für Gitlab Webhooks', 'Integrations' => 'Integration', @@ -635,7 +637,7 @@ return array( 'Project manager' => 'Projektmanager', 'Project member' => 'Projektmitglied', 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Ein Projektmanager kann die Projekteinstellungen ändern und hat mehr Rechte als ein normaler Benutzer.', - 'Gitlab Issue' => 'Gitlab Thema', + 'Gitlab Issue' => 'Gitlab Fehler', 'Subtask Id' => 'Teilaufgaben Id', 'Subtasks' => 'Teilaufgaben', 'Subtasks Export' => 'Teilaufgaben Export', @@ -734,4 +736,114 @@ return array( 'Filter recently updated' => 'Zuletzt geänderte anzeigen', 'since %B %e, %Y at %k:%M %p' => 'seit %B %e, %Y um %k:%M %p', 'More filters' => 'Mehr Filter', + 'Compact view' => 'Kompaktansicht', + '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', + 'All day' => 'ganztägig', + 'Day' => 'Tag', + 'Manage timetable' => 'Zeitplan verwalten', + 'Overtime timetable' => 'Überstunden Zeitplan', + 'Time off timetable' => 'Freizeit Zeitplan', + 'Timetable' => 'Zeitplan', + 'Work timetable' => 'Arbeitszeitplan', + 'Week timetable' => 'Wochenzeitplan', + 'Day timetable' => 'Tageszeitplan', + 'From' => 'von', + 'To' => 'bis', + 'Time slot created successfully.' => 'Zeitfenster erfolgreich erstellt.', + 'Unable to save this time slot.' => 'Nicht in der Lage, dieses Zeitfenster zu speichern.', + 'Time slot removed successfully.' => 'Zeitfenster erfolgreich entfernt.', + 'Unable to remove this time slot.' => 'Nicht in der Lage, dieses Zeitfenster zu entfernen', + 'Do you really want to remove this time slot?' => 'Soll diese Zeitfenster wirklich gelöscht werden?', + 'Remove time slot' => 'Zeitfenster entfernen', + 'Add new time slot' => 'Neues Zeitfenster hinzufügen', + 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Dieses Zeitfenster wird verwendet, wenn die Checkbox "gantägig" für Freizeit und Überstunden angeklickt ist.', + 'Files' => 'Dateien', + 'Images' => 'Bilder', + 'Private project' => 'privates Projekt', + 'Amount' => 'Betrag', + // 'AUD - Australian Dollar' => '', + 'Budget' => 'Budget', + 'Budget line' => 'Budgetlinie', + 'Budget line removed successfully.' => 'Budgetlinie erfolgreich entfernt', + 'Budget lines' => 'Budgetlinien', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + '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' => '', + 'Expenses' => 'Kosten', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + 'New budget line' => 'Neue Budgetlinie', + // 'NZD - New Zealand Dollar' => '', + 'Remove a budget line' => 'Budgetlinie entfernen', + 'Remove budget line' => 'Budgetlinie entfernen', + // 'RSD - Serbian 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' => '', + '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.', + 'Source column' => 'Quellspalte', + 'Show subtask estimates in the user calendar' => 'Teilaufgabenschätzung in Benutzerkalender anzeigen.', + 'Transitions' => 'Übergänge', + 'Executer' => 'Ausführender', + 'Time spent in the column' => 'Zeit in Spalte verbracht', + 'Task transitions' => 'Aufgaben Übergänge', + 'Task transitions export' => 'Aufgaben Übergänge exportieren', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Diese Auswertung enthält alle Spaltenbewegungen für jede Aufgabe mit Datum, Benutzer und Zeit vor jedem Wechsel.', + 'Currency rates' => 'Währungskurse', + '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', + 'Send notifications to a Slack channel' => 'Benachrichtigung an einen Slack-Kanal senden', + 'Webhook URL' => 'Webhook URL', + 'Help on Slack integration' => 'Hilfe für Slack integration.', + '%s remove the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/es_ES/translations.php b/sources/app/Locale/es_ES/translations.php index 3981f12..4026d29 100644 --- a/sources/app/Locale/es_ES/translations.php +++ b/sources/app/Locale/es_ES/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Ninguno', 'edit' => 'modificar', 'Edit' => 'Modificar', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/fi_FI/translations.php b/sources/app/Locale/fi_FI/translations.php index 06b2195..5e15b28 100644 --- a/sources/app/Locale/fi_FI/translations.php +++ b/sources/app/Locale/fi_FI/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Ei mikään', 'edit' => 'muokkaa', 'Edit' => 'Muokkaa', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/fr_FR/translations.php b/sources/app/Locale/fr_FR/translations.php index b05f707..7b70183 100644 --- a/sources/app/Locale/fr_FR/translations.php +++ b/sources/app/Locale/fr_FR/translations.php @@ -1,6 +1,8 @@ ',', + 'number.thousands_separator' => ' ', 'None' => 'Aucun', 'edit' => 'modifier', 'Edit' => 'Modifier', @@ -736,4 +738,114 @@ return array( 'Filter recently updated' => 'Récemment modifié', 'since %B %e, %Y at %k:%M %p' => 'depuis le %d/%m/%Y à %H:%M', 'More filters' => 'Plus de filtres', + 'Compact view' => 'Vue compacte', + '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', + 'All day' => 'Toute la journée', + 'Day' => 'Jour', + 'Manage timetable' => 'Gérer les horaires', + 'Overtime timetable' => 'Heures supplémentaires', + 'Time off timetable' => 'Heures d\'absences', + 'Timetable' => 'Horaires', + 'Work timetable' => 'Horaires travaillés', + 'Week timetable' => 'Horaires de la semaine', + 'Day timetable' => 'Horaire d\'une journée', + 'From' => 'Depuis', + 'To' => 'À', + 'Time slot created successfully.' => 'Créneau horaire créé avec succès.', + 'Unable to save this time slot.' => 'Impossible de sauvegarder ce créneau horaire.', + 'Time slot removed successfully.' => 'Créneau horaire supprimé avec succès.', + 'Unable to remove this time slot.' => 'Impossible de supprimer ce créneau horaire.', + '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.', + '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', + 'Source column' => 'Colonne d\'origine', + 'Show subtask estimates in the user calendar' => 'Afficher le temps estimé des sous-tâches dans le calendrier utilisateur', + 'Transitions' => 'Transitions', + 'Executer' => 'Exécutant', + 'Time spent in the column' => 'Temps passé dans la colonne', + 'Task transitions' => 'Transitions des tâches', + 'Task transitions export' => 'Export des transitions des tâches', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ce rapport contient tous les mouvements de colonne pour chaque tâche avec la date, l\'utilisateur et le temps passé pour chaque transition.', + 'Currency rates' => 'Taux de change des devises', + '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', + 'Send notifications to a Slack channel' => 'Envoyer les notifications sur un salon de discussion Slack', + 'Webhook URL' => 'URL du webhook', + 'Help on Slack integration' => 'Aide sur l\'intégration avec Slack', + '%s remove the assignee of the task %s' => '%s a enlevé la personne assignée à la tâche %s', + 'Send notifications to Hipchat' => 'Envoyer les notifications vers Hipchat', + 'API URL' => 'URL de l\'api', + 'Room API ID or name' => 'Nom ou identifiant du salon de discussion', + 'Room notification token' => 'Jeton de sécurité du salon de discussion', + 'Help on Hipchat integration' => 'Aide sur l\'intégration avec Hipchat', + 'Enable Gravatar images' => 'Activer les images Gravatar', + 'Information' => 'Informations', + 'Check two factor authentication code' => 'Vérification du code pour l\'authentification à deux-facteurs', + 'The two factor authentication code is not valid.' => 'Le code pour l\'authentification à deux-facteurs n\'est pas valide.', + 'The two factor authentication code is valid.' => 'Le code pour l\'authentification à deux-facteurs est valide.', + '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).', + 'Check my code' => 'Vérifier mon code', + 'Secret key: ' => 'Clé secrète : ', + 'Test your device' => 'Testez votre appareil', + 'Assign a color when the task is moved to a specific column' => 'Assigner une couleur lorsque la tâche est déplacée dans une colonne spécifique', ); diff --git a/sources/app/Locale/hu_HU/translations.php b/sources/app/Locale/hu_HU/translations.php index 1780505..83821f2 100644 --- a/sources/app/Locale/hu_HU/translations.php +++ b/sources/app/Locale/hu_HU/translations.php @@ -1,11 +1,13 @@ ',', + 'number.thousands_separator' => ' ', 'None' => 'Nincs', 'edit' => 'szerkesztés', 'Edit' => 'Szerkesztés', - 'remove' => 'eltávolítás', - 'Remove' => 'Eltávolítás', + 'remove' => 'törlés', + 'Remove' => 'Törlés', 'Update' => 'Frissítés', 'Yes' => 'Igen', 'No' => 'Nem', @@ -104,7 +106,7 @@ return array( 'Open a task' => 'Feladat felnyitás', 'Do you really want to open this task: "%s"?' => 'Tényleg meg akarja nyitni ezt a feladatot: "%s"?', 'Back to the board' => 'Vissza a táblához', - 'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y.%m.%d %H:%M', + 'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y. %m. %d. %H:%M', 'There is nobody assigned' => 'Nincs felelős', 'Column on the board:' => 'Tábla oszlopa: ', 'Status is open' => 'Nyitott állapot', @@ -133,12 +135,12 @@ return array( 'The title is required' => 'A címet meg kell adni', 'The language is required' => 'A nyelvet meg kell adni', 'There is no active project, the first step is to create a new project.' => 'Nincs aktív projekt. Először létre kell hozni egy projektet.', - 'Settings saved successfully.' => 'A beállítások mentése sikeres.', + 'Settings saved successfully.' => 'A beállítások sikeresen mentve.', 'Unable to save your settings.' => 'A beállítások mentése sikertelen.', 'Database optimization done.' => 'Adatbázis optimalizálás kész.', 'Your project have been created successfully.' => 'Projekt sikeresen létrehozva', 'Unable to create your project.' => 'Projekt létrehozása sikertelen.', - 'Project updated successfully.' => 'Projekt frissítése sikeres.', + 'Project updated successfully.' => 'Projekt sikeresen frissítve.', 'Unable to update this project.' => 'Projekt frissítése sikertelen.', 'Unable to remove this project.' => 'Projekt törlése sikertelen.', 'Project removed successfully.' => 'Projekt sikeresen törölve.', @@ -146,7 +148,7 @@ return array( 'Unable to activate this project.' => 'Projekt aktiválása sikertelen.', 'Project disabled successfully.' => 'Projekt sikeresen letiltva.', 'Unable to disable this project.' => 'Projekt letiltása sikertelen.', - 'Unable to open this task.' => 'A feladat felnyitása nem sikerült.', + 'Unable to open this task.' => 'A feladat felnyitása sikertelen.', 'Task opened successfully.' => 'Feladat sikeresen megnyitva .', 'Unable to close this task.' => 'A feladat lezárása sikertelen.', 'Task closed successfully.' => 'Feladat sikeresen lezárva.', @@ -166,8 +168,8 @@ return array( 'Work in progress' => 'Folyamatban', 'Done' => 'Kész', 'Application version:' => 'Alkalmazás verzió:', - 'Completed on %B %e, %Y at %k:%M %p' => 'Elkészült %Y.%m.%d %H:%M ..', - '%B %e, %Y at %k:%M %p' => '%Y.%m.%d %H:%M', + 'Completed on %B %e, %Y at %k:%M %p' => 'Elkészült: %Y. %m. %d. %H:%M', + '%B %e, %Y at %k:%M %p' => '%Y. %m. %d. %H:%M', 'Date created' => 'Létrehozás időpontja', 'Date completed' => 'Befejezés időpontja', 'Id' => 'ID', @@ -211,9 +213,9 @@ return array( 'Edit this task' => 'Feladat módosítása', 'Due Date' => 'Határidő', 'Invalid date' => 'Érvénytelen dátum', - 'Must be done before %B %e, %Y' => 'Kész kell lennie %Y.%m.%d előtt', - '%B %e, %Y' => '%Y.%m.%d', - '%b %e, %Y' => '%Y.%m.%d', + 'Must be done before %B %e, %Y' => 'Kész kell lennie %Y. %m. %d. előtt', + '%B %e, %Y' => '%Y. %m. %d.', + '%b %e, %Y' => '%Y. %m. %d.', 'Automatic actions' => 'Automatikus intézkedések', 'Your automatic action have been created successfully.' => 'Az automatikus intézkedés sikeresen elkészült.', 'Unable to create your automatic action.' => 'Automatikus intézkedés létrehozása nem lehetséges.', @@ -254,7 +256,7 @@ return array( 'link' => 'link', 'Update this comment' => 'Hozzászólás frissítése', 'Comment updated successfully.' => 'Megjegyzés sikeresen frissítve.', - 'Unable to update your comment.' => 'Megjegyzés frissítése nem sikerült.', + 'Unable to update your comment.' => 'Megjegyzés frissítése sikertelen.', 'Remove a comment' => 'Megjegyzés törlése', 'Comment removed successfully.' => 'Megjegyzés sikeresen törölve.', 'Unable to remove this comment.' => 'Megjegyzés törölése nem lehetséges.', @@ -294,8 +296,8 @@ return array( 'Your Google Account is not linked anymore to your profile.' => 'Google Fiók már nincs a profilhoz kapcsolva.', 'Unable to unlink your Google Account.' => 'Leválasztás a Google fiókról nem lehetséges.', 'Google authentication failed' => 'Google azonosítás sikertelen', - 'Unable to link your Google Account.' => 'Google profilhoz kapcsolás nem sikerült.', - 'Your Google Account is linked to your profile successfully.' => 'Sikeresen összekapcsolva a Google fiókkal.', + 'Unable to link your Google Account.' => 'A Google profilhoz kapcsolás sikertelen.', + 'Your Google Account is linked to your profile successfully.' => 'Google fiókkal sikeresen összekapcsolva.', 'Email' => 'E-mail', 'Link my Google Account' => 'Kapcsold össze a Google fiókkal', 'Unlink my Google Account' => 'Válaszd le a Google fiókomat', @@ -377,7 +379,7 @@ return array( 'Link my GitHub Account' => 'GitHub fiók csatolása', 'Unlink my GitHub Account' => 'GitHub fiók leválasztása', 'Created by %s' => 'Készítette: %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Utolsó módosítás: %Y.%m.%d %H:%M', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Utolsó módosítás: %Y. %m. %d. %H:%M', 'Tasks Export' => 'Feladatok exportálása', 'Tasks exportation for "%s"' => 'Feladatok exportálása: "%s"', 'Start Date' => 'Kezdés dátuma', @@ -391,7 +393,7 @@ return array( 'Webhook URL for task modification' => 'Webhook URL a feladatot módosításakor', 'Clone' => 'Másolat', 'Clone Project' => 'Projekt másolása', - 'Project cloned successfully.' => 'A projekt másolása sikeres', + 'Project cloned successfully.' => 'A projekt sikeresen másolva.', 'Unable to clone this project.' => 'A projekt másolása sikertelen.', 'Email notifications' => 'E-mail értesítések', 'Enable email notifications' => 'E-mail értesítések engedélyezése', @@ -435,7 +437,7 @@ return array( 'Move the task to another project' => 'Feladat áthelyezése másik projektbe', 'Move to another project' => 'Áthelyezés másik projektbe', 'Do you really want to duplicate this task?' => 'Tényleg szeretné megkettőzni ezt a feladatot?', - 'Duplicate a task' => 'Feladat megkettőzése', + 'Duplicate a task' => 'Feladat másolása', 'External accounts' => 'Külső fiókok', 'Account type' => 'Fiók típusa', 'Local' => 'Helyi', @@ -517,7 +519,7 @@ return array( 'Label' => 'Címke', 'Database' => 'Adatbázis', 'About' => 'Kanboard információ', - 'Database driver:' => 'Adatbázis driver:', + 'Database driver:' => 'Adatbázis motor:', 'Board settings' => 'Tábla beállítások', 'URL and token' => 'URL és tokenek', 'Webhook settings' => 'Webhook beállítások', @@ -541,7 +543,7 @@ return array( 'Add' => 'Hozzáadás', 'Estimated time: %s hours' => 'Becsült idő: %s óra', 'Time spent: %s hours' => 'Eltöltött idő: %s óra', - 'Started on %B %e, %Y' => 'Elkezdve: %Y.%m.%d', + 'Started on %B %e, %Y' => 'Elkezdve: %Y. %m. %d.', 'Start date' => 'Kezdés dátuma', 'Time estimated' => 'Becsült időtartam', 'There is nothing assigned to you.' => 'Nincs kiosztott feladat.', @@ -574,8 +576,8 @@ return array( 'My subtasks' => 'Részfeladataim', 'User repartition' => 'Felhasználó újrafelosztás', 'User repartition for "%s"' => 'Felhasználó újrafelosztás: %s', - 'Clone this project' => 'Projekt megkettőzése', - 'Column removed successfully.' => 'Oszlop sikeresen eltávolítva.', + 'Clone this project' => 'Projekt másolása', + 'Column removed successfully.' => 'Oszlop sikeresen törölve.', 'Edit Project' => 'Projekt szerkesztése', 'Github Issue' => 'Github issue', 'Not enough data to show the graph.' => 'Nincs elég adat a grafikonhoz.', @@ -669,8 +671,8 @@ return array( 'There is nothing to show.' => 'Nincs megjelenítendő adat.', 'Time Tracking' => 'Idő követés', 'You already have one subtask in progress' => 'Már van egy folyamatban levő részfeladata', - 'Which parts of the project do you want to duplicate?' => 'A projekt mely részeit szeretné duplikálni?', - 'Change dashboard view' => 'Vezérlőpult megjelenítés változtatás', + 'Which parts of the project do you want to duplicate?' => 'A projekt mely részeit szeretné másolni?', + 'Change dashboard view' => 'Vezérlőpult megjelenés változtatás', 'Show/hide activities' => 'Tevékenységek megjelenítése/elrejtése', 'Show/hide projects' => 'Projektek megjelenítése/elrejtése', 'Show/hide subtasks' => 'Részfeladatok megjelenítése/elrejtése', @@ -728,10 +730,120 @@ return array( 'Close dialog box' => 'Ablak bezárása', 'Submit a form' => 'Űrlap beküldése', 'Board view' => 'Tábla nézet', - 'Keyboard shortcuts' => 'Billentyű kombináció', + 'Keyboard shortcuts' => 'Billentyű kombinációk', 'Open board switcher' => 'Tábla választó lenyitása', - // 'Application' => '', - // 'Filter recently updated' => '', - // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + 'Application' => 'Alkalmazás', + 'Filter recently updated' => 'Szűrés az utolsó módosítás ideje szerint', + 'since %B %e, %Y at %k:%M %p' => '%Y. %m. %d. %H:%M óta', + 'More filters' => 'További szűrők', + 'Compact view' => 'Kompakt nézet', + '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', + 'All day' => 'Egész nap', + 'Day' => 'Nap', + 'Manage timetable' => 'Időbeosztás kezelése', + 'Overtime timetable' => 'Túlóra időbeosztás', + 'Time off timetable' => 'Szabadság időbeosztás', + 'Timetable' => 'Időbeosztás', + 'Work timetable' => 'Munka időbeosztás', + 'Week timetable' => 'Heti időbeosztás', + 'Day timetable' => 'Napi időbeosztás', + 'From' => 'Feladó:', + 'To' => 'Címzett:', + 'Time slot created successfully.' => 'Időszelet sikeresen létrehozva.', + 'Unable to save this time slot.' => 'Időszelet mentése sikertelen.', + 'Time slot removed successfully.' => 'Időszelet sikeresen törölve.', + 'Unable to remove this time slot.' => 'Időszelet törlése sikertelen.', + 'Do you really want to remove this time slot?' => 'Biztos törli ezt az időszeletet?', + 'Remove time slot' => 'Időszelet törlése', + 'Add new time slot' => 'Új Időszelet', + 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ez az időbeosztás van használatban ha az "egész nap" jelölőnégyzet be van jelölve a tervezett szabadságnál és túlóránál.', + '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', + 'Source column' => 'Forrás oszlop', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/it_IT/translations.php b/sources/app/Locale/it_IT/translations.php index 525c828..8926504 100644 --- a/sources/app/Locale/it_IT/translations.php +++ b/sources/app/Locale/it_IT/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Nessuno', 'edit' => 'modificare', 'Edit' => 'Modificare', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/ja_JP/translations.php b/sources/app/Locale/ja_JP/translations.php index 90af6a0..a5f5d70 100644 --- a/sources/app/Locale/ja_JP/translations.php +++ b/sources/app/Locale/ja_JP/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'なし', 'edit' => '変更', 'Edit' => '変更', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/nl_NL/translations.php b/sources/app/Locale/nl_NL/translations.php new file mode 100644 index 0000000..e2167da --- /dev/null +++ b/sources/app/Locale/nl_NL/translations.php @@ -0,0 +1,849 @@ + '', + // 'number.thousands_separator' => '', + 'None' => 'Geen', + 'edit' => 'bewerken', + 'Edit' => 'Bewerken', + 'remove' => 'verwijderen', + 'Remove' => 'Verwijderen', + 'Update' => 'Update', + 'Yes' => 'Ja', + 'No' => 'Nee', + 'cancel' => 'annuleren', + 'or' => 'of', + 'Yellow' => 'Geel', + 'Blue' => 'Blauw', + 'Green' => 'Groen', + 'Purple' => 'Paars', + 'Red' => 'Rood', + 'Orange' => 'Oranje', + 'Grey' => 'Grijs', + 'Save' => 'Opslaan', + 'Login' => 'Inloggen', + 'Official website:' => 'Officiële website :', + 'Unassigned' => 'Niet toegewezen', + 'View this task' => 'Deze taak bekijken', + 'Remove user' => 'Gebruiker verwijderen', + 'Do you really want to remove this user: "%s"?' => 'Weet u zeker dat u deze gebruiker wil verwijderen : « %s » ?', + 'New user' => 'Nieuwe gebruiker', + 'All users' => 'Alle gebruikers', + 'Username' => 'Gebruikersnaam', + 'Password' => 'Wachtwoord', + 'Default project' => 'Standaard wachtwoord', + 'Administrator' => 'Administrator', + 'Sign in' => 'Inloggen', + 'Users' => 'Gebruikers', + 'No user' => 'Geen gebruiker', + 'Forbidden' => 'Geweigerd', + 'Access Forbidden' => 'Toegang geweigerd', + 'Only administrators can access to this page.' => 'Alleen administrators hebben toegang tot deze pagina.', + 'Edit user' => 'Gebruiker bewerken', + 'Logout' => 'Uitloggen', + 'Bad username or password' => 'Verkeerde gebruikersnaam of wachtwoord', + 'users' => 'gebruikers', + 'projects' => 'projecten', + 'Edit project' => 'Project bewerken', + 'Name' => 'Naam', + 'Activated' => 'Geactiveerd', + 'Projects' => 'Projecten', + 'No project' => 'Geen project', + 'Project' => 'Project', + 'Status' => 'Status', + 'Tasks' => 'Taken', + 'Board' => 'Bord', + 'Actions' => 'Acties', + 'Inactive' => 'Inactief', + 'Active' => 'Actief', + 'Column %d' => 'Kolom %d', + 'Add this column' => 'Deze kolom toevoegen', + '%d tasks on the board' => '%d taken op het bord', + '%d tasks in total' => '%d taken in totaal', + 'Unable to update this board.' => 'Update van dit bord niet mogelijk.', + 'Edit board' => 'Bord bewerken', + 'Disable' => 'Deactiveren', + 'Enable' => 'Activeren', + 'New project' => 'Nieuw project', + 'Do you really want to remove this project: "%s"?' => 'Weet u zeker dat u dit project wil verwijderen : « %s » ?', + 'Remove project' => 'Project verwijderen', + 'Boards' => 'Borden', + 'Edit the board for "%s"' => 'Bord bewerken voor « %s »', + 'All projects' => 'Alle projecten', + 'Change columns' => 'Kolommen veranderen', + 'Add a new column' => 'Kolom toevoegen', + 'Title' => 'Titel', + 'Add Column' => 'Kolom toevoegen', + 'Project "%s"' => 'Project « %s »', + 'Nobody assigned' => 'Niemand toegewezen', + 'Assigned to %s' => 'Toegewezen aan %s', + 'Remove a column' => 'Kolom verwijderen', + 'Remove a column from a board' => 'Kolom verwijderen van het bord', + 'Unable to remove this column.' => 'Verwijderen van deze kolom niet mogelijk.', + 'Do you really want to remove this column: "%s"?' => 'Weet u zeker dat u deze kolom wil verwijderen : « %s » ?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Deze actie zal ALLE TAKEN VERWIJDEREN die zijn geassocieerd met deze kolom!', + 'Settings' => 'Instellingen', + 'Application settings' => 'Applicatie instellingen', + 'Language' => 'Taal', + 'Webhook token:' => 'Webhook token :', + 'API token:' => 'API token :', + 'More information' => 'Meer informatie', + 'Database size:' => 'Database grootte :', + 'Download the database' => 'Download de database', + 'Optimize the database' => 'Optimaliseer de database', + '(VACUUM command)' => '(VACUUM commando)', + '(Gzip compressed Sqlite file)' => '(Gzip ingepakt Sqlite bestand)', + 'User settings' => 'Gebruikers instellingen', + 'My default project:' => 'Mijn standaard project : ', + 'Close a task' => 'Taak sluiten', + 'Do you really want to close this task: "%s"?' => 'Weet u zeker dat u deze taak wil sluiten : « %s » ?', + 'Edit a task' => 'Taak bewerken', + 'Column' => 'Kolom', + 'Color' => 'Kleur', + 'Assignee' => 'Toegewezene', + 'Create another task' => 'Nog een taak aanmaken', + 'New task' => 'Nieuwe taak', + 'Open a task' => 'Een taak openen', + 'Do you really want to open this task: "%s"?' => 'Weet u zeker dat u deze taak wil openen : « %s » ?', + 'Back to the board' => 'Terug naar het bord', + 'Created on %B %e, %Y at %k:%M %p' => 'Aangemaakt op %d/%m/%Y à %H:%M', + 'There is nobody assigned' => 'Er is niemand toegewezen', + 'Column on the board:' => 'Kolom op het bord : ', + 'Status is open' => 'Status is open', + 'Status is closed' => 'Status is gesloten', + 'Close this task' => 'Deze taak sluiten', + 'Open this task' => 'Deze taak openen', + 'There is no description.' => 'Er is geen omschrijving.', + 'Add a new task' => 'Een nieuwe taak toevoegen', + 'The username is required' => 'De gebruikersnaam is verplicht', + 'The maximum length is %d characters' => 'De maximale lengte is %d karakters', + 'The minimum length is %d characters' => 'De minimale lengte is %d karakters', + 'The password is required' => 'Het wachtwoord is verplicht', + 'This value must be an integer' => 'Deze waarde dient een integer te zijn', + 'The username must be unique' => 'De gebruikersnaam moet uniek zijn', + 'The username must be alphanumeric' => 'De gebruikersnaam moet alfanumeriek zijn', + 'The user id is required' => 'Het gebruikers id is verplicht', + 'Passwords don\'t match' => 'De wachtwoorden komen niet overeen', + 'The confirmation is required' => 'De bevestiging is verplicht', + 'The column is required' => 'De kolom is verplicht', + 'The project is required' => 'Het project is verplicht', + 'The color is required' => 'De kleur is verplicht', + 'The id is required' => 'Het id is verplicht', + 'The project id is required' => 'Het project id is verplicht', + 'The project name is required' => 'De projectnaam is verplicht', + 'This project must be unique' => 'Dit project moet uniek zijn', + 'The title is required' => 'De titel is verplicht', + 'The language is required' => 'De taal is verplicht', + 'There is no active project, the first step is to create a new project.' => 'Er is geen actief project, de eerste stap is een nieuw project aanmaken.', + 'Settings saved successfully.' => 'Instellingen succesvol opgeslagen.', + 'Unable to save your settings.' => 'Instellingen opslaan niet gelukt.', + 'Database optimization done.' => 'Database optimaliseren voltooid.', + 'Your project have been created successfully.' => 'Uw project is succesvol aangemaakt.', + 'Unable to create your project.' => 'Het aanmaken van het project is niet gelukt.', + 'Project updated successfully.' => 'Project succesvol geupdate.', + 'Unable to update this project.' => 'Updaten van project niet gelukt.', + 'Unable to remove this project.' => 'Verwijderen van project niet gelukt.', + 'Project removed successfully.' => 'Project succesvol verwijderd.', + 'Project activated successfully.' => 'Project succesvol geactiveerd.', + 'Unable to activate this project.' => 'Project activeren niet gelukt.', + 'Project disabled successfully.' => 'Project uitschakelen succesvol.', + 'Unable to disable this project.' => 'Project uitschakelen niet gelukt.', + 'Unable to open this task.' => 'Openen van deze taak niet gelukt.', + 'Task opened successfully.' => 'Taak succesvol geopend.', + 'Unable to close this task.' => 'Sluiten van deze taak niet gelukt.', + 'Task closed successfully.' => 'Taak succesvol gesloten.', + 'Unable to update your task.' => 'Updaten van uw taak mislukt.', + 'Task updated successfully.' => 'Taak succesvol geupdate.', + 'Unable to create your task.' => 'Taak aanmaken niet gelukt.', + 'Task created successfully.' => 'Taak succesvol aangemaakt.', + 'User created successfully.' => 'Gebruiker succesvol aangemaakt.', + 'Unable to create your user.' => 'Aanmaken van gebruiker niet gelukt.', + 'User updated successfully.' => 'Gebruiker succesvol geupdate', + 'Unable to update your user.' => 'Updaten van gebruiker niet gelukt.', + 'User removed successfully.' => 'Gebruiker succesvol verwijderd.', + 'Unable to remove this user.' => 'Verwijderen van gebruikers niet gelukt.', + 'Board updated successfully.' => 'Board succesvol geupdate.', + 'Ready' => 'Klaar', + 'Backlog' => 'En attente', + 'Work in progress' => 'In behandeling', + 'Done' => 'Klaar', + 'Application version:' => 'Applicatie versie :', + 'Completed on %B %e, %Y at %k:%M %p' => 'Voltooid op %d/%m/%Y à %H:%M', + '%B %e, %Y at %k:%M %p' => '%d/%m/%Y op %H:%M', + 'Date created' => 'Datum aangemaakt', + 'Date completed' => 'Datum voltooid', + 'Id' => 'Id', + 'No task' => 'Geen taak', + 'Completed tasks' => 'Voltooide taken', + 'List of projects' => 'Lijst van projecten', + 'Completed tasks for "%s"' => 'Vooltooide taken voor « %s »', + '%d closed tasks' => '%d gesloten taken', + 'No task for this project' => 'Geen taken voor dit project', + 'Public link' => 'Publieke link', + 'There is no column in your project!' => 'Er is geen kolom in uw project !', + 'Change assignee' => 'Toegewezene aanpassen', + 'Change assignee for the task "%s"' => 'Toegewezene aanpassen voor taak « %s »', + 'Timezone' => 'Tijdzone', + 'Sorry, I didn\'t find this information in my database!' => 'Sorry deze informatie kon niet worden gevonden in de database !', + 'Page not found' => 'Pagina niet gevonden', + 'Complexity' => 'Complexiteit', + 'limit' => 'Limiet', + 'Task limit' => 'Taak limiet.', + 'Task count' => 'Aantal taken', + 'This value must be greater than %d' => 'Deze waarde moet groter zijn dan %d', + 'Edit project access list' => 'Aanpassen toegangsrechten project', + 'Edit users access' => 'Gebruikerstoegang aanpassen', + 'Allow this user' => 'Deze gebruiker toestaan', + 'Only those users have access to this project:' => 'Alleen deze gebruikers hebben toegang tot dit project :', + 'Don\'t forget that administrators have access to everything.' => 'Vergeet niet dat administrators overal toegang hebben.', + 'Revoke' => 'Intrekken', + 'List of authorized users' => 'Lijst met geautoriseerde gebruikers', + 'User' => 'Gebruiker', + 'Nobody have access to this project.' => 'Niemand heeft toegang tot dit project', + 'You are not allowed to access to this project.' => 'U heeft geen toegang tot dit project.', + 'Comments' => 'Commentaar', + 'Post comment' => 'Commentaar toevoegen', + 'Write your text in Markdown' => 'Schrijf uw tekst in Markdown', + 'Leave a comment' => 'Schrijf een commentaar', + 'Comment is required' => 'Commentaar is verplicht', + 'Leave a description' => 'Schrijf een omschrijving', + 'Comment added successfully.' => 'Commentaar succesvol toegevoegd.', + 'Unable to create your comment.' => 'Commentaar toevoegen niet gelukt.', + 'The description is required' => 'Omschrijving is verplicht', + 'Edit this task' => 'Deze taak aanpassen', + 'Due Date' => 'Vervaldag', + 'Invalid date' => 'Ongeldige datum', + 'Must be done before %B %e, %Y' => 'Moet voltooid zijn voor %d/%m/%Y', + '%B %e, %Y' => '%d %B %Y', + '%b %e, %Y' => '%d/%m/%Y', + 'Automatic actions' => 'Geautomatiseerd acties', + 'Your automatic action have been created successfully.' => 'Geautomatiseerde actie succesvol aangemaakt.', + 'Unable to create your automatic action.' => 'Geautomatiseerde actie aanmaken niet gelukt.', + 'Remove an action' => 'Actie verwijderen', + 'Unable to remove this action.' => 'Actie verwijderen niet gelukt', + 'Action removed successfully.' => 'Actie succesvol verwijder.', + 'Automatic actions for the project "%s"' => 'Automatiseer acties voor project « %s »', + 'Defined actions' => 'Gedefinieerde acties', + 'Add an action' => 'Actie toevoegen', + 'Event name' => 'Naam gebeurtenis', + 'Action name' => 'Actie naam', + 'Action parameters' => 'Actie paramaters', + 'Action' => 'Actie', + 'Event' => 'Evenement', + 'When the selected event occurs execute the corresponding action.' => 'Als de geselecteerde gebeurtenis optreedt de volgende actie uitvoeren', + 'Next step' => 'Volgende stap', + 'Define action parameters' => 'Bepaal actie parameters', + 'Save this action' => 'Actie opslaan', + 'Do you really want to remove this action: "%s"?' => 'Weet u zeker dat u de volgende actie wil verwijderen : « %s » ?', + 'Remove an automatic action' => 'Automatische actie verwijderen', + 'Close the task' => 'Taak sluiten', + 'Assign the task to a specific user' => 'Taak toewijzen aan een gebruiker', + 'Assign the task to the person who does the action' => 'Taak toewijzen aan een gebruiker die de actie uitvoert', + 'Duplicate the task to another project' => 'Taak dupliceren in een ander project', + 'Move a task to another column' => 'Taak verplaatsen naar een andere kolom', + 'Move a task to another position in the same column' => 'Taak verplaatsen naar een andere positie in dezelfde kolom', + 'Task modification' => 'Taak aanpassen', + 'Task creation' => 'Taak aanmaken', + 'Open a closed task' => 'Gesloten taak openen', + 'Closing a task' => 'Taak sluiten', + 'Assign a color to a specific user' => 'Wijs een kleur toe aan een gebruiker', + 'Column title' => 'Kolom titel', + 'Position' => 'Positie', + 'Move Up' => 'Omhoog verplaatsen', + 'Move Down' => 'Omlaag verplaatsen', + 'Duplicate to another project' => 'Dupliceren in een ander project', + 'Duplicate' => 'Dupliceren', + 'link' => 'koppelen', + 'Update this comment' => 'Commentaar aanpassen', + 'Comment updated successfully.' => 'Commentaar succesvol aangepast.', + 'Unable to update your comment.' => 'Commentaar aanpassen niet gelukt.', + 'Remove a comment' => 'Commentaar verwijderen', + 'Comment removed successfully.' => 'Commentaar succesvol verwijder.', + 'Unable to remove this comment.' => 'Commentaar verwijderen niet gelukt.', + 'Do you really want to remove this comment?' => 'Weet u zeker dat u dit commentaar wil verwijderen ?', + 'Only administrators or the creator of the comment can access to this page.' => 'Alleen administrators of de aanmaker van het commentaar hebben toegang tot deze pagina.', + 'Details' => 'Details', + 'Current password for the user "%s"' => 'Huidig wachtwoord voor gebruiker « %s »', + 'The current password is required' => 'Huidig wachtwoord is verplicht', + 'Wrong password' => 'Onjuist wachtwoord', + 'Reset all tokens' => 'Alle tokens resetten', + 'All tokens have been regenerated.' => 'Alle tokens zijn opnieuw gegenereerd.', + 'Unknown' => 'Onbekend', + 'Last logins' => 'Laatste logins', + 'Login date' => 'Login datum', + 'Authentication method' => 'Authenticatie methode', + 'IP address' => 'IP adres', + 'User agent' => 'User agent', + 'Persistent connections' => 'Persistente connectie', + 'No session.' => 'Geen sessie.', + 'Expiration date' => 'Verloopdatum', + 'Remember Me' => 'Onthoud mij', + 'Creation date' => 'Aanmaakdatum', + 'Filter by user' => 'Filter op gebruiker', + 'Filter by due date' => 'Filter op vervaldatum', + 'Everybody' => 'Iedereen', + 'Open' => 'Open', + 'Closed' => 'Gesloten', + 'Search' => 'Zoek', + 'Nothing found.' => 'Niets gevonden.', + 'Search in the project "%s"' => 'Zoek in project « %s »', + 'Due date' => 'Vervaldatum', + 'Others formats accepted: %s and %s' => 'Andere toegestane formaten : %s en %s', + 'Description' => 'Omschrijving', + '%d comments' => '%d commentaren', + '%d comment' => '%d commentaar', + 'Email address invalid' => 'Ongeldig emailadres', + 'Your Google Account is not linked anymore to your profile.' => 'Uw Google Account is niet meer aan uw profiel gelinkt.', + 'Unable to unlink your Google Account.' => 'Verwijderen link met Google Account niet gelukt.', + 'Google authentication failed' => 'Google authenticatie niet gelukt', + 'Unable to link your Google Account.' => 'Linken met Google Account niet gelukt', + 'Your Google Account is linked to your profile successfully.' => 'Linken met Google Account succesvol.', + 'Email' => 'Email', + 'Link my Google Account' => 'Link mijn Google Account', + 'Unlink my Google Account' => 'Link met Google Account verwijderen', + 'Login with my Google Account' => 'Inloggen met mijn Google Account', + 'Project not found.' => 'Project niet gevonden.', + 'Task #%d' => 'Taak %d', + 'Task removed successfully.' => 'Taak succesvol verwijderd.', + 'Unable to remove this task.' => 'Taak verwijderen niet gelukt.', + 'Remove a task' => 'Taak verwijderen', + 'Do you really want to remove this task: "%s"?' => 'Weet u zeker dat u deze taak wil verwijderen « %s » ?', + 'Assign automatically a color based on a category' => 'Automatisch een kleur toewijzen aan de hand van een categorie', + 'Assign automatically a category based on a color' => 'Automatisch een categorie toewijzen aan de hand van een kleur', + 'Task creation or modification' => 'Taak aanmaken of wijzigen', + 'Category' => 'Categorie', + 'Category:' => 'Categorie :', + 'Categories' => 'Categorieën', + 'Category not found.' => 'Categorie niet gevonden', + 'Your category have been created successfully.' => 'Categorie succesvol aangemaakt.', + 'Unable to create your category.' => 'Categorie aanmaken niet gelukt.', + 'Your category have been updated successfully.' => 'Categorie succesvol aangepast.', + 'Unable to update your category.' => 'Aanpassen van categorie niet gelukt.', + 'Remove a category' => 'Categorie verwijderen', + 'Category removed successfully.' => 'Categorie succesvol verwijderd.', + 'Unable to remove this category.' => 'Categorie verwijderen niet gelukt.', + 'Category modification for the project "%s"' => 'Categorie aanpassen voor project « %s »', + 'Category Name' => 'Categorie naam', + 'Categories for the project "%s"' => 'Categorieën voor project « %s »', + 'Add a new category' => 'Categorie toevoegen', + 'Do you really want to remove this category: "%s"?' => 'Weet u zeker dat u deze categorie wil verwijderen: « %s » ?', + 'Filter by category' => 'Filter op categorie', + 'All categories' => 'Alle categorieën', + 'No category' => 'Geen categorie', + 'The name is required' => 'De naam is verplicht', + 'Remove a file' => 'Bestand verwijderen', + 'Unable to remove this file.' => 'Bestand verwijderen niet gelukt.', + 'File removed successfully.' => 'Bestand succesvol verwijdered.', + 'Attach a document' => 'Document toevoegen', + 'Do you really want to remove this file: "%s"?' => 'Weet u zeker dat u dit bestand wil verwijderen: « %s » ?', + 'open' => 'openen', + 'Attachments' => 'Bijlages', + 'Edit the task' => 'Taak aanpassen', + 'Edit the description' => 'Omschrijving aanpassen', + 'Add a comment' => 'Commentaar toevoegen', + 'Edit a comment' => 'Commentaar aanpassen', + 'Summary' => 'Samenvatting', + 'Time tracking' => 'Tijdschrijven', + 'Estimate:' => 'Schatting :', + 'Spent:' => 'Besteed :', + 'Do you really want to remove this sub-task?' => 'Weet u zeker dat u deze subtaak wil verwijderen ?', + 'Remaining:' => 'Restant :', + 'hours' => 'uren', + 'spent' => 'besteed', + 'estimated' => 'geschat', + 'Sub-Tasks' => 'Subtaken', + 'Add a sub-task' => 'Subtaak toevoegen', + 'Original estimate' => 'Orginele schatting', + 'Create another sub-task' => 'Nog een subtaak toevoegen', + 'Time spent' => 'Tijd besteed', + 'Edit a sub-task' => 'Subtaak aanpassen', + 'Remove a sub-task' => 'Subtaak verwijderen', + 'The time must be a numeric value' => 'De tijd moet een numerieke waarde zijn', + 'Todo' => 'Nog te doen', + 'In progress' => 'In behandeling', + 'Sub-task removed successfully.' => 'Subtaak succesvol verwijderd.', + 'Unable to remove this sub-task.' => 'Subtaak verwijderen niet gelukt.', + 'Sub-task updated successfully.' => 'Subtaak succesvol aangepast.', + 'Unable to update your sub-task.' => 'Subtaak aanpassen niet gelukt.', + 'Unable to create your sub-task.' => 'Subtaak aanmaken niet gelukt.', + 'Sub-task added successfully.' => 'Subtaak succesvol aangemaakt.', + 'Maximum size: ' => 'Maximale grootte : ', + 'Unable to upload the file.' => 'Uploaden van bestand niet gelukt.', + 'Display another project' => 'Een ander project weergeven', + 'Your GitHub account was successfully linked to your profile.' => 'Uw Github Account is succesvol gelinkt aan uw profiel.', + 'Unable to link your GitHub Account.' => 'Linken van uw Github Account niet gelukt.', + 'GitHub authentication failed' => 'Github Authenticatie niet gelukt', + 'Your GitHub account is no longer linked to your profile.' => 'Uw Github Account is niet langer gelinkt aan uw profiel.', + 'Unable to unlink your GitHub Account.' => 'Verwijdern van de link met uw Github Account niet gelukt.', + 'Login with my GitHub Account' => 'Login met mijn Github Account', + 'Link my GitHub Account' => 'Link met mijn Github', + 'Unlink my GitHub Account' => 'Link met mijn Github verwijderen', + 'Created by %s' => 'Aangemaakt door %s', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Laatst gewijzigd op %d/%m/%Y à %H:%M', + 'Tasks Export' => 'Taken exporteren', + 'Tasks exportation for "%s"' => 'Taken exporteren voor « %s »', + 'Start Date' => 'Startdatum', + 'End Date' => 'Einddatum', + 'Execute' => 'Uitvoeren', + 'Task Id' => 'Taak Id', + 'Creator' => 'Aangemaakt door', + 'Modification date' => 'Wijzigingsdatum', + 'Completion date' => 'Afgerond op', + 'Webhook URL for task creation' => 'Webhook URL voor aanmaken taak', + 'Webhook URL for task modification' => 'Webhook URL voor wijzigen taak', + 'Clone' => 'Kloon', + 'Clone Project' => 'Project klonen', + 'Project cloned successfully.' => 'Project succesvol gekloond.', + 'Unable to clone this project.' => 'Klonen van project niet gelukt.', + 'Email notifications' => 'Email notificatie', + 'Enable email notifications' => 'Email notificatie aanzetten', + 'Task position:' => 'Taak positie :', + 'The task #%d have been opened.' => 'Taak #%d is geopend.', + 'The task #%d have been closed.' => 'Taak #%d is gesloten.', + 'Sub-task updated' => 'Subtaak aangepast', + 'Title:' => 'Titel :', + 'Status:' => 'Status :', + 'Assignee:' => 'Toegewezene :', + 'Time tracking:' => 'Tijdschrijven :', + 'New sub-task' => 'Nieuwe subtaak', + 'New attachment added "%s"' => 'Nieuwe bijlage toegevoegd « %s »', + 'Comment updated' => 'Commentaar aangepast', + 'New comment posted by %s' => 'Nieuw commentaar geplaatst door « %s »', + 'List of due tasks for the project "%s"' => 'Lijst van taken die binnenkort voltooid moeten worden voor project « %s »', + 'New attachment' => 'Nieuwe bijlage', + 'New comment' => 'Nieuw commentaar', + 'New subtask' => 'Nieuwe subtaak', + 'Subtask updated' => 'Subtaak aangepast', + 'Task updated' => 'Taak aangepast', + 'Task closed' => 'Taak gesloten', + 'Task opened' => 'Taak geopend', + '[%s][Due tasks]' => '[%s][binnekort te voltooien taken]', + '[Kanboard] Notification' => '[Kanboard] Notificatie', + 'I want to receive notifications only for those projects:' => 'Ik wil notificaties ontvangen van de volgende projecten :', + 'view the task on Kanboard' => 'taak bekijken op Kanboard', + 'Public access' => 'Publieke toegang', + 'Category management' => 'Categorie management', + 'User management' => 'Gebruikers management', + 'Active tasks' => 'Actieve taken', + 'Disable public access' => 'Publieke toegang uitschakelen', + 'Enable public access' => 'Publieke toegang inschakelen', + 'Active projects' => 'Actieve projecten', + 'Inactive projects' => 'Inactieve projecten', + 'Public access disabled' => 'Publieke toegang uitgeschakeld', + 'Do you really want to disable this project: "%s"?' => 'Weet u zeker dat u dit project wil uitschakelen : « %s » ?', + 'Do you really want to duplicate this project: "%s"?' => 'Weet u zeker dat u dit project wil dupliceren : « %s » ?', + 'Do you really want to enable this project: "%s"?' => 'Weet u zeker dat u dit project wil activeren : « %s » ?', + 'Project activation' => 'Project activatie', + 'Move the task to another project' => 'Taak verplaatsen naar een ander project', + 'Move to another project' => 'Verplaats naar een ander project', + 'Do you really want to duplicate this task?' => 'Weet u zeker dat u deze taak wil dupliceren ?', + 'Duplicate a task' => 'Taak dupliceren', + 'External accounts' => 'Externe accounts', + 'Account type' => 'Account type', + 'Local' => 'Lokaal', + 'Remote' => 'Remote', + 'Enabled' => 'Actief', + 'Disabled' => 'Inactief', + 'Google account linked' => 'Gelinkt Google Account', + 'Github account linked' => 'Gelinkt Github Account', + 'Username:' => 'Gebruikersnaam :', + 'Name:' => 'Naam :', + 'Email:' => 'Email :', + 'Default project:' => 'Standaard project :', + 'Notifications:' => 'Notificaties :', + 'Notifications' => 'Notificaties', + 'Group:' => 'Groep :', + 'Regular user' => 'Normale gebruiker', + 'Account type:' => 'Account type:', + 'Edit profile' => 'Profiel aanpassen', + 'Change password' => 'Wachtwoord aanpassen', + 'Password modification' => 'Wachtwoord aanpassen', + 'External authentications' => 'Externe authenticatie', + 'Google Account' => 'Google Account', + 'Github Account' => 'Github Account', + 'Never connected.' => 'Nooit verbonden.', + 'No account linked.' => 'Geen account gelinkt.', + 'Account linked.' => 'Account gelinkt.', + 'No external authentication enabled.' => 'Geen externe authenticatie aangezet.', + 'Password modified successfully.' => 'Wachtwoord succesvol aangepast.', + 'Unable to change the password.' => 'Aanpassen van wachtwoord niet gelukt.', + 'Change category for the task "%s"' => 'Pas categorie aan voor taak « %s »', + 'Change category' => 'Categorie aanpassen', + '%s updated the task %s' => '%s heeft taak %s aangepast', + '%s opened the task %s' => '%s heeft taak %s geopend', + '%s moved the task %s to the position #%d in the column "%s"' => '%s heeft taak %s naar positie %d in de kolom « %s » verplaatst', + '%s moved the task %s to the column "%s"' => '%s heeft taak %s verplaatst naar kolom « %s »', + '%s created the task %s' => '%s heeft taak %s aangemaakt', + '%s closed the task %s' => '%s heeft taak %s gesloten', + '%s created a subtask for the task %s' => '%s heeft een subtaak aangemaakt voor taak %s', + '%s updated a subtask for the task %s' => '%s heeft een subtaak aangepast voor taak %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Toegewezen aan %s met een schatting van %s/%sh', + 'Not assigned, estimate of %sh' => 'Niet toegewezen, schatting: %sh', + '%s updated a comment on the task %s' => '%s heeft een commentaar aangepast voor taak %s', + '%s commented the task %s' => '%s heeft een commentaar geplaatst voor taak %s', + '%s\'s activity' => 'Activiteiten van %s', + 'No activity.' => 'Geen activiteiten.', + 'RSS feed' => 'RSS feed', + '%s updated a comment on the task #%d' => '%s heeft een commentaar aangepast voor taak %d', + '%s commented on the task #%d' => '%s heeft commentaar geplaatst voor taak %d', + '%s updated a subtask for the task #%d' => '%s heeft een commentaar aangepast voor subtaak %d', + '%s created a subtask for the task #%d' => '%s heeft een subtaak aangemaakt voor taak %d', + '%s updated the task #%d' => '%s heeft taak %d aangepast', + '%s created the task #%d' => '%s heeft taak %d aangemaakt', + '%s closed the task #%d' => '%s heeft taak %d gesloten', + '%s open the task #%d' => '%s a heeft taak %d geopend', + '%s moved the task #%d to the column "%s"' => '%s heeft taak %d verplaatst naar kolom « %s »', + '%s moved the task #%d to the position %d in the column "%s"' => '%s heeft taak %d verplaatst naar positie %d in kolom « %s »', + 'Activity' => 'Activiteit', + 'Default values are "%s"' => 'Standaardwaarden zijn « %s »', + 'Default columns for new projects (Comma-separated)' => 'Standaard kolommen voor nieuw projecten (komma gescheiden)', + 'Task assignee change' => 'Taak toegewezene verandering', + '%s change the assignee of the task #%d to %s' => '%s heeft de toegewezene voor taak %d veranderd in %s', + '%s changed the assignee of the task %s to %s' => '%s heeft de toegewezene voor taak %d veranderd in %s', + 'Column Change' => 'Kolom verandering', + 'Position Change' => 'Positie verandering', + 'Assignee Change' => 'Toegewezene verandering', + 'New password for the user "%s"' => 'Nieuw wachtwoord voor gebruiker « %s »', + 'Choose an event' => 'Kies een gebeurtenis', + 'Github commit received' => 'Github commentaar ontvangen', + 'Github issue opened' => 'Github issue geopend', + 'Github issue closed' => 'Github issue gesloten', + 'Github issue reopened' => 'Github issue heropend', + 'Github issue assignee change' => 'Github toegewezen veranderd', + 'Github issue label change' => 'Github issue label verander', + 'Create a task from an external provider' => 'Maak een taak aan vanuit een externe provider', + 'Change the assignee based on an external username' => 'Verander de toegewezene aan de hand van de externe gebruikersnaam', + 'Change the category based on an external label' => 'Verander de categorie aan de hand van een extern label', + 'Reference' => 'Referentie', + 'Reference: %s' => 'Referentie : %s', + 'Label' => 'Label', + 'Database' => 'Database', + 'About' => 'Over', + 'Database driver:' => 'Database driver :', + 'Board settings' => 'Bord instellingen', + 'URL and token' => 'URL en token', + 'Webhook settings' => 'Webhook instellingen', + 'URL for task creation:' => 'URL voor aanmaken taken :', + 'Reset token' => 'Token resetten', + 'API endpoint:' => 'API endpoint :', + 'Refresh interval for private board' => 'Verversingsinterval voor private borden', + 'Refresh interval for public board' => 'Verversingsinterval voor publieke borden', + 'Task highlight period' => 'Taak highlight periode', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (in seconden) om aan te geven of een taak recent is aangepast (0 om uit te schakelen, standaard 2 dagen)', + 'Frequency in second (60 seconds by default)' => 'Frequentie in seconden (stadaard 60)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequentie in seconden (0 om uit te schakelen, standaard 10)', + 'Application URL' => 'Applicatie URL', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Voorbeeld: http://example.kanboard.net/ (gebruikt voor email notificaties)', + 'Token regenerated.' => 'Token opnieuw gegenereerd.', + 'Date format' => 'Datum formaat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formaat is altijd geaccepteerd, bijvoorbeeld : « %s » et « %s »', + 'New private project' => 'Nieuw privé project', + 'This project is private' => 'Dit project is privé', + 'Type here to create a new sub-task' => 'Typ hier om een nieuwe subtaak aan te maken', + 'Add' => 'Toevoegen', + 'Estimated time: %s hours' => 'Geschatte tijd: %s hours', + 'Time spent: %s hours' => 'Tijd besteed : %s heures', + 'Started on %B %e, %Y' => 'Gestart op %d/%m/%Y', + 'Start date' => 'Startdatum', + 'Time estimated' => 'Geschatte tijd', + 'There is nothing assigned to you.' => 'Er is niets aan u toegewezen.', + 'My tasks' => 'Mijn taken', + 'Activity stream' => 'Activiteiten', + 'Dashboard' => 'Dashboard', + // 'Confirmation' => '', + 'Allow everybody to access to this project' => 'Geef iedereen toegang tot dit project', + 'Everybody have access to this project.' => 'Iedereen heeft toegang tot dit project.', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Integration' => 'Integratue', + 'Github webhooks' => 'Github webhooks', + 'Help on Github webhooks' => 'Hulp bij Github webhooks', + 'Create a comment from an external provider' => 'Voeg een commentaar toe van een externe provider', + 'Github issue comment created' => 'Github issue commentaar aangemaakt', + 'Configure' => 'Configureren', + 'Project management' => 'Project management', + 'My projects' => 'Mijn projecten', + 'Columns' => 'Kolommen', + 'Task' => 'Taak', + 'Your are not member of any project.' => 'U bent van geen enkel project lid.', + 'Percentage' => 'Percentage', + 'Number of tasks' => 'Aantal taken', + 'Task distribution' => 'Distributie van taken', + 'Reportings' => 'Rapporten', + 'Task repartition for "%s"' => 'Taakverdeling voor « %s »', + 'Analytics' => 'Analytics', + 'Subtask' => 'Subtaak', + 'My subtasks' => 'Mijn subtaken', + 'User repartition' => 'Gebruikerverdeling', + 'User repartition for "%s"' => 'Gebruikerverdeling voor « %s »', + 'Clone this project' => 'Kloon dit project', + 'Column removed successfully.' => 'Kolom succesvol verwijderd.', + 'Edit Project' => 'Project aanpassen', + 'Github Issue' => 'Github issue', + 'Not enough data to show the graph.' => 'Niet genoeg data om de grafiek te laten zien.', + // 'Previous' => '', + 'The id must be an integer' => 'Het id moet een integer zijn', + 'The project id must be an integer' => 'Het project id moet een integer zijn', + 'The status must be an integer' => 'De status moet een integer zijn', + 'The subtask id is required' => 'Het id van de subtaak is verplicht', + 'The subtask id must be an integer' => 'Het id van de subtaak moet een integer zijn', + 'The task id is required' => 'Het id van de taak is verplicht', + 'The task id must be an integer' => 'Het id van de taak moet een integer zijn', + 'The user id must be an integer' => 'Het id van de gebruiker moet een integer zijn', + 'This value is required' => 'Deze waarde is verplicht', + 'This value must be numeric' => 'Deze waarde moet numeriek zijn', + 'Unable to create this task.' => 'Aanmaken van de taak mislukt', + 'Cumulative flow diagram' => 'Cummulatief stroomdiagram', + 'Cumulative flow diagram for "%s"' => 'Cummulatief stroomdiagram voor « %s »', + 'Daily project summary' => 'Dagelijkse project samenvatting', + 'Daily project summary export' => 'Dagelijkse project samenvatting export', + 'Daily project summary export for "%s"' => 'Dagelijkse project samenvatting voor « %s »', + 'Exports' => 'Exports', + 'This export contains the number of tasks per column grouped per day.' => 'Dit rapport bevat het aantal taken per kolom gegroupeerd per dag.', + 'Nothing to preview...' => 'Niets om te previewen...', + 'Preview' => 'Preview', + 'Write' => 'Schrijf', + 'Active swimlanes' => 'Actieve swinlanes', + 'Add a new swimlane' => 'Nieuwe swimlane toevoegen', + 'Change default swimlane' => 'Standaard swimlane aapassen', + 'Default swimlane' => 'Standaard swinlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Weet u zeker dat u deze swimlane wil verwijderen : « %s » ?', + 'Inactive swimlanes' => 'Inactieve swinlanes', + 'Set project manager' => 'Project manager instellen', + 'Set project member' => 'Project lid instellen', + 'Remove a swimlane' => 'Verwijder swinlane', + 'Rename' => 'Hernoemen', + 'Show default swimlane' => 'Standaard swimlane tonen', + 'Swimlane modification for the project "%s"' => 'Swinlane aanpassing voor project « %s »', + 'Swimlane not found.' => 'Swimlane niet gevonden.', + 'Swimlane removed successfully.' => 'Swimlane succesvol verwijderd.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane succesvol aangepast.', + 'The default swimlane have been updated successfully.' => 'De standaard swimlane is succesvol aangepast.', + 'Unable to create your swimlane.' => 'Swimlane aanmaken niet gelukt.', + 'Unable to remove this swimlane.' => 'Swimlane verwijderen niet gelukt.', + 'Unable to update this swimlane.' => 'Swimlane aanpassen niet gelukt.', + 'Your swimlane have been created successfully.' => 'Swimlane succesvol aangemaakt.', + 'Example: "Bug, Feature Request, Improvement"' => 'Voorbeeld: « Bug, Feature Request, Improvement »', + 'Default categories for new projects (Comma-separated)' => 'Standaard categorieën voor nieuwe projecten (komma gescheiden)', + 'Gitlab commit received' => 'Gitlab commir ontvangen', + 'Gitlab issue opened' => 'Gitlab issue geopend', + 'Gitlab issue closed' => 'Gitlab issue gesloten', + 'Gitlab webhooks' => 'Gitlab webhooks', + 'Help on Gitlab webhooks' => 'Hulp bij Gitlab webhooks', + 'Integrations' => 'Integraties', + 'Integration with third-party services' => 'Integratie met derde-partij-services', + 'Role for this project' => 'Rol voor dit project', + 'Project manager' => 'Project manager', + 'Project member' => 'Project lid', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Een project manager kan de instellingen van het project wijzigen en heeft meer rechten dan een normale gebruiker.', + 'Gitlab Issue' => 'Gitlab issue', + 'Subtask Id' => 'Subtaak id', + 'Subtasks' => 'Subtaken', + 'Subtasks Export' => 'Subtaken exporteren', + 'Subtasks exportation for "%s"' => 'Subtaken exporteren voor project « %s »', + 'Task Title' => 'Taak title', + 'Untitled' => 'Geen titel', + 'Application default' => 'Standaard taal voor applicatie', + 'Language:' => 'Taal :', + 'Timezone:' => 'Tijdzone :', + 'All columns' => 'Alle kolommen', + 'Calendar for "%s"' => 'Agenda voor « %s »', + 'Filter by column' => 'Filter op kolom', + 'Filter by status' => 'Filter op status', + 'Calendar' => 'Agenda', + 'Next' => 'Volgende', + '#%d' => '%d', + 'Filter by color' => 'Filter op kleur', + 'Filter by swimlane' => 'Filter op swimlane', + 'All swimlanes' => 'Alle swimlanes', + 'All colors' => 'Alle kleuren', + 'All status' => 'Alle statussen', + 'Add a comment logging moving the task between columns' => 'Voeg een commentaar toe bij het verplaatsen van een taak tussen kolommen', + 'Moved to column %s' => 'Verplaatst naar kolom', + 'Change description' => 'Verandering omschrijving', + 'User dashboard' => 'Gebruiker dashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Sta maximaal één subtaak in behandeling toe per gebruiker', + 'Edit column "%s"' => 'Kolom « %s » aanpassen', + 'Enable time tracking for subtasks' => 'Activeer tijdschrijven voor subtaken', + 'Select the new status of the subtask: "%s"' => 'Selecteer nieuwe status voor subtaak : « %s »', + 'Subtask timesheet' => 'Subtaak timesheet', + 'There is nothing to show.' => 'Er is niets om te laten zijn.', + 'Time Tracking' => 'Tijdschrijven', + 'You already have one subtask in progress' => 'U heeft al een subtaak in behandeling', + 'Which parts of the project do you want to duplicate?' => 'Welke onderdelen van het project wilt u dupliceren?', + 'Change dashboard view' => 'Pas dashboard aan', + 'Show/hide activities' => 'Toon/verberg activiteiten', + 'Show/hide projects' => 'Toon/verberg projecten', + 'Show/hide subtasks' => 'Toon/verberg subtaken', + 'Show/hide tasks' => 'Toon/verberg taken', + 'Disable login form' => 'Schakel login scherm uit', + 'Show/hide calendar' => 'Toon/verberg agenda', + 'User calendar' => 'Agenda gebruiker', + 'Bitbucket commit received' => 'Bitbucket commit ontvangen', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Help bij Bitbucket webhooks', + 'Start' => 'Start', + 'End' => 'Eind', + 'Task age in days' => 'Leeftijd taak in dagen', + 'Days in this column' => 'Dagen in deze kolom', + '%dd' => '%dj', + 'Add a link' => 'Link toevoegen', + 'Add a new link' => 'Nieuwe link toevoegen', + 'Do you really want to remove this link: "%s"?' => 'Weet u zeker dat u deze link wil verwijderen : « %s » ?', + 'Do you really want to remove this link with task #%d?' => 'Weet u zeker dat u deze link met taak %d wil verwijderen?', + 'Field required' => 'Veld verplicht', + 'Link added successfully.' => 'Link succesvol toegevoegd.', + 'Link updated successfully.' => 'Link succesvol aangepast.', + 'Link removed successfully.' => 'Link succesvol verwijderd.', + 'Link labels' => 'Link labels', + 'Link modification' => 'Link aanpassing', + 'Links' => 'Links', + 'Link settings' => 'Link instellingen', + 'Opposite label' => 'Tegenovergesteld label', + 'Remove a link' => 'Link verwijderen', + 'Task\'s links' => 'Links van taak', + 'The labels must be different' => 'De labels moeten verschillend zijn', + 'There is no link.' => 'Er is geen link.', + 'This label must be unique' => 'Dit label moet uniek zijn', + 'Unable to create your link.' => 'Link aanmaken niet gelukt.', + 'Unable to update your link.' => 'Link aanpassen niet gelukt.', + 'Unable to remove this link.' => 'Link verwijderen niet gelukt.', + 'relates to' => 'is gerelateerd aan', + 'blocks' => 'blokkeert', + 'is blocked by' => 'is geblokkeerd door', + 'duplicates' => 'dupliceert', + 'is duplicated by' => 'is gedupliceerd', + 'is a child of' => 'is een kind van', + 'is a parent of' => 'is een ouder van', + 'targets milestone' => 'is nodig voor milestone', + 'is a milestone of' => 'is een milestone voor', + 'fixes' => 'corrigeert', + 'is fixed by' => 'word gecorrigeerd door', + 'This task' => 'Deze taal', + '<1h' => '<1h', + '%dh' => '%dh', + '%b %e' => '%e %b', + 'Expand tasks' => 'Taken uitklappen', + 'Collapse tasks' => 'Taken inklappen', + 'Expand/collapse tasks' => 'Taken in/uiklappen', + 'Close dialog box' => 'Venster sluiten', + 'Submit a form' => 'Formulier insturen', + 'Board view' => 'Bord weergave', + 'Keyboard shortcuts' => 'Keyboard snelkoppelingen', + 'Open board switcher' => 'Open bord switcher', + 'Application' => 'Applicatie', + 'Filter recently updated' => 'Filter recent aangepast', + 'since %B %e, %Y at %k:%M %p' => 'sinds %d/%m/%Y à %H:%M', + 'More filters' => 'Meer filters', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', +); diff --git a/sources/app/Locale/pl_PL/translations.php b/sources/app/Locale/pl_PL/translations.php index c6becf4..2ab71b9 100644 --- a/sources/app/Locale/pl_PL/translations.php +++ b/sources/app/Locale/pl_PL/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Brak', 'edit' => 'edytuj', 'Edit' => 'Edytuj', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/pt_BR/translations.php b/sources/app/Locale/pt_BR/translations.php index 89f7e29..4b8900a 100644 --- a/sources/app/Locale/pt_BR/translations.php +++ b/sources/app/Locale/pt_BR/translations.php @@ -1,6 +1,8 @@ ',', + 'number.thousands_separator' => ' ', 'None' => 'Nenhum', 'edit' => 'editar', 'Edit' => 'Editar', @@ -719,19 +721,129 @@ return array( 'fixes' => 'corrige', 'is fixed by' => 'foi corrigido por', 'This task' => 'Esta tarefa', - // '<1h' => '', - // '%dh' => '', - // '%b %e' => '', + '<1h' => '<1h', + '%dh' => '%dh', + '%b %e' => '%e %b', 'Expand tasks' => 'Expandir tarefas', 'Collapse tasks' => 'Contrair tarefas', 'Expand/collapse tasks' => 'Expandir/Contrair tarefas', - // 'Close dialog box' => '', + 'Close dialog box' => 'Fechar a caixa de diálogo', 'Submit a form' => 'Envia o formulário', - // 'Board view' => '', - // 'Keyboard shortcuts' => '', - // 'Open board switcher' => '', + 'Board view' => 'Página do painel', + 'Keyboard shortcuts' => 'Atalhos de teclado', + 'Open board switcher' => 'Abrir o comutador de painel', 'Application' => 'Aplicação', 'Filter recently updated' => 'Filtro recentemente atualizado', - // 'since %B %e, %Y at %k:%M %p' => '', + 'since %B %e, %Y at %k:%M %p' => 'desde o %d/%m/%Y às %H:%M', 'More filters' => 'Mais filtros', + 'Compact view' => 'Vista reduzida', + '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', + 'All day' => 'Dia inteiro', + 'Day' => 'Dia', + 'Manage timetable' => 'Gestão dos horários', + 'Overtime timetable' => 'Horas extras', + 'Time off timetable' => 'Horas de ausência', + 'Timetable' => 'Horários', + 'Work timetable' => 'Horas trabalhadas', + 'Week timetable' => 'Horário da semana', + 'Day timetable' => 'Horário de un dia', + 'From' => 'Desde', + 'To' => 'A', + 'Time slot created successfully.' => 'Intervalo de tempo criado com sucesso.', + 'Unable to save this time slot.' => 'Impossível de guardar este intervalo de tempo.', + 'Time slot removed successfully.' => 'Intervalo de tempo removido com sucesso.', + 'Unable to remove this time slot.' => 'Impossível de remover esse intervalo de tempo.', + 'Do you really want to remove this time slot?' => 'Você deseja realmente remover este intervalo de tempo?', + 'Remove time slot' => 'Remover um intervalo de tempo', + 'Add new time slot' => 'Adicionar um intervalo de tempo', + 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Esses horários são usados quando a caixa de seleção "Dia inteiro" está marcada para Horas de ausência ou Extras', + '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', + 'Source column' => 'Coluna de origem', + 'Show subtask estimates in the user calendar' => 'Mostrar o tempo estimado das subtarefas no calendário do usuário', + 'Transitions' => 'Transições', + 'Executer' => 'Executor(a)', + 'Time spent in the column' => 'Tempo gasto na coluna', + 'Task transitions' => 'Transições das tarefas', + 'Task transitions export' => 'Exportação das transições das tarefas', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Este relatório contém todos os movimentos de coluna para cada tarefa com a data, o usuário e o tempo gasto para cada transição.', + 'Currency rates' => 'Taxas de câmbio das moedas estrangeiras', + '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.', + 'Send notifications to a Slack channel' => 'Enviar as notificações em um canal Slack', + 'Webhook URL' => 'URL do webhook', + 'Help on Slack integration' => 'Ajuda na integração com o Slack', + '%s remove the assignee of the task %s' => '%s removeu a pessoa designada para a tarefa %s', + 'Send notifications to Hipchat' => 'Enviar as notificações para o Hipchat', + 'API URL' => 'URL da API', + 'Room API ID or name' => 'Nome ou ID da sala de discussão', + 'Room notification token' => 'Código de segurança da sala de discussão', + 'Help on Hipchat integration' => 'Ajuda na integração com o Hipchat', + 'Enable Gravatar images' => 'Ativar imagem Gravatar', + 'Information' => 'Informações', + 'Check two factor authentication code' => 'Verificação do código de autenticação à fator duplo', + 'The two factor authentication code is not valid.' => 'O código de autenticação à fator duplo não é válido', + 'The two factor authentication code is valid.' => 'O código de autenticação à fator duplo é válido', + 'Code' => 'Código', + 'Two factor authentication' => 'Autenticação à fator duplo', + 'Enable/disable two factor authentication' => 'Ativar/Desativar autenticação à fator duplo', + 'This QR code contains the key URI: ' => 'Este Código QR contém a chave URI:', + 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Salve esta chave secreta no seu software TOTP (por exemplo Google Authenticator ou FreeOTP).', + 'Check my code' => 'Verifique o meu código', + 'Secret key: ' => 'Chave secreta:', + 'Test your device' => 'Teste o seu dispositivo', + 'Assign a color when the task is moved to a specific column' => 'Atribuir uma cor quando a tarefa é movida em uma coluna específica', ); diff --git a/sources/app/Locale/ru_RU/translations.php b/sources/app/Locale/ru_RU/translations.php index 02a66b5..8eed913 100644 --- a/sources/app/Locale/ru_RU/translations.php +++ b/sources/app/Locale/ru_RU/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Отсутствует', 'edit' => 'изменить', 'Edit' => 'Изменить', @@ -20,7 +22,7 @@ return array( 'Grey' => 'Серый', 'Save' => 'Сохранить', 'Login' => 'Вход', - 'Official website:' => 'Официальный сайт :', + 'Official website:' => 'Официальный сайт:', 'Unassigned' => 'Не назначена', 'View this task' => 'Посмотреть задачу', 'Remove user' => 'Удалить пользователя', @@ -63,7 +65,7 @@ return array( 'Disable' => 'Деактивировать', 'Enable' => 'Активировать', 'New project' => 'Новый проект', - 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить этот проект? : « %s » ?', + 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить проект: « %s » ?', 'Remove project' => 'Удалить проект', 'Boards' => 'Доски', 'Edit the board for "%s"' => 'Изменить доску для « %s »', @@ -78,7 +80,7 @@ return array( 'Remove a column' => 'Удалить колонку', 'Remove a column from a board' => 'Удалить колонку с доски', 'Unable to remove this column.' => 'Не удалось удалить колонку.', - 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку : « %s » ?', + 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку: « %s » ?', 'This action will REMOVE ALL TASKS associated to this column!' => 'Вы УДАЛИТЕ ВСЕ ЗАДАЧИ находящиеся в этой колонке !', 'Settings' => 'Настройки', 'Application settings' => 'Настройки приложения', @@ -92,9 +94,9 @@ return array( '(VACUUM command)' => '(Команда VACUUM)', '(Gzip compressed Sqlite file)' => '(Сжать GZip файл SQLite)', 'User settings' => 'Настройки пользователя', - 'My default project:' => 'Мой проект по умолчанию : ', + 'My default project:' => 'Мой проект по умолчанию:', 'Close a task' => 'Закрыть задачу', - 'Do you really want to close this task: "%s"?' => 'Вы точно хотите закрыть задачу : « %s » ?', + 'Do you really want to close this task: "%s"?' => 'Вы точно хотите закрыть задачу: « %s » ?', 'Edit a task' => 'Изменить задачу', 'Column' => 'Колонка', 'Color' => 'Цвет', @@ -102,7 +104,7 @@ return array( 'Create another task' => 'Создать другую задачу', 'New task' => 'Новая задача', 'Open a task' => 'Открыть задачу', - 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу : « %s » ?', + 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу: « %s » ?', 'Back to the board' => 'Вернуться на доску', 'Created on %B %e, %Y at %k:%M %p' => 'Создано %d/%m/%Y в %H:%M', 'There is nobody assigned' => 'Никто не назначен', @@ -119,7 +121,7 @@ return array( 'The password is required' => 'Требуется пароль', 'This value must be an integer' => 'Это значение должно быть целым', 'The username must be unique' => 'Требуется уникальное имя пользователя', - 'The username must be alphanumeric' => 'Имя пользователя должно быть букво-цифровым', + 'The username must be alphanumeric' => 'Имя пользователя должно быть буквенно-цифровым', 'The user id is required' => 'Требуется ID пользователя', 'Passwords don\'t match' => 'Пароли не совпадают', 'The confirmation is required' => 'Требуется подтверждение', @@ -144,13 +146,13 @@ return array( 'Project removed successfully.' => 'Проект удален.', 'Project activated successfully.' => 'Проект активирован.', 'Unable to activate this project.' => 'Невозможно активировать проект.', - 'Project disabled successfully.' => 'Проект успешно выключен.', - 'Unable to disable this project.' => 'Не удалось выключить проект.', + 'Project disabled successfully.' => 'Проект успешно деактивирован.', + 'Unable to disable this project.' => 'Не удалось деактивировать проект.', 'Unable to open this task.' => 'Не удалось открыть задачу.', 'Task opened successfully.' => 'Задача открыта.', 'Unable to close this task.' => 'Не удалось закрыть задачу.', 'Task closed successfully.' => 'Задача закрыта.', - 'Unable to update your task.' => 'Не удалось обновить вашу задачу.', + 'Unable to update your task.' => 'Не удалось обновить задачу.', 'Task updated successfully.' => 'Задача обновлена.', 'Unable to create your task.' => 'Не удалось создать задачу.', 'Task created successfully.' => 'Задача создана.', @@ -160,12 +162,12 @@ return array( 'Unable to update your user.' => 'Не удалось обновить пользователя.', 'User removed successfully.' => 'Пользователь удален.', 'Unable to remove this user.' => 'Не удалось удалить пользователя.', - 'Board updated successfully.' => 'Доска обновлена.', + 'Board updated successfully.' => 'Доска успешно обновлена.', 'Ready' => 'Готовые', 'Backlog' => 'Ожидающие', 'Work in progress' => 'В процессе', 'Done' => 'Выполнена', - 'Application version:' => 'Версия приложения :', + 'Application version:' => 'Версия приложения:', 'Completed on %B %e, %Y at %k:%M %p' => 'Завершен %d/%m/%Y в %H:%M', '%B %e, %Y at %k:%M %p' => '%d/%m/%Y в %H:%M', 'Date created' => 'Дата создания', @@ -174,11 +176,11 @@ return array( 'No task' => 'Нет задачи', 'Completed tasks' => 'Завершенные задачи', 'List of projects' => 'Список проектов', - 'Completed tasks for "%s"' => 'Задачи завершенные для « %s »', + 'Completed tasks for "%s"' => 'Завершенные задачи для « %s »', '%d closed tasks' => '%d завершенных задач', - 'No task for this project' => 'нет задач для этого проекта', + 'No task for this project' => 'Нет задач для этого проекта', 'Public link' => 'Ссылка для просмотра', - 'There is no column in your project!' => 'Нет колонки в вашем проекте !', + 'There is no column in your project!' => 'Нет колонки в вашем проекте!', 'Change assignee' => 'Сменить назначенного', 'Change assignee for the task "%s"' => 'Сменить назначенного для задачи « %s »', 'Timezone' => 'Часовой пояс', @@ -192,19 +194,19 @@ return array( 'Edit project access list' => 'Изменить доступ к проекту', 'Edit users access' => 'Изменить доступ пользователей', 'Allow this user' => 'Разрешить этого пользователя', - 'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту :', - 'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ ко всему.', - 'Revoke' => 'отозвать', + 'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту:', + 'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет неограниченные права.', + 'Revoke' => 'Отозвать', 'List of authorized users' => 'Список авторизованных пользователей', 'User' => 'Пользователь', 'Nobody have access to this project.' => 'Ни у кого нет доступа к этому проекту', - 'You are not allowed to access to this project.' => 'Вам запрешен доступ к этому проекту.', + 'You are not allowed to access to this project.' => 'Вам запрещен доступ к этому проекту.', 'Comments' => 'Комментарии', 'Post comment' => 'Оставить комментарий', 'Write your text in Markdown' => 'Справка по синтаксису Markdown', 'Leave a comment' => 'Оставить комментарий 2', 'Comment is required' => 'Нужен комментарий', - 'Leave a description' => 'Оставьте описание', + 'Leave a description' => 'Напишите описание', 'Comment added successfully.' => 'Комментарий успешно добавлен.', 'Unable to create your comment.' => 'Невозможно создать комментарий.', 'The description is required' => 'Требуется описание', @@ -215,7 +217,7 @@ return array( '%B %e, %Y' => '%d/%m/%Y', // '%b %e, %Y' => '', 'Automatic actions' => 'Автоматические действия', - 'Your automatic action have been created successfully.' => 'Автоматика настроена.', + 'Your automatic action have been created successfully.' => 'Автоматика успешно настроена.', 'Unable to create your automatic action.' => 'Не удалось создать автоматизированное действие.', 'Remove an action' => 'Удалить действие', 'Unable to remove this action.' => 'Не удалось удалить действие', @@ -258,8 +260,8 @@ return array( 'Remove a comment' => 'Удалить комментарий', 'Comment removed successfully.' => 'Комментарий удален.', 'Unable to remove this comment.' => 'Не удалось удалить этот комментарий.', - 'Do you really want to remove this comment?' => 'Вы точно хотите удалить этот комментарий ?', - 'Only administrators or the creator of the comment can access to this page.' => 'Только администратор или автор комментарий могут получить доступ.', + 'Do you really want to remove this comment?' => 'Вы точно хотите удалить этот комментарий?', + 'Only administrators or the creator of the comment can access to this page.' => 'Только администратор и автор комментария имеют доступ к этой странице.', 'Details' => 'Подробности', 'Current password for the user "%s"' => 'Текущий пароль для пользователя « %s »', 'The current password is required' => 'Требуется текущий пароль', @@ -278,7 +280,7 @@ return array( 'Remember Me' => 'Запомнить меня', 'Creation date' => 'Дата создания', 'Filter by user' => 'Фильтр по пользователям', - 'Filter by due date' => 'Фильтр по сроку', + 'Filter by due date' => 'Фильтр по дате', 'Everybody' => 'Все', 'Open' => 'Открытый', 'Closed' => 'Закрытый', @@ -286,17 +288,17 @@ return array( 'Nothing found.' => 'Ничего не найдено.', 'Search in the project "%s"' => 'Искать в проекте « %s »', 'Due date' => 'Срок', - 'Others formats accepted: %s and %s' => 'Другой формат приемлем : %s и %s', + 'Others formats accepted: %s and %s' => 'Другой формат приемлем: %s и %s', 'Description' => 'Описание', '%d comments' => '%d комментариев', '%d comment' => '%d комментарий', - 'Email address invalid' => 'Adresse email invalide', + 'Email address invalid' => 'Некорректный e-mail адрес', 'Your Google Account is not linked anymore to your profile.' => 'Ваш аккаунт в Google больше не привязан к вашему профилю.', 'Unable to unlink your Google Account.' => 'Не удалось отвязать ваш профиль от Google.', 'Google authentication failed' => 'Аутентификация Google не удалась', 'Unable to link your Google Account.' => 'Не удалось привязать ваш профиль к Google.', 'Your Google Account is linked to your profile successfully.' => 'Ваш профиль успешно привязан к Google.', - 'Email' => 'Email', + 'Email' => 'E-mail', 'Link my Google Account' => 'Привязать мой профиль к Google', 'Unlink my Google Account' => 'Отвязать мой профиль от Google', 'Login with my Google Account' => 'Аутентификация через Google', @@ -307,10 +309,10 @@ return array( 'Remove a task' => 'Удалить задачу', 'Do you really want to remove this task: "%s"?' => 'Вы точно хотите удалить эту задачу « %s » ?', 'Assign automatically a color based on a category' => 'Автоматически назначать цвет по категории', - 'Assign automatically a category based on a color' => 'Автоматически назначать категорию по цвету ', + 'Assign automatically a category based on a color' => 'Автоматически назначать категорию по цвету', 'Task creation or modification' => 'Создание или изменение задачи', 'Category' => 'Категория', - 'Category:' => 'Категория :', + 'Category:' => 'Категория:', 'Categories' => 'Категории', 'Category not found.' => 'Категория не найдена', 'Your category have been created successfully.' => 'Категория создана.', @@ -342,10 +344,10 @@ return array( 'Edit a comment' => 'Изменить комментарий', 'Summary' => 'Сводка', 'Time tracking' => 'Отслеживание времени', - 'Estimate:' => 'Приблизительно :', - 'Spent:' => 'Затрачено :', - 'Do you really want to remove this sub-task?' => 'Вы точно хотите удалить подзадачу ?', - 'Remaining:' => 'Осталось :', + 'Estimate:' => 'Приблизительно:', + 'Spent:' => 'Затрачено:', + 'Do you really want to remove this sub-task?' => 'Вы точно хотите удалить подзадачу?', + 'Remaining:' => 'Осталось:', 'hours' => 'часов', 'spent' => 'затрачено', 'estimated' => 'расчетное', @@ -365,11 +367,11 @@ return array( 'Unable to update your sub-task.' => 'Не удалось обновить подзадачу.', 'Unable to create your sub-task.' => 'Не удалось создать подзадачу.', 'Sub-task added successfully.' => 'Подзадача добавлена.', - 'Maximum size: ' => 'Максимальный размер : ', + 'Maximum size: ' => 'Максимальный размер: ', 'Unable to upload the file.' => 'Не удалось загрузить файл.', 'Display another project' => 'Показать другой проект', 'Your GitHub account was successfully linked to your profile.' => 'Ваш GitHub привязан к вашему профилю.', - 'Unable to link your GitHub Account.' => 'Не удалось привязать ваш профиль к Github.', + 'Unable to link your GitHub Account.' => 'Не удалось привязать ваш профиль к GitHub.', 'GitHub authentication failed' => 'Аутентификация в GitHub не удалась', 'Your GitHub account is no longer linked to your profile.' => 'Ваш GitHub отвязан от вашего профиля.', 'Unable to unlink your GitHub Account.' => 'Не удалось отвязать ваш профиль от GitHub.', @@ -393,16 +395,16 @@ return array( 'Clone Project' => 'Клонировать проект', 'Project cloned successfully.' => 'Проект клонирован.', 'Unable to clone this project.' => 'Не удалось клонировать проект.', - 'Email notifications' => 'Уведомления по email', - 'Enable email notifications' => 'Включить уведомления по email', - 'Task position:' => 'Позиция задачи :', + 'Email notifications' => 'Уведомления по e-mail', + 'Enable email notifications' => 'Включить уведомления по e-mail', + 'Task position:' => 'Позиция задачи:', 'The task #%d have been opened.' => 'Задача #%d была открыта.', 'The task #%d have been closed.' => 'Задача #%d была закрыта.', 'Sub-task updated' => 'Подзадача обновлена', - 'Title:' => 'Название :', - 'Status:' => 'Статус :', - 'Assignee:' => 'Назначена :', - 'Time tracking:' => 'Отслеживание времени :', + 'Title:' => 'Название:', + 'Status:' => 'Статус:', + 'Assignee:' => 'Назначена:', + 'Time tracking:' => 'Отслеживание времени:', 'New sub-task' => 'Новая подзадача', 'New attachment added "%s"' => 'Добавлено вложение « %s »', 'Comment updated' => 'Комментарий обновлен', @@ -417,7 +419,7 @@ return array( // 'Task opened' => '', '[%s][Due tasks]' => '[%s][Текущие задачи]', '[Kanboard] Notification' => '[Kanboard] Оповещение', - 'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам :', + 'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам:', 'view the task on Kanboard' => 'посмотреть задачу на Kanboard', 'Public access' => 'Общий доступ', 'Category management' => 'Управление категориями', @@ -428,9 +430,9 @@ return array( 'Active projects' => 'Активные проекты', 'Inactive projects' => 'Неактивные проекты', 'Public access disabled' => 'Общий доступ отключен', - 'Do you really want to disable this project: "%s"?' => 'Вы точно хотите отключить проект: "%s"?', + 'Do you really want to disable this project: "%s"?' => 'Вы точно хотите деактивировать проект: "%s"?', 'Do you really want to duplicate this project: "%s"?' => 'Вы точно хотите клонировать проект: "%s"?', - 'Do you really want to enable this project: "%s"?' => 'Вы точно хотите включить проект: "%s"?', + 'Do you really want to enable this project: "%s"?' => 'Вы точно хотите активировать проект: "%s"?', 'Project activation' => 'Активация проекта', 'Move the task to another project' => 'Переместить задачу в другой проект', 'Move to another project' => 'Переместить в другой проект', @@ -446,7 +448,7 @@ return array( 'Github account linked' => 'Профиль GitHub связан', 'Username:' => 'Имя пользователя:', 'Name:' => 'Имя:', - 'Email:' => 'Email:', + 'Email:' => 'E-mail:', 'Default project:' => 'Проект по умолчанию:', 'Notifications:' => 'Уведомления:', 'Notifications' => 'Уведомления', @@ -483,7 +485,7 @@ return array( 'No activity.' => 'Нет активности', 'RSS feed' => 'RSS лента', '%s updated a comment on the task #%d' => '%s обновил комментарий задачи #%d', - '%s commented on the task #%d' => '%s откомментировал задачу #%d', + '%s commented on the task #%d' => '%s прокомментировал задачу #%d', '%s updated a subtask for the task #%d' => '%s обновил подзадачу задачи #%d', '%s created a subtask for the task #%d' => '%s создал подзадачу для задачи #%d', '%s updated the task #%d' => '%s обновил задачу #%d', @@ -501,14 +503,14 @@ return array( // 'Column Change' => '', // 'Position Change' => '', // 'Assignee Change' => '', - 'New password for the user "%s"' => 'Новый пароль для пользователя %s"', + 'New password for the user "%s"' => 'Новый пароль для пользователя "%s"', 'Choose an event' => 'Выберите событие', - 'Github commit received' => 'Github: коммит получен', - 'Github issue opened' => 'Github: новая проблема', - 'Github issue closed' => 'Github: проблема закрыта', - 'Github issue reopened' => 'Github: проблема переоткрыта', - 'Github issue assignee change' => 'Github: сменить ответственного за проблему', - 'Github issue label change' => 'Github: ярлык проблемы изменен', + 'Github commit received' => 'GitHub: коммит получен', + 'Github issue opened' => 'GitHub: новая проблема', + 'Github issue closed' => 'GitHub: проблема закрыта', + 'Github issue reopened' => 'GitHub: проблема переоткрыта', + 'Github issue assignee change' => 'GitHub: сменить ответственного за проблему', + 'Github issue label change' => 'GitHub: ярлык проблемы изменен', 'Create a task from an external provider' => 'Создать задачу из внешнего источника', 'Change the assignee based on an external username' => 'Изменить назначенного основываясь на внешнем имени пользователя', 'Change the category based on an external label' => 'Изменить категорию основываясь на внешнем ярлыке', @@ -560,9 +562,9 @@ return array( // 'Github issue comment created' => '', // 'Configure' => '', // 'Project management' => '', - // 'My projects' => '', - // 'Columns' => '', - // 'Task' => '', + 'My projects' => 'Мои проекты', + 'Columns' => 'Колонки', + 'Task' => 'Задача', // 'Your are not member of any project.' => '', // 'Percentage' => '', // 'Number of tasks' => '', @@ -570,8 +572,8 @@ return array( // 'Reportings' => '', // 'Task repartition for "%s"' => '', // 'Analytics' => '', - // 'Subtask' => '', - // 'My subtasks' => '', + 'Subtask' => 'Подзадача', + 'My subtasks' => 'Мои подзадачи', // 'User repartition' => '', // 'User repartition for "%s"' => '', // 'Clone this project' => '', @@ -646,10 +648,10 @@ return array( // 'Language:' => '', // 'Timezone:' => '', // 'All columns' => '', - // 'Calendar for "%s"' => '', - // 'Filter by column' => '', - // 'Filter by status' => '', - // 'Calendar' => '', + 'Calendar for "%s"' => 'Календарь для "%s"', + 'Filter by column' => 'Фильтр по колонке', + 'Filter by status' => 'Фильтр по статусу', + 'Calendar' => 'Календарь', // 'Next' => '', // '#%d' => '', // 'Filter by color' => '', @@ -686,18 +688,18 @@ return array( // 'Task age in days' => '', // 'Days in this column' => '', // '%dd' => '', - // 'Add a link' => '', - // 'Add a new link' => '', - // 'Do you really want to remove this link: "%s"?' => '', - // 'Do you really want to remove this link with task #%d?' => '', - // 'Field required' => '', - // 'Link added successfully.' => '', - // 'Link updated successfully.' => '', - // 'Link removed successfully.' => '', - // 'Link labels' => '', - // 'Link modification' => '', - // 'Links' => '', - // 'Link settings' => '', + 'Add a link' => 'Добавить ссылку на другие задачи', + 'Add a new link' => 'Добавление новой ссылки', + 'Do you really want to remove this link: "%s"?' => 'Вы уверены что хотите удалить ссылку: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Вы уверены что хотите удалить ссылку вместе с задачей #%d?', + 'Field required' => 'Поле обязательно для заполнения', + 'Link added successfully.' => 'Ссылка успешно добавлена', + 'Link updated successfully.' => 'Ссылка успешно обновлена', + 'Link removed successfully.' => 'Ссылка успешно удалена', + 'Link labels' => 'Метки для ссылки', + 'Link modification' => 'Обновление ссылки', + 'Links' => 'Ссылки', + 'Link settings' => 'Настройки ссылки', // 'Opposite label' => '', // 'Remove a link' => '', // 'Task\'s links' => '', @@ -707,31 +709,141 @@ return array( // 'Unable to create your link.' => '', // 'Unable to update your link.' => '', // 'Unable to remove this link.' => '', - // 'relates to' => '', - // 'blocks' => '', - // 'is blocked by' => '', - // 'duplicates' => '', - // 'is duplicated by' => '', - // 'is a child of' => '', - // 'is a parent of' => '', - // 'targets milestone' => '', - // 'is a milestone of' => '', - // 'fixes' => '', - // 'is fixed by' => '', - // 'This task' => '', + 'relates to' => 'связана с', + 'blocks' => 'блокирует', + 'is blocked by' => 'заблокирована в', + 'duplicates' => 'дублирует', + 'is duplicated by' => 'дублирована в', + 'is a child of' => 'наследник', + 'is a parent of' => 'родитель', + 'targets milestone' => 'часть этапа', + 'is a milestone of' => '', + 'fixes' => 'исправляет', + 'is fixed by' => 'исправлено в', + 'This task' => 'Эта задача', // '<1h' => '', // '%dh' => '', // '%b %e' => '', - // 'Expand tasks' => '', - // 'Collapse tasks' => '', - // 'Expand/collapse tasks' => '', - // 'Close dialog box' => '', - // 'Submit a form' => '', - // 'Board view' => '', - // 'Keyboard shortcuts' => '', - // 'Open board switcher' => '', - // 'Application' => '', - // 'Filter recently updated' => '', + 'Expand tasks' => 'Развернуть задачи', + 'Collapse tasks' => 'Свернуть задачи', + 'Expand/collapse tasks' => 'Развернуть/свернуть задачи', + 'Close dialog box' => 'Закрыть диалог', + 'Submit a form' => 'Отправить форму', + 'Board view' => 'Просмотр доски', + 'Keyboard shortcuts' => 'Горячие клавиши', + 'Open board switcher' => 'Открыть переключатель доски', + 'Application' => 'Приложение', + 'Filter recently updated' => 'Сортировать по дате обновления', // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + 'More filters' => 'Использовать фильтры', + 'Compact view' => 'Компактный вид', + '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/sr_Latn_RS/translations.php b/sources/app/Locale/sr_Latn_RS/translations.php new file mode 100644 index 0000000..8df5b9f --- /dev/null +++ b/sources/app/Locale/sr_Latn_RS/translations.php @@ -0,0 +1,849 @@ + '', + // 'number.thousands_separator' => '', + 'None' => 'None', + 'edit' => 'izmeni', + 'Edit' => 'Izmeni', + 'remove' => 'ukloni', + 'Remove' => 'Ukloni', + 'Update' => 'Ažuriraj', + 'Yes' => 'Da', + 'No' => 'Ne', + 'cancel' => 'odustani', + 'or' => 'ili', + 'Yellow' => 'Žuta', + 'Blue' => 'Plava', + 'Green' => 'Zelena', + 'Purple' => 'Ljubičasta', + 'Red' => 'Crvena', + 'Orange' => 'Narandžasta', + 'Grey' => 'Siva', + 'Save' => 'Snimi', + 'Login' => 'Prijava', + 'Official website:' => 'Zvanična strana:', + 'Unassigned' => 'Nedodeljen', + 'View this task' => 'Pregledaj zadatak', + 'Remove user' => 'Ukloni korisnika', + 'Do you really want to remove this user: "%s"?' => 'Da li zaista želiš da ukloniš korisnika: "%s"?', + 'New user' => 'novi korisnik', + 'All users' => 'Svi korisnici', + 'Username' => 'Korisnik', + 'Password' => 'Lozinka', + 'Default project' => 'Podrazumevani projekat', + 'Administrator' => 'Administrator', + 'Sign in' => 'Odjava', + 'Users' => 'Korisnik', + 'No user' => 'Ne', + 'Forbidden' => 'Zabranjeno', + 'Access Forbidden' => 'Zabranjen prostup', + 'Only administrators can access to this page.' => 'Samo administrator može videti ovu stranu.', + 'Edit user' => 'Izmeni korisnika', + 'Logout' => 'Odjava', + 'Bad username or password' => 'Loše korisničko ime ili lozinka', + 'users' => 'korisnici', + 'projects' => 'projekti', + 'Edit project' => 'Izmeni projekat', + 'Name' => 'Ime', + 'Activated' => 'Aktiviran', + 'Projects' => 'Projekti', + 'No project' => 'Bez projekta', + 'Project' => 'Projekat', + 'Status' => 'Status', + 'Tasks' => 'Zadatak', + 'Board' => 'Tabla', + 'Actions' => 'Akcje', + 'Inactive' => 'Neaktivan', + 'Active' => 'Aktivan', + 'Column %d' => 'Kolona %d', + 'Add this column' => 'Dodaj kolonu', + '%d tasks on the board' => '%d zadataka na tabli', + '%d tasks in total' => '%d zadataka ukupno', + 'Unable to update this board.' => 'Nemogu da ažuriram ovu tablu.', + 'Edit board' => 'Izmeni tablu', + 'Disable' => 'Onemogući', + 'Enable' => 'Omogući', + 'New project' => 'Novi projekat', + 'Do you really want to remove this project: "%s"?' => 'Da li želiš da ukloniš projekat: "%s"?', + 'Remove project' => 'Ukloni projekat', + 'Boards' => 'Table', + 'Edit the board for "%s"' => 'Izmeni tablu za "%s"', + 'All projects' => 'Svi projekti', + 'Change columns' => 'Zameni kolonu', + 'Add a new column' => 'Dodaj novu kolonu', + 'Title' => 'Naslov', + 'Add Column' => 'Dodaj kolunu', + 'Project "%s"' => 'Projekt "%s"', + 'Nobody assigned' => 'Niko nije dodeljen', + 'Assigned to %s' => 'Dodeljen korisniku %s', + 'Remove a column' => 'Ukloni kolonu', + 'Remove a column from a board' => 'Ukloni kolonu sa table', + 'Unable to remove this column.' => 'Nemoguće uklanjanje kolone.', + 'Do you really want to remove this column: "%s"?' => 'Da li zaista želiš da ukoniš ovu kolonu: "%s"?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Ova akcija BRIŠE SVE ZADATKE vezane za ovu kolonu!', + 'Settings' => 'Podešavanja', + 'Application settings' => 'Podešavanja aplikacije', + 'Language' => 'Jezik', + 'Webhook token:' => 'Token :', + 'API token:' => 'Token za API', + 'More information' => 'Još informacja', + 'Database size:' => 'Veličina baze :', + 'Download the database' => 'Preuzmi bazu', + 'Optimize the database' => 'Optimizuj bazu', + '(VACUUM command)' => '(komanda VACUUM)', + '(Gzip compressed Sqlite file)' => '(Sqlite baza spakovana Gzip-om)', + 'User settings' => 'Korisnička podešavanja', + 'My default project:' => 'Moj podrazumevani projekat:', + 'Close a task' => 'Zatvori zadatak', + 'Do you really want to close this task: "%s"?' => 'Da li zaista želiš da zatvoriš ovaj zadatak: "%s"?', + 'Edit a task' => 'Izmeni zadatak', + 'Column' => 'Kolona', + 'Color' => 'Boja', + 'Assignee' => 'Dodeli', + 'Create another task' => 'Dodaj zadatak', + 'New task' => 'Novi zadatak', + 'Open a task' => 'Otvori zadatak', + 'Do you really want to open this task: "%s"?' => 'Da li zaista želiš da otvoriš zadatak: "%s"?', + 'Back to the board' => 'Nazad na tablu', + 'Created on %B %e, %Y at %k:%M %p' => 'Kreiran %e %B %Y o %k:%M', + 'There is nobody assigned' => 'Niko nije dodeljen!', + 'Column on the board:' => 'Kolona na tabli:', + 'Status is open' => 'Status otvoren', + 'Status is closed' => 'Status zatvoren', + 'Close this task' => 'Zatvori ovaj zadatak', + 'Open this task' => 'Otvori ovaj zadatak', + 'There is no description.' => 'Bez opisa.', + 'Add a new task' => 'Dodaj zadatak', + 'The username is required' => 'Korisničko ime je obavezno', + 'The maximum length is %d characters' => 'Maksimalna dužina je %d znakova', + 'The minimum length is %d characters' => 'Minimalna dužina je %d znakova', + 'The password is required' => 'Lozinka je obavezna', + 'This value must be an integer' => 'Mora biti ceo broj', + 'The username must be unique' => 'Korisničko ime mora biti jedinstveno', + 'The username must be alphanumeric' => 'Korisničko ime sme sadržati samo brojeve i slova', + 'The user id is required' => 'ID korisnika je obavezan', + 'Passwords don\'t match' => 'Lozinke se ne podudaraju', + 'The confirmation is required' => 'Potvrda je obavezna', + 'The column is required' => 'Kolona je obavezna', + 'The project is required' => 'Projekat je obavezan', + 'The color is required' => 'Boja je obavezna', + 'The id is required' => 'ID je obavezan', + 'The project id is required' => 'ID projekta je obavezan', + 'The project name is required' => 'Naziv projekta je obavezan', + 'This project must be unique' => 'Projekat mora biti jedinstven', + 'The title is required' => 'Naslov je obavezan', + 'The language is required' => 'Jezik je obavezan', + 'There is no active project, the first step is to create a new project.' => 'Nema aktivnih projekata. Potrebno je prvo napraviti novi projekat.', + 'Settings saved successfully.' => 'Podešavanja uspešno snimljena.', + 'Unable to save your settings.' => 'Nemoguće snimanje podešavanja.', + 'Database optimization done.' => 'Optimizacija baze je završena.', + 'Your project have been created successfully.' => 'Projekat je uspešno napravljen.', + 'Unable to create your project.' => 'Nemoguće kreiranje projekta.', + 'Project updated successfully.' => 'Projekt je uspešno ažuriran.', + 'Unable to update this project.' => 'Nemoguće ažuriranje projekta.', + 'Unable to remove this project.' => 'Nemoguće uklanjanje projekta.', + 'Project removed successfully.' => 'Projekat uspešno uklonjen.', + 'Project activated successfully.' => 'Projekt uspešno aktiviran.', + 'Unable to activate this project.' => 'Nemoguće aktiviranje projekta.', + 'Project disabled successfully.' => 'Projekat uspešno deaktiviran.', + 'Unable to disable this project.' => 'nemoguće deaktiviranje projekta.', + 'Unable to open this task.' => 'Nemoguće otvaranje zadatka.', + 'Task opened successfully.' => 'Zadatak uspešno otvoren.', + 'Unable to close this task.' => 'Nije moguće zatvaranje ovog zadatka.', + 'Task closed successfully.' => 'Zadatak uspešno zatvoren.', + 'Unable to update your task.' => 'Nije moguće ažuriranje zadatka.', + 'Task updated successfully.' => 'Zadatak uspešno ažuriran.', + 'Unable to create your task.' => 'Nije moguće kreiranje zadatka.', + 'Task created successfully.' => 'Zadatak uspešno kreiran.', + 'User created successfully.' => 'Korisnik uspešno kreiran', + 'Unable to create your user.' => 'Nije uspelo kreiranje korisnika.', + 'User updated successfully.' => 'Korisnik uspešno ažuriran.', + 'Unable to update your user.' => 'Nije moguće ažuriranje korisnika.', + 'User removed successfully.' => 'Korisnik uspešno uklonjen.', + 'Unable to remove this user.' => 'Nije moguće uklanjanje korisnika.', + 'Board updated successfully.' => 'Tabla uspešno ažurirana.', + 'Ready' => 'Spreman', + 'Backlog' => 'Log', + 'Work in progress' => 'U radu', + 'Done' => 'Gotovo', + 'Application version:' => 'Verzija aplikacije:', + 'Completed on %B %e, %Y at %k:%M %p' => 'Završeno u %e %B %Y o %k:%M', + '%B %e, %Y at %k:%M %p' => '%e %B %Y o %k:%M', + 'Date created' => 'Kreiran dana', + 'Date completed' => 'Završen dana', + 'Id' => 'Id', + 'No task' => 'bez zadataka', + 'Completed tasks' => 'Zatvoreni zadaci', + 'List of projects' => 'Spisak projekata', + 'Completed tasks for "%s"' => 'zatvoreni zadaci za "%s"', + '%d closed tasks' => '%d zatvorenih zadataka', + 'No task for this project' => 'Nema dodeljenih zadataka ovom projektu', + 'Public link' => 'Javni link', + 'There is no column in your project!' => 'Nema dodeljenih kolona ovom projektu', + 'Change assignee' => 'Izmeni dodelu', + 'Change assignee for the task "%s"' => 'Izmeni dodelu za ovaj zadatak "%s"', + 'Timezone' => 'Vremenska zona', + 'Sorry, I didn\'t find this information in my database!' => 'Na žalost, nije pronađena informacija u bazi', + 'Page not found' => 'Strana nije pronađena', + 'Complexity' => 'Složenost', + 'limit' => 'ograničenje', + 'Task limit' => 'Ograničenje zadatka', + 'Task count' => 'Broj zadataka', + 'This value must be greater than %d' => 'Vrednost mora biti veća od %d', + 'Edit project access list' => 'Izmeni prava pristupa projektu', + 'Edit users access' => 'Izmeni korisnička prava', + 'Allow this user' => 'Dozvoli ovog korisnika', + 'Only those users have access to this project:' => 'Samo ovi korisnici imaju pristup projektu:', + 'Don\'t forget that administrators have access to everything.' => 'Zapamti: Administrator može pristupiti svemu!', + 'Revoke' => 'Povuci', + 'List of authorized users' => 'Spisak odobrenih korisnika', + 'User' => 'Korisnik', + 'Nobody have access to this project.' => 'Niko nema pristup ovom projektu', + 'You are not allowed to access to this project.' => 'Nije ti dozvoljen pristup ovom projektu.', + 'Comments' => 'Komentari', + 'Post comment' => 'Dodaj komentar', + 'Write your text in Markdown' => 'Pisanje teksta pomoću Markdown', + 'Leave a comment' => 'Ostavi komentar', + 'Comment is required' => 'Komentar je obavezan', + 'Leave a description' => 'Dodaj opis', + 'Comment added successfully.' => 'Komentar uspešno ostavljen', + 'Unable to create your comment.' => 'Nemoguće kreiranje komentara', + 'The description is required' => 'Opis je obavezan', + 'Edit this task' => 'Izmeni ovaj zadatak', + 'Due Date' => 'Termin', + 'Invalid date' => 'Loš datum', + 'Must be done before %B %e, %Y' => 'Termin do %e %B %Y', + '%B %e, %Y' => '%e %B %Y', + // '%b %e, %Y' => '', + 'Automatic actions' => 'Automatske akcije', + 'Your automatic action have been created successfully.' => 'Uspešno kreirana automatska akcija', + 'Unable to create your automatic action.' => 'Nemoguće kreiranje automatske akcije', + 'Remove an action' => 'Obriši akciju', + 'Unable to remove this action.' => 'Nije moguće obrisati akciju', + 'Action removed successfully.' => 'Akcija obrisana', + 'Automatic actions for the project "%s"' => 'Akcje za automatizaciju projekta "%s"', + 'Defined actions' => 'Definisane akcje', + 'Add an action' => 'dodaj akcju', + 'Event name' => 'Naziv događaja', + 'Action name' => 'Naziv akcije', + 'Action parameters' => 'Parametri akcije', + 'Action' => 'Akcija', + 'Event' => 'Događaj', + 'When the selected event occurs execute the corresponding action.' => 'Kad se događaj desi izvrši odgovarajuću akciju', + 'Next step' => 'Sledeći korak', + 'Define action parameters' => 'Definiši parametre akcije', + 'Save this action' => 'Snimi akciju', + 'Do you really want to remove this action: "%s"?' => 'Da li da obrišem akciju "%s"?', + 'Remove an automatic action' => 'Obriši automatsku akciju', + 'Close the task' => 'Zatvori zadatak', + 'Assign the task to a specific user' => 'Dodeli zadatak određenom korisniku', + 'Assign the task to the person who does the action' => 'Dodeli zadatak korisniku koji je izvršio akciju', + 'Duplicate the task to another project' => 'Kopiraj akciju u drugi projekat', + 'Move a task to another column' => 'Premesti zadatak u drugu kolonu', + 'Move a task to another position in the same column' => 'Promeni poziciju zadatka u istoj koloni', + 'Task modification' => 'Izman zadatka', + 'Task creation' => 'Kreiranje zadatka', + 'Open a closed task' => 'Otvori zatvoreni zadatak', + 'Closing a task' => 'Zatvaranja zadatka', + 'Assign a color to a specific user' => 'Dodeli boju korisniku', + 'Column title' => 'Naslov kolone', + 'Position' => 'Pozicija', + 'Move Up' => 'Podigni', + 'Move Down' => 'Spusti', + 'Duplicate to another project' => 'Kopiraj u drugi projekat', + 'Duplicate' => 'Napravi kopiju', + 'link' => 'link', + 'Update this comment' => 'Ažuriraj komentar', + 'Comment updated successfully.' => 'Komentar uspešno ažuriran.', + 'Unable to update your comment.' => 'Neuspešno ažuriranje komentara.', + 'Remove a comment' => 'Obriši komentar', + 'Comment removed successfully.' => 'Komentar je uspešno obrisan.', + 'Unable to remove this comment.' => 'Neuspešno brisanje komentara.', + 'Do you really want to remove this comment?' => 'Da li da obrišem ovaj komentar?', + 'Only administrators or the creator of the comment can access to this page.' => 'Samo administrator i kreator komentara mogu ga obrisati.', + 'Details' => 'Detalji', + 'Current password for the user "%s"' => 'Trenutna lozinka za korisnika "%s"', + 'The current password is required' => 'Trenutna lozinka je obavezna', + 'Wrong password' => 'Pogrešna lozinka', + 'Reset all tokens' => 'Resetuj tokene', + 'All tokens have been regenerated.' => 'Svi tokeni su ponovo generisani.', + 'Unknown' => 'Nepoznat', + 'Last logins' => 'Poslednja prijava', + 'Login date' => 'Datum prijave', + 'Authentication method' => 'Metod autentikacije', + 'IP address' => 'IP adresa', + 'User agent' => 'Browser', + 'Persistent connections' => 'Stalna konekcija', + 'No session.' => 'Bez sesjie', + 'Expiration date' => 'Ističe', + 'Remember Me' => 'Zapamti me', + 'Creation date' => 'Datum kreiranja', + 'Filter by user' => 'Po korisniku', + 'Filter by due date' => 'Po terminu', + 'Everybody' => 'Svi', + 'Open' => 'Otvoreni', + 'Closed' => 'Zatvoreni', + 'Search' => 'Traži', + 'Nothing found.' => 'Ništa nije pronađeno', + 'Search in the project "%s"' => 'Traži u prijektu "%s"', + 'Due date' => 'Termin', + 'Others formats accepted: %s and %s' => 'Ostali formati: %s i %s', + 'Description' => 'Opis', + '%d comments' => '%d Komentara', + '%d comment' => '%d Komentar', + 'Email address invalid' => 'Pogrešan e-mail', + 'Your Google Account is not linked anymore to your profile.' => 'Tvoj google nalog više nije povezan sa profilom', + 'Unable to unlink your Google Account.' => 'Neuspešno ukidanje veze od Google naloga', + 'Google authentication failed' => 'Neuspešna Google autentikacija', + 'Unable to link your Google Account.' => 'Neuspešno povezivanje sa Google nalogom', + 'Your Google Account is linked to your profile successfully.' => 'Vaš Google nalog je uspešno povezan sa vašim profilom', + 'Email' => 'E-mail', + 'Link my Google Account' => 'Poveži sa Google nalogom', + 'Unlink my Google Account' => 'Ukini vezu sa Google nalogom', + 'Login with my Google Account' => 'Prijavi se preko Google naloga', + 'Project not found.' => 'Projekat nije pronađen.', + 'Task #%d' => 'Zadatak #%d', + 'Task removed successfully.' => 'Zadatak uspešno uklonjen.', + 'Unable to remove this task.' => 'Nemoguće uklanjanje zadatka.', + 'Remove a task' => 'Ukloni zadatak', + 'Do you really want to remove this task: "%s"?' => 'Da li da obrišem zadatak "%s"?', + 'Assign automatically a color based on a category' => 'Automatski dodeli boju po kategoriji', + 'Assign automatically a category based on a color' => 'Automatski dodeli kategoriju po boji', + 'Task creation or modification' => 'Kreiranje ili izmena zadatka', + 'Category' => 'Kategorija', + 'Category:' => 'Kategorija:', + 'Categories' => 'Kategorije', + 'Category not found.' => 'Kategorija nije pronađena', + 'Your category have been created successfully.' => 'Uspešno kreirana kategorija.', + 'Unable to create your category.' => 'Nije moguće kreirati kategoriju.', + 'Your category have been updated successfully.' => 'Kategorija je uspešno izmenjena', + 'Unable to update your category.' => 'Nemoguće izmeniti kategoriju', + 'Remove a category' => 'Obriši kategoriju', + 'Category removed successfully.' => 'Kategorija uspešno uklonjena.', + 'Unable to remove this category.' => 'Nije moguće ukloniti kategoriju.', + 'Category modification for the project "%s"' => 'Izmena kategorije za projekat "%s"', + 'Category Name' => 'Naziv kategorije', + 'Categories for the project "%s"' => 'Kategorije u projektu', + 'Add a new category' => 'Dodaj novu kategoriju', + 'Do you really want to remove this category: "%s"?' => 'Da li zaista želiš da ukloniš kategoriju: "%s"?', + 'Filter by category' => 'Po kategoriji', + 'All categories' => 'Sve kategorije', + 'No category' => 'Bez kategorije', + 'The name is required' => 'Naziv je obavezan', + 'Remove a file' => 'Ukloni fajl', + 'Unable to remove this file.' => 'Fajl nije moguće ukloniti.', + 'File removed successfully.' => 'Uspešno uklonjen fajl.', + 'Attach a document' => 'Prikači dokument', + 'Do you really want to remove this file: "%s"?' => 'Da li da uklonim fajl: "%s"?', + 'open' => 'otvori', + 'Attachments' => 'Prilozi', + 'Edit the task' => 'Izmena Zadatka', + 'Edit the description' => 'Izmena opisa', + 'Add a comment' => 'Dodaj komentar', + 'Edit a comment' => 'Izmeni komentar', + 'Summary' => 'Pregled', + 'Time tracking' => 'Praćenje vremena', + 'Estimate:' => 'Procena:', + 'Spent:' => 'Potrošeno:', + 'Do you really want to remove this sub-task?' => 'Da li da uklonim pod-zdadatak?', + 'Remaining:' => 'Preostalo:', + 'hours' => 'sati', + 'spent' => 'potrošeno', + 'estimated' => 'procenjeno', + 'Sub-Tasks' => 'Pod-zadaci', + 'Add a sub-task' => 'Dodaj pod-zadatak', + 'Original estimate' => 'Originalna procena', + 'Create another sub-task' => 'Dodaj novi pod-zadatak', + 'Time spent' => 'Utrošeno vreme', + 'Edit a sub-task' => 'Izmeni pod-zadatak', + 'Remove a sub-task' => 'Ukloni pod-zadatak', + 'The time must be a numeric value' => 'Vreme mora biti broj', + 'Todo' => 'Za rad', + 'In progress' => 'U radu', + 'Sub-task removed successfully.' => 'Pod-zadatak uspešno uklonjen.', + 'Unable to remove this sub-task.' => 'Nie można usunąć tego pod-zadania.', + 'Sub-task updated successfully.' => 'Pod-zadatak zaktualizowane pomyślnie.', + 'Unable to update your sub-task.' => 'Nie można zaktalizować tego pod-zadania.', + 'Unable to create your sub-task.' => 'Nie można utworzyć tego pod-zadania.', + 'Sub-task added successfully.' => 'Pod-zadatak utworzone pomyślnie', + 'Maximum size: ' => 'Maksimalna veličina: ', + 'Unable to upload the file.' => 'Nije moguće snimiti fajl.', + 'Display another project' => 'Prikaži drugi projekat', + 'Your GitHub account was successfully linked to your profile.' => 'Konto Github podłączone pomyślnie.', + 'Unable to link your GitHub Account.' => 'Nie można połączyć z kontem Github.', + 'GitHub authentication failed' => 'Autentykacja Github nieudana', + 'Your GitHub account is no longer linked to your profile.' => 'Konto Github nie jest już podłączone do twojego profilu.', + 'Unable to unlink your GitHub Account.' => 'Nie można odłączyć konta Github.', + 'Login with my GitHub Account' => 'Zaloguj przy użyciu konta Github', + 'Link my GitHub Account' => 'Podłącz konto Github', + 'Unlink my GitHub Account' => 'Odłącz konto Github', + 'Created by %s' => 'Kreirao %s', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Poslednja izmena %e %B %Y o %k:%M', + 'Tasks Export' => 'Izvoz zadataka', + 'Tasks exportation for "%s"' => 'Izvoz zadataka za "%s"', + 'Start Date' => 'Početni datum', + 'End Date' => 'Krajni datum', + 'Execute' => 'Izvrši', + 'Task Id' => 'Identifikator Zadatka', + 'Creator' => 'Autor', + 'Modification date' => 'Datum izmene', + 'Completion date' => 'Datum kompletiranja', + 'Webhook URL for task creation' => 'Webhook URL zadatka za kreiranje', + 'Webhook URL for task modification' => 'Webhook URL zadatka za izmenu', + 'Clone' => 'Iskopiraj', + 'Clone Project' => 'Iskopiraj projekat', + 'Project cloned successfully.' => 'Projekat uspešno iskopiran.', + 'Unable to clone this project.' => 'Nije moguće iskopirati projekat.', + 'Email notifications' => 'Obaveštenje e-mailom', + 'Enable email notifications' => 'Omogući obaveštenja e-mailom', + 'Task position:' => 'Pozicija zadatka:', + 'The task #%d have been opened.' => 'Zadatak #%d je otvoren.', + 'The task #%d have been closed.' => 'Zadatak #$d je zatvoren.', + 'Sub-task updated' => 'Pod-zadatak izmenjen', + 'Title:' => 'Naslov:', + // 'Status:' => '', + 'Assignee:' => 'Dodeli:', + 'Time tracking:' => 'Praćenje vremena: ', + 'New sub-task' => 'Novi Pod-zadatak', + 'New attachment added "%s"' => 'Novi prilog ubačen "%s"', + 'Comment updated' => 'Komentar izmenjen', + 'New comment posted by %s' => 'Novi komentar ostavio %s', + 'List of due tasks for the project "%s"' => 'Spisak dospelih zadataka za projekat "%s"', + // 'New attachment' => '', + // 'New comment' => '', + // 'New subtask' => '', + // 'Subtask updated' => '', + // 'Task updated' => '', + 'Task closed' => 'Zadatak je zatvoren', + 'Task opened' => 'Zadatak je otvoren', + '[%s][Due tasks]' => '[%s][Dospeli zadaci]', + '[Kanboard] Notification' => '[Kanboard] Obaveštenja', + 'I want to receive notifications only for those projects:' => 'Želim obaveštenja samo za ovaj projekat:', + 'view the task on Kanboard' => 'Pregledaj zadatke', + 'Public access' => 'Javni pristup', + 'Category management' => 'Uređivanje kategorija', + 'User management' => 'Uređivanje korisnika', + 'Active tasks' => 'Aktivni zadaci', + 'Disable public access' => 'Zabrani javni pristup', + 'Enable public access' => 'Dozvoli javni pristup', + 'Active projects' => 'Aktivni projekti', + 'Inactive projects' => 'Neaktivni projekti', + 'Public access disabled' => 'Javni pristup onemogućen!', + 'Do you really want to disable this project: "%s"?' => 'Da li zaista želiš da deaktiviraš projekat: "%s"?', + 'Do you really want to duplicate this project: "%s"?' => 'Da li da napravim kopiju ovog projekta: "%s"?', + 'Do you really want to enable this project: "%s"?' => 'Da li zaista želiš da aktiviraš projekat: "%s"?', + 'Project activation' => 'Aktivacija projekta', + 'Move the task to another project' => 'Premesti zadatak u drugi projekat', + 'Move to another project' => 'Premesti u drugi projekat', + 'Do you really want to duplicate this task?' => 'Da li da napravim kopiju ovog projekta: "%s"?', + 'Duplicate a task' => 'Kopiraj zadatak', + 'External accounts' => 'Spoljni nalozi', + 'Account type' => 'Tip naloga', + 'Local' => 'Lokalno', + '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: ', + 'Default project:' => 'Osnovni projekat:', + 'Notifications:' => 'Obaveštenja: ', + 'Notifications' => 'Obaveštenja', + 'Group:' => 'Grupa:', + 'Regular user' => 'Standardni korisnik', + 'Account type:' => 'Vrsta naloga:', + 'Edit profile' => 'Izmeni profil', + 'Change password' => 'Izmeni lozinku', + 'Password modification' => 'Izmena lozinke', + 'External authentications' => 'Spoljne akcije', + 'Google Account' => 'Google nalog', + 'Github Account' => 'Github nalog', + 'Never connected.' => 'Bez konekcija.', + 'No account linked.' => 'Bez povezanih naloga.', + 'Account linked.' => 'Nalog povezan.', + 'No external authentication enabled.' => 'Bez omogućenih spoljnih autentikacija.', + 'Password modified successfully.' => 'Uspešna izmena lozinke.', + 'Unable to change the password.' => 'Nije moguće izmeniti lozinku.', + 'Change category for the task "%s"' => 'Izmeni kategoriju zadatka "%s"', + 'Change category' => 'Izmeni kategoriju', + '%s updated the task %s' => '%s izmeni zadatak %s', + '%s opened the task %s' => '%s aktivni zadaci %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s premešten zadatak %s na poziciju #%d u koloni "%s"', + '%s moved the task %s to the column "%s"' => '%s premešten zadatak %s u kolonu "%s"', + '%s created the task %s' => '%s kreirao zadatak %s', + '%s closed the task %s' => '%s zatvorio zadatak %s', + '%s created a subtask for the task %s' => '%s kreiran pod-zadatak zadatka %s', + '%s updated a subtask for the task %s' => '%s izmenjen pod-zadatak zadatka %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Dodeljen korisniku %s uz procenu vremena %s/%sh', + 'Not assigned, estimate of %sh' => 'Ne dodeljen, procenjeno vreme %sh', + '%s updated a comment on the task %s' => '%s izmenjen komentar zadatka %s', + '%s commented the task %s' => '%s komentarisao zadatak %s', + '%s\'s activity' => 'Aktivnosti %s', + 'No activity.' => 'Bez aktivnosti.', + 'RSS feed' => 'RSS kanal', + '%s updated a comment on the task #%d' => '%s izmenjen komentar zadatka #%d', + '%s commented on the task #%d' => '%s komentarisao zadatak #%d', + '%s updated a subtask for the task #%d' => '%s izmenjen pod-zadatak zadatka #%d', + '%s created a subtask for the task #%d' => '%s kreirao pod-zadatak zadatka #%d', + '%s updated the task #%d' => '%s izmenjen zadatak #%d', + '%s created the task #%d' => '%s kreirao zadatak #%d', + '%s closed the task #%d' => '%s zatvorio zadatak #%d', + '%s open the task #%d' => '%s otvorio zadatak #%d', + '%s moved the task #%d to the column "%s"' => '%s premestio zadatak #%d u kolonu "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s premestio zadatak #%d na pozycję %d w kolmnie "%s"', + 'Activity' => 'Aktivnosti', + 'Default values are "%s"' => 'Osnovne vrednosti su: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Osnovne kolone za novi projekat (Odvojeni zarezom)', + 'Task assignee change' => 'Zmień osobę odpowiedzialną', + '%s change the assignee of the task #%d to %s' => '%s zamena dodele za zadatak #%d na %s', + '%s changed the assignee of the task %s to %s' => '%s zamena dodele za zadatak %s na %s', + // 'Column Change' => '', + // 'Position Change' => '', + // 'Assignee Change' => '', + 'New password for the user "%s"' => 'Nova lozinka za korisnika "%s"', + 'Choose an event' => 'Izaberi događaj', + // 'Github commit received' => '', + // 'Github issue opened' => '', + // 'Github issue closed' => '', + // 'Github issue reopened' => '', + // 'Github issue assignee change' => '', + // 'Github issue label change' => '', + 'Create a task from an external provider' => 'Kreiraj zadatak preko posrednika', + 'Change the assignee based on an external username' => 'Zmień osobę odpowiedzialną na podstawie zewnętrznej nazwy użytkownika', + 'Change the category based on an external label' => 'Zmień kategorię na podstawie zewnętrzenj etykiety', + // 'Reference' => '', + // 'Reference: %s' => '', + 'Label' => 'Etikieta', + 'Database' => 'Baza', + 'About' => 'Informacje', + 'Database driver:' => 'Database driver:', + 'Board settings' => 'Podešavanje table', + 'URL and token' => 'URL i token', + // 'Webhook settings' => '', + 'URL for task creation:' => 'URL za kreiranje zadataka', + 'Reset token' => 'Resetuj token', + // 'API endpoint:' => '', + 'Refresh interval for private board' => 'Interval osvežavanja privatnih tabli', + 'Refresh interval for public board' => 'Interval osvežavanja javnih tabli', + 'Task highlight period' => 'Task highlight period', + // 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '', + // 'Frequency in second (60 seconds by default)' => '', + // 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '', + 'Application URL' => 'Adres URL aplikacji', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Primer: http://example.kanboard.net/ (koristi se u obaveštenjima putem mail-a)', + 'Token regenerated.' => 'Token wygenerowany ponownie.', + 'Date format' => 'Format daty', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO je uvek prihvatljiv, primer: "%s", "%s"', + 'New private project' => 'Novi privatni projekat', + 'This project is private' => 'Ovaj projekat je privatan', + 'Type here to create a new sub-task' => 'Kucaj ovde za kreiranje novog pod-zadatka', + 'Add' => 'Dodaj', + 'Estimated time: %s hours' => 'Procenjeno vreme: %s godzin', + 'Time spent: %s hours' => 'Utrošeno vreme: %s godzin', + 'Started on %B %e, %Y' => 'Započeto dana %e %B %Y', + 'Start date' => 'Datum početka', + 'Time estimated' => 'Procenjeno vreme', + 'There is nothing assigned to you.' => 'Ništa vam nije dodeljeno', + 'My tasks' => 'Moji zadaci', + 'Activity stream' => 'Spisak aktinosti', + 'Dashboard' => 'Panel', + 'Confirmation' => 'Potvrda', + 'Allow everybody to access to this project' => 'Dozvoli svima pristup projektu', + 'Everybody have access to this project.' => 'Svima je dozvoljen pristup.', + // 'Webhooks' => '', + // 'API' => '', + 'Integration' => 'Integracja', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', + // 'Create a comment from an external provider' => '', + // 'Github issue comment created' => '', + 'Configure' => 'Podesi', + 'Project management' => 'Uređivanje projekata', + 'My projects' => 'Moji projekti', + 'Columns' => 'Kolone', + 'Task' => 'Zadaci', + 'Your are not member of any project.' => 'Nisi član ni jednog projekta', + 'Percentage' => 'Procenat', + 'Number of tasks' => 'Broj zadataka', + 'Task distribution' => 'Podela zadataka', + 'Reportings' => 'Izveštaji', + 'Task repartition for "%s"' => 'Zaduženja zadataka za "%s"', + 'Analytics' => 'Analiza', + 'Subtask' => 'Pod-zadatak', + 'My subtasks' => 'Moji pod-zadaci', + 'User repartition' => 'Zaduženja korisnika', + 'User repartition for "%s"' => 'Zaduženja korisnika za "%s"', + 'Clone this project' => 'Kopiraj projekat', + 'Column removed successfully.' => 'Kolumna usunięta pomyslnie.', + 'Edit Project' => 'Izmeni projekat', + // 'Github Issue' => '', + 'Not enough data to show the graph.' => 'Nedovoljno podataka za grafikon.', + 'Previous' => 'Prethodni', + 'The id must be an integer' => 'ID musi być liczbą całkowitą', + 'The project id must be an integer' => 'ID projektu musi być liczbą całkowitą', + 'The status must be an integer' => 'Status musi być liczbą całkowitą', + 'The subtask id is required' => 'ID pod-zadatak jest wymagane', + 'The subtask id must be an integer' => 'ID pod-zadania musi być liczbą całkowitą', + 'The task id is required' => 'ID zadania jest wymagane', + 'The task id must be an integer' => 'ID zadatka mora biti broj', + 'The user id must be an integer' => 'ID korisnika mora biti broj', + 'This value is required' => 'Vrednost je obavezna', + 'This value must be numeric' => 'Vrednost mora biti broj', + 'Unable to create this task.' => 'Nije moguće kreirati zadatak.', + 'Cumulative flow diagram' => 'Zbirni dijagram toka', + 'Cumulative flow diagram for "%s"' => 'Zbirni dijagram toka za "%s"', + 'Daily project summary' => 'Zbirni pregled po danima', + 'Daily project summary export' => 'Izvoz zbirnog pregleda po danima', + 'Daily project summary export for "%s"' => 'Izvoz zbirnig pregleda po danima za "%s"', + 'Exports' => 'Izvoz', + // 'This export contains the number of tasks per column grouped per day.' => '', + 'Nothing to preview...' => 'Ništa za prikazivanje...', + 'Preview' => 'Pregled', + 'Write' => 'Piši', + 'Active swimlanes' => 'Aktivni razdelnik', + 'Add a new swimlane' => 'Dodaj razdelnik', + 'Change default swimlane' => 'Zameni osnovni razdelnik', + 'Default swimlane' => 'Osnovni razdelnik', + 'Do you really want to remove this swimlane: "%s"?' => 'Da li da uklonim razdelnik: "%s"?', + 'Inactive swimlanes' => 'Neaktivni razdelniki', + 'Set project manager' => 'Podesi menadžera projekta', + 'Set project member' => 'Podesi učesnika projekat', + 'Remove a swimlane' => 'Ukloni razdelnik', + 'Rename' => 'Preimenuj', + 'Show default swimlane' => 'Prikaži osnovni razdelnik', + 'Swimlane modification for the project "%s"' => 'Izmena razdelnika za projekat "%s"', + 'Swimlane not found.' => 'Razdelnik nije pronađen.', + 'Swimlane removed successfully.' => 'Razdelnik uspešno uklonjen.', + 'Swimlanes' => 'Razdelnici', + 'Swimlane updated successfully.' => 'Razdelnik zaktualizowany pomyślnie.', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + 'Your swimlane have been created successfully.' => 'Razdelnik je uspešno kreiran.', + 'Example: "Bug, Feature Request, Improvement"' => 'Npr: "Greška, Zahtev za izmenama, Poboljšanje"', + 'Default categories for new projects (Comma-separated)' => 'Osnovne kategorije za projekat', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + 'Integrations' => 'Integracje', + 'Integration with third-party services' => 'Integracja sa uslugama spoljnih servisa', + 'Role for this project' => 'Uloga u ovom projektu', + 'Project manager' => 'Manadžer projekta', + 'Project member' => 'Učesnik projekta', + // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '', + // 'Gitlab Issue' => '', + 'Subtask Id' => 'ID pod-zadania', + 'Subtasks' => 'Pod-zadataka', + 'Subtasks Export' => 'Eksport pod-zadań', + 'Subtasks exportation for "%s"' => 'Izvoz pod-zadań dla "%s"', + 'Task Title' => 'Naslov zadatka', + 'Untitled' => 'Bez naslova', + 'Application default' => 'Postavke aplikacje', + 'Language:' => 'Jezik:', + 'Timezone:' => 'Vremenska zona:', + 'All columns' => 'Sve kolone', + 'Calendar for "%s"' => 'Kalendar za "%s"', + 'Filter by column' => 'Po koloni', + 'Filter by status' => 'Po statusu', + 'Calendar' => 'Kalendar', + 'Next' => 'Sledeći', + // '#%d' => '', + 'Filter by color' => 'Po boji', + 'Filter by swimlane' => 'Po razdelniku', + 'All swimlanes' => 'Svi razdelniki', + 'All colors' => 'Sve boje', + 'All status' => 'Svi statusi', + 'Add a comment logging moving the task between columns' => 'Dodaj logovanje premeštanja zadataka po kolonama', + 'Moved to column %s' => 'Premešten u kolonu %s', + // 'Change description' => '', + 'User dashboard' => 'Korisnički panel', + // 'Allow only one subtask in progress at the same time for a user' => '', + // 'Edit column "%s"' => '', + // 'Enable time tracking for subtasks' => '', + // 'Select the new status of the subtask: "%s"' => '', + // 'Subtask timesheet' => '', + 'There is nothing to show.' => 'Nema podataka', + 'Time Tracking' => 'Praćenje vremena', + // 'You already have one subtask in progress' => '', + 'Which parts of the project do you want to duplicate?' => 'Koje delove projekta želite da kopirate', + // 'Change dashboard view' => '', + // 'Show/hide activities' => '', + // 'Show/hide projects' => '', + // 'Show/hide subtasks' => '', + // 'Show/hide tasks' => '', + // 'Disable login form' => '', + // 'Show/hide calendar' => '', + // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', + // 'Start' => '', + // 'End' => '', + // 'Task age in days' => '', + // 'Days in this column' => '', + // '%dd' => '', + 'Add a link' => 'Dodaj link', + // 'Add a new link' => '', + // 'Do you really want to remove this link: "%s"?' => '', + // 'Do you really want to remove this link with task #%d?' => '', + // 'Field required' => '', + // 'Link added successfully.' => '', + // 'Link updated successfully.' => '', + // 'Link removed successfully.' => '', + // 'Link labels' => '', + // 'Link modification' => '', + // 'Links' => '', + // 'Link settings' => '', + // 'Opposite label' => '', + // 'Remove a link' => '', + // 'Task\'s links' => '', + // 'The labels must be different' => '', + // 'There is no link.' => '', + // 'This label must be unique' => '', + // 'Unable to create your link.' => '', + // 'Unable to update your link.' => '', + // 'Unable to remove this link.' => '', + // 'relates to' => '', + // 'blocks' => '', + // 'is blocked by' => '', + // 'duplicates' => '', + // 'is duplicated by' => '', + // 'is a child of' => '', + // 'is a parent of' => '', + // 'targets milestone' => '', + // 'is a milestone of' => '', + // 'fixes' => '', + // 'is fixed by' => '', + // 'This task' => '', + // '<1h' => '', + // '%dh' => '', + // '%b %e' => '', + // 'Expand tasks' => '', + // 'Collapse tasks' => '', + // 'Expand/collapse tasks' => '', + // 'Close dialog box' => '', + // 'Submit a form' => '', + // 'Board view' => '', + // 'Keyboard shortcuts' => '', + // 'Open board switcher' => '', + // 'Application' => '', + // 'Filter recently updated' => '', + // 'since %B %e, %Y at %k:%M %p' => '', + // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', +); diff --git a/sources/app/Locale/sv_SE/translations.php b/sources/app/Locale/sv_SE/translations.php index 2fcc3ce..ab07d8c 100644 --- a/sources/app/Locale/sv_SE/translations.php +++ b/sources/app/Locale/sv_SE/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'Ingen', 'edit' => 'redigera', 'Edit' => 'Redigera', @@ -408,13 +410,13 @@ return array( 'Comment updated' => 'Kommentaren har uppdaterats', 'New comment posted by %s' => 'Ny kommentar postad av %s', 'List of due tasks for the project "%s"' => 'Lista med uppgifter för projektet "%s"', - // 'New attachment' => '', - // 'New comment' => '', - // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', + 'New attachment' => 'Ny bifogning', + 'New comment' => 'Ny kommentar', + 'New subtask' => 'Ny deluppgift', + 'Subtask updated' => 'Deluppgiften har uppdaterats', + 'Task updated' => 'Uppgiften har uppdaterats', + 'Task closed' => 'Uppgiften har stängts', + 'Task opened' => 'Uppgiften har öppnats', '[%s][Due tasks]' => '[%s][Förfallen uppgift]', '[Kanboard] Notification' => '[Kanboard] Notis', 'I want to receive notifications only for those projects:' => 'Jag vill endast få notiser för dessa projekt:', @@ -498,9 +500,9 @@ return array( 'Task assignee change' => 'Ändra tilldelning av uppgiften', '%s change the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s', '%s changed the assignee of the task %s to %s' => '%s byt tilldelning av uppgiften %s till %s', - // 'Column Change' => '', - // 'Position Change' => '', - // 'Assignee Change' => '', + 'Column Change' => 'Ändring av kolumn', + 'Position Change' => 'Ändring av position', + 'Assignee Change' => 'Ändring av tilldelning', 'New password for the user "%s"' => 'Nytt lösenord för användaren "%s"', 'Choose an event' => 'Välj en händelse', 'Github commit received' => 'Github-bidrag mottaget', @@ -645,93 +647,203 @@ return array( 'Application default' => 'Applikationsstandard', 'Language:' => 'Språk', 'Timezone:' => 'Tidszon', - // 'All columns' => '', - // 'Calendar for "%s"' => '', - // 'Filter by column' => '', - // 'Filter by status' => '', - // 'Calendar' => '', + 'All columns' => 'Alla kolumner', + 'Calendar for "%s"' => 'Kalender för "%s"', + 'Filter by column' => 'Filtrera på kolumn', + 'Filter by status' => 'Filtrera på status', + 'Calendar' => 'Kalender', 'Next' => 'Nästa', - // '#%d' => '', - // 'Filter by color' => '', - // 'Filter by swimlane' => '', - // 'All swimlanes' => '', - // 'All colors' => '', - // 'All status' => '', - // 'Add a comment logging moving the task between columns' => '', - // 'Moved to column %s' => '', - // 'Change description' => '', - // 'User dashboard' => '', - // 'Allow only one subtask in progress at the same time for a user' => '', - // 'Edit column "%s"' => '', - // 'Enable time tracking for subtasks' => '', - // 'Select the new status of the subtask: "%s"' => '', - // 'Subtask timesheet' => '', - // 'There is nothing to show.' => '', - // 'Time Tracking' => '', - // 'You already have one subtask in progress' => '', - // 'Which parts of the project do you want to duplicate?' => '', - // 'Change dashboard view' => '', - // 'Show/hide activities' => '', - // 'Show/hide projects' => '', - // 'Show/hide subtasks' => '', - // 'Show/hide tasks' => '', - // 'Disable login form' => '', - // 'Show/hide calendar' => '', - // 'User calendar' => '', - // 'Bitbucket commit received' => '', - // 'Bitbucket webhooks' => '', - // 'Help on Bitbucket webhooks' => '', - // 'Start' => '', - // 'End' => '', - // 'Task age in days' => '', - // 'Days in this column' => '', - // '%dd' => '', - // 'Add a link' => '', - // 'Add a new link' => '', - // 'Do you really want to remove this link: "%s"?' => '', - // 'Do you really want to remove this link with task #%d?' => '', - // 'Field required' => '', - // 'Link added successfully.' => '', - // 'Link updated successfully.' => '', - // 'Link removed successfully.' => '', - // 'Link labels' => '', - // 'Link modification' => '', - // 'Links' => '', - // 'Link settings' => '', - // 'Opposite label' => '', - // 'Remove a link' => '', - // 'Task\'s links' => '', - // 'The labels must be different' => '', - // 'There is no link.' => '', - // 'This label must be unique' => '', - // 'Unable to create your link.' => '', - // 'Unable to update your link.' => '', - // 'Unable to remove this link.' => '', - // 'relates to' => '', - // 'blocks' => '', - // 'is blocked by' => '', - // 'duplicates' => '', - // 'is duplicated by' => '', - // 'is a child of' => '', - // 'is a parent of' => '', - // 'targets milestone' => '', - // 'is a milestone of' => '', - // 'fixes' => '', - // 'is fixed by' => '', - // 'This task' => '', - // '<1h' => '', - // '%dh' => '', - // '%b %e' => '', - // 'Expand tasks' => '', - // 'Collapse tasks' => '', - // 'Expand/collapse tasks' => '', - // 'Close dialog box' => '', - // 'Submit a form' => '', - // 'Board view' => '', - // 'Keyboard shortcuts' => '', - // 'Open board switcher' => '', - // 'Application' => '', - // 'Filter recently updated' => '', - // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + '#%d' => '#%d', + 'Filter by color' => 'Filtrera på färg', + 'Filter by swimlane' => 'Filtrera på swimlane', + 'All swimlanes' => 'Alla swimlanes', + 'All colors' => 'Alla färger', + 'All status' => 'Alla status', + 'Add a comment logging moving the task between columns' => 'Lägg till en kommentar för att logga förflyttning av en uppgift mellan kolumner', + 'Moved to column %s' => 'Flyttad till kolumn %s', + 'Change description' => 'Ändra beskrivning', + 'User dashboard' => 'Användardashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Tillåt endast en deluppgift igång samtidigt för en användare', + 'Edit column "%s"' => 'Ändra kolumn "%s"', + 'Enable time tracking for subtasks' => 'Aktivera tidsbevakning för deluppgifter', + 'Select the new status of the subtask: "%s"' => 'Välj ny status för deluppgiften: "%s"', + 'Subtask timesheet' => 'Tidrapport för deluppgiften', + 'There is nothing to show.' => 'Det finns inget att visa', + 'Time Tracking' => 'Tidsbevakning', + 'You already have one subtask in progress' => 'Du har redan en deluppgift igång', + 'Which parts of the project do you want to duplicate?' => 'Vilka delar av projektet vill du duplicera?', + 'Change dashboard view' => 'Ändra dashboard vy', + 'Show/hide activities' => 'Visa/dölj aktiviteter', + 'Show/hide projects' => 'Visa/dölj projekt', + 'Show/hide subtasks' => 'Visa/dölj deluppgifter', + 'Show/hide tasks' => 'Visa/dölj uppgifter', + 'Disable login form' => 'Inaktivera loginformuläret', + 'Show/hide calendar' => 'Visa/dölj kalender', + 'User calendar' => 'Användarkalender', + 'Bitbucket commit received' => 'Bitbucket bidrag mottaget', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Hjälp för Bitbucket webhooks', + 'Start' => 'Start', + 'End' => 'Slut', + 'Task age in days' => 'Uppgiftsålder i dagar', + 'Days in this column' => 'Dagar i denna kolumn', + '%dd' => '%dd', + 'Add a link' => 'Lägg till länk', + 'Add a new link' => 'Lägg till ny länk', + 'Do you really want to remove this link: "%s"?' => 'Vill du verkligen ta bort länken: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Vill du verkligen ta bort länken till uppgiften #%d?', + 'Field required' => 'Fältet krävs', + 'Link added successfully.' => 'Länken har lagts till', + 'Link updated successfully.' => 'Länken har uppdaterats', + 'Link removed successfully.' => 'Länken har tagits bort', + 'Link labels' => 'Länketiketter', + 'Link modification' => 'Länkändring', + 'Links' => 'Länkar', + 'Link settings' => 'Länkinställningar', + 'Opposite label' => 'Motpartslänk', + 'Remove a link' => 'Ta bort en länk', + 'Task\'s links' => 'Uppgiftslänkar', + 'The labels must be different' => 'Etiketterna måste vara olika', + 'There is no link.' => 'Det finns ingen länk', + 'This label must be unique' => 'Länken måste vara unik', + 'Unable to create your link.' => 'Kunde inte skapa din länk', + 'Unable to update your link.' => 'Kunde inte uppdatera din länk', + 'Unable to remove this link.' => 'Kunde inte ta bort din länk', + 'relates to' => 'relaterar till', + 'blocks' => 'blockerar', + 'is blocked by' => 'blockeras av', + 'duplicates' => 'dupplicerar', + 'is duplicated by' => 'är duplicerad av', + 'is a child of' => 'är underliggande till', + 'is a parent of' => 'är överliggande till', + 'targets milestone' => 'milstolpemål', + 'is a milestone of' => 'är en milstolpe för', + 'fixes' => 'åtgärdar', + 'is fixed by' => 'åtgärdas av', + 'This task' => 'Denna uppgift', + '<1h' => '<1h', + '%dh' => '%dh', + '%b %e' => '%b %e', + 'Expand tasks' => 'Expandera uppgifter', + 'Collapse tasks' => 'Minimera uppgifter', + 'Expand/collapse tasks' => 'Expandera/minimera uppgifter', + 'Close dialog box' => 'Stäng dialogruta', + 'Submit a form' => 'Sänd formulär', + 'Board view' => 'Tavelvy', + 'Keyboard shortcuts' => 'Tangentbordsgenvägar', + 'Open board switcher' => 'Växling av öppen tavla', + 'Application' => 'Applikation', + 'Filter recently updated' => 'Filter som uppdaterats nyligen', + 'since %B %e, %Y at %k:%M %p' => 'sedan %B %e, %Y at %k:%M %p', + 'More filters' => 'Fler filter', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/th_TH/translations.php b/sources/app/Locale/th_TH/translations.php index 1945f04..d777691 100644 --- a/sources/app/Locale/th_TH/translations.php +++ b/sources/app/Locale/th_TH/translations.php @@ -1,6 +1,8 @@ '', + // 'number.thousands_separator' => '', 'None' => 'ไม่มี', 'edit' => 'แก้ไข', 'Edit' => 'แก้ไข', @@ -734,4 +736,114 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', ); diff --git a/sources/app/Locale/tr_TR/translations.php b/sources/app/Locale/tr_TR/translations.php new file mode 100644 index 0000000..67af710 --- /dev/null +++ b/sources/app/Locale/tr_TR/translations.php @@ -0,0 +1,849 @@ + '', + // 'number.thousands_separator' => '', + 'None' => 'Hiçbiri', + 'edit' => 'düzenle', + 'Edit' => 'Düzenle', + 'remove' => 'sil', + 'Remove' => 'Sil', + 'Update' => 'Güncelle', + 'Yes' => 'Evet', + 'No' => 'Hayır', + 'cancel' => 'İptal', + 'or' => 'veya', + 'Yellow' => 'Sarı', + 'Blue' => 'Mavi', + 'Green' => 'Yeşil', + 'Purple' => 'Mor', + 'Red' => 'Kırmızı', + 'Orange' => 'Turuncu', + 'Grey' => 'Gri', + 'Save' => 'Kaydet', + 'Login' => 'Giriş', + 'Official website:' => 'Resmi internet sitesi:', + 'Unassigned' => 'Atanmamış', + 'View this task' => 'Bu görevi görüntüle', + 'Remove user' => 'Kullanıcıyı kaldır', + 'Do you really want to remove this user: "%s"?' => 'Bu kullanıcıyı gerçekten silmek istiyor musunuz: "%s"?', + 'New user' => 'Yeni kullanıcı', + 'All users' => 'Tüm kullanıcılar', + 'Username' => 'Kullanıcı adı', + 'Password' => 'Şifre', + // 'Default project' => '', + 'Administrator' => 'Yönetici', + 'Sign in' => 'Giriş yap', + 'Users' => 'Kullanıcılar', + 'No user' => 'Kullanıcı yok', + 'Forbidden' => 'Yasak', + 'Access Forbidden' => 'Erişim yasak', + 'Only administrators can access to this page.' => 'Bu sayfaya yalnızca yöneticiler erişebilir.', + 'Edit user' => 'Kullanıcıyı düzenle', + 'Logout' => 'Çıkış yap', + 'Bad username or password' => 'Hatalı kullanıcı adı veya şifre', + 'users' => 'kullanıcılar', + 'projects' => 'projeler', + 'Edit project' => 'Projeyi düzenle', + 'Name' => 'İsim', + 'Activated' => 'Aktif', + 'Projects' => 'Projeler', + 'No project' => 'Proje yok', + 'Project' => 'Proje', + 'Status' => 'Durum', + 'Tasks' => 'Görevler', + 'Board' => 'Tablo', + 'Actions' => 'İşlemler', + 'Inactive' => 'Aktif değil', + 'Active' => 'Aktif', + 'Column %d' => 'Sütun %d', + 'Add this column' => 'Bu sütunu ekle', + '%d tasks on the board' => '%d görev bu tabloda', + '%d tasks in total' => '%d görev toplam', + 'Unable to update this board.' => 'Bu tablo güncellenemiyor.', + 'Edit board' => 'Tabloyu düzenle', + 'Disable' => 'Devre dışı bırak', + 'Enable' => 'Etkinleştir', + 'New project' => 'Yeni proje', + 'Do you really want to remove this project: "%s"?' => 'Bu projeyi gerçekten silmek istiyor musunuz: "%s"?', + 'Remove project' => 'Projeyi sil', + 'Boards' => 'Tablolar', + 'Edit the board for "%s"' => 'Tabloyu "%s" için güncelle', + 'All projects' => 'Tüm projeler', + 'Change columns' => 'Sütunları değiştir', + 'Add a new column' => 'Yeni sütun ekle', + 'Title' => 'Başlık', + 'Add Column' => 'Sütun ekle', + 'Project "%s"' => 'Proje "%s"', + 'Nobody assigned' => 'Kullanıcı atanmamış', + 'Assigned to %s' => '%s kullanıcısına atanmış', + 'Remove a column' => 'Bir sütunu sil', + 'Remove a column from a board' => 'Tablodan bir sütunu sil', + 'Unable to remove this column.' => 'Bu sütun silinemiyor.', + 'Do you really want to remove this column: "%s"?' => 'Bu sütunu gerçekten silmek istiyor musunuz: "%s"?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Bu komut sütun içindeki TÜM GÖREVLERİ silecek!', + 'Settings' => 'Ayarlar', + 'Application settings' => 'Uygulama ayarları', + 'Language' => 'Dil', + // 'Webhook token:' => '', + 'API token:' => 'API Token:', + 'More information' => 'Daha fazla bilgi', + 'Database size:' => 'Veritabanı boyutu :', + 'Download the database' => 'Veritabanını indir', + 'Optimize the database' => 'Veritabanını optimize et', + '(VACUUM command)' => '(VACUUM komutu)', + '(Gzip compressed Sqlite file)' => '(Gzip ile sıkıştırılmış Sqlite dosyası)', + 'User settings' => 'Kullanıcı ayarları', + 'My default project:' => 'Benim varsayılan projem:', + 'Close a task' => 'Bir görevi kapat', + 'Do you really want to close this task: "%s"?' => 'Bu görevi gerçekten kapatmak istiyor musunuz: "%s"?', + 'Edit a task' => 'Bir görevi düzenle', + 'Column' => 'Sütun', + 'Color' => 'Renk', + 'Assignee' => 'Atanan', + 'Create another task' => 'Başka bir görev oluştur', + 'New task' => 'Nouvelle tâche', + 'Open a task' => 'Bir görevi aç', + 'Do you really want to open this task: "%s"?' => 'Bu görevi gerçekten açmak istiyor musunuz: "%s"?', + 'Back to the board' => 'Tabloya dön', + // 'Created on %B %e, %Y at %k:%M %p' => '', + 'There is nobody assigned' => 'Kimse atanmamış', + 'Column on the board:' => 'Tablodaki sütun:', + 'Status is open' => 'Açık durumda', + 'Status is closed' => 'Kapalı durumda', + 'Close this task' => 'Görevi kapat', + 'Open this task' => 'Görevi aç', + 'There is no description.' => 'Açıklama yok.', + 'Add a new task' => 'Yeni görev ekle', + 'The username is required' => 'Kullanıcı adı gerekli', + 'The maximum length is %d characters' => 'Maksimum uzunluk %d karakterdir', + 'The minimum length is %d characters' => 'Minimum uzunluk %d karakterdir', + 'The password is required' => 'Şifre gerekli', + 'This value must be an integer' => 'Bu değer bir rakam olmak zorunda', + 'The username must be unique' => 'Kullanıcı adı daha önceden var', + 'The username must be alphanumeric' => 'Kullanıcı adı alfanumerik olmalı (geçersiz karakter var)', + 'The user id is required' => 'Kullanıcı kodu gerekli', + 'Passwords don\'t match' => 'Şifreler uyuşmuyor', + 'The confirmation is required' => 'Onay gerekli', + 'The column is required' => 'Sütun gerekli', + 'The project is required' => 'Proje gerekli', + 'The color is required' => 'Renk gerekli', + 'The id is required' => 'Kod gerekli', + 'The project id is required' => 'Proje kodu gerekli', + 'The project name is required' => 'Proje adı gerekli', + 'This project must be unique' => 'Bu projenin tekil olması gerekli', + 'The title is required' => 'Başlık gerekli', + 'The language is required' => 'Dil seçimi gerekli', + 'There is no active project, the first step is to create a new project.' => 'Aktif bir proje yok. İlk aşama yeni bir proje oluşturmak olmalı.', + 'Settings saved successfully.' => 'Ayarlar başarıyla kaydedildi.', + 'Unable to save your settings.' => 'Ayarlarınız kaydedilemedi.', + 'Database optimization done.' => 'Veritabanı optimizasyonu tamamlandı.', + 'Your project have been created successfully.' => 'Projeniz başarıyla oluşturuldu.', + 'Unable to create your project.' => 'Proje oluşturulamadı.', + 'Project updated successfully.' => 'Proje başarıyla güncellendi.', + 'Unable to update this project.' => 'Bu proje güncellenemedi.', + 'Unable to remove this project.' => 'Bu proje silinemedi.', + 'Project removed successfully.' => 'Proje başarıyla silindi.', + 'Project activated successfully.' => 'Proje başarıyla aktive edildi.', + 'Unable to activate this project.' => 'Bu proje aktive edilemedi.', + 'Project disabled successfully.' => 'Proje devre dışı bırakıldı.', + 'Unable to disable this project.' => 'Bu proje devre dışı bırakılamadı.', + 'Unable to open this task.' => 'Bu görev açılamıyor.', + 'Task opened successfully.' => 'Görev başarıyla açıldı.', + 'Unable to close this task.' => 'Bu görev kapatılamıyor.', + 'Task closed successfully.' => 'Görev başarıyla kapatıldı.', + 'Unable to update your task.' => 'Görev güncellenemiyor.', + 'Task updated successfully.' => 'Görev başarıyla güncellendi.', + 'Unable to create your task.' => 'Görev oluşturulamadı.', + 'Task created successfully.' => 'Görev başarıyla oluşturuldu.', + 'User created successfully.' => 'Kullanıcı başarıyla oluşturuldu', + 'Unable to create your user.' => 'Kullanıcı oluşturulamıyor.', + 'User updated successfully.' => 'Kullanıcı başarıyla güncellendi.', + 'Unable to update your user.' => 'Kullanıcı güncellenemiyor.', + 'User removed successfully.' => 'Kullanıcı silindi.', + 'Unable to remove this user.' => 'Bu kullanıcı silinemiyor.', + 'Board updated successfully.' => 'Tablo başarıyla güncellendi.', + 'Ready' => 'Hazır', + 'Backlog' => 'Bekleme listesi', + 'Work in progress' => 'İşlemde', + 'Done' => 'Tamamlandı', + 'Application version:' => 'Uygulama versiyonu:', + // 'Completed on %B %e, %Y at %k:%M %p' => '', + // '%B %e, %Y at %k:%M %p' => '', + 'Date created' => 'Oluşturulma tarihi', + 'Date completed' => 'Tamamlanma tarihi', + 'Id' => 'Kod', + 'No task' => 'Görev yok', + 'Completed tasks' => 'Tamamlanan görevler', + 'List of projects' => 'Proje listesi', + 'Completed tasks for "%s"' => '"%s" için tamamlanan görevler', + '%d closed tasks' => '%d kapatılmış görevler', + // 'No task for this project' => '', + 'Public link' => 'Dışa açık link', + 'There is no column in your project!' => 'Projenizde hiç sütun yok', + 'Change assignee' => 'Atanmış Kullanıcıyı değiştir', + 'Change assignee for the task "%s"' => '"%s" görevi için atanmış kullanıcıyı değiştir', + 'Timezone' => 'Saat dilimi', + // 'Sorry, I didn\'t find this information in my database!' => '', + 'Page not found' => 'Sayfa bulunamadı', + 'Complexity' => 'Zorluk seviyesi', + 'limit' => 'limit', + 'Task limit' => 'Görev limiti', + 'Task count' => 'Görev sayısı', + 'This value must be greater than %d' => 'Bu değer %d den büyük olmalı', + 'Edit project access list' => 'Proje erişim listesini düzenle', + 'Edit users access' => 'Kullanıcı erişim haklarını düzenle', + 'Allow this user' => 'Bu kullanıcıya izin ver', + 'Only those users have access to this project:' => 'Bu projeye yalnızca şu kullanıcılar erişebilir:', + 'Don\'t forget that administrators have access to everything.' => 'Dikkat: Yöneticilerin herşeye erişimi olduğunu unutmayın!', + 'Revoke' => 'Iptal et', + 'List of authorized users' => 'Yetkili kullanıcıların listesi', + 'User' => 'Kullanıcı', + 'Nobody have access to this project.' => 'Bu projeye kimsenin erişimi yok.', + 'You are not allowed to access to this project.' => 'Bu projeye giriş yetkiniz yok.', + 'Comments' => 'Yorumlar', + 'Post comment' => 'Yorum ekle', + 'Write your text in Markdown' => 'Yazınızı Markdown ile yazın', + 'Leave a comment' => 'Bir yorum ekle', + 'Comment is required' => 'Yorum gerekli', + 'Leave a description' => 'Açıklama ekleyin', + 'Comment added successfully.' => 'Yorum eklendi', + 'Unable to create your comment.' => 'Yorumunuz oluşturulamadı', + 'The description is required' => 'Açıklama gerekli', + 'Edit this task' => 'Bu görevi değiştir', + 'Due Date' => 'Termin', + 'Invalid date' => 'Geçersiz tarihi', + // 'Must be done before %B %e, %Y' => '', + '%B %e, %Y' => '%d %B %Y', + '%b %e, %Y' => '%d/%m/%Y', + 'Automatic actions' => 'Otomatik işlemler', + 'Your automatic action have been created successfully.' => 'Otomatik işlem başarıyla oluşturuldu', + 'Unable to create your automatic action.' => 'Otomatik işleminiz oluşturulamadı', + 'Remove an action' => 'Bir işlemi sil', + 'Unable to remove this action.' => 'Bu işlem silinemedi', + 'Action removed successfully.' => 'İşlem başarıyla silindi', + 'Automatic actions for the project "%s"' => '"%s" projesi için otomatik işlemler', + 'Defined actions' => 'Tanımlanan işlemler', + 'Add an action' => 'İşlem ekle', + 'Event name' => 'Durum adı', + 'Action name' => 'İşlem adı', + 'Action parameters' => 'İşlem parametreleri', + 'Action' => 'İşlem', + 'Event' => 'Durum', + 'When the selected event occurs execute the corresponding action.' => 'Seçilen durum oluştuğunda ilgili eylemi gerçekleştir.', + 'Next step' => 'Sonraki adım', + 'Define action parameters' => 'İşlem parametrelerini düzenle', + 'Save this action' => 'Bu işlemi kaydet', + 'Do you really want to remove this action: "%s"?' => 'Bu işlemi silmek istediğinize emin misiniz: "%s"?', + 'Remove an automatic action' => 'Bir otomatik işlemi sil', + 'Close the task' => 'Görevi kapat', + 'Assign the task to a specific user' => 'Görevi bir kullanıcıya ata', + 'Assign the task to the person who does the action' => 'Görevi, işlemi gerçekleştiren kullanıcıya ata', + 'Duplicate the task to another project' => 'Görevi bir başka projeye kopyala', + 'Move a task to another column' => 'Bir görevi başka bir sütuna taşı', + 'Move a task to another position in the same column' => 'Bir görevin aynı sütunda yerini değiştir', + 'Task modification' => 'Görev düzenleme', + 'Task creation' => 'Görev oluşturma', + 'Open a closed task' => 'Kapalı bir görevi aç', + 'Closing a task' => 'Bir görev kapatılıyor', + 'Assign a color to a specific user' => 'Bir kullanıcıya renk tanımla', + 'Column title' => 'Sütun başlığı', + 'Position' => 'Pozisyon', + 'Move Up' => 'Yukarı taşı', + 'Move Down' => 'Aşağı taşı', + 'Duplicate to another project' => 'Başka bir projeye kopyala', + 'Duplicate' => 'Kopya oluştur', + 'link' => 'link', + 'Update this comment' => 'Bu yorumu güncelle', + 'Comment updated successfully.' => 'Yorum güncellendi.', + 'Unable to update your comment.' => 'Yorum güncellenemedi.', + 'Remove a comment' => 'Bir yorumu sil', + 'Comment removed successfully.' => 'Yorum silindi.', + 'Unable to remove this comment.' => 'Bu yorum silinemiyor.', + 'Do you really want to remove this comment?' => 'Bu yorumu silmek istediğinize emin misiniz?', + 'Only administrators or the creator of the comment can access to this page.' => 'Bu sayfaya yalnızca yorum sahibi ve yöneticiler erişebilir.', + 'Details' => 'Detaylar', + 'Current password for the user "%s"' => 'Kullanıcı için mevcut şifre "%s"', + 'The current password is required' => 'Mevcut şifre gerekli', + 'Wrong password' => 'Yanlış Şifre', + 'Reset all tokens' => 'Tüm fişleri sıfırla', + 'All tokens have been regenerated.' => 'Tüm fişler yeniden oluşturuldu.', + 'Unknown' => 'Bilinmeyen', + 'Last logins' => 'Son kullanıcı girişleri', + 'Login date' => 'Giriş tarihi', + 'Authentication method' => 'Doğrulama yöntemi', + 'IP address' => 'IP adresi', + 'User agent' => 'Kullanıcı sistemi', + 'Persistent connections' => 'Kalıcı bağlantılar', + // 'No session.' => '', + 'Expiration date' => 'Geçerlilik sonu', + 'Remember Me' => 'Beni hatırla', + 'Creation date' => 'Oluşturulma tarihi', + 'Filter by user' => 'Kullanıcıya göre filtrele', + 'Filter by due date' => 'Termine göre filtrele', + 'Everybody' => 'Herkes', + 'Open' => 'Açık', + 'Closed' => 'Kapalı', + 'Search' => 'Ara', + 'Nothing found.' => 'Hiçbir şey bulunamadı', + 'Search in the project "%s"' => '"%s" Projesinde ara', + 'Due date' => 'Termin', + 'Others formats accepted: %s and %s' => 'Diğer kabul edilen formatlar: %s ve %s', + 'Description' => 'Açıklama', + '%d comments' => '%d yorumlar', + '%d comment' => '%d yorum', + 'Email address invalid' => 'E-Posta adresi geçersiz', + 'Your Google Account is not linked anymore to your profile.' => 'Google hesabınız artık profilinize bağlı değil', + 'Unable to unlink your Google Account.' => 'Google hesabınızla bağ koparılamadı', + 'Google authentication failed' => 'Google hesap doğrulaması başarısız', + 'Unable to link your Google Account.' => 'Google hesabınızla bağ oluşturulamadı', + 'Your Google Account is linked to your profile successfully.' => 'Google hesabınız profilinize başarıyla bağlandı', + 'Email' => 'E-Posta', + 'Link my Google Account' => 'Google hesabımla bağ oluştur', + 'Unlink my Google Account' => 'Google hesabımla bağı kaldır', + 'Login with my Google Account' => 'Google hesabımla giriş yap', + 'Project not found.' => 'Proje bulunamadı', + 'Task #%d' => 'Görev #%d', + 'Task removed successfully.' => 'Görev silindi', + 'Unable to remove this task.' => 'Görev silinemiyor', + 'Remove a task' => 'Bir görevi sil', + 'Do you really want to remove this task: "%s"?' => 'Bu görevi silmek istediğinize emin misiniz: "%s"?', + 'Assign automatically a color based on a category' => 'Kategoriye göre otomatik renk ata', + 'Assign automatically a category based on a color' => 'Rengine göre otomatik kategori ata', + 'Task creation or modification' => 'Görev oluşturma veya değiştirme', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategoriler', + 'Category not found.' => 'Kategori bulunamadı', + 'Your category have been created successfully.' => 'Kategori oluşturuldu', + 'Unable to create your category.' => 'Kategori oluşturulamadı', + 'Your category have been updated successfully.' => 'Kategori başarıyla güncellendi', + 'Unable to update your category.' => 'Kategori güncellenemedi', + 'Remove a category' => 'Bir kategoriyi sil', + 'Category removed successfully.' => 'Kategori silindi', + 'Unable to remove this category.' => 'Bu kategori silinemedi', + 'Category modification for the project "%s"' => '"%s" projesi için kategori değiştirme', + 'Category Name' => 'Kategori adı', + 'Categories for the project "%s"' => '"%s" Projesi için kategoriler', + 'Add a new category' => 'Yeni kategori ekle', + 'Do you really want to remove this category: "%s"?' => 'Bu kategoriyi silmek istediğinize emin misiniz: "%s"?', + 'Filter by category' => 'Kategoriye göre filtrele', + 'All categories' => 'Tüm kategoriler', + 'No category' => 'Kategori Yok', + 'The name is required' => 'İsim gerekli', + 'Remove a file' => 'Dosya sil', + 'Unable to remove this file.' => 'Dosya silinemedi', + 'File removed successfully.' => 'Dosya silindi', + 'Attach a document' => 'Dosya ekle', + 'Do you really want to remove this file: "%s"?' => 'Bu dosyayı silmek istediğinize emin misiniz: "%s"?', + 'open' => 'aç', + 'Attachments' => 'Ekler', + 'Edit the task' => 'Görevi değiştir', + 'Edit the description' => 'Açıklamayı değiştir', + 'Add a comment' => 'Yorum ekle', + 'Edit a comment' => 'Yorum değiştir', + 'Summary' => 'Özet', + 'Time tracking' => 'Zaman takibi', + 'Estimate:' => 'Tahmini:', + 'Spent:' => 'Harcanan:', + 'Do you really want to remove this sub-task?' => 'Bu alt görevi silmek istediğinize emin misiniz', + 'Remaining:' => 'Kalan', + 'hours' => 'saat', + 'spent' => 'harcanan', + 'estimated' => 'tahmini', + 'Sub-Tasks' => 'Alt Görev', + 'Add a sub-task' => 'Alt görev ekle', + // 'Original estimate' => '', + 'Create another sub-task' => 'Başka bir alt görev daha oluştur', + // 'Time spent' => '', + 'Edit a sub-task' => 'Alt görev düzenle', + 'Remove a sub-task' => 'Alt görev sil', + 'The time must be a numeric value' => 'Zaman alfanumerik bir değer olmalı', + 'Todo' => 'Yapılacaklar', + 'In progress' => 'İşlemde', + 'Sub-task removed successfully.' => 'Alt görev silindi', + 'Unable to remove this sub-task.' => 'Alt görev silinemedi', + 'Sub-task updated successfully.' => 'Alt görev güncellendi', + 'Unable to update your sub-task.' => 'Alt görev güncellenemiyor', + 'Unable to create your sub-task.' => 'Alt görev oluşturulamadı', + 'Sub-task added successfully.' => 'Alt görev başarıyla eklendii', + 'Maximum size: ' => 'Maksimum boyutu', + 'Unable to upload the file.' => 'Karşıya yükleme başarısız', + 'Display another project' => 'Başka bir proje göster', + 'Your GitHub account was successfully linked to your profile.' => 'GitHub Hesabınız Profilinize bağlandı.', + 'Unable to link your GitHub Account.' => 'GitHub hesabınızla bağ oluşturulamadı.', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', + 'Created by %s' => '%s tarafından oluşturuldu', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Son değişiklik tarihi %d.%m.%Y, saati %H:%M', + 'Tasks Export' => 'Görevleri dışa aktar', + 'Tasks exportation for "%s"' => '"%s" için görevleri dışa aktar', + 'Start Date' => 'Başlangıç tarihi', + 'End Date' => 'Bitiş tarihi', + 'Execute' => 'Gerçekleştir', + 'Task Id' => 'Görev No', + 'Creator' => 'Oluşturan', + 'Modification date' => 'Değişiklik tarihi', + 'Completion date' => 'Tamamlanma tarihi', + // 'Webhook URL for task creation' => '', + // 'Webhook URL for task modification' => '', + 'Clone' => 'Kopya oluştur', + 'Clone Project' => 'Projenin kopyasını oluştur', + 'Project cloned successfully.' => 'Proje kopyası başarıyla oluşturuldu.', + 'Unable to clone this project.' => 'Proje kopyası oluşturulamadı.', + 'Email notifications' => 'E-Posta bilgilendirmesi', + 'Enable email notifications' => 'E-Posta bilgilendirmesini aç', + 'Task position:' => 'Görev pozisyonu', + 'The task #%d have been opened.' => '#%d numaralı görev açıldı.', + 'The task #%d have been closed.' => '#%d numaralı görev kapatıldı.', + 'Sub-task updated' => 'Alt görev güncellendi', + 'Title:' => 'Başlık', + 'Status:' => 'Durum', + 'Assignee:' => 'Sorumlu:', + 'Time tracking:' => 'Zaman takibi', + 'New sub-task' => 'Yeni alt görev', + 'New attachment added "%s"' => 'Yeni dosya "%s" eklendi.', + 'Comment updated' => 'Yorum güncellendi', + 'New comment posted by %s' => '%s tarafından yeni yorum eklendi', + 'List of due tasks for the project "%s"' => '"%s" projesi için ilgili görevlerin listesi', + 'New attachment' => 'Yeni dosya eki', + 'New comment' => 'Yeni yorum', + 'New subtask' => 'Yeni alt görev', + 'Subtask updated' => 'Alt görev güncellendi', + 'Task updated' => 'Görev güncellendi', + 'Task closed' => 'Görev kapatıldı', + 'Task opened' => 'Görev açıldı', + '[%s][Due tasks]' => '[%s][İlgili görevler]', + '[Kanboard] Notification' => '[Kanboard] Bildirim', + 'I want to receive notifications only for those projects:' => 'Yalnızca bu projelerle ilgili bildirim almak istiyorum:', + 'view the task on Kanboard' => 'bu görevi Kanboard\'da göster', + 'Public access' => 'Dışa açık erişim', + 'Category management' => 'Kategori yönetimi', + 'User management' => 'Kullanıcı yönetimi', + 'Active tasks' => 'Aktif görevler', + 'Disable public access' => 'Dışa açık erişimi kapat', + 'Enable public access' => 'Dışa açık erişimi aç', + 'Active projects' => 'Aktif projeler', + 'Inactive projects' => 'Aktif olmayan projeler', + 'Public access disabled' => 'Dışa açık erişim kapatıldı', + 'Do you really want to disable this project: "%s"?' => 'Bu projeyi devre dışı bırakmak istediğinize emin misiniz?: "%s"', + 'Do you really want to duplicate this project: "%s"?' => 'Bu projenin kopyasını oluşturmak istediğinize emin misiniz?: "%s"', + 'Do you really want to enable this project: "%s"?' => 'Bu projeyi aktive etmek istediğinize emin misiniz?: "%s"', + 'Project activation' => 'Proje aktivasyonu', + 'Move the task to another project' => 'Görevi başka projeye taşı', + 'Move to another project' => 'Başka projeye taşı', + 'Do you really want to duplicate this task?' => 'Bu görevin kopyasını oluşturmak istediğinize emin misiniz?', + 'Duplicate a task' => 'Görevin kopyasını oluştur', + 'External accounts' => 'Dış hesaplar', + 'Account type' => 'Hesap türü', + 'Local' => 'Yerel', + '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', + 'Default project:' => 'Varsayılan Proje:', + 'Notifications:' => 'Bildirimler:', + 'Notifications' => 'Bildirimler', + 'Group:' => 'Grup', + 'Regular user' => 'Varsayılan kullanıcı', + 'Account type:' => 'Hesap türü:', + 'Edit profile' => 'Profili değiştir', + 'Change password' => 'Şifre değiştir', + 'Password modification' => 'Şifre değişimi', + 'External authentications' => 'Dış kimlik doğrulamaları', + 'Google Account' => 'Google hesabı', + 'Github Account' => 'Github hesabı', + 'Never connected.' => 'Hiç bağlanmamış.', + 'No account linked.' => 'Bağlanmış hesap yok.', + 'Account linked.' => 'Hesap bağlandı', + 'No external authentication enabled.' => 'Dış kimlik doğrulama kapalı.', + 'Password modified successfully.' => 'Şifre başarıyla değiştirildi.', + 'Unable to change the password.' => 'Şifre değiştirilemedi.', + 'Change category for the task "%s"' => '"%s" görevi için kategori değiştirme', + 'Change category' => 'Kategori değiştirme', + '%s updated the task %s' => '%s kullanıcısı %s görevini güncelledi', + '%s opened the task %s' => '%s kullanıcısı %s görevini açtı', + '%s moved the task %s to the position #%d in the column "%s"' => '%s kullanıcısı %s görevini #%d pozisyonu "%s" sütununa taşıdı', + '%s moved the task %s to the column "%s"' => '%s kullanıcısı %s görevini "%s" sütununa taşıdı', + '%s created the task %s' => '%s kullanıcısı %s görevini oluşturdu', + '%s closed the task %s' => '%s kullanıcısı %s görevini kapattı', + '%s created a subtask for the task %s' => '%s kullanıcısı %s görevi için bir alt görev oluşturdu', + '%s updated a subtask for the task %s' => '%s kullanıcısı %s görevinin bir alt görevini güncelledi', + 'Assigned to %s with an estimate of %s/%sh' => '%s kullanıcısına tahmini %s/%s saat tamamlanma süresi ile atanmış', + 'Not assigned, estimate of %sh' => 'Kimseye atanmamış, tahmini süre %s saat', + '%s updated a comment on the task %s' => '%s kullanıcısı %s görevinde bir yorumu güncelledi', + '%s commented the task %s' => '%s kullanıcısı %s görevine yorum ekledi', + '%s\'s activity' => '%s\'in aktivitesi', + 'No activity.' => 'Aktivite yok.', + 'RSS feed' => 'RSS kaynağı', + '%s updated a comment on the task #%d' => '%s kullanıcısı #%d nolu görevde bir yorumu güncelledi', + '%s commented on the task #%d' => '%s kullanıcısı #%d nolu göreve yorum ekledi', + '%s updated a subtask for the task #%d' => '%s kullanıcısı #%d nolu görevin bir alt görevini güncelledi', + '%s created a subtask for the task #%d' => '%s kullanıcısı #%d nolu göreve bir alt görev ekledi', + '%s updated the task #%d' => '%s kullanıcısı #%d nolu görevi güncelledi', + '%s created the task #%d' => '%s kullanıcısı #%d nolu görevi oluşturdu', + '%s closed the task #%d' => '%s kullanıcısı #%d nolu görevi kapattı', + '%s open the task #%d' => '%s kullanıcısı #%d nolu görevi açtı', + '%s moved the task #%d to the column "%s"' => '%s kullanıcısı #%d nolu görevi "%s" sütununa taşıdı', + '%s moved the task #%d to the position %d in the column "%s"' => '%s kullanıcısı #%d nolu görevi %d pozisyonu "%s" sütununa taşıdı', + 'Activity' => 'Aktivite', + 'Default values are "%s"' => 'Varsayılan değerler "%s"', + 'Default columns for new projects (Comma-separated)' => 'Yeni projeler için varsayılan sütunlar (virgül ile ayrılmış)', + 'Task assignee change' => 'Göreve atanan kullanıcı değişikliği', + '%s change the assignee of the task #%d to %s' => '%s kullanıcısı #%d nolu görevin sorumlusunu %s olarak değiştirdi', + '%s changed the assignee of the task %s to %s' => '%s kullanıcısı %s görevinin sorumlusunu %s olarak değiştirdi', + 'Column Change' => 'Sütun değişikliği', + 'Position Change' => 'Konum değişikliği', + 'Assignee Change' => 'Sorumlu değişikliği', + 'New password for the user "%s"' => '"%s" kullanıcısı için yeni şifre', + 'Choose an event' => 'Bir durum seçin', + // 'Github commit received' => '', + // 'Github issue opened' => '', + // 'Github issue closed' => '', + // 'Github issue reopened' => '', + // 'Github issue assignee change' => '', + // 'Github issue label change' => '', + 'Create a task from an external provider' => 'Dış sağlayıcı ile bir görev oluştur', + 'Change the assignee based on an external username' => 'Dış kaynaklı kullanıcı adı ile göreve atananı değiştir', + 'Change the category based on an external label' => 'Dış kaynaklı bir etiket ile kategori değiştir', + 'Reference' => 'Referans', + 'Reference: %s' => 'Referans: %s', + 'Label' => 'Etiket', + 'Database' => 'Veri bankası', + 'About' => 'Hakkında', + 'Database driver:' => 'Veri bankası sürücüsü', + 'Board settings' => 'Tablo ayarları', + 'URL and token' => 'URL veya Token', + 'Webhook settings' => 'Webhook ayarları', + 'URL for task creation:' => 'Görev oluşturma için URL', + 'Reset token' => 'Reset Token', + 'API endpoint:' => 'API endpoint', + 'Refresh interval for private board' => 'Özel tablolar için yenileme sıklığı', + 'Refresh interval for public board' => 'Dışa açık tablolar için yenileme sıklığı', + 'Task highlight period' => 'Görevi öne çıkarma süresi', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Bir görevin yeni değiştirilmiş sayılması için süre (saniye olarak) (Bu özelliği iptal etmek için 0, varsayılan değer 2 gün)', + 'Frequency in second (60 seconds by default)' => 'Saniye olarak frekans (varsayılan 60 saniye)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Saniye olarak frekans (Bu özelliği iptal etmek için 0, varsayılan değer 10 saniye)', + 'Application URL' => 'Uygulama URL', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Örneğin: http://example.kanboard.net/ (E-posta bildirimleri için kullanılıyor)', + 'Token regenerated.' => 'Token yeniden oluşturuldu.', + 'Date format' => 'Tarih formatı', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formatı her zaman kabul edilir, örneğin: "%s" ve "%s"', + 'New private project' => 'Yeni özel proje', + 'This project is private' => 'Bu proje özel', + 'Type here to create a new sub-task' => 'Yeni bir alt görev oluşturmak için buraya yazın', + 'Add' => 'Ekle', + 'Estimated time: %s hours' => 'Tahmini süre: %s Saat', + 'Time spent: %s hours' => 'Kullanılan süre: %s Saat', + 'Started on %B %e, %Y' => '%B %e %Y tarihinde başlatıldı', + 'Start date' => 'Başlangıç tarihi', + 'Time estimated' => 'Tahmini süre', + 'There is nothing assigned to you.' => 'Size atanan hiçbir şey yok.', + 'My tasks' => 'Görevlerim', + 'Activity stream' => 'Güncel olay akışı', + 'Dashboard' => 'Anasayfa', + 'Confirmation' => 'Onay', + 'Allow everybody to access to this project' => 'Bu projeye herkesin erişimine izin ver', + 'Everybody have access to this project.' => 'Bu projeye herkesin erişimi var.', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Integration' => 'Entegrasyon', + 'Github webhooks' => 'Github Webhook', + 'Help on Github webhooks' => 'Github Webhooks hakkında yardım', + 'Create a comment from an external provider' => 'Dış sağlayıcı ile bir yorum oluştur', + 'Github issue comment created' => 'Github hata yorumu oluşturuldu', + 'Configure' => 'Ayarla', + 'Project management' => 'Proje yönetimi', + 'My projects' => 'Projelerim', + 'Columns' => 'Sütunlar', + 'Task' => 'Görev', + 'Your are not member of any project.' => 'Hiç bir projenin üyesi değilsiniz.', + 'Percentage' => 'Yüzde', + 'Number of tasks' => 'Görev sayısı', + 'Task distribution' => 'Görev dağılımı', + 'Reportings' => 'Raporlar', + 'Task repartition for "%s"' => '"%s" için görev dağılımı', + 'Analytics' => 'Analiz', + 'Subtask' => 'Alt görev', + 'My subtasks' => 'Alt görevlerim', + 'User repartition' => 'Kullanıcı dağılımı', + 'User repartition for "%s"' => '"%s" için kullanıcı dağılımı', + 'Clone this project' => 'Projenin kopyasını oluştur', + 'Column removed successfully.' => 'Sütun başarıyla kaldırıldı.', + 'Edit Project' => 'Projeyi düzenle', + 'Github Issue' => 'Github Issue', + 'Not enough data to show the graph.' => 'Grafik gösterimi için yeterli veri yok.', + 'Previous' => 'Önceki', + 'The id must be an integer' => 'ID bir tamsayı olmalı', + 'The project id must be an integer' => 'Proje numarası bir tam sayı olmalı', + 'The status must be an integer' => 'Durum bir tam sayı olmalı', + 'The subtask id is required' => 'Alt görev numarası gerekli', + 'The subtask id must be an integer' => 'Alt görev numarası bir tam sayı olmalı', + 'The task id is required' => 'Görev numarası gerekli', + 'The task id must be an integer' => 'Görev numarası bir tam sayı olmalı', + 'The user id must be an integer' => 'Kullanıcı numarası bir tam sayı olmalı', + 'This value is required' => 'Bu değer gerekli', + 'This value must be numeric' => 'Bu değer sayı olmalı', + 'Unable to create this task.' => 'Bu görev oluşturulamıyor.', + 'Cumulative flow diagram' => 'Kümülatif akış diyagramı', + 'Cumulative flow diagram for "%s"' => '"%s" için kümülatif akış diyagramı', + 'Daily project summary' => 'Günlük proje özeti', + 'Daily project summary export' => 'Günlük proje özetini dışa aktar', + 'Daily project summary export for "%s"' => '"%s" için günlük proje özetinin dışa', + 'Exports' => 'Dışa aktarımlar', + 'This export contains the number of tasks per column grouped per day.' => 'Bu dışa aktarım günlük gruplanmış olarak her sütundaki görev sayısını içerir.', + 'Nothing to preview...' => 'Önizleme yapılacak bir şey yok ...', + 'Preview' => 'Önizleme', + 'Write' => 'Değiştir', + 'Active swimlanes' => 'Aktif Kulvar', + 'Add a new swimlane' => 'Yeni bir Kulvar ekle', + 'Change default swimlane' => 'Varsayılan Kulvarı değiştir', + 'Default swimlane' => 'Varsayılan Kulvar', + 'Do you really want to remove this swimlane: "%s"?' => 'Bu Kulvarı silmek istediğinize emin misiniz?: "%s"?', + 'Inactive swimlanes' => 'Pasif Kulvarlar', + 'Set project manager' => 'Proje yöneticisi olarak ata', + 'Set project member' => 'Proje üyesi olarak ata', + 'Remove a swimlane' => 'Kulvarı sil', + 'Rename' => 'Yeniden adlandır', + 'Show default swimlane' => 'Varsayılan Kulvarı göster', + 'Swimlane modification for the project "%s"' => '"% s" Projesi için Kulvar değişikliği', + 'Swimlane not found.' => 'Kulvar bulunamadı', + 'Swimlane removed successfully.' => 'Kulvar başarıyla kaldırıldı.', + 'Swimlanes' => 'Kulvarlar', + 'Swimlane updated successfully.' => 'Kulvar başarıyla güncellendi.', + 'The default swimlane have been updated successfully.' => 'Varsayılan Kulvarlar başarıyla güncellendi.', + 'Unable to create your swimlane.' => 'Bu Kulvarı oluşturmak mümkün değil.', + 'Unable to remove this swimlane.' => 'Bu Kulvarı silmek mümkün değil.', + 'Unable to update this swimlane.' => 'Bu Kulvarı değiştirmek mümkün değil.', + 'Your swimlane have been created successfully.' => 'Kulvar başarıyla oluşturuldu.', + 'Example: "Bug, Feature Request, Improvement"' => 'Örnek: "Sorun, Özellik talebi, İyileştirme"', + 'Default categories for new projects (Comma-separated)' => 'Yeni projeler için varsayılan kategoriler (Virgül ile ayrılmış)', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + 'Integrations' => 'Entegrasyon', + 'Integration with third-party services' => 'Dış servislerle entegrasyon', + 'Role for this project' => 'Bu proje için rol', + 'Project manager' => 'Proje Yöneticisi', + 'Project member' => 'Proje Üyesi', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Bir Proje Yöneticisi proje ayarlarını değiştirebilir ve bir üyeden daha fazla yetkiye sahiptir.', + // 'Gitlab Issue' => '', + 'Subtask Id' => 'Alt görev No:', + 'Subtasks' => 'Alt görevler', + 'Subtasks Export' => 'Alt görevleri dışa aktar', + 'Subtasks exportation for "%s"' => '"%s" için alt görevleri dışa aktarımı', + 'Task Title' => 'Görev Başlığı', + 'Untitled' => 'Başlıksız', + 'Application default' => 'Uygulama varsayılanları', + 'Language:' => 'Dil:', + 'Timezone:' => 'Saat dilimi:', + 'All columns' => 'Tüm sütunlar', + 'Calendar for "%s"' => '"%s" için takvim', + 'Filter by column' => 'Sütuna göre filtrele', + 'Filter by status' => 'Duruma göre filtrele', + 'Calendar' => 'Takvim', + 'Next' => 'Sonraki', + '#%d' => '#%d', + 'Filter by color' => 'Renklere göre filtrele', + 'Filter by swimlane' => 'Kulvara göre filtrele', + 'All swimlanes' => 'Tüm Kulvarlar', + 'All colors' => 'Tüm Renkler', + 'All status' => 'Tüm Durumlar', + 'Add a comment logging moving the task between columns' => 'Sütun değiştiğinde kayıt olarak yorum ekle', + 'Moved to column %s' => '%s Sütununa taşındı', + 'Change description' => 'Açıklamayı değiştir', + 'User dashboard' => 'Kullanıcı Anasayfası', + 'Allow only one subtask in progress at the same time for a user' => 'Bir kullanıcı için aynı anda yalnızca bir alt göreve izin ver', + 'Edit column "%s"' => '"%s" sütununu değiştir', + 'Enable time tracking for subtasks' => 'Alt görevler için zaman takibini etkinleştir', + 'Select the new status of the subtask: "%s"' => '"%s" alt görevi için yeni durum seçin.', + 'Subtask timesheet' => 'Alt görev için zaman takip tablosu', + 'There is nothing to show.' => 'Gösterilecek hiçbir şey yok.', + 'Time Tracking' => 'Zaman takibi', + 'You already have one subtask in progress' => 'Zaten işlemde olan bir alt görev var', + 'Which parts of the project do you want to duplicate?' => 'Projenin hangi kısımlarının kopyasını oluşturmak istiyorsunuz?', + 'Change dashboard view' => 'Anasayfa görünümünü değiştir', + 'Show/hide activities' => 'Aktiviteleri göster/gizle', + 'Show/hide projects' => 'Projeleri göster/gizle', + 'Show/hide subtasks' => 'Alt görevleri göster/gizle', + 'Show/hide tasks' => 'Görevleri göster/gizle', + 'Disable login form' => 'Giriş formunu devre dışı bırak', + 'Show/hide calendar' => 'Takvimi göster/gizle', + 'User calendar' => 'Kullanıcı takvimi', + 'Bitbucket commit received' => 'Bitbucket commit alındı', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Bitbucket webhooks için yardım', + 'Start' => 'Başlangıç', + 'End' => 'Son', + 'Task age in days' => 'Görev yaşı gün olarak', + 'Days in this column' => 'Bu sütunda geçirilen gün', + '%dd' => '%dG', + 'Add a link' => 'Link ekle', + 'Add a new link' => 'Yeni link ekle', + 'Do you really want to remove this link: "%s"?' => '"%s" linkini gerçekten silmek istiyor musunuz?', + 'Do you really want to remove this link with task #%d?' => '#%d numaralı görev ile linki gerçekten silmek istiyor musunuz?', + 'Field required' => 'Bu alan gerekli', + 'Link added successfully.' => 'Link başarıyla eklendi.', + 'Link updated successfully.' => 'Link başarıyla güncellendi.', + 'Link removed successfully.' => 'Link başarıyla silindi.', + 'Link labels' => 'Link etiketleri', + 'Link modification' => 'Link değiştirme', + 'Links' => 'Links', + 'Link settings' => 'Link ayarları', + 'Opposite label' => 'Zıt etiket', + 'Remove a link' => 'Bir link silmek', + 'Task\'s links' => 'Görevin linkleri', + 'The labels must be different' => 'Etiketler farklı olmalı', + 'There is no link.' => 'Hiç bir bağ yok', + 'This label must be unique' => 'Bu etiket tek olmalı', + 'Unable to create your link.' => 'Link oluşturulamadı.', + 'Unable to update your link.' => 'Link güncellenemiyor.', + 'Unable to remove this link.' => 'Link kaldırılamıyor', + 'relates to' => 'şununla ilgili', + 'blocks' => 'şunu engelliyor', + 'is blocked by' => 'şunun tarafından engelleniyor', + 'duplicates' => 'şunun kopyasını oluşturuyor', + 'is duplicated by' => 'şunun tarafından kopyası oluşturuluyor', + 'is a child of' => 'şunun astı', + 'is a parent of' => 'şunun üstü', + 'targets milestone' => 'şu kilometre taşını hedefliyor', + 'is a milestone of' => 'şunun için bir kilometre taşı', + 'fixes' => 'düzeltiyor', + 'is fixed by' => 'şunun tarafından düzeltildi', + 'This task' => 'Bu görev', + '<1h' => '<1s', + '%dh' => '%ds', + // '%b %e' => '', + 'Expand tasks' => 'Görevleri genişlet', + 'Collapse tasks' => 'Görevleri daralt', + 'Expand/collapse tasks' => 'Görevleri genişlet/daralt', + 'Close dialog box' => 'İletiyi kapat', + 'Submit a form' => 'Formu gönder', + 'Board view' => 'Tablo görünümü', + 'Keyboard shortcuts' => 'Klavye kısayolları', + 'Open board switcher' => 'Tablo seçim listesini aç', + 'Application' => 'Uygulama', + 'Filter recently updated' => 'Son güncellenenleri göster', + 'since %B %e, %Y at %k:%M %p' => '%B %e, %Y saat %k:%M %p\'den beri', + 'More filters' => 'Daha fazla filtre', + 'Compact view' => 'Ekrana sığdır', + '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' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // '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' => '', + // 'Source column' => '', + // 'Show subtask estimates in the user calendar' => '', + // 'Transitions' => '', + // 'Executer' => '', + // 'Time spent in the column' => '', + // 'Task transitions' => '', + // 'Task transitions export' => '', + // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '', + // 'Currency rates' => '', + // '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.' => '', + // 'Send notifications to a Slack channel' => '', + // 'Webhook URL' => '', + // 'Help on Slack integration' => '', + // '%s remove the assignee of the task %s' => '', + // 'Send notifications to Hipchat' => '', + // 'API URL' => '', + // 'Room API ID or name' => '', + // 'Room notification token' => '', + // 'Help on Hipchat integration' => '', + // 'Enable Gravatar images' => '', + // 'Information' => '', + // 'Check two factor authentication code' => '', + // 'The two factor authentication code is not valid.' => '', + // 'The two factor authentication code is valid.' => '', + // 'Code' => '', + // 'Two factor authentication' => '', + // 'Enable/disable two factor authentication' => '', + // 'This QR code contains the key URI: ' => '', + // 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '', + // 'Check my code' => '', + // 'Secret key: ' => '', + // 'Test your device' => '', + // 'Assign a color when the task is moved to a specific column' => '', +); diff --git a/sources/app/Locale/zh_CN/translations.php b/sources/app/Locale/zh_CN/translations.php index cd1e2eb..5225cf1 100644 --- a/sources/app/Locale/zh_CN/translations.php +++ b/sources/app/Locale/zh_CN/translations.php @@ -1,6 +1,8 @@ '.', + 'number.thousands_separator' => ',', 'None' => '无', 'edit' => '编辑', 'Edit' => '编辑', @@ -379,7 +381,7 @@ return array( 'Created by %s' => '创建者:%s', 'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M', 'Tasks Export' => '任务导出', - 'Tasks exportation for "%s"' => '导出任务"%s"', + 'Tasks exportation for "%s"' => '导出"%s"的任务', 'Start Date' => '开始时间', 'End Date' => '结束时间', 'Execute' => '执行', @@ -408,13 +410,13 @@ return array( 'Comment updated' => '更新了评论', 'New comment posted by %s' => '%s 的新评论', 'List of due tasks for the project "%s"' => '项目"%s"的到期任务列表', - // 'New attachment' => '', - // 'New comment' => '', - // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', + 'New attachment' => '新建附件', + 'New comment' => '新建评论', + 'New subtask' => '新建子任务', + 'Subtask updated' => '子任务更新', + 'Task updated' => '任务更新', + 'Task closed' => '任务关闭', + 'Task opened' => '任务开启', '[%s][Due tasks]' => '[%s][到期任务]', '[Kanboard] Notification' => '[Kanboard] 通知', 'I want to receive notifications only for those projects:' => '我仅需要收到下面项目的通知:', @@ -498,9 +500,9 @@ return array( 'Task assignee change' => '任务分配变更', '%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s', '%s changed the assignee of the task %s to %s' => '%s 将任务 %s 分配给 %s', - // 'Column Change' => '', - // 'Position Change' => '', - // 'Assignee Change' => '', + 'Column Change' => '栏目变更', + 'Position Change' => '位置变更', + 'Assignee Change' => '负责人变更', 'New password for the user "%s"' => '用户"%s"的新密码', 'Choose an event' => '选择一个事件', 'Github commit received' => '收到了Github提交', @@ -607,8 +609,8 @@ return array( 'Default swimlane' => '默认泳道', 'Do you really want to remove this swimlane: "%s"?' => '确定要删除泳道:"%s"?', 'Inactive swimlanes' => '非活动泳道', - // 'Set project manager' => '', - // 'Set project member' => '', + 'Set project manager' => '设为项目经理', + 'Set project member' => '设为项目成员', 'Remove a swimlane' => '删除泳道', 'Rename' => '重命名', 'Show default swimlane' => '显示默认泳道', @@ -622,92 +624,92 @@ return array( 'Unable to remove this swimlane.' => '无法删除此泳道', 'Unable to update this swimlane.' => '无法更新此泳道', 'Your swimlane have been created successfully.' => '已经成功创建泳道。', - // 'Example: "Bug, Feature Request, Improvement"' => '', - // 'Default categories for new projects (Comma-separated)' => '', - // 'Gitlab commit received' => '', - // 'Gitlab issue opened' => '', - // 'Gitlab issue closed' => '', - // 'Gitlab webhooks' => '', - // 'Help on Gitlab webhooks' => '', - // 'Integrations' => '', - // 'Integration with third-party services' => '', - // 'Role for this project' => '', - // 'Project manager' => '', - // 'Project member' => '', - // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '', - // 'Gitlab Issue' => '', - // 'Subtask Id' => '', - // 'Subtasks' => '', - // 'Subtasks Export' => '', - // 'Subtasks exportation for "%s"' => '', - // 'Task Title' => '', - // 'Untitled' => '', - // 'Application default' => '', - // 'Language:' => '', - // 'Timezone:' => '', - // 'All columns' => '', - // 'Calendar for "%s"' => '', - // 'Filter by column' => '', - // 'Filter by status' => '', - // 'Calendar' => '', - // 'Next' => '', - // '#%d' => '', - // 'Filter by color' => '', - // 'Filter by swimlane' => '', - // 'All swimlanes' => '', - // 'All colors' => '', - // 'All status' => '', - // 'Add a comment logging moving the task between columns' => '', - // 'Moved to column %s' => '', - // 'Change description' => '', - // 'User dashboard' => '', - // 'Allow only one subtask in progress at the same time for a user' => '', - // 'Edit column "%s"' => '', - // 'Enable time tracking for subtasks' => '', - // 'Select the new status of the subtask: "%s"' => '', - // 'Subtask timesheet' => '', - // 'There is nothing to show.' => '', - // 'Time Tracking' => '', - // 'You already have one subtask in progress' => '', - // 'Which parts of the project do you want to duplicate?' => '', - // 'Change dashboard view' => '', - // 'Show/hide activities' => '', - // 'Show/hide projects' => '', - // 'Show/hide subtasks' => '', - // 'Show/hide tasks' => '', - // 'Disable login form' => '', - // 'Show/hide calendar' => '', - // 'User calendar' => '', - // 'Bitbucket commit received' => '', - // 'Bitbucket webhooks' => '', - // 'Help on Bitbucket webhooks' => '', - // 'Start' => '', - // 'End' => '', - // 'Task age in days' => '', - // 'Days in this column' => '', - // '%dd' => '', - // 'Add a link' => '', - // 'Add a new link' => '', - // 'Do you really want to remove this link: "%s"?' => '', - // 'Do you really want to remove this link with task #%d?' => '', - // 'Field required' => '', - // 'Link added successfully.' => '', - // 'Link updated successfully.' => '', - // 'Link removed successfully.' => '', - // 'Link labels' => '', - // 'Link modification' => '', - // 'Links' => '', - // 'Link settings' => '', - // 'Opposite label' => '', - // 'Remove a link' => '', - // 'Task\'s links' => '', - // 'The labels must be different' => '', - // 'There is no link.' => '', - // 'This label must be unique' => '', - // 'Unable to create your link.' => '', - // 'Unable to update your link.' => '', - // 'Unable to remove this link.' => '', - // 'relates to' => '', + 'Example: "Bug, Feature Request, Improvement"' => '示例:“缺陷,功能需求,提升', + 'Default categories for new projects (Comma-separated)' => '新项目的默认分类(用逗号分隔)', + 'Gitlab commit received' => '收到 Gitlab 提交', + 'Gitlab issue opened' => '开启 Gitlab 问题', + 'Gitlab issue closed' => '关闭 Gitlab 问题', + 'Gitlab webhooks' => 'Gitlab 网络钩子', + 'Help on Gitlab webhooks' => 'Gitlab 网络钩子帮助', + 'Integrations' => '整合', + 'Integration with third-party services' => '与第三方服务进行整合', + 'Role for this project' => '项目角色', + 'Project manager' => '项目管理员', + 'Project member' => '项目成员', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => '项目经理可以修改项目的设置,比标准用户多了一些权限', + 'Gitlab Issue' => 'Gitlab 问题', + 'Subtask Id' => '子任务 Id', + 'Subtasks' => '子任务', + 'Subtasks Export' => '子任务导出', + 'Subtasks exportation for "%s"' => '导出"%s"的子任务', + 'Task Title' => '任务标题', + 'Untitled' => '无标题', + 'Application default' => '程序默认', + 'Language:' => '语言:', + 'Timezone:' => '时区:', + 'All columns' => '全部栏目', + 'Calendar for "%s"' => '"%s"的日程表', + 'Filter by column' => '按栏目过滤', + 'Filter by status' => '按状态过滤', + 'Calendar' => '日程表', + 'Next' => '前进', + '#%d' => '#%d', + 'Filter by color' => '按颜色过滤', + 'Filter by swimlane' => '按泳道过滤', + 'All swimlanes' => '全部泳道', + 'All colors' => '全部颜色', + 'All status' => '全部状态', + 'Add a comment logging moving the task between columns' => '在不同栏目间移动任务时添加一个评论', + 'Moved to column %s' => '移动到栏目 %s', + 'Change description' => '修改描述', + 'User dashboard' => '用户仪表板', + 'Allow only one subtask in progress at the same time for a user' => '每用户同时仅有一个活动子任务', + 'Edit column "%s"' => '编辑栏目"%s"', + 'Enable time tracking for subtasks' => '启用子任务的时间记录', + 'Select the new status of the subtask: "%s"' => '选择子任务的新状态:"%s"', + 'Subtask timesheet' => '子任务时间', + 'There is nothing to show.' => '无内容。', + 'Time Tracking' => '时间记录', + 'You already have one subtask in progress' => '你已经有了一个进行中的子任务', + 'Which parts of the project do you want to duplicate?' => '要复制项目的哪些内容?', + 'Change dashboard view' => '修改仪表板视图', + 'Show/hide activities' => '显示/隐藏活动', + 'Show/hide projects' => '显示/隐藏项目', + 'Show/hide subtasks' => '显示/隐藏子任务', + 'Show/hide tasks' => '显示/隐藏任务', + 'Disable login form' => '禁用登录界面', + 'Show/hide calendar' => '显示/隐藏日程表', + 'User calendar' => '用户日程表', + 'Bitbucket commit received' => '收到Bitbucket提交', + 'Bitbucket webhooks' => 'Bitbucket网络钩子', + 'Help on Bitbucket webhooks' => 'Bitbucket网络钩子帮助', + 'Start' => '开始', + 'End' => '结束', + 'Task age in days' => '任务存在天数', + 'Days in this column' => '在此栏目的天数', + '%dd' => '%d天', + 'Add a link' => '添加一个关联', + 'Add a new link' => '添加一个新关联', + 'Do you really want to remove this link: "%s"?' => '确认要删除此关联吗:"%s"?', + 'Do you really want to remove this link with task #%d?' => '确认要删除到任务 #%d 的关联吗?', + 'Field required' => '必须的字段', + 'Link added successfully.' => '成功添加关联。', + 'Link updated successfully.' => '成功更新关联。', + 'Link removed successfully.' => '成功删除关联。', + 'Link labels' => '关联标签', + 'Link modification' => '关联修改', + 'Links' => '关联', + 'Link settings' => '关联设置', + 'Opposite label' => '反向标签', + 'Remove a link' => '删除关联', + 'Task\'s links' => '任务的关联', + 'The labels must be different' => '标签不能一样', + 'There is no link.' => '没有关联', + 'This label must be unique' => '关联必须唯一', + 'Unable to create your link.' => '无法创建关联。', + 'Unable to update your link.' => '无法更新关联。', + 'Unable to remove this link.' => '无法删除关联。', + 'relates to' => '关联到', // 'blocks' => '', // 'is blocked by' => '', // 'duplicates' => '', @@ -718,20 +720,130 @@ return array( // 'is a milestone of' => '', // 'fixes' => '', // 'is fixed by' => '', - // 'This task' => '', - // '<1h' => '', - // '%dh' => '', + 'This task' => '此任务', + '<1h' => '<1h', + '%dh' => '%h', // '%b %e' => '', - // 'Expand tasks' => '', - // 'Collapse tasks' => '', - // 'Expand/collapse tasks' => '', - // 'Close dialog box' => '', - // 'Submit a form' => '', - // 'Board view' => '', - // 'Keyboard shortcuts' => '', - // 'Open board switcher' => '', - // 'Application' => '', - // 'Filter recently updated' => '', + 'Expand tasks' => '展开任务', + 'Collapse tasks' => '收缩任务', + 'Expand/collapse tasks' => '展开/收缩任务', + 'Close dialog box' => '关闭对话框', + 'Submit a form' => '提交表单', + 'Board view' => '面板视图', + 'Keyboard shortcuts' => '键盘快捷方式', + 'Open board switcher' => '打开面板切换器', + 'Application' => '应用程序', + 'Filter recently updated' => '过滤最近的更新', // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + 'More filters' => '更多过滤', + 'Compact view' => '紧凑视图', + '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' => '注释', + 'All day' => '全天', + 'Day' => '日期', + 'Manage timetable' => '管理时间表', + 'Overtime timetable' => '', + 'Time off timetable' => '加班时间表', + 'Timetable' => '时间表', + 'Work timetable' => '工作时间表', + 'Week timetable' => '周时间表', + 'Day timetable' => '日时间表', + 'From' => '从', + 'To' => '到', + 'Time slot created successfully.' => '成功创建时间段。', + 'Unable to save this time slot.' => '无法保存此时间段。', + 'Time slot removed successfully.' => '成功删除时间段。', + 'Unable to remove this time slot.' => '无法删除此时间段。', + 'Do you really want to remove this time slot?' => '确认要删除此时间段吗?', + 'Remove time slot' => '删除时间段', + 'Add new time slot' => '添加新时间段', + 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '如果在放假和加班计划中选择全天,则会使用这里配置的时间段。', + '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' => '移除负责人时移动到其它栏目', + 'Source column' => '原栏目', + 'Show subtask estimates in the user calendar' => '在用户日历中显示子任务预估', + 'Transitions' => '变更', + 'Executer' => '执行者', + 'Time spent in the column' => '栏目中的时间消耗', + 'Task transitions' => '任务变更', + 'Task transitions export' => '导出任务变更', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '此报告记录任务的变更,包含日期、用户和时间消耗。', + 'Currency rates' => '汇率', + '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.' => '无法添加此汇率', + 'Send notifications to a Slack channel' => '发送通知到 Slack 频道', + 'Webhook URL' => '网络钩子 URL', + 'Help on Slack integration' => 'Slack 整合帮助', + '%s remove the assignee of the task %s' => '%s删除了任务%s的负责人', + 'Send notifications to Hipchat' => '发送通知到 Hipchat', + 'API URL' => 'API URL', + 'Room API ID or name' => '房间 API ID 或名称', + 'Room notification token' => '房间通知令牌', + 'Help on Hipchat integration' => 'Hipchat 整合帮助', + 'Enable Gravatar images' => '启用 Gravatar 图像', + 'Information' => '信息', + 'Check two factor authentication code' => '检查双重认证码', + 'The two factor authentication code is not valid.' => '双重认证码不正确。', + 'The two factor authentication code is valid.' => '双重认证码正确。', + 'Code' => '认证码', + 'Two factor authentication' => '双重认证', + 'Enable/disable two factor authentication' => '启用/禁用双重认证', + 'This QR code contains the key URI: ' => '此二维码包含密码 URI:', + 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '将密码保存到 TOTP 软件(例如Google 认证或 FreeOTP)', + 'Check my code' => '检查我的认证码', + 'Secret key: ' => '密码:', + 'Test your device' => '测试设备', + 'Assign a color when the task is moved to a specific column' => '任务移动到指定栏目时设置颜色', ); diff --git a/sources/app/Model/Acl.php b/sources/app/Model/Acl.php index 9fc8174..403c45d 100644 --- a/sources/app/Model/Acl.php +++ b/sources/app/Model/Acl.php @@ -56,6 +56,7 @@ class Acl extends Base 'export' => array('tasks', 'subtasks', 'summary'), 'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'), 'swimlane' => '*', + 'budget' => '*', ); /** @@ -70,6 +71,8 @@ class Acl extends Base 'config' => '*', 'link' => '*', 'project' => array('remove'), + 'hourlyrate' => '*', + 'currency' => '*', ); /** diff --git a/sources/app/Model/Action.php b/sources/app/Model/Action.php index 6fb2a2f..4489bca 100644 --- a/sources/app/Model/Action.php +++ b/sources/app/Model/Action.php @@ -45,6 +45,9 @@ class Action extends Base 'TaskAssignCurrentUser' => t('Assign the task to the person who does the action'), 'TaskDuplicateAnotherProject' => t('Duplicate the task to another project'), 'TaskMoveAnotherProject' => t('Move the task to another project'), + 'TaskMoveColumnAssigned' => t('Move the task to another column when assigned to a user'), + 'TaskMoveColumnUnAssigned' => t('Move the task to another column when assignee is cleared'), + 'TaskAssignColorColumn' => t('Assign a color when the task is moved to a specific column'), 'TaskAssignColorUser' => t('Assign a color to a specific user'), 'TaskAssignColorCategory' => t('Assign automatically a color based on a category'), 'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'), @@ -211,7 +214,7 @@ class Action extends Base * * @access public * @param array $values Required parameters to save an action - * @return bool Success or not + * @return boolean|integer */ public function create(array $values) { @@ -248,7 +251,7 @@ class Action extends Base // $this->container['fileCache']->remove('proxy_action_getAll'); - return true; + return $action_id; } /** diff --git a/sources/app/Model/Base.php b/sources/app/Model/Base.php index f836231..b0d8240 100644 --- a/sources/app/Model/Base.php +++ b/sources/app/Model/Base.php @@ -16,6 +16,7 @@ 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\Comment $comment * @property \Model\CommentHistory $commentHistory @@ -42,7 +43,11 @@ use Pimple\Container; * @property \Model\TaskLink $taskLink * @property \Model\TaskPosition $taskPosition * @property \Model\TaskValidator $taskValidator - * @property \Model\TimeTracking $timeTracking + * @property \Model\Timetable $timetable + * @property \Model\TimetableDay $timetableDay + * @property \Model\TimetableExtra $timetableExtra + * @property \Model\TimetableOff $timetableOfff + * @property \Model\TimetableWeek $timetableWeek * @property \Model\SubtaskTimeTracking $subtaskTimeTracking * @property \Model\User $user * @property \Model\UserSession $userSession diff --git a/sources/app/Model/Budget.php b/sources/app/Model/Budget.php new file mode 100644 index 0000000..d74dd87 --- /dev/null +++ b/sources/app/Model/Budget.php @@ -0,0 +1,214 @@ +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) + ->filter(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/sources/app/Model/Comment.php b/sources/app/Model/Comment.php index a36f2b4..844f0c8 100644 --- a/sources/app/Model/Comment.php +++ b/sources/app/Model/Comment.php @@ -47,7 +47,8 @@ class Comment extends Base self::TABLE.'.user_id', self::TABLE.'.comment', User::TABLE.'.username', - User::TABLE.'.name' + User::TABLE.'.name', + User::TABLE.'.email' ) ->join(User::TABLE, 'id', 'user_id') ->orderBy(self::TABLE.'.date', 'ASC') diff --git a/sources/app/Model/Config.php b/sources/app/Model/Config.php index 48640f4..736ae08 100644 --- a/sources/app/Model/Config.php +++ b/sources/app/Model/Config.php @@ -21,6 +21,28 @@ class Config extends Base */ const TABLE = 'settings'; + /** + * Get available currencies + * + * @access public + * @return array + */ + public function getCurrencies() + { + return array( + 'USD' => t('USD - US Dollar'), + 'EUR' => t('EUR - Euro'), + 'GBP' => t('GBP - British Pound'), + 'CHF' => t('CHF - Swiss Francs'), + 'CAD' => t('CAD - Canadian Dollar'), + 'AUD' => t('AUD - Australian Dollar'), + 'NZD' => t('NZD - New Zealand Dollar'), + 'INR' => t('INR - Indian Rupee'), + 'JPY' => t('JPY - Japanese Yen'), + 'RSD' => t('RSD - Serbian dinar'), + ); + } + /** * Get available timezones * @@ -58,11 +80,14 @@ class Config extends Base 'fr_FR' => 'Français', 'it_IT' => 'Italiano', 'hu_HU' => 'Magyar', + 'nl_NL' => 'Nederlands', 'pl_PL' => 'Polski', 'pt_BR' => 'Português (Brasil)', 'ru_RU' => 'Русский', + 'sr_Latn_RS' => 'Srpski', 'fi_FI' => 'Suomi', 'sv_SE' => 'Svenska', + 'tr_TR' => 'Türkçe', 'zh_CN' => '中文(简体)', 'ja_JP' => '日本語', 'th_TH' => 'ไทย', @@ -99,6 +124,7 @@ class Config extends Base 'zh_CN' => 'zh-cn', 'ja_JP' => 'ja', 'th_TH' => 'th', + 'tr_TR' => 'tr', ); $lang = $this->getCurrentLanguage(); diff --git a/sources/app/Model/Currency.php b/sources/app/Model/Currency.php new file mode 100644 index 0000000..bc42333 --- /dev/null +++ b/sources/app/Model/Currency.php @@ -0,0 +1,104 @@ +db->table(self::TABLE)->findAll(); + } + + /** + * Calculate the price for the reference currency + * + * @access public + * @return array + */ + public function getPrice($currency, $price) + { + static $rates = null; + $reference = $this->config->get('application_currency', 'USD'); + + if ($reference !== $currency) { + $rates = $rates === null ? $this->db->hashtable(self::TABLE)->getAll('currency', 'rate') : array(); + $rate = isset($rates[$currency]) ? $rates[$currency] : 1; + + return $rate * $price; + } + + return $price; + } + + /** + * Add a new currency rate + * + * @access public + * @param string $currency + * @param float $rate + * @return boolean|integer + */ + public function create($currency, $rate) + { + if ($this->db->table(self::TABLE)->eq('currency', $currency)->count() === 1) { + return $this->update($currency, $rate); + } + + return $this->persist(self::TABLE, compact('currency', 'rate')); + } + + /** + * Update a currency rate + * + * @access public + * @param string $currency + * @param float $rate + * @return boolean + */ + public function update($currency, $rate) + { + return $this->db->table(self::TABLE)->eq('currency', $currency)->update(array('rate' => $rate)); + } + + /** + * Validate + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validate(array $values) + { + $v = new Validator($values, array( + new Validators\Required('currency', t('Field required')), + new Validators\Required('rate', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/sources/app/Model/DateParser.php b/sources/app/Model/DateParser.php index 8a4d3ed..a0d10a3 100644 --- a/sources/app/Model/DateParser.php +++ b/sources/app/Model/DateParser.php @@ -12,6 +12,47 @@ use DateTime; */ class DateParser extends Base { + /** + * Return true if the date is within the date range + * + * @access public + * @param DateTime $date + * @param DateTime $start + * @param DateTime $end + * @return boolean + */ + public function withinDateRange(DateTime $date, DateTime $start, DateTime $end) + { + return $date >= $start && $date <= $end; + } + + /** + * Get the total number of hours between 2 datetime objects + * Minutes are rounded to the nearest quarter + * + * @access public + * @param DateTime $d1 + * @param DateTime $d2 + * @return float + */ + public function getHours(DateTime $d1, DateTime $d2) + { + $seconds = $this->getRoundedSeconds(abs($d1->getTimestamp() - $d2->getTimestamp())); + return round($seconds / 3600, 2); + } + + /** + * Round the timestamp to the nearest quarter + * + * @access public + * @param integer $seconds Timestamp + * @return integer + */ + public function getRoundedSeconds($seconds) + { + return (int) round($seconds / (15 * 60)) * (15 * 60); + } + /** * Return a timestamp if the given date format is correct otherwise return 0 * diff --git a/sources/app/Model/File.php b/sources/app/Model/File.php index 1b9351d..a8cce9f 100644 --- a/sources/app/Model/File.php +++ b/sources/app/Model/File.php @@ -17,7 +17,7 @@ class File extends Base * * @var string */ - const TABLE = 'task_has_files'; + const TABLE = 'files'; /** * Events @@ -112,6 +112,38 @@ class File extends Base ->findAll(); } + /** + * Get all images for a given task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllImages($task_id) + { + return $this->db->table(self::TABLE) + ->eq('task_id', $task_id) + ->eq('is_image', 1) + ->asc('name') + ->findAll(); + } + + /** + * Get all files without images for a given task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllDocuments($task_id) + { + return $this->db->table(self::TABLE) + ->eq('task_id', $task_id) + ->eq('is_image', 0) + ->asc('name') + ->findAll(); + } + /** * Check if a filename is an image * @@ -121,7 +153,17 @@ class File extends Base */ public function isImage($filename) { - return getimagesize($filename) !== false; + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + switch ($extension) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return true; + } + + return false; } /** @@ -188,7 +230,7 @@ class File extends Base $task_id, $original_filename, $destination_filename, - $this->isImage(FILES_DIR.$destination_filename) + $this->isImage($original_filename) ); } } diff --git a/sources/app/Model/HourlyRate.php b/sources/app/Model/HourlyRate.php new file mode 100644 index 0000000..1550bda --- /dev/null +++ b/sources/app/Model/HourlyRate.php @@ -0,0 +1,121 @@ +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/sources/app/Model/ProjectActivity.php b/sources/app/Model/ProjectActivity.php index 652cc84..27f1cfc 100644 --- a/sources/app/Model/ProjectActivity.php +++ b/sources/app/Model/ProjectActivity.php @@ -81,22 +81,68 @@ class ProjectActivity extends Base return array(); } - $query = $this->db->table(self::TABLE) - ->columns( - self::TABLE.'.*', - User::TABLE.'.username AS author_username', - User::TABLE.'.name AS author_name' - ) - ->in('project_id', $project_ids) - ->join(User::TABLE, 'id', 'creator_id') - ->desc(self::TABLE.'.id') - ->limit($limit); + $query = $this + ->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.*', + User::TABLE.'.username AS author_username', + User::TABLE.'.name AS author_name', + User::TABLE.'.email' + ) + ->in('project_id', $project_ids) + ->join(User::TABLE, 'id', 'creator_id') + ->desc(self::TABLE.'.id') + ->limit($limit); - if(!is_null($start)){ + return $this->getEvents($query, $start, $end); + } + + /** + * Get all events for the given task + * + * @access public + * @param integer $task_id Task id + * @param integer $limit Maximum events number + * @param integer $start Timestamp of earliest activity + * @param integer $end Timestamp of latest activity + * @return array + */ + public function getTask($task_id, $limit = 50, $start = null, $end = null) + { + $query = $this + ->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.*', + User::TABLE.'.username AS author_username', + User::TABLE.'.name AS author_name', + User::TABLE.'.email' + ) + ->eq('task_id', $task_id) + ->join(User::TABLE, 'id', 'creator_id') + ->desc(self::TABLE.'.id') + ->limit($limit); + + return $this->getEvents($query, $start, $end); + } + + /** + * Common function to return events + * + * @access public + * @param \PicoDb\Table $query PicoDb Query + * @param integer $start Timestamp of earliest activity + * @param integer $end Timestamp of latest activity + * @return array + */ + private function getEvents(\PicoDb\Table $query, $start, $end) + { + if (! is_null($start)){ $query->gte('date_creation', $start); } - if(!is_null($end)){ + if (! is_null($end)){ $query->lte('date_creation', $end); } @@ -162,7 +208,13 @@ class ProjectActivity extends Base { switch ($event['event_name']) { case Task::EVENT_ASSIGNEE_CHANGE: - return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $event['task']['assignee_name'] ?: $event['task']['assignee_username']); + $assignee = $event['task']['assignee_name'] ?: $event['task']['assignee_username']; + + if (! empty($assignee)) { + return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $assignee); + } + + return t('%s remove the assignee of the task %s', $event['author'], e('#%d', $event['task']['id'])); case Task::EVENT_UPDATE: return t('%s updated the task #%d', $event['author'], $event['task']['id']); case Task::EVENT_CREATE: diff --git a/sources/app/Model/ProjectPermission.php b/sources/app/Model/ProjectPermission.php index 12bd930..d4f44f6 100644 --- a/sources/app/Model/ProjectPermission.php +++ b/sources/app/Model/ProjectPermission.php @@ -316,7 +316,10 @@ class ProjectPermission extends Base { return $this->db ->hashtable(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->join(self::TABLE, 'project_id', 'id') ->getAll('projects.id', 'name'); } @@ -332,7 +335,10 @@ class ProjectPermission extends Base { return $this->db ->table(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->join(self::TABLE, 'project_id', 'id') ->findAllByColumn('projects.id'); } @@ -348,7 +354,10 @@ class ProjectPermission extends Base { return $this->db ->table(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->eq(Project::TABLE.'.is_active', Project::ACTIVE) ->join(self::TABLE, 'project_id', 'id') ->findAllByColumn('projects.id'); @@ -365,7 +374,10 @@ class ProjectPermission extends Base { return $this->db ->hashtable(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->eq(Project::TABLE.'.is_active', Project::ACTIVE) ->join(self::TABLE, 'project_id', 'id') ->getAll('projects.id', 'name'); diff --git a/sources/app/Model/Subtask.php b/sources/app/Model/Subtask.php index 048594b..492f3a7 100644 --- a/sources/app/Model/Subtask.php +++ b/sources/app/Model/Subtask.php @@ -19,7 +19,7 @@ class Subtask extends Base * * @var string */ - const TABLE = 'task_has_subtasks'; + const TABLE = 'subtasks'; /** * Task "done" status @@ -98,6 +98,7 @@ class Subtask extends Base Subtask::TABLE.'.*', Task::TABLE.'.project_id', Task::TABLE.'.color_id', + Task::TABLE.'.title AS task_name', Project::TABLE.'.name AS project_name' ) ->eq('user_id', $user_id) @@ -122,7 +123,7 @@ class Subtask extends Base ->eq('task_id', $task_id) ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name') ->join(User::TABLE, 'id', 'user_id') - ->asc(self::TABLE.'.id') + ->asc(self::TABLE.'.position') ->filter(array($this, 'addStatusName')) ->findAll(); } @@ -163,6 +164,22 @@ class Subtask extends Base $this->resetFields($values, array('time_estimated', 'time_spent')); } + /** + * Get the position of the last column for a given project + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getLastPosition($task_id) + { + return (int) $this->db + ->table(self::TABLE) + ->eq('task_id', $task_id) + ->desc('position') + ->findOneColumn('position'); + } + /** * Create a new subtask * @@ -173,6 +190,8 @@ class Subtask extends Base public function create(array $values) { $this->prepare($values); + $values['position'] = $this->getLastPosition($values['task_id']) + 1; + $subtask_id = $this->persist(self::TABLE, $values); if ($subtask_id) { @@ -208,6 +227,64 @@ class Subtask extends Base return $result; } + /** + * Move a subtask down, increment the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveDown($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) { + + $position = ++$subtasks[$subtask_id]; + $subtasks[$positions[$position]]--; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** + * Move a subtask up, decrement the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveUp($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] > 1) { + + $position = --$subtasks[$subtask_id]; + $subtasks[$positions[$position]]++; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + /** * Change the status of subtask * @@ -286,9 +363,9 @@ class Subtask extends Base return $this->db->transaction(function ($db) use ($src_task_id, $dst_task_id) { $subtasks = $db->table(Subtask::TABLE) - ->columns('title', 'time_estimated') + ->columns('title', 'time_estimated', 'position') ->eq('task_id', $src_task_id) - ->asc('id') // Explicit sorting for postgresql + ->asc('position') ->findAll(); foreach ($subtasks as &$subtask) { @@ -380,7 +457,7 @@ class Subtask extends Base return array( new Validators\Integer('id', t('The subtask id must be an integer')), new Validators\Integer('task_id', t('The task id must be an integer')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 100), 100), + new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), new Validators\Integer('user_id', t('The user id must be an integer')), new Validators\Integer('status', t('The status must be an integer')), new Validators\Numeric('time_estimated', t('The time must be a numeric value')), diff --git a/sources/app/Model/SubtaskForecast.php b/sources/app/Model/SubtaskForecast.php new file mode 100644 index 0000000..0cb3175 --- /dev/null +++ b/sources/app/Model/SubtaskForecast.php @@ -0,0 +1,124 @@ +db + ->table(Subtask::TABLE) + ->columns(Subtask::TABLE.'.id', Task::TABLE.'.project_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title', Subtask::TABLE.'.time_estimated') + ->join(Task::TABLE, 'id', 'task_id') + ->asc(Task::TABLE.'.position') + ->asc(Subtask::TABLE.'.position') + ->gt(Subtask::TABLE.'.time_estimated', 0) + ->eq(Subtask::TABLE.'.status', Subtask::STATUS_TODO) + ->eq(Subtask::TABLE.'.user_id', $user_id) + ->findAll(); + } + + /** + * Get the start date for the forecast + * + * @access public + * @param integer $user_id + * @return array + */ + public function getStartDate($user_id) + { + $subtask = $this->db->table(Subtask::TABLE) + ->columns(Subtask::TABLE.'.time_estimated', SubtaskTimeTracking::TABLE.'.start') + ->eq(SubtaskTimeTracking::TABLE.'.user_id', $user_id) + ->eq(SubtaskTimeTracking::TABLE.'.end', 0) + ->status('status', Subtask::STATUS_INPROGRESS) + ->join(SubtaskTimeTracking::TABLE, 'subtask_id', 'id') + ->findOne(); + + if ($subtask && $subtask['time_estimated'] && $subtask['start']) { + return date('Y-m-d H:i', $subtask['start'] + $subtask['time_estimated'] * 3600); + } + + return date('Y-m-d H:i'); + } + + /** + * Get all calendar events according to the user timetable and the subtasks estimates + * + * @access public + * @param integer $user_id + * @param string $end End date of the calendar + * @return array + */ + public function getCalendarEvents($user_id, $end) + { + $events = array(); + $start_date = new DateTime($this->getStartDate($user_id)); + $timetable = $this->timetable->calculate($user_id, $start_date, new DateTime($end)); + $subtasks = $this->getSubtasks($user_id); + $total = count($subtasks); + $offset = 0; + + foreach ($timetable as $slot) { + + $interval = $this->dateParser->getHours($slot[0], $slot[1]); + $start = $slot[0]->getTimestamp(); + + if ($slot[0] < $start_date) { + + if (! $this->dateParser->withinDateRange($start_date, $slot[0], $slot[1])) { + continue; + } + + $interval = $this->dateParser->getHours(new DateTime, $slot[1]); + $start = time(); + } + + while ($offset < $total) { + + $event = array( + 'id' => $subtasks[$offset]['id'].'-'.$subtasks[$offset]['task_id'].'-'.$offset, + 'subtask_id' => $subtasks[$offset]['id'], + 'title' => t('#%d', $subtasks[$offset]['task_id']).' '.$subtasks[$offset]['title'], + 'url' => $this->helper->url('task', 'show', array('task_id' => $subtasks[$offset]['task_id'], 'project_id' => $subtasks[$offset]['project_id'])), + 'editable' => false, + 'start' => date('Y-m-d\TH:i:s', $start), + ); + + if ($subtasks[$offset]['time_estimated'] <= $interval) { + + $start += $subtasks[$offset]['time_estimated'] * 3600; + $interval -= $subtasks[$offset]['time_estimated']; + $offset++; + + $event['end'] = date('Y-m-d\TH:i:s', $start); + $events[] = $event; + } + else { + $subtasks[$offset]['time_estimated'] -= $interval; + $event['end'] = $slot[1]->format('Y-m-d\TH:i:s'); + $events[] = $event; + break; + } + } + } + + return $events; + } +} diff --git a/sources/app/Model/SubtaskTimeTracking.php b/sources/app/Model/SubtaskTimeTracking.php index 8b197c4..a984533 100644 --- a/sources/app/Model/SubtaskTimeTracking.php +++ b/sources/app/Model/SubtaskTimeTracking.php @@ -2,6 +2,8 @@ namespace Model; +use DateTime; + /** * Subtask timesheet * @@ -33,6 +35,7 @@ class SubtaskTimeTracking extends Base self::TABLE.'.subtask_id', self::TABLE.'.end', self::TABLE.'.start', + self::TABLE.'.time_spent', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', Task::TABLE.'.title AS task_title', @@ -60,6 +63,7 @@ class SubtaskTimeTracking extends Base self::TABLE.'.subtask_id', self::TABLE.'.end', self::TABLE.'.start', + self::TABLE.'.time_spent', self::TABLE.'.user_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', @@ -89,6 +93,7 @@ class SubtaskTimeTracking extends Base self::TABLE.'.subtask_id', self::TABLE.'.end', self::TABLE.'.start', + self::TABLE.'.time_spent', self::TABLE.'.user_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', @@ -133,6 +138,8 @@ class SubtaskTimeTracking extends Base ->addCondition($this->getCalendarCondition($start, $end)) ->findAll(); + $result = $this->timetable->calculateEventsIntersect($user_id, $result, $start, $end); + return $this->toCalendarEvents($result); } @@ -235,7 +242,11 @@ class SubtaskTimeTracking extends Base */ public function logEndTime($subtask_id, $user_id) { - $this->updateSubtaskTimeSpent($subtask_id, $user_id); + $time_spent = $this->getTimeSpent($subtask_id, $user_id); + + if ($time_spent > 0) { + $this->updateSubtaskTimeSpent($subtask_id, $time_spent); + } return $this->db ->table(self::TABLE) @@ -243,10 +254,59 @@ class SubtaskTimeTracking extends Base ->eq('user_id', $user_id) ->eq('end', 0) ->update(array( - 'end' => time() + 'end' => time(), + 'time_spent' => $time_spent, )); } + /** + * Calculate the time spent when the clock is stopped + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @return float + */ + public function getTimeSpent($subtask_id, $user_id) + { + $start_time = $this->db + ->table(self::TABLE) + ->eq('subtask_id', $subtask_id) + ->eq('user_id', $user_id) + ->eq('end', 0) + ->findOneColumn('start'); + + if ($start_time) { + + $start = new DateTime; + $start->setTimestamp($start_time); + + return $this->timetable->calculateEffectiveDuration($user_id, $start, new DateTime); + } + + return 0; + } + + /** + * Update subtask time spent + * + * @access public + * @param integer $subtask_id + * @param float $time_spent + * @return bool + */ + public function updateSubtaskTimeSpent($subtask_id, $time_spent) + { + $subtask = $this->subtask->getById($subtask_id); + + // Fire the event subtask.update + return $this->subtask->update(array( + 'id' => $subtask['id'], + 'time_spent' => $subtask['time_spent'] + $time_spent, + 'task_id' => $subtask['task_id'], + )); + } + /** * Update task time tracking based on subtasks time tracking * @@ -289,31 +349,4 @@ class SubtaskTimeTracking extends Base ) ->findOne(); } - - /** - * Update subtask time spent based on the punch clock table - * - * @access public - * @param integer $subtask_id - * @param integer $user_id - * @return bool - */ - public function updateSubtaskTimeSpent($subtask_id, $user_id) - { - $start_time = $this->db - ->table(self::TABLE) - ->eq('subtask_id', $subtask_id) - ->eq('user_id', $user_id) - ->eq('end', 0) - ->findOneColumn('start'); - - $subtask = $this->subtask->getById($subtask_id); - - return $start_time && - $this->subtask->update(array( // Fire the event subtask.update - 'id' => $subtask['id'], - 'time_spent' => $subtask['time_spent'] + round((time() - $start_time) / 3600, 1), - 'task_id' => $subtask['task_id'], - )); - } } diff --git a/sources/app/Model/Swimlane.php b/sources/app/Model/Swimlane.php index c9bc43e..cf2103c 100644 --- a/sources/app/Model/Swimlane.php +++ b/sources/app/Model/Swimlane.php @@ -74,6 +74,22 @@ class Swimlane extends Base ->findOneColumn('id'); } + /** + * Get a swimlane by the project and the name + * + * @access public + * @param integer $project_id Project id + * @param string $name Swimlane name + * @return array + */ + public function getByName($project_id, $name) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('name', $name) + ->findAll(); + } + /** * Get default swimlane properties * @@ -83,10 +99,16 @@ class Swimlane extends Base */ public function getDefault($project_id) { - return $this->db->table(Project::TABLE) - ->eq('id', $project_id) - ->columns('id', 'default_swimlane', 'show_default_swimlane') - ->findOne(); + $result = $this->db->table(Project::TABLE) + ->eq('id', $project_id) + ->columns('id', 'default_swimlane', 'show_default_swimlane') + ->findOne(); + + if ($result['default_swimlane'] === 'Default swimlane') { + $result['default_swimlane'] = t($result['default_swimlane']); + } + + return $result; } /** @@ -150,6 +172,11 @@ class Swimlane extends Base ->findOneColumn('default_swimlane'); if ($default_swimlane) { + + if ($default_swimlane === 'Default swimlane') { + $default_swimlane = t($default_swimlane); + } + array_unshift($swimlanes, array('id' => 0, 'name' => $default_swimlane)); } @@ -167,14 +194,17 @@ class Swimlane extends Base public function getList($project_id, $prepend = false) { $swimlanes = array(); - $swimlanes[] = $this->db->table(Project::TABLE)->eq('id', $project_id)->findOneColumn('default_swimlane'); + $default = $this->db->table(Project::TABLE)->eq('id', $project_id)->eq('show_default_swimlane', 1)->findOneColumn('default_swimlane'); - $swimlanes = array_merge( - $swimlanes, - $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->orderBy('name', 'asc')->getAll('id', 'name') - ); + if ($prepend) { + $swimlanes[-1] = t('All swimlanes'); + } - return $prepend ? array(-1 => t('All swimlanes')) + $swimlanes : $swimlanes; + if (! empty($default)) { + $swimlanes[0] = $default === 'Default swimlane' ? t($default) : $default; + } + + return $swimlanes + $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->orderBy('position', 'asc')->getAll('id', 'name'); } /** diff --git a/sources/app/Model/TaskFinder.php b/sources/app/Model/TaskFinder.php index 98ece4e..7216e92 100644 --- a/sources/app/Model/TaskFinder.php +++ b/sources/app/Model/TaskFinder.php @@ -80,11 +80,11 @@ class TaskFinder extends Base return $this->db ->table(Task::TABLE) ->columns( - '(SELECT count(*) FROM comments WHERE task_id=tasks.id) AS nb_comments', - '(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files', - '(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id) AS nb_subtasks', - '(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id AND status=2) AS nb_completed_subtasks', - '(SELECT count(*) FROM ' . TaskLink::TABLE . ' WHERE ' . TaskLink::TABLE . '.task_id = tasks.id) AS nb_links', + '(SELECT count(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT count(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT count(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', 'tasks.id', 'tasks.reference', 'tasks.title', diff --git a/sources/app/Model/TaskLink.php b/sources/app/Model/TaskLink.php index f8e9f99..6239137 100644 --- a/sources/app/Model/TaskLink.php +++ b/sources/app/Model/TaskLink.php @@ -4,6 +4,7 @@ namespace Model; use SimpleValidator\Validator; use SimpleValidator\Validators; +use PicoDb\Table; /** * TaskLink model @@ -57,6 +58,7 @@ class TaskLink extends Base ->join(Link::TABLE, 'id', 'link_id') ->join(Task::TABLE, 'id', 'opposite_task_id') ->join(Board::TABLE, 'id', 'column_id', Task::TABLE) + ->orderBy(Link::TABLE.'.id ASC, '.Board::TABLE.'.position ASC, '.Task::TABLE.'.is_active DESC, '.Task::TABLE.'.id', Table::SORT_ASC) ->findAll(); } diff --git a/sources/app/Model/TaskPosition.php b/sources/app/Model/TaskPosition.php index 1e49543..ab5fe43 100644 --- a/sources/app/Model/TaskPosition.php +++ b/sources/app/Model/TaskPosition.php @@ -21,21 +21,24 @@ class TaskPosition extends Base * @param integer $column_id Column id * @param integer $position Position (must be >= 1) * @param integer $swimlane_id Swimlane id + * @param boolean $fire_events Fire events * @return boolean */ - public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0) + public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true) { $original_task = $this->taskFinder->getById($task_id); $result = $this->calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id); if ($result) { - + if ($original_task['swimlane_id'] != $swimlane_id) { $this->calculateAndSave($project_id, 0, $column_id, 1, $original_task['swimlane_id']); } - $this->fireEvents($original_task, $column_id, $position, $swimlane_id); + if ($fire_events) { + $this->fireEvents($original_task, $column_id, $position, $swimlane_id); + } } return $result; @@ -140,6 +143,9 @@ class TaskPosition extends Base 'position' => $new_position, 'column_id' => $new_column_id, 'swimlane_id' => $new_swimlane_id, + 'src_column_id' => $task['column_id'], + 'dst_column_id' => $new_column_id, + 'date_moved' => $task['date_moved'], ); if ($task['swimlane_id'] != $new_swimlane_id) { diff --git a/sources/app/Model/Timetable.php b/sources/app/Model/Timetable.php new file mode 100644 index 0000000..6ddf826 --- /dev/null +++ b/sources/app/Model/Timetable.php @@ -0,0 +1,356 @@ +setTime(0, 0); + + $end_dt = new DateTime($end); + $end_dt->setTime(23, 59); + + $timetable = $this->calculate($user_id, $start_dt, $end_dt); + + // The user has no timetable + if (empty($this->week)) { + return $events; + } + + $results = array(); + + foreach ($events as $event) { + $results = array_merge($results, $this->calculateEventIntersect($event, $timetable)); + } + + return $results; + } + + /** + * Get a serie of events based on the timetable and the provided event + * + * @access public + * @param array $event + * @param array $timetable + * @return array + */ + public function calculateEventIntersect(array $event, array $timetable) + { + $events = array(); + + foreach ($timetable as $slot) { + + $start_ts = $slot[0]->getTimestamp(); + $end_ts = $slot[1]->getTimestamp(); + + if ($start_ts > $event['end']) { + break; + } + + if ($event['start'] <= $start_ts) { + $event['start'] = $start_ts; + } + + if ($event['start'] >= $start_ts && $event['start'] <= $end_ts) { + + if ($event['end'] >= $end_ts) { + $events[] = array_merge($event, array('end' => $end_ts)); + } + else { + $events[] = $event; + break; + } + } + } + + return $events; + } + + /** + * Calculate effective worked hours by taking into consideration the timetable + * + * @access public + * @param integer $user_id + * @param \DateTime $start + * @param \DateTime $end + * @return float + */ + public function calculateEffectiveDuration($user_id, DateTime $start, DateTime $end) + { + $end_timetable = clone($end); + $end_timetable->setTime(23, 59); + + $timetable = $this->calculate($user_id, $start, $end_timetable); + $found_start = false; + $hours = 0; + + // The user has no timetable + if (empty($this->week)) { + return $this->dateParser->getHours($start, $end); + } + + foreach ($timetable as $slot) { + + $isStartSlot = $this->dateParser->withinDateRange($start, $slot[0], $slot[1]); + $isEndSlot = $this->dateParser->withinDateRange($end, $slot[0], $slot[1]); + + // Start and end are within the same time slot + if ($isStartSlot && $isEndSlot) { + return $this->dateParser->getHours($start, $end); + } + + // We found the start slot + if (! $found_start && $isStartSlot) { + $found_start = true; + $hours = $this->dateParser->getHours($start, $slot[1]); + } + else if ($found_start) { + + // We found the end slot + if ($isEndSlot) { + $hours += $this->dateParser->getHours($slot[0], $end); + break; + } + else { + + // Sum hours of the intermediate time slots + $hours += $this->dateParser->getHours($slot[0], $slot[1]); + } + } + } + + // The start date was not found in regular hours so we get the nearest time slot + if (! empty($timetable) && ! $found_start) { + $slot = $this->findClosestTimeSlot($start, $timetable); + + if ($start < $slot[0]) { + return $this->calculateEffectiveDuration($user_id, $slot[0], $end); + } + } + + return $hours; + } + + /** + * Find the nearest time slot + * + * @access public + * @param DateTime $date + * @param array $timetable + * @return array + */ + public function findClosestTimeSlot(DateTime $date, array $timetable) + { + $values = array(); + + foreach ($timetable as $slot) { + $t1 = abs($slot[0]->getTimestamp() - $date->getTimestamp()); + $t2 = abs($slot[1]->getTimestamp() - $date->getTimestamp()); + + $values[] = min($t1, $t2); + } + + asort($values); + return $timetable[key($values)]; + } + + /** + * Get the timetable for a user for a given date range + * + * @access public + * @param integer $user_id + * @param \DateTime $start + * @param \DateTime $end + * @return array + */ + public function calculate($user_id, DateTime $start, DateTime $end) + { + $timetable = array(); + + $this->day = $this->timetableDay->getByUser($user_id); + $this->week = $this->timetableWeek->getByUser($user_id); + $this->overtime = $this->timetableExtra->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); + $this->timeoff = $this->timetableOff->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); + + for ($today = clone($start); $today <= $end; $today->add(new DateInterval('P1D'))) { + $week_day = $today->format('N'); + $timetable = array_merge($timetable, $this->getWeekSlots($today, $week_day)); + $timetable = array_merge($timetable, $this->getOvertimeSlots($today, $week_day)); + } + + return $timetable; + } + + /** + * Return worked time slots for the given day + * + * @access public + * @param \DateTime $today + * @param string $week_day + * @return array + */ + public function getWeekSlots(DateTime $today, $week_day) + { + $slots = array(); + $dayoff = $this->getDayOff($today); + + if (! empty($dayoff) && $dayoff['all_day'] == 1) { + return array(); + } + + foreach ($this->week as $slot) { + if ($week_day == $slot['day']) { + $slots = array_merge($slots, $this->getDayWorkSlots($slot, $dayoff, $today)); + } + } + + return $slots; + } + + /** + * Get the overtime time slots for the given day + * + * @access public + * @param \DateTime $today + * @param string $week_day + * @return array + */ + public function getOvertimeSlots(DateTime $today, $week_day) + { + $slots = array(); + + foreach ($this->overtime as $slot) { + + $day = new DateTime($slot['date']); + + if ($week_day == $day->format('N')) { + + if ($slot['all_day'] == 1) { + $slots = array_merge($slots, $this->getDaySlots($today)); + } + else { + $slots[] = $this->getTimeSlot($slot, $day); + } + } + } + + return $slots; + } + + /** + * Get worked time slots and remove time off + * + * @access public + * @param array $slot + * @param array $dayoff + * @param \DateTime $today + * @return array + */ + public function getDayWorkSlots(array $slot, array $dayoff, DateTime $today) + { + $slots = array(); + + if (! empty($dayoff) && $dayoff['start'] < $slot['end']) { + + if ($dayoff['start'] > $slot['start']) { + $slots[] = $this->getTimeSlot(array('end' => $dayoff['start']) + $slot, $today); + } + + if ($dayoff['end'] < $slot['end']) { + $slots[] = $this->getTimeSlot(array('start' => $dayoff['end']) + $slot, $today); + } + } + else { + $slots[] = $this->getTimeSlot($slot, $today); + } + + return $slots; + } + + /** + * Get regular day work time slots + * + * @access public + * @param \DateTime $today + * @return array + */ + public function getDaySlots(DateTime $today) + { + $slots = array(); + + foreach ($this->day as $day) { + $slots[] = $this->getTimeSlot($day, $today); + } + + return $slots; + } + + /** + * Get the start and end time slot for a given day + * + * @access public + * @param array $slot + * @param \DateTime $today + * @return array + */ + public function getTimeSlot(array $slot, DateTime $today) + { + $date = $today->format('Y-m-d'); + + return array( + new DateTime($date.' '.$slot['start']), + new DateTime($date.' '.$slot['end']), + ); + } + + /** + * Return day off time slot + * + * @access public + * @param \DateTime $today + * @return array + */ + public function getDayOff(DateTime $today) + { + foreach ($this->timeoff as $day) { + + if ($day['date'] === $today->format('Y-m-d')) { + return $day; + } + } + + return array(); + } +} diff --git a/sources/app/Model/TimetableDay.php b/sources/app/Model/TimetableDay.php new file mode 100644 index 0000000..0c7bf20 --- /dev/null +++ b/sources/app/Model/TimetableDay.php @@ -0,0 +1,87 @@ +db->table(self::TABLE)->eq('user_id', $user_id)->asc('start')->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $start Start hour (24h format) + * @param string $end End hour (24h format) + * @return boolean|integer + */ + public function create($user_id, $start, $end) + { + $values = array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(self::TABLE)->eq('id', $slot_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('start', t('Field required')), + new Validators\Required('end', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/sources/app/Model/TimetableExtra.php b/sources/app/Model/TimetableExtra.php new file mode 100644 index 0000000..48db662 --- /dev/null +++ b/sources/app/Model/TimetableExtra.php @@ -0,0 +1,22 @@ +db->table(static::TABLE)->eq('user_id', $user_id); + } + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(static::TABLE)->eq('user_id', $user_id)->desc('date')->asc('start')->findAll(); + } + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @param string $start_date + * @param string $end_date + * @return array + */ + public function getByUserAndDate($user_id, $start_date, $end_date) + { + return $this->db->table(static::TABLE) + ->eq('user_id', $user_id) + ->gte('date', $start_date) + ->lte('date', $end_date) + ->desc('date') + ->asc('start') + ->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $date Day (ISO8601 format) + * @param boolean $all_day All day flag + * @param float $start Start hour (24h format) + * @param float $end End hour (24h format) + * @param string $comment + * @return boolean|integer + */ + public function create($user_id, $date, $all_day, $start = '', $end = '', $comment = '') + { + $values = array( + 'user_id' => $user_id, + 'date' => $date, + 'all_day' => (int) $all_day, // Postgres fix + 'start' => $all_day ? '' : $start, + 'end' => $all_day ? '' : $end, + 'comment' => $comment, + ); + + return $this->persist(static::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(static::TABLE)->eq('id', $slot_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('date', t('Field required')), + new Validators\Numeric('all_day', t('This value must be numeric')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/sources/app/Model/TimetableWeek.php b/sources/app/Model/TimetableWeek.php new file mode 100644 index 0000000..b22b3b7 --- /dev/null +++ b/sources/app/Model/TimetableWeek.php @@ -0,0 +1,91 @@ +db->table(self::TABLE)->eq('user_id', $user_id)->asc('day')->asc('start')->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $day Day of the week (ISO-8601) + * @param string $start Start hour (24h format) + * @param string $end End hour (24h format) + * @return boolean|integer + */ + public function create($user_id, $day, $start, $end) + { + $values = array( + 'user_id' => $user_id, + 'day' => $day, + 'start' => $start, + 'end' => $end, + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(self::TABLE)->eq('id', $slot_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('day', t('Field required')), + new Validators\Numeric('day', t('This value must be numeric')), + new Validators\Required('start', t('Field required')), + new Validators\Required('end', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/sources/app/Model/Transition.php b/sources/app/Model/Transition.php new file mode 100644 index 0000000..cb759e4 --- /dev/null +++ b/sources/app/Model/Transition.php @@ -0,0 +1,170 @@ +db->table(self::TABLE)->insert(array( + 'user_id' => $user_id, + 'project_id' => $task['project_id'], + 'task_id' => $task['task_id'], + 'src_column_id' => $task['src_column_id'], + 'dst_column_id' => $task['dst_column_id'], + 'date' => time(), + 'time_spent' => time() - $task['date_moved'] + )); + } + + /** + * Get all transitions by task + * + * @access public + * @param integer $task_id + * @return array + */ + public function getAllByTask($task_id) + { + return $this->db->table(self::TABLE) + ->columns( + 'src.title as src_column', + 'dst.title as dst_column', + User::TABLE.'.name', + User::TABLE.'.username', + self::TABLE.'.user_id', + self::TABLE.'.date', + self::TABLE.'.time_spent' + ) + ->eq('task_id', $task_id) + ->desc('date') + ->join(User::TABLE, 'id', 'user_id') + ->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src') + ->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst') + ->findAll(); + } + + /** + * Get all transitions by project + * + * @access public + * @param integer $project_id + * @param mixed $from Start date (timestamp or user formatted date) + * @param mixed $to End date (timestamp or user formatted date) + * @return array + */ + public function getAllByProjectAndDate($project_id, $from, $to) + { + if (! is_numeric($from)) { + $from = $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($from)); + } + + if (! is_numeric($to)) { + $to = $this->dateParser->removeTimeFromTimestamp(strtotime('+1 day', $this->dateParser->getTimestamp($to))); + } + + return $this->db->table(self::TABLE) + ->columns( + Task::TABLE.'.id', + Task::TABLE.'.title', + 'src.title as src_column', + 'dst.title as dst_column', + User::TABLE.'.name', + User::TABLE.'.username', + self::TABLE.'.user_id', + self::TABLE.'.date', + self::TABLE.'.time_spent' + ) + ->gte('date', $from) + ->lte('date', $to) + ->eq(self::TABLE.'.project_id', $project_id) + ->desc('date') + ->join(Task::TABLE, 'id', 'task_id') + ->join(User::TABLE, 'id', 'user_id') + ->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src') + ->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst') + ->findAll(); + } + + /** + * Get project export + * + * @access public + * @param integer $project_id Project id + * @param mixed $from Start date (timestamp or user formatted date) + * @param mixed $to End date (timestamp or user formatted date) + * @return array + */ + public function export($project_id, $from, $to) + { + $results = array($this->getColumns()); + $transitions = $this->getAllByProjectAndDate($project_id, $from, $to); + + foreach ($transitions as $transition) { + $results[] = $this->format($transition); + } + + return $results; + } + + /** + * Get column titles + * + * @access public + * @return string[] + */ + public function getColumns() + { + return array( + e('Id'), + e('Task Title'), + e('Source column'), + e('Destination column'), + e('Executer'), + e('Date'), + e('Time spent'), + ); + } + + /** + * Format the output of a transition array + * + * @access public + * @param array $transition + * @return array + */ + public function format(array $transition) + { + $values = array(); + $values[] = $transition['id']; + $values[] = $transition['title']; + $values[] = $transition['src_column']; + $values[] = $transition['dst_column']; + $values[] = $transition['name'] ?: $transition['username']; + $values[] = date('Y-m-d H:i', $transition['date']); + $values[] = round($transition['time_spent'] / 3600, 2); + + return $values; + } +} diff --git a/sources/app/Model/User.php b/sources/app/Model/User.php index 7586f3c..6c348ca 100644 --- a/sources/app/Model/User.php +++ b/sources/app/Model/User.php @@ -60,7 +60,8 @@ class User extends Base 'is_ldap_user', 'notifications_enabled', 'google_id', - 'github_id' + 'github_id', + 'twofactor_activated' ); } diff --git a/sources/app/Model/UserSession.php b/sources/app/Model/UserSession.php index 6d9a2eb..efb0272 100644 --- a/sources/app/Model/UserSession.php +++ b/sources/app/Model/UserSession.php @@ -28,14 +28,41 @@ class UserSession extends Base unset($user['password']); } + if (isset($user['twofactor_secret'])) { + unset($user['twofactor_secret']); + } + $user['id'] = (int) $user['id']; $user['default_project_id'] = (int) $user['default_project_id']; $user['is_admin'] = (bool) $user['is_admin']; $user['is_ldap_user'] = (bool) $user['is_ldap_user']; + $user['twofactor_activated'] = (bool) $user['twofactor_activated']; $this->session['user'] = $user; } + /** + * Return true if the user has validated the 2FA key + * + * @access public + * @return bool + */ + public function check2FA() + { + return isset($this->session['2fa_validated']) && $this->session['2fa_validated'] === true; + } + + /** + * Return true if the user has 2FA enabled + * + * @access public + * @return bool + */ + public function has2FA() + { + return isset($this->session['user']['twofactor_activated']) && $this->session['user']['twofactor_activated'] === true; + } + /** * Return true if the logged user is admin * diff --git a/sources/app/Model/Webhook.php b/sources/app/Model/Webhook.php index 7edffa6..b360381 100644 --- a/sources/app/Model/Webhook.php +++ b/sources/app/Model/Webhook.php @@ -10,27 +10,6 @@ namespace Model; */ class Webhook extends Base { - /** - * HTTP connection timeout in seconds - * - * @var integer - */ - const HTTP_TIMEOUT = 1; - - /** - * Number of maximum redirections for the HTTP client - * - * @var integer - */ - const HTTP_MAX_REDIRECTS = 3; - - /** - * HTTP client user agent - * - * @var string - */ - const HTTP_USER_AGENT = 'Kanboard Webhook'; - /** * Call the external URL * @@ -42,22 +21,6 @@ class Webhook extends Base { $token = $this->config->get('webhook_token'); - $headers = array( - 'Connection: close', - 'User-Agent: '.self::HTTP_USER_AGENT, - ); - - $context = stream_context_create(array( - 'http' => array( - 'method' => 'POST', - 'protocol_version' => 1.1, - 'timeout' => self::HTTP_TIMEOUT, - 'max_redirects' => self::HTTP_MAX_REDIRECTS, - 'header' => implode("\r\n", $headers), - 'content' => json_encode($task) - ) - )); - if (strpos($url, '?') !== false) { $url .= '&token='.$token; } @@ -65,6 +28,6 @@ class Webhook extends Base $url .= '?token='.$token; } - @file_get_contents($url, false, $context); + return $this->httpClient->post($url, $task); } } diff --git a/sources/app/Schema/Mysql.php b/sources/app/Schema/Mysql.php index 947a62b..e5269d9 100644 --- a/sources/app/Schema/Mysql.php +++ b/sources/app/Schema/Mysql.php @@ -6,7 +6,190 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 46; +const VERSION = 61; + +function version_61($pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN twofactor_activated TINYINT(1) DEFAULT 0'); + $pdo->exec('ALTER TABLE users ADD COLUMN twofactor_secret CHAR(16)'); +} + +function version_60($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_gravatar', '0')); +} + +function version_59($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_hipchat', '0')); + $rq->execute(array('integration_hipchat_api_url', 'https://api.hipchat.com')); + $rq->execute(array('integration_hipchat_room_id', '')); + $rq->execute(array('integration_hipchat_room_token', '')); +} + +function version_58($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_slack_webhook', '0')); + $rq->execute(array('integration_slack_webhook_url', '')); +} + +function version_57($pdo) +{ + $pdo->exec('CREATE TABLE currencies (`currency` CHAR(3) NOT NULL UNIQUE, `rate` FLOAT DEFAULT 0) ENGINE=InnoDB CHARSET=utf8'); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_currency', 'USD')); +} + +function version_56($pdo) +{ + $pdo->exec('CREATE TABLE transitions ( + `id` INT NOT NULL AUTO_INCREMENT, + `user_id` INT NOT NULL, + `project_id` INT NOT NULL, + `task_id` INT NOT NULL, + `src_column_id` INT NOT NULL, + `dst_column_id` INT NOT NULL, + `date` INT NOT NULL, + `time_spent` INT DEFAULT 0, + FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)"); + $pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)"); + $pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)"); +} + +function version_55($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_forecast', '0')); +} + +function version_54($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +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 ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + start VARCHAR(5) NOT NULL, + end VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_week ( + id INT NOT NULL AUTO_INCREMENT, + user_id INTEGER NOT NULL, + day INT NOT NULL, + start VARCHAR(5) NOT NULL, + end VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_off ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date VARCHAR(10) NOT NULL, + all_day TINYINT(1) DEFAULT 0, + start VARCHAR(5) DEFAULT 0, + end VARCHAR(5) DEFAULT 0, + comment TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_extra ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date VARCHAR(10) NOT NULL, + all_day TINYINT(1) DEFAULT 0, + start VARCHAR(5) DEFAULT 0, + end VARCHAR(5) DEFAULT 0, + comment TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) 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'); + + $task_id = 0; + $position = 1; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} + +function version_48($pdo) +{ + $pdo->exec('RENAME TABLE task_has_files TO files'); + $pdo->exec('RENAME TABLE task_has_subtasks TO subtasks'); +} + +function version_47($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} function version_46($pdo) { diff --git a/sources/app/Schema/Postgres.php b/sources/app/Schema/Postgres.php index 027401f..2c5e0f2 100644 --- a/sources/app/Schema/Postgres.php +++ b/sources/app/Schema/Postgres.php @@ -6,7 +6,183 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 27; +const VERSION = 42; + +function version_42($pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN twofactor_activated BOOLEAN DEFAULT \'0\''); + $pdo->exec('ALTER TABLE users ADD COLUMN twofactor_secret CHAR(16)'); +} + +function version_41($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_gravatar', '0')); +} + +function version_40($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_hipchat', '0')); + $rq->execute(array('integration_hipchat_api_url', 'https://api.hipchat.com')); + $rq->execute(array('integration_hipchat_room_id', '')); + $rq->execute(array('integration_hipchat_room_token', '')); +} + +function version_39($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_slack_webhook', '0')); + $rq->execute(array('integration_slack_webhook_url', '')); +} + +function version_38($pdo) +{ + $pdo->exec('CREATE TABLE currencies ("currency" CHAR(3) NOT NULL UNIQUE, "rate" REAL DEFAULT 0)'); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_currency', 'USD')); +} + +function version_37($pdo) +{ + $pdo->exec('CREATE TABLE transitions ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "project_id" INTEGER NOT NULL, + "task_id" INTEGER NOT NULL, + "src_column_id" INTEGER NOT NULL, + "dst_column_id" INTEGER NOT NULL, + "date" INTEGER NOT NULL, + "time_spent" INTEGER DEFAULT 0, + FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + )'); + + $pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)"); + $pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)"); + $pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)"); +} + +function version_36($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_forecast', '0')); +} + +function version_35($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +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 ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "start" VARCHAR(5) NOT NULL, + "end" VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_week ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "start" VARCHAR(5) NOT NULL, + "end" VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_off ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "all_day" BOOLEAN DEFAULT \'0\', + "start" VARCHAR(5) DEFAULT 0, + "end" VARCHAR(5) DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_extra ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "all_day" BOOLEAN DEFAULT \'0\', + "start" VARCHAR(5) DEFAULT 0, + "end" VARCHAR(5) DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); +} + +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'); + + $task_id = 0; + $position = 1; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} + +function version_29($pdo) +{ + $pdo->exec('ALTER TABLE task_has_files RENAME TO files'); + $pdo->exec('ALTER TABLE task_has_subtasks RENAME TO subtasks'); +} + +function version_28($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} function version_27($pdo) { diff --git a/sources/app/Schema/Sqlite.php b/sources/app/Schema/Sqlite.php index c6dec33..b9c264b 100644 --- a/sources/app/Schema/Sqlite.php +++ b/sources/app/Schema/Sqlite.php @@ -6,7 +6,185 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 45; +const VERSION = 60; + +function version_60($pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN twofactor_activated INTEGER DEFAULT 0'); + $pdo->exec('ALTER TABLE users ADD COLUMN twofactor_secret TEXT'); +} + +function version_59($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_gravatar', '0')); +} + +function version_58($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_hipchat', '0')); + $rq->execute(array('integration_hipchat_api_url', 'https://api.hipchat.com')); + $rq->execute(array('integration_hipchat_room_id', '')); + $rq->execute(array('integration_hipchat_room_token', '')); +} + +function version_57($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_slack_webhook', '0')); + $rq->execute(array('integration_slack_webhook_url', '')); +} + +function version_56($pdo) +{ + $pdo->exec('CREATE TABLE currencies ("currency" TEXT NOT NULL UNIQUE, "rate" REAL DEFAULT 0)'); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_currency', 'USD')); +} + +function version_55($pdo) +{ + $pdo->exec('CREATE TABLE transitions ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "project_id" INTEGER NOT NULL, + "task_id" INTEGER NOT NULL, + "src_column_id" INTEGER NOT NULL, + "dst_column_id" INTEGER NOT NULL, + "date" INTEGER NOT NULL, + "time_spent" INTEGER DEFAULT 0, + FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + )'); + + $pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)"); + $pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)"); + $pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)"); +} + +function version_54($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_forecast', '0')); +} + +function version_53($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +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 ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "start" TEXT NOT NULL, + "end" TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_week ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "start" TEXT NOT NULL, + "end" TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_off ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" TEXT NOT NULL, + "all_day" INTEGER DEFAULT 0, + "start" TEXT DEFAULT 0, + "end" TEXT DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_extra ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" TEXT NOT NULL, + "all_day" INTEGER DEFAULT 0, + "start" TEXT DEFAULT 0, + "end" TEXT DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); +} + +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'); + + // Migrate all subtasks position + + $task_id = 0; + $position = 1; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} + +function version_47($pdo) +{ + $pdo->exec('ALTER TABLE task_has_files RENAME TO files'); + $pdo->exec('ALTER TABLE task_has_subtasks RENAME TO subtasks'); +} + +function version_46($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} function version_45($pdo) { diff --git a/sources/app/ServiceProvider/ClassProvider.php b/sources/app/ServiceProvider/ClassProvider.php index 213972e..6a12ea5 100644 --- a/sources/app/ServiceProvider/ClassProvider.php +++ b/sources/app/ServiceProvider/ClassProvider.php @@ -17,12 +17,15 @@ class ClassProvider implements ServiceProviderInterface 'Action', 'Authentication', 'Board', + 'Budget', 'Category', 'Color', 'Comment', 'Config', + 'Currency', 'DateParser', 'File', + 'HourlyRate', 'LastLogin', 'Link', 'Notification', @@ -34,6 +37,7 @@ class ClassProvider implements ServiceProviderInterface 'ProjectPermission', 'Subtask', 'SubtaskExport', + 'SubtaskForecast', 'SubtaskTimeTracking', 'Swimlane', 'Task', @@ -48,7 +52,12 @@ class ClassProvider implements ServiceProviderInterface 'TaskPosition', 'TaskStatus', 'TaskValidator', - 'TimeTracking', + 'Timetable', + 'TimetableDay', + 'TimetableWeek', + 'TimetableOff', + 'TimetableExtra', + 'Transition', 'User', 'UserSession', 'Webhook', @@ -60,11 +69,14 @@ class ClassProvider implements ServiceProviderInterface 'MemoryCache', 'FileCache', 'Request', + 'HttpClient', ), 'Integration' => array( 'GitlabWebhook', 'GithubWebhook', 'BitbucketWebhook', + 'Hipchat', + 'SlackWebhook', ) ); diff --git a/sources/app/ServiceProvider/DatabaseProvider.php b/sources/app/ServiceProvider/DatabaseProvider.php index 4218f5f..7ee35d5 100644 --- a/sources/app/ServiceProvider/DatabaseProvider.php +++ b/sources/app/ServiceProvider/DatabaseProvider.php @@ -79,6 +79,7 @@ class DatabaseProvider implements ServiceProviderInterface 'password' => DB_PASSWORD, 'database' => DB_NAME, 'charset' => 'utf8', + 'port' => DB_PORT, )); } @@ -97,6 +98,7 @@ class DatabaseProvider implements ServiceProviderInterface 'username' => DB_USERNAME, 'password' => DB_PASSWORD, 'database' => DB_NAME, + 'port' => DB_PORT, )); } } diff --git a/sources/app/ServiceProvider/EventDispatcherProvider.php b/sources/app/ServiceProvider/EventDispatcherProvider.php index ec38220..f002db7 100644 --- a/sources/app/ServiceProvider/EventDispatcherProvider.php +++ b/sources/app/ServiceProvider/EventDispatcherProvider.php @@ -14,6 +14,7 @@ use Subscriber\ProjectModificationDateSubscriber; use Subscriber\WebhookSubscriber; use Subscriber\SubtaskTimesheetSubscriber; use Subscriber\TaskMovedDateSubscriber; +use Subscriber\TransitionSubscriber; class EventDispatcherProvider implements ServiceProviderInterface { @@ -29,6 +30,7 @@ class EventDispatcherProvider implements ServiceProviderInterface $container['dispatcher']->addSubscriber(new NotificationSubscriber($container)); $container['dispatcher']->addSubscriber(new SubtaskTimesheetSubscriber($container)); $container['dispatcher']->addSubscriber(new TaskMovedDateSubscriber($container)); + $container['dispatcher']->addSubscriber(new TransitionSubscriber($container)); // Automatic actions $container['action']->attachEvents(); diff --git a/sources/app/Subscriber/Base.php b/sources/app/Subscriber/Base.php index f90d960..97e230b 100644 --- a/sources/app/Subscriber/Base.php +++ b/sources/app/Subscriber/Base.php @@ -10,6 +10,8 @@ use Pimple\Container; * @package subscriber * @author Frederic Guillot * + * @property \Integration\SlackWebhook $slackWebhook + * @property \Integration\Hipchat $hipchat * @property \Model\Board $board * @property \Model\Config $config * @property \Model\Comment $comment diff --git a/sources/app/Subscriber/NotificationSubscriber.php b/sources/app/Subscriber/NotificationSubscriber.php index 1b7187f..09ca762 100644 --- a/sources/app/Subscriber/NotificationSubscriber.php +++ b/sources/app/Subscriber/NotificationSubscriber.php @@ -47,10 +47,13 @@ class NotificationSubscriber extends Base implements EventSubscriberInterface public function execute(GenericEvent $event, $event_name) { $values = $this->getTemplateData($event); - $users = $this->notification->getUsersList($values['task']['project_id']); - if ($users) { - $this->notification->sendEmails($this->templates[$event_name], $users, $values); + if (isset($values['task']['project_id'])) { + $users = $this->notification->getUsersList($values['task']['project_id']); + + if (! empty($users)) { + $this->notification->sendEmails($this->templates[$event_name], $users, $values); + } } } @@ -64,11 +67,11 @@ class NotificationSubscriber extends Base implements EventSubscriberInterface break; case 'Event\SubtaskEvent': $values['subtask'] = $this->subtask->getById($event['id'], true); - $values['task'] = $this->taskFinder->getDetails($event['task_id']); + $values['task'] = $this->taskFinder->getDetails($values['subtask']['task_id']); break; case 'Event\FileEvent': $values['file'] = $event->getAll(); - $values['task'] = $this->taskFinder->getDetails($event['task_id']); + $values['task'] = $this->taskFinder->getDetails($values['file']['task_id']); break; case 'Event\CommentEvent': $values['comment'] = $this->comment->getById($event['id']); diff --git a/sources/app/Subscriber/ProjectActivitySubscriber.php b/sources/app/Subscriber/ProjectActivitySubscriber.php index 00f5b04..4231463 100644 --- a/sources/app/Subscriber/ProjectActivitySubscriber.php +++ b/sources/app/Subscriber/ProjectActivitySubscriber.php @@ -41,6 +41,33 @@ class ProjectActivitySubscriber extends Base implements EventSubscriberInterface $event_name, $values ); + + $this->sendSlackNotification($event_name, $values); + $this->sendHipchatNotification($event_name, $values); + } + } + + private function sendSlackNotification($event_name, array $values) + { + if ($this->config->get('integration_slack_webhook') == 1) { + $this->slackWebhook->notify( + $values['task']['project_id'], + $values['task']['id'], + $event_name, + $values + ); + } + } + + private function sendHipchatNotification($event_name, array $values) + { + if ($this->config->get('integration_hipchat') == 1) { + $this->hipchat->notify( + $values['task']['project_id'], + $values['task']['id'], + $event_name, + $values + ); } } diff --git a/sources/app/Subscriber/TransitionSubscriber.php b/sources/app/Subscriber/TransitionSubscriber.php new file mode 100644 index 0000000..347dd37 --- /dev/null +++ b/sources/app/Subscriber/TransitionSubscriber.php @@ -0,0 +1,26 @@ + array('execute', 0), + ); + } + + public function execute(TaskEvent $event) + { + $user_id = $this->userSession->getId(); + + if (! empty($user_id)) { + $this->transition->save($user_id, $event->getAll()); + } + } +} \ No newline at end of file diff --git a/sources/app/Template/analytic/layout.php b/sources/app/Template/analytic/layout.php index 8c94669..c6e3a96 100644 --- a/sources/app/Template/analytic/layout.php +++ b/sources/app/Template/analytic/layout.php @@ -1,5 +1,5 @@ js('assets/js/vendor/d3.v3.4.8.min.js') ?> -js('assets/js/vendor/dimple.v2.1.0.min.js') ?> +js('assets/js/vendor/dimple.v2.1.2.min.js') ?>
@@ -53,7 +53,7 @@

- render('project/events', array('events' => $events)) ?> + render('event/events', array('events' => $events)) ?>
diff --git a/sources/app/Template/app/projects.php b/sources/app/Template/app/projects.php index 4740c4b..b274464 100644 --- a/sources/app/Template/app/projects.php +++ b/sources/app/Template/app/projects.php @@ -17,9 +17,15 @@ isManager($project['id'])): ?> a('', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>  - + a('', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?>  + a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?> + + '> + + + @@ -32,4 +38,4 @@ - \ No newline at end of file + diff --git a/sources/app/Template/app/subtasks.php b/sources/app/Template/app/subtasks.php index fdfbdf2..487b66f 100644 --- a/sources/app/Template/app/subtasks.php +++ b/sources/app/Template/app/subtasks.php @@ -6,6 +6,7 @@ order('Id', 'tasks.id') ?> order(t('Project'), 'project_name') ?> + order(t('Task'), 'task_name') ?> order(t('Subtask'), 'title') ?> @@ -17,6 +18,9 @@ a($this->e($subtask['project_name']), 'board', 'show', array('project_id' => $subtask['project_id'])) ?> + + a($this->e($subtask['task_name']), 'task', 'show', array('task_id' => $subtask['task_id'], 'project_id' => $subtask['project_id'])) ?> + toggleSubtaskStatus($subtask, 'dashboard') ?> diff --git a/sources/app/Template/board/edit.php b/sources/app/Template/board/edit.php index b9b1788..a6df100 100644 --- a/sources/app/Template/board/edit.php +++ b/sources/app/Template/board/edit.php @@ -13,7 +13,7 @@ e($column['title']) ?> - + '> @@ -52,12 +52,12 @@ formLabel(t('Title'), 'title') ?> formText('title', $values, $errors, array('required', 'maxlength="50"')) ?> - + formLabel(t('Task limit'), 'task_limit') ?> formNumber('task_limit', $values, $errors) ?> - + formLabel(t('Description'), 'description') ?> - +
formTextarea('description', $values, $errors) ?> diff --git a/sources/app/Template/board/files.php b/sources/app/Template/board/files.php index 278b906..851a118 100644 --- a/sources/app/Template/board/files.php +++ b/sources/app/Template/board/files.php @@ -1,14 +1,31 @@
- - - - a( - $this->e($file['name']), - 'file', - 'download', - array('file_id' => $file['id'], 'task_id' => $file['task_id'], 'project_id' => $task['project_id']) - ) ?> - -
- + + + + + + + + + + + + + + + + + +
+ + e($file['name']) ?> + + a(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + a(t('open'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> +
+ + e($file['name']) ?> + + a(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> +
diff --git a/sources/app/Template/board/filters.php b/sources/app/Template/board/filters.php index a0de5fd..47304d7 100644 --- a/sources/app/Template/board/filters.php +++ b/sources/app/Template/board/filters.php @@ -1,8 +1,8 @@